Skip to content

Commit 265f830

Browse files
committed
Auto merge of rust-lang#14433 - hecatia-elegua:alias-based-completion, 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.
2 parents c3ed59c + 170822b commit 265f830

File tree

17 files changed

+334
-41
lines changed

17 files changed

+334
-41
lines changed

crates/hir-def/src/attr.rs

+112
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! A higher level attributes based on TokenTree, with also some shortcuts.
22
3+
#[cfg(test)]
4+
mod tests;
5+
36
use std::{hash::Hash, ops, sync::Arc};
47

58
use base_db::CrateId;
@@ -245,6 +248,14 @@ impl Attrs {
245248
})
246249
}
247250

251+
pub fn doc_exprs(&self) -> impl Iterator<Item = DocExpr> + '_ {
252+
self.by_key("doc").tt_values().map(DocExpr::parse)
253+
}
254+
255+
pub fn doc_aliases(&self) -> impl Iterator<Item = SmolStr> + '_ {
256+
self.doc_exprs().flat_map(|doc_expr| doc_expr.aliases().to_vec())
257+
}
258+
248259
pub fn is_proc_macro(&self) -> bool {
249260
self.by_key("proc_macro").exists()
250261
}
@@ -258,6 +269,107 @@ impl Attrs {
258269
}
259270
}
260271

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

crates/hir-def/src/attr/tests.rs

+40
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/src/semantics.rs

+1
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

+8-3
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

+3-3
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

+3-3
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

+7-5
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

+3-3
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

+4-4
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)