Skip to content

Commit

Permalink
Auto merge of rust-lang#14433 - hecatia-elegua:alias-based-completion…
Browse files Browse the repository at this point in the history
…, r=Veykril

Add doc-alias based completion

Closes rust-lang#14406.

I adapted the parsing code from the CfgExpr parsing code, maybe there's a better abstraction for both, or attribute parsing in general. It also includes `doc(hidden)`-parsing, which means it could replace the other function.
There are a few tests for parsing.

`process_all_names` changed the most, I added some docs there to explain what happens.

Many call sites just pass an empy vec to `add_path_resolution`'s `doc_aliases`, since either it doesn't make sense to pass anything (e.g. visibility completion) or I don't know where to get them from. Shouldn't really matter, as it will just not show aliases if the vec is empty and we can extend alias completion in these cases later.

I added two tests in `special.rs` for struct name completion (which was the main thing I wanted). I also tried function and field names, but these don't work yet. I want to add those in a follow-up PR.
  • Loading branch information
bors committed Apr 5, 2023
2 parents c3ed59c + 170822b commit 265f830
Show file tree
Hide file tree
Showing 17 changed files with 334 additions and 41 deletions.
112 changes: 112 additions & 0 deletions crates/hir-def/src/attr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! A higher level attributes based on TokenTree, with also some shortcuts.

#[cfg(test)]
mod tests;

use std::{hash::Hash, ops, sync::Arc};

use base_db::CrateId;
Expand Down Expand Up @@ -245,6 +248,14 @@ impl Attrs {
})
}

pub fn doc_exprs(&self) -> impl Iterator<Item = DocExpr> + '_ {
self.by_key("doc").tt_values().map(DocExpr::parse)
}

pub fn doc_aliases(&self) -> impl Iterator<Item = SmolStr> + '_ {
self.doc_exprs().flat_map(|doc_expr| doc_expr.aliases().to_vec())
}

pub fn is_proc_macro(&self) -> bool {
self.by_key("proc_macro").exists()
}
Expand All @@ -258,6 +269,107 @@ impl Attrs {
}
}

use std::slice::Iter as SliceIter;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum DocAtom {
/// eg. `#[doc(hidden)]`
Flag(SmolStr),
/// eg. `#[doc(alias = "x")]`
///
/// Note that a key can have multiple values that are all considered "active" at the same time.
/// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`.
KeyValue { key: SmolStr, value: SmolStr },
}

// Adapted from `CfgExpr` parsing code
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
// #[cfg_attr(test, derive(derive_arbitrary::Arbitrary))]
pub enum DocExpr {
Invalid,
/// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]`
Atom(DocAtom),
/// eg. `#[doc(alias("x", "y"))]`
Alias(Vec<SmolStr>),
}

impl From<DocAtom> for DocExpr {
fn from(atom: DocAtom) -> Self {
DocExpr::Atom(atom)
}
}

impl DocExpr {
fn parse<S>(tt: &tt::Subtree<S>) -> DocExpr {
next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid)
}

pub fn aliases(&self) -> &[SmolStr] {
match self {
DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => {
std::slice::from_ref(value)
}
DocExpr::Alias(aliases) => aliases,
_ => &[],
}
}
}

fn next_doc_expr<S>(it: &mut SliceIter<'_, tt::TokenTree<S>>) -> Option<DocExpr> {
let name = match it.next() {
None => return None,
Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(),
Some(_) => return Some(DocExpr::Invalid),
};

// Peek
let ret = match it.as_slice().first() {
Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
match it.as_slice().get(1) {
Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
it.next();
it.next();
// FIXME: escape? raw string?
let value =
SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
DocAtom::KeyValue { key: name, value }.into()
}
_ => return Some(DocExpr::Invalid),
}
}
Some(tt::TokenTree::Subtree(subtree)) => {
it.next();
let subs = parse_comma_sep(subtree);
match name.as_str() {
"alias" => DocExpr::Alias(subs),
_ => DocExpr::Invalid,
}
}
_ => DocAtom::Flag(name).into(),
};

// Eat comma separator
if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() {
if punct.char == ',' {
it.next();
}
}
Some(ret)
}

fn parse_comma_sep<S>(subtree: &tt::Subtree<S>) -> Vec<SmolStr> {
subtree
.token_trees
.iter()
.filter_map(|tt| match tt {
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
// FIXME: escape? raw string?
Some(SmolStr::new(lit.text.trim_start_matches('"').trim_end_matches('"')))
}
_ => None,
})
.collect()
}

impl AttrsWithOwner {
pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
let _p = profile::span("attrs_query");
Expand Down
40 changes: 40 additions & 0 deletions crates/hir-def/src/attr/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! This module contains tests for doc-expression parsing.
//! Currently, it tests `#[doc(hidden)]` and `#[doc(alias)]`.

use mbe::syntax_node_to_token_tree;
use syntax::{ast, AstNode};

use crate::attr::{DocAtom, DocExpr};

fn assert_parse_result(input: &str, expected: DocExpr) {
let (tt, _) = {
let source_file = ast::SourceFile::parse(input).ok().unwrap();
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
syntax_node_to_token_tree(tt.syntax())
};
let cfg = DocExpr::parse(&tt);
assert_eq!(cfg, expected);
}

#[test]
fn test_doc_expr_parser() {
assert_parse_result("#![doc(hidden)]", DocAtom::Flag("hidden".into()).into());

assert_parse_result(
r#"#![doc(alias = "foo")]"#,
DocAtom::KeyValue { key: "alias".into(), value: "foo".into() }.into(),
);

assert_parse_result(r#"#![doc(alias("foo"))]"#, DocExpr::Alias(["foo".into()].into()));
assert_parse_result(
r#"#![doc(alias("foo", "bar", "baz"))]"#,
DocExpr::Alias(["foo".into(), "bar".into(), "baz".into()].into()),
);

assert_parse_result(
r#"
#[doc(alias("Bar", "Qux"))]
struct Foo;"#,
DocExpr::Alias(["Bar".into(), "Qux".into()].into()),
);
}
1 change: 1 addition & 0 deletions crates/hir/src/semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1644,6 +1644,7 @@ impl<'a> SemanticsScope<'a> {
VisibleTraits(resolver.traits_in_scope(self.db.upcast()))
}

/// Calls the passed closure `f` on all names in scope.
pub fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
let scope = self.resolver.names_in_scope(self.db.upcast());
for (name, entries) in scope {
Expand Down
11 changes: 8 additions & 3 deletions crates/ide-completion/src/completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ impl Completions {
ctx: &CompletionContext<'_>,
path_ctx: &PathCompletionCtx,
) {
ctx.process_all_names(&mut |name, res| match res {
ctx.process_all_names(&mut |name, res, doc_aliases| match res {
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => {
self.add_module(ctx, path_ctx, m, name);
self.add_module(ctx, path_ctx, m, name, doc_aliases);
}
_ => (),
});
Expand All @@ -179,6 +179,7 @@ impl Completions {
path_ctx: &PathCompletionCtx,
local_name: hir::Name,
resolution: hir::ScopeDef,
doc_aliases: Vec<syntax::SmolStr>,
) {
let is_private_editable = match ctx.def_is_visible(&resolution) {
Visible::Yes => false,
Expand All @@ -187,7 +188,9 @@ impl Completions {
};
self.add(
render_path_resolution(
RenderContext::new(ctx).private_editable(is_private_editable),
RenderContext::new(ctx)
.private_editable(is_private_editable)
.doc_aliases(doc_aliases),
path_ctx,
local_name,
resolution,
Expand Down Expand Up @@ -236,12 +239,14 @@ impl Completions {
path_ctx: &PathCompletionCtx,
module: hir::Module,
local_name: hir::Name,
doc_aliases: Vec<syntax::SmolStr>,
) {
self.add_path_resolution(
ctx,
path_ctx,
local_name,
hir::ScopeDef::ModuleDef(module.into()),
doc_aliases,
);
}

Expand Down
6 changes: 3 additions & 3 deletions crates/ide-completion/src/completions/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub(crate) fn complete_attribute_path(
acc.add_macro(ctx, path_ctx, m, name)
}
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
acc.add_module(ctx, path_ctx, m, name)
acc.add_module(ctx, path_ctx, m, name, vec![])
}
_ => (),
}
Expand All @@ -104,12 +104,12 @@ pub(crate) fn complete_attribute_path(
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
// only show modules in a fresh UseTree
Qualified::No => {
ctx.process_all_names(&mut |name, def| match def {
ctx.process_all_names(&mut |name, def, doc_aliases| match def {
hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_attr(ctx.db) => {
acc.add_macro(ctx, path_ctx, m, name)
}
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
acc.add_module(ctx, path_ctx, m, name)
acc.add_module(ctx, path_ctx, m, name, doc_aliases)
}
_ => (),
});
Expand Down
6 changes: 3 additions & 3 deletions crates/ide-completion/src/completions/attribute/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub(crate) fn complete_derive_path(
acc.add_macro(ctx, path_ctx, mac, name)
}
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
acc.add_module(ctx, path_ctx, m, name)
acc.add_module(ctx, path_ctx, m, name, vec![])
}
_ => (),
}
Expand All @@ -43,15 +43,15 @@ pub(crate) fn complete_derive_path(
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
// only show modules in a fresh UseTree
Qualified::No => {
ctx.process_all_names(&mut |name, def| {
ctx.process_all_names(&mut |name, def, doc_aliases| {
let mac = match def {
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
{
mac
}
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
return acc.add_module(ctx, path_ctx, m, name);
return acc.add_module(ctx, path_ctx, m, name, doc_aliases);
}
_ => return,
};
Expand Down
12 changes: 7 additions & 5 deletions crates/ide-completion/src/completions/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ pub(crate) fn complete_expr_path(
let module_scope = module.scope(ctx.db, Some(ctx.module));
for (name, def) in module_scope {
if scope_def_applicable(def) {
acc.add_path_resolution(ctx, path_ctx, name, def);
acc.add_path_resolution(ctx, path_ctx, name, def, vec![]);
}
}
}
Expand Down Expand Up @@ -212,20 +212,22 @@ pub(crate) fn complete_expr_path(
}
}
}
ctx.process_all_names(&mut |name, def| match def {
ctx.process_all_names(&mut |name, def, doc_aliases| match def {
ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
let assocs = t.items_with_supertraits(ctx.db);
match &*assocs {
// traits with no assoc items are unusable as expressions since
// there is no associated item path that can be constructed with them
[] => (),
// FIXME: Render the assoc item with the trait qualified
&[_item] => acc.add_path_resolution(ctx, path_ctx, name, def),
&[_item] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
// FIXME: Append `::` to the thing here, since a trait on its own won't work
[..] => acc.add_path_resolution(ctx, path_ctx, name, def),
[..] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
}
}
_ if scope_def_applicable(def) => acc.add_path_resolution(ctx, path_ctx, name, def),
_ if scope_def_applicable(def) => {
acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases)
}
_ => (),
});

Expand Down
6 changes: 3 additions & 3 deletions crates/ide-completion/src/completions/item_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub(crate) fn complete_item_list(
acc.add_macro(ctx, path_ctx, m, name)
}
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
acc.add_module(ctx, path_ctx, m, name)
acc.add_module(ctx, path_ctx, m, name, vec![])
}
_ => (),
}
Expand All @@ -55,12 +55,12 @@ pub(crate) fn complete_item_list(
}
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
Qualified::No if ctx.qualifier_ctx.none() => {
ctx.process_all_names(&mut |name, def| match def {
ctx.process_all_names(&mut |name, def, doc_aliases| match def {
hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_fn_like(ctx.db) => {
acc.add_macro(ctx, path_ctx, m, name)
}
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
acc.add_module(ctx, path_ctx, m, name)
acc.add_module(ctx, path_ctx, m, name, doc_aliases)
}
_ => (),
});
Expand Down
8 changes: 4 additions & 4 deletions crates/ide-completion/src/completions/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub(crate) fn complete_pattern(

// FIXME: ideally, we should look at the type we are matching against and
// suggest variants + auto-imports
ctx.process_all_names(&mut |name, res| {
ctx.process_all_names(&mut |name, res, _| {
let add_simple_path = match res {
hir::ScopeDef::ModuleDef(def) => match def {
hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
Expand Down Expand Up @@ -127,7 +127,7 @@ pub(crate) fn complete_pattern_path(
};

if add_resolution {
acc.add_path_resolution(ctx, path_ctx, name, def);
acc.add_path_resolution(ctx, path_ctx, name, def, vec![]);
}
}
}
Expand Down Expand Up @@ -164,7 +164,7 @@ pub(crate) fn complete_pattern_path(
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
Qualified::No => {
// this will only be hit if there are brackets or braces, otherwise this will be parsed as an ident pattern
ctx.process_all_names(&mut |name, res| {
ctx.process_all_names(&mut |name, res, doc_aliases| {
// FIXME: we should check what kind of pattern we are in and filter accordingly
let add_completion = match res {
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
Expand All @@ -175,7 +175,7 @@ pub(crate) fn complete_pattern_path(
_ => false,
};
if add_completion {
acc.add_path_resolution(ctx, path_ctx, name, res);
acc.add_path_resolution(ctx, path_ctx, name, res, doc_aliases);
}
});

Expand Down
Loading

0 comments on commit 265f830

Please sign in to comment.