From 0863389dd10ed84072cbcc27be6c9007420fa69f Mon Sep 17 00:00:00 2001 From: hecatia-elegua <108802164+hecatia-elegua@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:08:25 +0200 Subject: [PATCH 1/6] Add doc-alias based completion --- crates/hir-def/src/attr.rs | 112 ++++++++++++++++++ crates/hir-def/src/attr_tests.rs | 40 +++++++ crates/hir-def/src/lib.rs | 2 + crates/hir/src/semantics.rs | 1 + crates/ide-completion/src/completions.rs | 11 +- .../src/completions/attribute.rs | 6 +- .../src/completions/attribute/derive.rs | 6 +- crates/ide-completion/src/completions/expr.rs | 12 +- .../src/completions/item_list.rs | 6 +- .../ide-completion/src/completions/pattern.rs | 8 +- crates/ide-completion/src/completions/type.rs | 10 +- crates/ide-completion/src/completions/use_.rs | 4 +- crates/ide-completion/src/completions/vis.rs | 2 +- crates/ide-completion/src/context.rs | 19 ++- crates/ide-completion/src/item.rs | 8 ++ crates/ide-completion/src/lib.rs | 2 +- crates/ide-completion/src/render.rs | 20 +++- crates/ide-completion/src/tests/special.rs | 97 +++++++++++++++ 18 files changed, 330 insertions(+), 36 deletions(-) create mode 100644 crates/hir-def/src/attr_tests.rs diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index 200072c172ebe..45da0c8af5a4a 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -7,6 +7,7 @@ use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ attrs::{collect_attrs, Attr, AttrId, RawAttrs}, + name::{AsName, Name}, HirFileId, InFile, }; use itertools::Itertools; @@ -238,6 +239,17 @@ impl Attrs { }) } + pub fn doc_exprs(&self) -> Vec { + self.by_key("doc").tt_values().map(DocExpr::parse).collect() + } + + pub fn doc_aliases(&self) -> Vec { + self.doc_exprs() + .into_iter() + .flat_map(|doc_expr| doc_expr.aliases()) + .collect() + } + pub fn is_proc_macro(&self) -> bool { self.by_key("proc_macro").exists() } @@ -251,6 +263,106 @@ 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 }, +} + +#[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), +} + +impl From for DocExpr { + fn from(atom: DocAtom) -> Self { + DocExpr::Atom(atom) + } +} + +impl DocExpr { + pub fn parse(tt: &tt::Subtree) -> DocExpr { + next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid) + } + + pub fn aliases(self) -> Vec { + match self { + DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => { + vec![value] + } + DocExpr::Alias(aliases) => aliases, + _ => vec![], + } + } +} + +fn next_doc_expr(it: &mut SliceIter<'_, tt::TokenTree>) -> Option { + 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(subtree: &tt::Subtree) -> Vec { + 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 { // FIXME: this should use `Trace` to avoid duplication in `source_map` below diff --git a/crates/hir-def/src/attr_tests.rs b/crates/hir-def/src/attr_tests.rs new file mode 100644 index 0000000000000..e4c8d446af7bd --- /dev/null +++ b/crates/hir-def/src/attr_tests.rs @@ -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()), + ); +} diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 8c2e93f090597..59d672d003284 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -53,6 +53,8 @@ pub mod import_map; mod test_db; #[cfg(test)] mod macro_expansion_tests; +#[cfg(test)] +mod attr_tests; mod pretty; use std::{ diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 9709970db1eec..2b2a2966c1c38 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -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 { diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index c3136f6df4b39..b6a066f4f5180 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -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); } _ => (), }); @@ -179,6 +179,7 @@ impl Completions { path_ctx: &PathCompletionCtx, local_name: hir::Name, resolution: hir::ScopeDef, + doc_aliases: Vec, ) { let is_private_editable = match ctx.def_is_visible(&resolution) { Visible::Yes => false, @@ -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, @@ -236,12 +239,14 @@ impl Completions { path_ctx: &PathCompletionCtx, module: hir::Module, local_name: hir::Name, + doc_aliases: Vec, ) { self.add_path_resolution( ctx, path_ctx, local_name, hir::ScopeDef::ModuleDef(module.into()), + doc_aliases, ); } diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs index bb950c76f8831..13c5832a519b1 100644 --- a/crates/ide-completion/src/completions/attribute.rs +++ b/crates/ide-completion/src/completions/attribute.rs @@ -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![]) } _ => (), } @@ -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) } _ => (), }); diff --git a/crates/ide-completion/src/completions/attribute/derive.rs b/crates/ide-completion/src/completions/attribute/derive.rs index 793c22630bf89..a92fe6c7bc1a2 100644 --- a/crates/ide-completion/src/completions/attribute/derive.rs +++ b/crates/ide-completion/src/completions/attribute/derive.rs @@ -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![]) } _ => (), } @@ -43,7 +43,7 @@ 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) => @@ -51,7 +51,7 @@ pub(crate) fn complete_derive_path( 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, }; diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index cfe4787f734d7..70c91e6a10269 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -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![]); } } } @@ -212,7 +212,7 @@ 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 { @@ -220,12 +220,14 @@ pub(crate) fn complete_expr_path( // 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) + } _ => (), }); diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index 60d05ae46b916..5ea6a49b1ae15 100644 --- a/crates/ide-completion/src/completions/item_list.rs +++ b/crates/ide-completion/src/completions/item_list.rs @@ -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![]) } _ => (), } @@ -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) } _ => (), }); diff --git a/crates/ide-completion/src/completions/pattern.rs b/crates/ide-completion/src/completions/pattern.rs index 58d5bf114cca4..40b2c831a5d01 100644 --- a/crates/ide-completion/src/completions/pattern.rs +++ b/crates/ide-completion/src/completions/pattern.rs @@ -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)) => { @@ -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![]); } } } @@ -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), @@ -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); } }); diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index 69c05a76df4ce..2ad9520cd6edc 100644 --- a/crates/ide-completion/src/completions/type.rs +++ b/crates/ide-completion/src/completions/type.rs @@ -85,7 +85,7 @@ pub(crate) fn complete_type_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![]); } } } @@ -141,7 +141,7 @@ pub(crate) fn complete_type_path( match location { TypeLocation::TypeBound => { acc.add_nameref_keywords_with_colon(ctx); - ctx.process_all_names(&mut |name, res| { + ctx.process_all_names(&mut |name, res, doc_aliases| { let add_resolution = match res { ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => { mac.is_fn_like(ctx.db) @@ -152,7 +152,7 @@ pub(crate) fn complete_type_path( _ => false, }; if add_resolution { - acc.add_path_resolution(ctx, path_ctx, name, res); + acc.add_path_resolution(ctx, path_ctx, name, res, doc_aliases); } }); return; @@ -215,9 +215,9 @@ pub(crate) fn complete_type_path( }; acc.add_nameref_keywords_with_colon(ctx); - ctx.process_all_names(&mut |name, def| { + ctx.process_all_names(&mut |name, def, doc_aliases| { if scope_def_applicable(def) { - acc.add_path_resolution(ctx, path_ctx, name, def); + acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases); } }); } diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs index 2555c34aa7477..546a1f4c499cf 100644 --- a/crates/ide-completion/src/completions/use_.rs +++ b/crates/ide-completion/src/completions/use_.rs @@ -91,10 +91,10 @@ pub(crate) fn complete_use_path( // only show modules and non-std enum in a fresh UseTree Qualified::No => { cov_mark::hit!(unqualified_path_selected_only); - ctx.process_all_names(&mut |name, res| { + ctx.process_all_names(&mut |name, res, doc_aliases| { match res { ScopeDef::ModuleDef(hir::ModuleDef::Module(module)) => { - acc.add_module(ctx, path_ctx, module, name); + acc.add_module(ctx, path_ctx, module, name, doc_aliases); } ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => { // exclude prelude enum diff --git a/crates/ide-completion/src/completions/vis.rs b/crates/ide-completion/src/completions/vis.rs index 5e6cf4bf9a521..e0a959ad0b340 100644 --- a/crates/ide-completion/src/completions/vis.rs +++ b/crates/ide-completion/src/completions/vis.rs @@ -23,7 +23,7 @@ pub(crate) fn complete_vis_path( if let Some(next) = next_towards_current { if let Some(name) = next.name(ctx.db) { cov_mark::hit!(visibility_qualified); - acc.add_module(ctx, path_ctx, next, name); + acc.add_module(ctx, path_ctx, next, name, vec![]); } } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 8cbf89e9c3019..7ad0e10062bc5 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -491,21 +491,22 @@ impl<'a> CompletionContext<'a> { ); } - /// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items. - pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) { + /// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items and + /// passes all doc-aliases along, to funnel it into [`Completions::add_path_resolution`]. + pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef, Vec)) { let _p = profile::span("CompletionContext::process_all_names"); self.scope.process_all_names(&mut |name, def| { if self.is_scope_def_hidden(def) { return; } - - f(name, def); + let doc_aliases = self.doc_aliases(def); + f(name, def, doc_aliases); }); } pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) { let _p = profile::span("CompletionContext::process_all_names_raw"); - self.scope.process_all_names(&mut |name, def| f(name, def)); + self.scope.process_all_names(f); } fn is_scope_def_hidden(&self, scope_def: ScopeDef) -> bool { @@ -545,6 +546,14 @@ impl<'a> CompletionContext<'a> { // `doc(hidden)` items are only completed within the defining crate. self.krate != defining_crate && attrs.has_doc_hidden() } + + pub fn doc_aliases(&self, scope_def: ScopeDef) -> Vec { + if let Some(attrs) = scope_def.attrs(self.db) { + attrs.doc_aliases() + } else { + vec![] + } + } } // CompletionContext construction diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index bb9fa7ccaccc6..a3b46ef2bbcfb 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -353,6 +353,7 @@ impl CompletionItem { relevance: CompletionRelevance::default(), ref_match: None, imports_to_add: Default::default(), + doc_aliases: None, } } @@ -385,6 +386,7 @@ pub(crate) struct Builder { source_range: TextRange, imports_to_add: SmallVec<[LocatedImport; 1]>, trait_name: Option, + doc_aliases: Option, label: SmolStr, insert_text: Option, is_snippet: bool, @@ -424,6 +426,8 @@ impl Builder { } } else if let Some(trait_name) = self.trait_name { label = SmolStr::from(format!("{label} (as {trait_name})")); + } else if let Some(doc_aliases) = self.doc_aliases { + label = SmolStr::from(format!("{label} (alias {doc_aliases})")); } let text_edit = match self.text_edit { @@ -459,6 +463,10 @@ impl Builder { self.trait_name = Some(trait_name); self } + pub(crate) fn doc_aliases(&mut self, doc_aliases: SmolStr) -> &mut Builder { + self.doc_aliases = Some(doc_aliases); + self + } pub(crate) fn insert_text(&mut self, insert_text: impl Into) -> &mut Builder { self.insert_text = Some(insert_text.into()); self diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 6fe7811140399..a06b7e9577858 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -97,7 +97,7 @@ pub use crate::{ /// Main entry point for completion. We run completion as a two-phase process. /// -/// First, we look at the position and collect a so-called `CompletionContext. +/// First, we look at the position and collect a so-called `CompletionContext`. /// This is a somewhat messy process, because, during completion, syntax tree is /// incomplete and can look really weird. /// diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index c1f51aabb9676..514a68472665a 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -14,6 +14,7 @@ use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef}; use ide_db::{ helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind, }; +use itertools::Itertools; use syntax::{AstNode, SmolStr, SyntaxKind, TextRange}; use crate::{ @@ -32,11 +33,17 @@ pub(crate) struct RenderContext<'a> { completion: &'a CompletionContext<'a>, is_private_editable: bool, import_to_add: Option, + doc_aliases: Vec, } impl<'a> RenderContext<'a> { pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> { - RenderContext { completion, is_private_editable: false, import_to_add: None } + RenderContext { + completion, + is_private_editable: false, + import_to_add: None, + doc_aliases: vec![], + } } pub(crate) fn private_editable(mut self, private_editable: bool) -> Self { @@ -49,6 +56,11 @@ impl<'a> RenderContext<'a> { self } + pub(crate) fn doc_aliases(mut self, doc_aliases: Vec) -> Self { + self.doc_aliases = doc_aliases; + self + } + fn snippet_cap(&self) -> Option { self.completion.config.snippet_cap } @@ -348,6 +360,12 @@ fn render_resolution_simple_( if let Some(import_to_add) = ctx.import_to_add { item.add_import(import_to_add); } + + let doc_aliases = ctx.doc_aliases; + if !doc_aliases.is_empty() { + let doc_aliases = doc_aliases.into_iter().join(", ").into(); + item.doc_aliases(doc_aliases); + } item } diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs index f8a6f6cd3ed06..1749e8e70f810 100644 --- a/crates/ide-completion/src/tests/special.rs +++ b/crates/ide-completion/src/tests/special.rs @@ -989,3 +989,100 @@ fn foo { crate::::$0 } expect![""], ) } + +#[test] +fn completes_struct_via_doc_alias_in_fn_body() { + check( + r#" +#[doc(alias = "Bar")] +struct Foo; + +fn here_we_go() { + $0 +} +"#, + expect![[r#" + fn here_we_go() fn() + st Foo (alias Bar) + bt u32 + kw const + kw crate:: + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); +} + +#[test] +fn completes_struct_via_multiple_doc_aliases_in_fn_body() { + check( + r#" +#[doc(alias("Bar", "Qux"))] +#[doc(alias = "Baz")] +struct Foo; + +fn here_we_go() { + B$0 +} +"#, + expect![[r#" + fn here_we_go() fn() + st Foo (alias Bar, Qux, Baz) + bt u32 + kw const + kw crate:: + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); +} From c351f6bf43a1f8b07ef17c0cf993c2649bb53686 Mon Sep 17 00:00:00 2001 From: hecatia-elegua <108802164+hecatia-elegua@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:27:20 +0200 Subject: [PATCH 2/6] Fix --- crates/hir-def/src/attr.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index 45da0c8af5a4a..a13f660d7e964 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -7,7 +7,6 @@ use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ attrs::{collect_attrs, Attr, AttrId, RawAttrs}, - name::{AsName, Name}, HirFileId, InFile, }; use itertools::Itertools; From 53afac72d2ae052d6117fb0ba8b29458b4cafa74 Mon Sep 17 00:00:00 2001 From: hecatia-elegua <108802164+hecatia-elegua@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:34:06 +0200 Subject: [PATCH 3/6] Fix2 --- crates/ide-completion/src/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 7ad0e10062bc5..fbc6f11060ad0 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -547,7 +547,7 @@ impl<'a> CompletionContext<'a> { self.krate != defining_crate && attrs.has_doc_hidden() } - pub fn doc_aliases(&self, scope_def: ScopeDef) -> Vec { + fn doc_aliases(&self, scope_def: ScopeDef) -> Vec { if let Some(attrs) = scope_def.attrs(self.db) { attrs.doc_aliases() } else { From ba2b48d1b89f91e5c1df94326368038cf7df4146 Mon Sep 17 00:00:00 2001 From: hecatia-elegua <108802164+hecatia-elegua@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:58:33 +0200 Subject: [PATCH 4/6] Fix3 --- crates/hir-def/src/attr.rs | 5 +---- crates/ide-completion/src/context.rs | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index a13f660d7e964..d44f84e56912e 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -243,10 +243,7 @@ impl Attrs { } pub fn doc_aliases(&self) -> Vec { - self.doc_exprs() - .into_iter() - .flat_map(|doc_expr| doc_expr.aliases()) - .collect() + self.doc_exprs().into_iter().flat_map(|doc_expr| doc_expr.aliases()).collect() } pub fn is_proc_macro(&self) -> bool { diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index fbc6f11060ad0..072e8c52637ee 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -17,7 +17,7 @@ use ide_db::{ }; use syntax::{ ast::{self, AttrKind, NameOrNameRef}, - AstNode, + AstNode, SmolStr, SyntaxKind::{self, *}, SyntaxToken, TextRange, TextSize, T, }; @@ -493,7 +493,7 @@ impl<'a> CompletionContext<'a> { /// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items and /// passes all doc-aliases along, to funnel it into [`Completions::add_path_resolution`]. - pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef, Vec)) { + pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef, Vec)) { let _p = profile::span("CompletionContext::process_all_names"); self.scope.process_all_names(&mut |name, def| { if self.is_scope_def_hidden(def) { @@ -547,7 +547,7 @@ impl<'a> CompletionContext<'a> { self.krate != defining_crate && attrs.has_doc_hidden() } - fn doc_aliases(&self, scope_def: ScopeDef) -> Vec { + fn doc_aliases(&self, scope_def: ScopeDef) -> Vec { if let Some(attrs) = scope_def.attrs(self.db) { attrs.doc_aliases() } else { From c469936aac1b1060b3d520315305891fa8cced2c Mon Sep 17 00:00:00 2001 From: hecatia-elegua <108802164+hecatia-elegua@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:35:57 +0200 Subject: [PATCH 5/6] Address review comments part 1 --- crates/hir-def/src/attr.rs | 19 +++++++++++-------- .../src/{attr_tests.rs => attr/tests.rs} | 0 crates/hir-def/src/lib.rs | 2 -- crates/ide-completion/src/context.rs | 2 +- crates/ide-completion/src/item.rs | 13 +++++++------ 5 files changed, 19 insertions(+), 17 deletions(-) rename crates/hir-def/src/{attr_tests.rs => attr/tests.rs} (100%) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index d44f84e56912e..db1c6bc818151 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -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; @@ -238,12 +241,12 @@ impl Attrs { }) } - pub fn doc_exprs(&self) -> Vec { - self.by_key("doc").tt_values().map(DocExpr::parse).collect() + pub fn doc_exprs(&self) -> impl Iterator + '_ { + self.by_key("doc").tt_values().map(DocExpr::parse) } - pub fn doc_aliases(&self) -> Vec { - self.doc_exprs().into_iter().flat_map(|doc_expr| doc_expr.aliases()).collect() + pub fn doc_aliases(&self) -> impl Iterator + '_ { + self.doc_exprs().flat_map(|doc_expr| doc_expr.aliases().to_vec()) } pub fn is_proc_macro(&self) -> bool { @@ -288,17 +291,17 @@ impl From for DocExpr { } impl DocExpr { - pub fn parse(tt: &tt::Subtree) -> DocExpr { + fn parse(tt: &tt::Subtree) -> DocExpr { next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid) } - pub fn aliases(self) -> Vec { + pub fn aliases(&self) -> &[SmolStr] { match self { DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => { - vec![value] + std::slice::from_ref(value) } DocExpr::Alias(aliases) => aliases, - _ => vec![], + _ => &[], } } } diff --git a/crates/hir-def/src/attr_tests.rs b/crates/hir-def/src/attr/tests.rs similarity index 100% rename from crates/hir-def/src/attr_tests.rs rename to crates/hir-def/src/attr/tests.rs diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 59d672d003284..8c2e93f090597 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -53,8 +53,6 @@ pub mod import_map; mod test_db; #[cfg(test)] mod macro_expansion_tests; -#[cfg(test)] -mod attr_tests; mod pretty; use std::{ diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 072e8c52637ee..f6478d2ceb27b 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -549,7 +549,7 @@ impl<'a> CompletionContext<'a> { fn doc_aliases(&self, scope_def: ScopeDef) -> Vec { if let Some(attrs) = scope_def.attrs(self.db) { - attrs.doc_aliases() + attrs.doc_aliases().collect() } else { vec![] } diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs index a3b46ef2bbcfb..c2c4a663c6123 100644 --- a/crates/ide-completion/src/item.rs +++ b/crates/ide-completion/src/item.rs @@ -45,7 +45,7 @@ pub struct CompletionItem { /// /// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it /// contains `bar` sub sequence), and `quux` will rejected. - pub lookup: Option, + pub lookup: SmolStr, /// Additional info to show in the UI pop up. pub detail: Option, @@ -359,7 +359,7 @@ impl CompletionItem { /// What string is used for filtering. pub fn lookup(&self) -> &str { - self.lookup.as_deref().unwrap_or(&self.label) + self.lookup.as_str() } pub fn ref_match(&self) -> Option<(String, text_edit::Indel, CompletionRelevance)> { @@ -415,19 +415,20 @@ impl Builder { let _p = profile::span("item::Builder::build"); let mut label = self.label; - let mut lookup = self.lookup; + let mut lookup = self.lookup.unwrap_or_else(|| label.clone()); let insert_text = self.insert_text.unwrap_or_else(|| label.to_string()); + if let Some(doc_aliases) = self.doc_aliases { + label = SmolStr::from(format!("{label} (alias {doc_aliases})")); + lookup = SmolStr::from(format!("{lookup} {doc_aliases}")); + } if let [import_edit] = &*self.imports_to_add { // snippets can have multiple imports, but normal completions only have up to one if let Some(original_path) = import_edit.original_path.as_ref() { - lookup = lookup.or_else(|| Some(label.clone())); label = SmolStr::from(format!("{label} (use {original_path})")); } } else if let Some(trait_name) = self.trait_name { label = SmolStr::from(format!("{label} (as {trait_name})")); - } else if let Some(doc_aliases) = self.doc_aliases { - label = SmolStr::from(format!("{label} (alias {doc_aliases})")); } let text_edit = match self.text_edit { From 170822b01892d58cffc1694cf14fd2ea48040681 Mon Sep 17 00:00:00 2001 From: hecatia-elegua <108802164+hecatia-elegua@users.noreply.github.com> Date: Sun, 2 Apr 2023 16:03:42 +0200 Subject: [PATCH 6/6] Add note for future me or others --- crates/hir-def/src/attr.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index db1c6bc818151..b32479a3f46b3 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -274,6 +274,7 @@ pub enum DocAtom { 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 {