about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-04-05 13:44:51 +0000
committerbors <bors@rust-lang.org>2023-04-05 13:44:51 +0000
commit265f83031f31fec1990d5e679857f9625338c601 (patch)
tree3acfa4761f72f3fa1bdd334276824cd009d24136
parentc3ed59ce1c5d0f1d8ee867ea2013dc47ccedd712 (diff)
parent170822b01892d58cffc1694cf14fd2ea48040681 (diff)
downloadrust-265f83031f31fec1990d5e679857f9625338c601.tar.gz
rust-265f83031f31fec1990d5e679857f9625338c601.zip
Auto merge of #14433 - hecatia-elegua:alias-based-completion, r=Veykril
Add doc-alias based completion

Closes #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.
-rw-r--r--crates/hir-def/src/attr.rs112
-rw-r--r--crates/hir-def/src/attr/tests.rs40
-rw-r--r--crates/hir/src/semantics.rs1
-rw-r--r--crates/ide-completion/src/completions.rs11
-rw-r--r--crates/ide-completion/src/completions/attribute.rs6
-rw-r--r--crates/ide-completion/src/completions/attribute/derive.rs6
-rw-r--r--crates/ide-completion/src/completions/expr.rs12
-rw-r--r--crates/ide-completion/src/completions/item_list.rs6
-rw-r--r--crates/ide-completion/src/completions/pattern.rs8
-rw-r--r--crates/ide-completion/src/completions/type.rs10
-rw-r--r--crates/ide-completion/src/completions/use_.rs4
-rw-r--r--crates/ide-completion/src/completions/vis.rs2
-rw-r--r--crates/ide-completion/src/context.rs21
-rw-r--r--crates/ide-completion/src/item.rs17
-rw-r--r--crates/ide-completion/src/lib.rs2
-rw-r--r--crates/ide-completion/src/render.rs20
-rw-r--r--crates/ide-completion/src/tests/special.rs97
17 files changed, 334 insertions, 41 deletions
diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs
index 860df1b68b2..4dde093f968 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;
@@ -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()
     }
@@ -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");
diff --git a/crates/hir-def/src/attr/tests.rs b/crates/hir-def/src/attr/tests.rs
new file mode 100644
index 00000000000..e4c8d446af7
--- /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/src/semantics.rs b/crates/hir/src/semantics.rs
index 9709970db1e..2b2a2966c1c 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 c3136f6df4b..b6a066f4f51 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<syntax::SmolStr>,
     ) {
         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<syntax::SmolStr>,
     ) {
         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 bb950c76f88..13c5832a519 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 793c22630bf..a92fe6c7bc1 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 cfe4787f734..70c91e6a102 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 60d05ae46b9..5ea6a49b1ae 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 58d5bf114cc..40b2c831a5d 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 69c05a76df4..2ad9520cd6e 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 2555c34aa74..546a1f4c499 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 5e6cf4bf9a5..e0a959ad0b3 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 8cbf89e9c30..f6478d2ceb2 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,
 };
@@ -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<SmolStr>)) {
         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()
     }
+
+    fn doc_aliases(&self, scope_def: ScopeDef) -> Vec<SmolStr> {
+        if let Some(attrs) = scope_def.attrs(self.db) {
+            attrs.doc_aliases().collect()
+        } else {
+            vec![]
+        }
+    }
 }
 
 // CompletionContext construction
diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs
index bb9fa7ccacc..c2c4a663c61 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<SmolStr>,
+    pub lookup: SmolStr,
 
     /// Additional info to show in the UI pop up.
     pub detail: Option<String>,
@@ -353,12 +353,13 @@ impl CompletionItem {
             relevance: CompletionRelevance::default(),
             ref_match: None,
             imports_to_add: Default::default(),
+            doc_aliases: None,
         }
     }
 
     /// 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)> {
@@ -385,6 +386,7 @@ pub(crate) struct Builder {
     source_range: TextRange,
     imports_to_add: SmallVec<[LocatedImport; 1]>,
     trait_name: Option<SmolStr>,
+    doc_aliases: Option<SmolStr>,
     label: SmolStr,
     insert_text: Option<String>,
     is_snippet: bool,
@@ -413,13 +415,16 @@ 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 {
@@ -459,6 +464,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<String>) -> &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 6fe78111403..a06b7e95778 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 c1f51aabb96..514a6847266 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<LocatedImport>,
+    doc_aliases: Vec<SmolStr>,
 }
 
 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<SmolStr>) -> Self {
+        self.doc_aliases = doc_aliases;
+        self
+    }
+
     fn snippet_cap(&self) -> Option<SnippetCap> {
         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 f8a6f6cd3ed..1749e8e70f8 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
+        "#]],
+    );
+}