Add doc-alias based completion

This commit is contained in:
hecatia-elegua 2023-03-29 14:08:25 +02:00
parent 7a98e24777
commit 0863389dd1
18 changed files with 330 additions and 36 deletions

View File

@ -7,6 +7,7 @@ use cfg::{CfgExpr, CfgOptions};
use either::Either; use either::Either;
use hir_expand::{ use hir_expand::{
attrs::{collect_attrs, Attr, AttrId, RawAttrs}, attrs::{collect_attrs, Attr, AttrId, RawAttrs},
name::{AsName, Name},
HirFileId, InFile, HirFileId, InFile,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -238,6 +239,17 @@ impl Attrs {
}) })
} }
pub fn doc_exprs(&self) -> Vec<DocExpr> {
self.by_key("doc").tt_values().map(DocExpr::parse).collect()
}
pub fn doc_aliases(&self) -> Vec<SmolStr> {
self.doc_exprs()
.into_iter()
.flat_map(|doc_expr| doc_expr.aliases())
.collect()
}
pub fn is_proc_macro(&self) -> bool { pub fn is_proc_macro(&self) -> bool {
self.by_key("proc_macro").exists() 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<SmolStr>),
}
impl From<DocAtom> for DocExpr {
fn from(atom: DocAtom) -> Self {
DocExpr::Atom(atom)
}
}
impl DocExpr {
pub fn parse<S>(tt: &tt::Subtree<S>) -> DocExpr {
next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid)
}
pub fn aliases(self) -> Vec<SmolStr> {
match self {
DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => {
vec![value]
}
DocExpr::Alias(aliases) => aliases,
_ => vec![],
}
}
}
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 { impl AttrsWithOwner {
pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self { pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self {
// FIXME: this should use `Trace` to avoid duplication in `source_map` below // FIXME: this should use `Trace` to avoid duplication in `source_map` below

View File

@ -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()),
);
}

View File

@ -53,6 +53,8 @@ pub mod import_map;
mod test_db; mod test_db;
#[cfg(test)] #[cfg(test)]
mod macro_expansion_tests; mod macro_expansion_tests;
#[cfg(test)]
mod attr_tests;
mod pretty; mod pretty;
use std::{ use std::{

View File

@ -1644,6 +1644,7 @@ impl<'a> SemanticsScope<'a> {
VisibleTraits(resolver.traits_in_scope(self.db.upcast())) 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)) { pub fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
let scope = self.resolver.names_in_scope(self.db.upcast()); let scope = self.resolver.names_in_scope(self.db.upcast());
for (name, entries) in scope { for (name, entries) in scope {

View File

@ -165,9 +165,9 @@ impl Completions {
ctx: &CompletionContext<'_>, ctx: &CompletionContext<'_>,
path_ctx: &PathCompletionCtx, 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) => { 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, path_ctx: &PathCompletionCtx,
local_name: hir::Name, local_name: hir::Name,
resolution: hir::ScopeDef, resolution: hir::ScopeDef,
doc_aliases: Vec<syntax::SmolStr>,
) { ) {
let is_private_editable = match ctx.def_is_visible(&resolution) { let is_private_editable = match ctx.def_is_visible(&resolution) {
Visible::Yes => false, Visible::Yes => false,
@ -187,7 +188,9 @@ impl Completions {
}; };
self.add( self.add(
render_path_resolution( 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, path_ctx,
local_name, local_name,
resolution, resolution,
@ -236,12 +239,14 @@ impl Completions {
path_ctx: &PathCompletionCtx, path_ctx: &PathCompletionCtx,
module: hir::Module, module: hir::Module,
local_name: hir::Name, local_name: hir::Name,
doc_aliases: Vec<syntax::SmolStr>,
) { ) {
self.add_path_resolution( self.add_path_resolution(
ctx, ctx,
path_ctx, path_ctx,
local_name, local_name,
hir::ScopeDef::ModuleDef(module.into()), hir::ScopeDef::ModuleDef(module.into()),
doc_aliases,
); );
} }

View File

@ -93,7 +93,7 @@ pub(crate) fn complete_attribute_path(
acc.add_macro(ctx, path_ctx, m, name) acc.add_macro(ctx, path_ctx, m, name)
} }
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { 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), Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
// only show modules in a fresh UseTree // only show modules in a fresh UseTree
Qualified::No => { 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) => { hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_attr(ctx.db) => {
acc.add_macro(ctx, path_ctx, m, name) acc.add_macro(ctx, path_ctx, m, name)
} }
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { 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)
} }
_ => (), _ => (),
}); });

View File

@ -34,7 +34,7 @@ pub(crate) fn complete_derive_path(
acc.add_macro(ctx, path_ctx, mac, name) acc.add_macro(ctx, path_ctx, mac, name)
} }
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { 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), Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
// only show modules in a fresh UseTree // only show modules in a fresh UseTree
Qualified::No => { Qualified::No => {
ctx.process_all_names(&mut |name, def| { ctx.process_all_names(&mut |name, def, doc_aliases| {
let mac = match def { let mac = match def {
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) => if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
@ -51,7 +51,7 @@ pub(crate) fn complete_derive_path(
mac mac
} }
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { 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, _ => return,
}; };

View File

@ -88,7 +88,7 @@ pub(crate) fn complete_expr_path(
let module_scope = module.scope(ctx.db, Some(ctx.module)); let module_scope = module.scope(ctx.db, Some(ctx.module));
for (name, def) in module_scope { for (name, def) in module_scope {
if scope_def_applicable(def) { 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)) => { ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
let assocs = t.items_with_supertraits(ctx.db); let assocs = t.items_with_supertraits(ctx.db);
match &*assocs { match &*assocs {
@ -220,12 +220,14 @@ pub(crate) fn complete_expr_path(
// there is no associated item path that can be constructed with them // there is no associated item path that can be constructed with them
[] => (), [] => (),
// FIXME: Render the assoc item with the trait qualified // 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 // 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)
}
_ => (), _ => (),
}); });

View File

@ -45,7 +45,7 @@ pub(crate) fn complete_item_list(
acc.add_macro(ctx, path_ctx, m, name) acc.add_macro(ctx, path_ctx, m, name)
} }
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { 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::Absolute => acc.add_crate_roots(ctx, path_ctx),
Qualified::No if ctx.qualifier_ctx.none() => { 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) => { hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_fn_like(ctx.db) => {
acc.add_macro(ctx, path_ctx, m, name) acc.add_macro(ctx, path_ctx, m, name)
} }
hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => { 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)
} }
_ => (), _ => (),
}); });

View File

@ -64,7 +64,7 @@ pub(crate) fn complete_pattern(
// FIXME: ideally, we should look at the type we are matching against and // FIXME: ideally, we should look at the type we are matching against and
// suggest variants + auto-imports // suggest variants + auto-imports
ctx.process_all_names(&mut |name, res| { ctx.process_all_names(&mut |name, res, _| {
let add_simple_path = match res { let add_simple_path = match res {
hir::ScopeDef::ModuleDef(def) => match def { hir::ScopeDef::ModuleDef(def) => match def {
hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => { hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
@ -127,7 +127,7 @@ pub(crate) fn complete_pattern_path(
}; };
if add_resolution { 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::Absolute => acc.add_crate_roots(ctx, path_ctx),
Qualified::No => { Qualified::No => {
// this will only be hit if there are brackets or braces, otherwise this will be parsed as an ident pattern // 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 // FIXME: we should check what kind of pattern we are in and filter accordingly
let add_completion = match res { let add_completion = match res {
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db), ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
@ -175,7 +175,7 @@ pub(crate) fn complete_pattern_path(
_ => false, _ => false,
}; };
if add_completion { if add_completion {
acc.add_path_resolution(ctx, path_ctx, name, res); acc.add_path_resolution(ctx, path_ctx, name, res, doc_aliases);
} }
}); });

View File

@ -85,7 +85,7 @@ pub(crate) fn complete_type_path(
let module_scope = module.scope(ctx.db, Some(ctx.module)); let module_scope = module.scope(ctx.db, Some(ctx.module));
for (name, def) in module_scope { for (name, def) in module_scope {
if scope_def_applicable(def) { 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 { match location {
TypeLocation::TypeBound => { TypeLocation::TypeBound => {
acc.add_nameref_keywords_with_colon(ctx); 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 { let add_resolution = match res {
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => { ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => {
mac.is_fn_like(ctx.db) mac.is_fn_like(ctx.db)
@ -152,7 +152,7 @@ pub(crate) fn complete_type_path(
_ => false, _ => false,
}; };
if add_resolution { if add_resolution {
acc.add_path_resolution(ctx, path_ctx, name, res); acc.add_path_resolution(ctx, path_ctx, name, res, doc_aliases);
} }
}); });
return; return;
@ -215,9 +215,9 @@ pub(crate) fn complete_type_path(
}; };
acc.add_nameref_keywords_with_colon(ctx); 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) { 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);
} }
}); });
} }

View File

@ -91,10 +91,10 @@ pub(crate) fn complete_use_path(
// only show modules and non-std enum in a fresh UseTree // only show modules and non-std enum in a fresh UseTree
Qualified::No => { Qualified::No => {
cov_mark::hit!(unqualified_path_selected_only); 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 { match res {
ScopeDef::ModuleDef(hir::ModuleDef::Module(module)) => { 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))) => { ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
// exclude prelude enum // exclude prelude enum

View File

@ -23,7 +23,7 @@ pub(crate) fn complete_vis_path(
if let Some(next) = next_towards_current { if let Some(next) = next_towards_current {
if let Some(name) = next.name(ctx.db) { if let Some(name) = next.name(ctx.db) {
cov_mark::hit!(visibility_qualified); cov_mark::hit!(visibility_qualified);
acc.add_module(ctx, path_ctx, next, name); acc.add_module(ctx, path_ctx, next, name, vec![]);
} }
} }

View File

@ -491,21 +491,22 @@ impl<'a> CompletionContext<'a> {
); );
} }
/// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items. /// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items and
pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) { /// 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<syntax::SmolStr>)) {
let _p = profile::span("CompletionContext::process_all_names"); let _p = profile::span("CompletionContext::process_all_names");
self.scope.process_all_names(&mut |name, def| { self.scope.process_all_names(&mut |name, def| {
if self.is_scope_def_hidden(def) { if self.is_scope_def_hidden(def) {
return; return;
} }
let doc_aliases = self.doc_aliases(def);
f(name, def); f(name, def, doc_aliases);
}); });
} }
pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) { pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
let _p = profile::span("CompletionContext::process_all_names_raw"); 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 { 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. // `doc(hidden)` items are only completed within the defining crate.
self.krate != defining_crate && attrs.has_doc_hidden() self.krate != defining_crate && attrs.has_doc_hidden()
} }
pub fn doc_aliases(&self, scope_def: ScopeDef) -> Vec<syntax::SmolStr> {
if let Some(attrs) = scope_def.attrs(self.db) {
attrs.doc_aliases()
} else {
vec![]
}
}
} }
// CompletionContext construction // CompletionContext construction

View File

@ -353,6 +353,7 @@ impl CompletionItem {
relevance: CompletionRelevance::default(), relevance: CompletionRelevance::default(),
ref_match: None, ref_match: None,
imports_to_add: Default::default(), imports_to_add: Default::default(),
doc_aliases: None,
} }
} }
@ -385,6 +386,7 @@ pub(crate) struct Builder {
source_range: TextRange, source_range: TextRange,
imports_to_add: SmallVec<[LocatedImport; 1]>, imports_to_add: SmallVec<[LocatedImport; 1]>,
trait_name: Option<SmolStr>, trait_name: Option<SmolStr>,
doc_aliases: Option<SmolStr>,
label: SmolStr, label: SmolStr,
insert_text: Option<String>, insert_text: Option<String>,
is_snippet: bool, is_snippet: bool,
@ -424,6 +426,8 @@ impl Builder {
} }
} else if let Some(trait_name) = self.trait_name { } else if let Some(trait_name) = self.trait_name {
label = SmolStr::from(format!("{label} (as {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 { let text_edit = match self.text_edit {
@ -459,6 +463,10 @@ impl Builder {
self.trait_name = Some(trait_name); self.trait_name = Some(trait_name);
self 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 { pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
self.insert_text = Some(insert_text.into()); self.insert_text = Some(insert_text.into());
self self

View File

@ -97,7 +97,7 @@ pub use crate::{
/// Main entry point for completion. We run completion as a two-phase process. /// 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 /// This is a somewhat messy process, because, during completion, syntax tree is
/// incomplete and can look really weird. /// incomplete and can look really weird.
/// ///

View File

@ -14,6 +14,7 @@ use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef};
use ide_db::{ use ide_db::{
helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind, helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind,
}; };
use itertools::Itertools;
use syntax::{AstNode, SmolStr, SyntaxKind, TextRange}; use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
use crate::{ use crate::{
@ -32,11 +33,17 @@ pub(crate) struct RenderContext<'a> {
completion: &'a CompletionContext<'a>, completion: &'a CompletionContext<'a>,
is_private_editable: bool, is_private_editable: bool,
import_to_add: Option<LocatedImport>, import_to_add: Option<LocatedImport>,
doc_aliases: Vec<SmolStr>,
} }
impl<'a> RenderContext<'a> { impl<'a> RenderContext<'a> {
pub(crate) fn new(completion: &'a CompletionContext<'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 { pub(crate) fn private_editable(mut self, private_editable: bool) -> Self {
@ -49,6 +56,11 @@ impl<'a> RenderContext<'a> {
self 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> { fn snippet_cap(&self) -> Option<SnippetCap> {
self.completion.config.snippet_cap self.completion.config.snippet_cap
} }
@ -348,6 +360,12 @@ fn render_resolution_simple_(
if let Some(import_to_add) = ctx.import_to_add { if let Some(import_to_add) = ctx.import_to_add {
item.add_import(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 item
} }

View File

@ -989,3 +989,100 @@ fn foo { crate::::$0 }
expect![""], 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
"#]],
);
}