Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 0863389

Browse files
Add doc-alias based completion
1 parent 7a98e24 commit 0863389

File tree

18 files changed

+330
-36
lines changed

18 files changed

+330
-36
lines changed

crates/hir-def/src/attr.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use cfg::{CfgExpr, CfgOptions};
77
use either::Either;
88
use hir_expand::{
99
attrs::{collect_attrs, Attr, AttrId, RawAttrs},
10+
name::{AsName, Name},
1011
HirFileId, InFile,
1112
};
1213
use itertools::Itertools;
@@ -238,6 +239,17 @@ impl Attrs {
238239
})
239240
}
240241

242+
pub fn doc_exprs(&self) -> Vec<DocExpr> {
243+
self.by_key("doc").tt_values().map(DocExpr::parse).collect()
244+
}
245+
246+
pub fn doc_aliases(&self) -> Vec<SmolStr> {
247+
self.doc_exprs()
248+
.into_iter()
249+
.flat_map(|doc_expr| doc_expr.aliases())
250+
.collect()
251+
}
252+
241253
pub fn is_proc_macro(&self) -> bool {
242254
self.by_key("proc_macro").exists()
243255
}
@@ -251,6 +263,106 @@ impl Attrs {
251263
}
252264
}
253265

266+
use std::slice::Iter as SliceIter;
267+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
268+
pub enum DocAtom {
269+
/// eg. `#[doc(hidden)]`
270+
Flag(SmolStr),
271+
/// eg. `#[doc(alias = "x")]`
272+
///
273+
/// Note that a key can have multiple values that are all considered "active" at the same time.
274+
/// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`.
275+
KeyValue { key: SmolStr, value: SmolStr },
276+
}
277+
278+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
279+
// #[cfg_attr(test, derive(derive_arbitrary::Arbitrary))]
280+
pub enum DocExpr {
281+
Invalid,
282+
/// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]`
283+
Atom(DocAtom),
284+
/// eg. `#[doc(alias("x", "y"))]`
285+
Alias(Vec<SmolStr>),
286+
}
287+
288+
impl From<DocAtom> for DocExpr {
289+
fn from(atom: DocAtom) -> Self {
290+
DocExpr::Atom(atom)
291+
}
292+
}
293+
294+
impl DocExpr {
295+
pub fn parse<S>(tt: &tt::Subtree<S>) -> DocExpr {
296+
next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid)
297+
}
298+
299+
pub fn aliases(self) -> Vec<SmolStr> {
300+
match self {
301+
DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => {
302+
vec![value]
303+
}
304+
DocExpr::Alias(aliases) => aliases,
305+
_ => vec![],
306+
}
307+
}
308+
}
309+
310+
fn next_doc_expr<S>(it: &mut SliceIter<'_, tt::TokenTree<S>>) -> Option<DocExpr> {
311+
let name = match it.next() {
312+
None => return None,
313+
Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(),
314+
Some(_) => return Some(DocExpr::Invalid),
315+
};
316+
317+
// Peek
318+
let ret = match it.as_slice().first() {
319+
Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
320+
match it.as_slice().get(1) {
321+
Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
322+
it.next();
323+
it.next();
324+
// FIXME: escape? raw string?
325+
let value =
326+
SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
327+
DocAtom::KeyValue { key: name, value }.into()
328+
}
329+
_ => return Some(DocExpr::Invalid),
330+
}
331+
}
332+
Some(tt::TokenTree::Subtree(subtree)) => {
333+
it.next();
334+
let subs = parse_comma_sep(subtree);
335+
match name.as_str() {
336+
"alias" => DocExpr::Alias(subs),
337+
_ => DocExpr::Invalid,
338+
}
339+
}
340+
_ => DocAtom::Flag(name).into(),
341+
};
342+
343+
// Eat comma separator
344+
if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() {
345+
if punct.char == ',' {
346+
it.next();
347+
}
348+
}
349+
Some(ret)
350+
}
351+
352+
fn parse_comma_sep<S>(subtree: &tt::Subtree<S>) -> Vec<SmolStr> {
353+
subtree
354+
.token_trees
355+
.iter()
356+
.filter_map(|tt| match tt {
357+
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
358+
// FIXME: escape? raw string?
359+
Some(SmolStr::new(lit.text.trim_start_matches('"').trim_end_matches('"')))
360+
}
361+
_ => None,
362+
})
363+
.collect()
364+
}
365+
254366
impl AttrsWithOwner {
255367
pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
256368
// FIXME: this should use `Trace` to avoid duplication in `source_map` below

crates/hir-def/src/attr_tests.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//! This module contains tests for doc-expression parsing.
2+
//! Currently, it tests `#[doc(hidden)]` and `#[doc(alias)]`.
3+
4+
use mbe::syntax_node_to_token_tree;
5+
use syntax::{ast, AstNode};
6+
7+
use crate::attr::{DocAtom, DocExpr};
8+
9+
fn assert_parse_result(input: &str, expected: DocExpr) {
10+
let (tt, _) = {
11+
let source_file = ast::SourceFile::parse(input).ok().unwrap();
12+
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
13+
syntax_node_to_token_tree(tt.syntax())
14+
};
15+
let cfg = DocExpr::parse(&tt);
16+
assert_eq!(cfg, expected);
17+
}
18+
19+
#[test]
20+
fn test_doc_expr_parser() {
21+
assert_parse_result("#![doc(hidden)]", DocAtom::Flag("hidden".into()).into());
22+
23+
assert_parse_result(
24+
r#"#![doc(alias = "foo")]"#,
25+
DocAtom::KeyValue { key: "alias".into(), value: "foo".into() }.into(),
26+
);
27+
28+
assert_parse_result(r#"#![doc(alias("foo"))]"#, DocExpr::Alias(["foo".into()].into()));
29+
assert_parse_result(
30+
r#"#![doc(alias("foo", "bar", "baz"))]"#,
31+
DocExpr::Alias(["foo".into(), "bar".into(), "baz".into()].into()),
32+
);
33+
34+
assert_parse_result(
35+
r#"
36+
#[doc(alias("Bar", "Qux"))]
37+
struct Foo;"#,
38+
DocExpr::Alias(["Bar".into(), "Qux".into()].into()),
39+
);
40+
}

crates/hir-def/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ pub mod import_map;
5353
mod test_db;
5454
#[cfg(test)]
5555
mod macro_expansion_tests;
56+
#[cfg(test)]
57+
mod attr_tests;
5658
mod pretty;
5759

5860
use std::{

crates/hir/src/semantics.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,6 +1644,7 @@ impl<'a> SemanticsScope<'a> {
16441644
VisibleTraits(resolver.traits_in_scope(self.db.upcast()))
16451645
}
16461646

1647+
/// Calls the passed closure `f` on all names in scope.
16471648
pub fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
16481649
let scope = self.resolver.names_in_scope(self.db.upcast());
16491650
for (name, entries) in scope {

crates/ide-completion/src/completions.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,9 @@ impl Completions {
165165
ctx: &CompletionContext<'_>,
166166
path_ctx: &PathCompletionCtx,
167167
) {
168-
ctx.process_all_names(&mut |name, res| match res {
168+
ctx.process_all_names(&mut |name, res, doc_aliases| match res {
169169
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => {
170-
self.add_module(ctx, path_ctx, m, name);
170+
self.add_module(ctx, path_ctx, m, name, doc_aliases);
171171
}
172172
_ => (),
173173
});
@@ -179,6 +179,7 @@ impl Completions {
179179
path_ctx: &PathCompletionCtx,
180180
local_name: hir::Name,
181181
resolution: hir::ScopeDef,
182+
doc_aliases: Vec<syntax::SmolStr>,
182183
) {
183184
let is_private_editable = match ctx.def_is_visible(&resolution) {
184185
Visible::Yes => false,
@@ -187,7 +188,9 @@ impl Completions {
187188
};
188189
self.add(
189190
render_path_resolution(
190-
RenderContext::new(ctx).private_editable(is_private_editable),
191+
RenderContext::new(ctx)
192+
.private_editable(is_private_editable)
193+
.doc_aliases(doc_aliases),
191194
path_ctx,
192195
local_name,
193196
resolution,
@@ -236,12 +239,14 @@ impl Completions {
236239
path_ctx: &PathCompletionCtx,
237240
module: hir::Module,
238241
local_name: hir::Name,
242+
doc_aliases: Vec<syntax::SmolStr>,
239243
) {
240244
self.add_path_resolution(
241245
ctx,
242246
path_ctx,
243247
local_name,
244248
hir::ScopeDef::ModuleDef(module.into()),
249+
doc_aliases,
245250
);
246251
}
247252

crates/ide-completion/src/completions/attribute.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ pub(crate) fn complete_attribute_path(
9393
acc.add_macro(ctx, path_ctx, m, name)
9494
}
9595
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
96-
acc.add_module(ctx, path_ctx, m, name)
96+
acc.add_module(ctx, path_ctx, m, name, vec![])
9797
}
9898
_ => (),
9999
}
@@ -104,12 +104,12 @@ pub(crate) fn complete_attribute_path(
104104
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
105105
// only show modules in a fresh UseTree
106106
Qualified::No => {
107-
ctx.process_all_names(&mut |name, def| match def {
107+
ctx.process_all_names(&mut |name, def, doc_aliases| match def {
108108
hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_attr(ctx.db) => {
109109
acc.add_macro(ctx, path_ctx, m, name)
110110
}
111111
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
112-
acc.add_module(ctx, path_ctx, m, name)
112+
acc.add_module(ctx, path_ctx, m, name, doc_aliases)
113113
}
114114
_ => (),
115115
});

crates/ide-completion/src/completions/attribute/derive.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub(crate) fn complete_derive_path(
3434
acc.add_macro(ctx, path_ctx, mac, name)
3535
}
3636
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
37-
acc.add_module(ctx, path_ctx, m, name)
37+
acc.add_module(ctx, path_ctx, m, name, vec![])
3838
}
3939
_ => (),
4040
}
@@ -43,15 +43,15 @@ pub(crate) fn complete_derive_path(
4343
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
4444
// only show modules in a fresh UseTree
4545
Qualified::No => {
46-
ctx.process_all_names(&mut |name, def| {
46+
ctx.process_all_names(&mut |name, def, doc_aliases| {
4747
let mac = match def {
4848
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
4949
if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
5050
{
5151
mac
5252
}
5353
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
54-
return acc.add_module(ctx, path_ctx, m, name);
54+
return acc.add_module(ctx, path_ctx, m, name, doc_aliases);
5555
}
5656
_ => return,
5757
};

crates/ide-completion/src/completions/expr.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ pub(crate) fn complete_expr_path(
8888
let module_scope = module.scope(ctx.db, Some(ctx.module));
8989
for (name, def) in module_scope {
9090
if scope_def_applicable(def) {
91-
acc.add_path_resolution(ctx, path_ctx, name, def);
91+
acc.add_path_resolution(ctx, path_ctx, name, def, vec![]);
9292
}
9393
}
9494
}
@@ -212,20 +212,22 @@ pub(crate) fn complete_expr_path(
212212
}
213213
}
214214
}
215-
ctx.process_all_names(&mut |name, def| match def {
215+
ctx.process_all_names(&mut |name, def, doc_aliases| match def {
216216
ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
217217
let assocs = t.items_with_supertraits(ctx.db);
218218
match &*assocs {
219219
// traits with no assoc items are unusable as expressions since
220220
// there is no associated item path that can be constructed with them
221221
[] => (),
222222
// FIXME: Render the assoc item with the trait qualified
223-
&[_item] => acc.add_path_resolution(ctx, path_ctx, name, def),
223+
&[_item] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
224224
// FIXME: Append `::` to the thing here, since a trait on its own won't work
225-
[..] => acc.add_path_resolution(ctx, path_ctx, name, def),
225+
[..] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
226226
}
227227
}
228-
_ if scope_def_applicable(def) => acc.add_path_resolution(ctx, path_ctx, name, def),
228+
_ if scope_def_applicable(def) => {
229+
acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases)
230+
}
229231
_ => (),
230232
});
231233

crates/ide-completion/src/completions/item_list.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub(crate) fn complete_item_list(
4545
acc.add_macro(ctx, path_ctx, m, name)
4646
}
4747
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
48-
acc.add_module(ctx, path_ctx, m, name)
48+
acc.add_module(ctx, path_ctx, m, name, vec![])
4949
}
5050
_ => (),
5151
}
@@ -55,12 +55,12 @@ pub(crate) fn complete_item_list(
5555
}
5656
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
5757
Qualified::No if ctx.qualifier_ctx.none() => {
58-
ctx.process_all_names(&mut |name, def| match def {
58+
ctx.process_all_names(&mut |name, def, doc_aliases| match def {
5959
hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_fn_like(ctx.db) => {
6060
acc.add_macro(ctx, path_ctx, m, name)
6161
}
6262
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
63-
acc.add_module(ctx, path_ctx, m, name)
63+
acc.add_module(ctx, path_ctx, m, name, doc_aliases)
6464
}
6565
_ => (),
6666
});

crates/ide-completion/src/completions/pattern.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ pub(crate) fn complete_pattern(
6464

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

129129
if add_resolution {
130-
acc.add_path_resolution(ctx, path_ctx, name, def);
130+
acc.add_path_resolution(ctx, path_ctx, name, def, vec![]);
131131
}
132132
}
133133
}
@@ -164,7 +164,7 @@ pub(crate) fn complete_pattern_path(
164164
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
165165
Qualified::No => {
166166
// this will only be hit if there are brackets or braces, otherwise this will be parsed as an ident pattern
167-
ctx.process_all_names(&mut |name, res| {
167+
ctx.process_all_names(&mut |name, res, doc_aliases| {
168168
// FIXME: we should check what kind of pattern we are in and filter accordingly
169169
let add_completion = match res {
170170
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
@@ -175,7 +175,7 @@ pub(crate) fn complete_pattern_path(
175175
_ => false,
176176
};
177177
if add_completion {
178-
acc.add_path_resolution(ctx, path_ctx, name, res);
178+
acc.add_path_resolution(ctx, path_ctx, name, res, doc_aliases);
179179
}
180180
});
181181

0 commit comments

Comments
 (0)