Skip to content

feat: Add doc-alias based completion #14433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions crates/hir-def/src/attr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! A higher level attributes based on TokenTree, with also some shortcuts.

#[cfg(test)]
mod tests;

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

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

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

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

pub fn is_proc_macro(&self) -> bool {
self.by_key("proc_macro").exists()
}
Expand All @@ -251,6 +262,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 {
// FIXME: this should use `Trace` to avoid duplication in `source_map` below
Expand Down
40 changes: 40 additions & 0 deletions crates/hir-def/src/attr/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! This module contains tests for doc-expression parsing.
//! Currently, it tests `#[doc(hidden)]` and `#[doc(alias)]`.

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

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

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

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

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

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

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

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

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

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

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

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

Expand Down
Loading