10943: feat: Enable completions for attributes r=Veykril a=Veykril



Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2021-12-05 15:14:10 +00:00 committed by GitHub
commit 6f84bbfa1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 217 additions and 34 deletions

View file

@ -1716,6 +1716,10 @@ impl MacroDef {
MacroKind::Attr | MacroKind::Derive => false, MacroKind::Attr | MacroKind::Derive => false,
} }
} }
pub fn is_attr(&self) -> bool {
matches!(self.kind(), MacroKind::Attr)
}
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]

View file

@ -1,13 +1,14 @@
//! See [`import_on_the_fly`]. //! See [`import_on_the_fly`].
use hir::ItemInNs;
use ide_db::helpers::{ use ide_db::helpers::{
import_assets::{ImportAssets, ImportCandidate}, import_assets::{ImportAssets, ImportCandidate, LocatedImport},
insert_use::ImportScope, insert_use::ImportScope,
}; };
use itertools::Itertools; use itertools::Itertools;
use syntax::{AstNode, SyntaxNode, T}; use syntax::{AstNode, SyntaxNode, T};
use crate::{ use crate::{
context::CompletionContext, context::{CompletionContext, PathKind},
render::{render_resolution_with_import, RenderContext}, render::{render_resolution_with_import, RenderContext},
ImportEdit, ImportEdit,
}; };
@ -135,10 +136,35 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
&ctx.sema, &ctx.sema,
)?; )?;
let ns_filter = |import: &LocatedImport| {
let kind = match ctx.path_kind() {
Some(kind) => kind,
None => {
return match import.original_item {
ItemInNs::Macros(mac) => mac.is_fn_like(),
_ => true,
}
}
};
match (kind, import.original_item) {
(PathKind::Expr, ItemInNs::Types(_) | ItemInNs::Values(_)) => true,
(PathKind::Type, ItemInNs::Types(_)) => true,
(PathKind::Type, ItemInNs::Values(_)) => false,
(PathKind::Expr | PathKind::Type, ItemInNs::Macros(mac)) => mac.is_fn_like(),
(PathKind::Attr, ItemInNs::Types(hir::ModuleDef::Module(_))) => true,
(PathKind::Attr, ItemInNs::Macros(mac)) => mac.is_attr(),
(PathKind::Attr, _) => false,
}
};
acc.add_all( acc.add_all(
import_assets import_assets
.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind) .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
.into_iter() .into_iter()
.filter(ns_filter)
.filter(|import| { .filter(|import| {
!ctx.is_item_hidden(&import.item_to_import) !ctx.is_item_hidden(&import.item_to_import)
&& !ctx.is_item_hidden(&import.original_item) && !ctx.is_item_hidden(&import.original_item)

View file

@ -2,6 +2,7 @@
use std::iter; use std::iter;
use hir::ScopeDef;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use syntax::{ast, AstNode}; use syntax::{ast, AstNode};
@ -31,12 +32,12 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => { Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => {
if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
for (name, def) in module.scope(ctx.db, context_module) { for (name, def) in module.scope(ctx.db, context_module) {
if let hir::ScopeDef::MacroDef(macro_def) = def { if let ScopeDef::MacroDef(macro_def) = def {
if macro_def.is_fn_like() { if macro_def.is_fn_like() {
acc.add_macro(ctx, Some(name.clone()), macro_def); acc.add_macro(ctx, Some(name.clone()), macro_def);
} }
} }
if let hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = def { if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = def {
acc.add_resolution(ctx, name, &def); acc.add_resolution(ctx, name, &def);
} }
} }
@ -44,22 +45,37 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
return; return;
} }
Some(ImmediateLocation::Visibility(_)) => { Some(ImmediateLocation::Visibility(_)) => {
if let hir::PathResolution::Def(hir::ModuleDef::Module(resolved)) = resolution { if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
if let Some(current_module) = ctx.scope.module() { if let Some(current_module) = ctx.scope.module() {
if let Some(next) = current_module if let Some(next) = current_module
.path_to_root(ctx.db) .path_to_root(ctx.db)
.into_iter() .into_iter()
.take_while(|&it| it != resolved) .take_while(|&it| it != module)
.next() .next()
{ {
if let Some(name) = next.name(ctx.db) { if let Some(name) = next.name(ctx.db) {
acc.add_resolution(ctx, name, &hir::ScopeDef::ModuleDef(next.into())); acc.add_resolution(ctx, name, &ScopeDef::ModuleDef(next.into()));
} }
} }
} }
} }
return; return;
} }
Some(ImmediateLocation::Attribute(_)) => {
if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
for (name, def) in module.scope(ctx.db, context_module) {
let add_resolution = match def {
ScopeDef::MacroDef(mac) => mac.is_attr(),
ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => true,
_ => false,
};
if add_resolution {
acc.add_resolution(ctx, name, &def);
}
}
}
return;
}
_ => (), _ => (),
} }
@ -91,7 +107,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
let module_scope = module.scope(ctx.db, context_module); let module_scope = module.scope(ctx.db, context_module);
for (name, def) in module_scope { for (name, def) in module_scope {
if ctx.in_use_tree() { if ctx.in_use_tree() {
if let hir::ScopeDef::Unknown = def { if let ScopeDef::Unknown = def {
if let Some(ast::NameLike::NameRef(name_ref)) = ctx.name_syntax.as_ref() { if let Some(ast::NameLike::NameRef(name_ref)) = ctx.name_syntax.as_ref() {
if name_ref.syntax().text() == name.to_smol_str().as_str() { if name_ref.syntax().text() == name.to_smol_str().as_str() {
// for `use self::foo$0`, don't suggest `foo` as a completion // for `use self::foo$0`, don't suggest `foo` as a completion
@ -104,16 +120,16 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
let add_resolution = match def { let add_resolution = match def {
// Don't suggest attribute macros and derives. // Don't suggest attribute macros and derives.
hir::ScopeDef::MacroDef(mac) => mac.is_fn_like(), ScopeDef::MacroDef(mac) => mac.is_fn_like(),
// no values in type places // no values in type places
hir::ScopeDef::ModuleDef( ScopeDef::ModuleDef(
hir::ModuleDef::Function(_) hir::ModuleDef::Function(_)
| hir::ModuleDef::Variant(_) | hir::ModuleDef::Variant(_)
| hir::ModuleDef::Static(_), | hir::ModuleDef::Static(_),
) )
| hir::ScopeDef::Local(_) => !ctx.expects_type(), | ScopeDef::Local(_) => !ctx.expects_type(),
// unless its a constant in a generic arg list position // unless its a constant in a generic arg list position
hir::ScopeDef::ModuleDef(hir::ModuleDef::Const(_)) => { ScopeDef::ModuleDef(hir::ModuleDef::Const(_)) => {
!ctx.expects_type() || ctx.expects_generic_arg() !ctx.expects_type() || ctx.expects_generic_arg()
} }
_ => true, _ => true,

View file

@ -56,6 +56,19 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
}); });
return; return;
} }
Some(ImmediateLocation::Attribute(_)) => {
ctx.process_all_names(&mut |name, res| {
let add_resolution = match res {
ScopeDef::MacroDef(mac) => mac.is_attr(),
ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => true,
_ => false,
};
if add_resolution {
acc.add_resolution(ctx, name, &res);
}
});
return;
}
_ => (), _ => (),
} }

View file

@ -30,10 +30,11 @@ pub(crate) enum PatternRefutability {
Irrefutable, Irrefutable,
} }
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
pub(super) enum PathKind { pub(super) enum PathKind {
Expr, Expr,
Type, Type,
Attr,
} }
#[derive(Debug)] #[derive(Debug)]
@ -232,8 +233,7 @@ impl<'a> CompletionContext<'a> {
} }
pub(crate) fn is_path_disallowed(&self) -> bool { pub(crate) fn is_path_disallowed(&self) -> bool {
self.attribute_under_caret.is_some() self.previous_token_is(T![unsafe])
|| self.previous_token_is(T![unsafe])
|| matches!( || matches!(
self.prev_sibling, self.prev_sibling,
Some(ImmediatePrevSibling::Attribute | ImmediatePrevSibling::Visibility) Some(ImmediatePrevSibling::Attribute | ImmediatePrevSibling::Visibility)
@ -241,8 +241,7 @@ impl<'a> CompletionContext<'a> {
|| matches!( || matches!(
self.completion_location, self.completion_location,
Some( Some(
ImmediateLocation::Attribute(_) ImmediateLocation::ModDeclaration(_)
| ImmediateLocation::ModDeclaration(_)
| ImmediateLocation::RecordPat(_) | ImmediateLocation::RecordPat(_)
| ImmediateLocation::RecordExpr(_) | ImmediateLocation::RecordExpr(_)
| ImmediateLocation::Rename | ImmediateLocation::Rename
@ -274,6 +273,10 @@ impl<'a> CompletionContext<'a> {
self.path_context.as_ref().and_then(|it| it.qualifier.as_ref()) self.path_context.as_ref().and_then(|it| it.qualifier.as_ref())
} }
pub(crate) fn path_kind(&self) -> Option<PathKind> {
self.path_context.as_ref().and_then(|it| it.kind)
}
/// Checks if an item is visible and not `doc(hidden)` at the completion site. /// Checks if an item is visible and not `doc(hidden)` at the completion site.
pub(crate) fn is_visible<I>(&self, item: &I) -> bool pub(crate) fn is_visible<I>(&self, item: &I) -> bool
where where
@ -785,6 +788,7 @@ impl<'a> CompletionContext<'a> {
match parent { match parent {
ast::PathType(_it) => Some(PathKind::Type), ast::PathType(_it) => Some(PathKind::Type),
ast::PathExpr(_it) => Some(PathKind::Expr), ast::PathExpr(_it) => Some(PathKind::Expr),
ast::Meta(_it) => Some(PathKind::Attr),
_ => None, _ => None,
} }
}; };

View file

@ -1,8 +1,12 @@
//! Renderer for macro invocations. //! Renderer for macro invocations.
use either::Either;
use hir::HasSource; use hir::HasSource;
use ide_db::SymbolKind; use ide_db::SymbolKind;
use syntax::{display::macro_label, SmolStr}; use syntax::{
display::{fn_as_proc_macro_label, macro_label},
SmolStr,
};
use crate::{ use crate::{
context::CallKind, context::CallKind,
@ -35,7 +39,8 @@ impl<'a> MacroRender<'a> {
let name = name.to_smol_str(); let name = name.to_smol_str();
let docs = ctx.docs(macro_); let docs = ctx.docs(macro_);
let docs_str = docs.as_ref().map_or("", |s| s.as_str()); let docs_str = docs.as_ref().map_or("", |s| s.as_str());
let (bra, ket) = guess_macro_braces(&name, docs_str); let (bra, ket) =
if macro_.is_fn_like() { guess_macro_braces(&name, docs_str) } else { ("", "") };
MacroRender { ctx, name, macro_, docs, bra, ket } MacroRender { ctx, name, macro_, docs, bra, ket }
} }
@ -47,15 +52,23 @@ impl<'a> MacroRender<'a> {
} else { } else {
Some(self.ctx.source_range()) Some(self.ctx.source_range())
}?; }?;
let mut item = CompletionItem::new(SymbolKind::Macro, source_range, self.label()); let kind = match self.macro_.kind() {
hir::MacroKind::Derive => SymbolKind::Derive,
hir::MacroKind::Attr => SymbolKind::Attribute,
hir::MacroKind::BuiltIn | hir::MacroKind::Declarative | hir::MacroKind::ProcMacro => {
SymbolKind::Macro
}
};
let mut item = CompletionItem::new(kind, source_range, self.label());
item.set_deprecated(self.ctx.is_deprecated(self.macro_)).set_detail(self.detail()); item.set_deprecated(self.ctx.is_deprecated(self.macro_)).set_detail(self.detail());
if let Some(import_to_add) = import_to_add { if let Some(import_to_add) = import_to_add {
item.add_import(import_to_add); item.add_import(import_to_add);
} }
let needs_bang = !(self.ctx.completion.in_use_tree() let needs_bang = self.macro_.is_fn_like()
|| matches!(self.ctx.completion.path_call_kind(), Some(CallKind::Mac))); && !(self.ctx.completion.in_use_tree()
|| matches!(self.ctx.completion.path_call_kind(), Some(CallKind::Mac)));
let has_parens = self.ctx.completion.path_call_kind().is_some(); let has_parens = self.ctx.completion.path_call_kind().is_some();
match self.ctx.snippet_cap() { match self.ctx.snippet_cap() {
@ -84,10 +97,10 @@ impl<'a> MacroRender<'a> {
} }
fn label(&self) -> SmolStr { fn label(&self) -> SmolStr {
if self.needs_bang() && self.ctx.snippet_cap().is_some() { if !self.macro_.is_fn_like() {
SmolStr::from_iter([&*self.name, "!", self.bra, "", self.ket])
} else if self.macro_.kind() == hir::MacroKind::Derive {
self.name.clone() self.name.clone()
} else if self.needs_bang() && self.ctx.snippet_cap().is_some() {
SmolStr::from_iter([&*self.name, "!", self.bra, "", self.ket])
} else { } else {
self.banged_name() self.banged_name()
} }
@ -98,8 +111,11 @@ impl<'a> MacroRender<'a> {
} }
fn detail(&self) -> Option<String> { fn detail(&self) -> Option<String> {
let ast_node = self.macro_.source(self.ctx.db())?.value.left()?; let detail = match self.macro_.source(self.ctx.db())?.value {
Some(macro_label(&ast_node)) Either::Left(node) => macro_label(&node),
Either::Right(node) => fn_as_proc_macro_label(&node),
};
Some(detail)
} }
} }

View file

@ -9,12 +9,12 @@ fn check(ra_fixture: &str, expect: Expect) {
} }
#[test] #[test]
fn doesnt_complete_items() { fn proc_macros() {
check( check(
r#" r#"
struct Foo; //- proc_macros: identity
#[$0] #[$0]
use self as this; struct Foo;
"#, "#,
expect![[r#" expect![[r#"
at allow() at allow()
@ -29,19 +29,29 @@ use self as this;
at doc(alias = "") at doc(alias = "")
at must_use at must_use
at no_mangle at no_mangle
at derive()
at repr()
at non_exhaustive
kw self
kw super
kw crate
md proc_macros
"#]], "#]],
) )
} }
#[test] #[test]
fn doesnt_complete_qualified() { fn proc_macros_qualified() {
check( check(
r#" r#"
//- proc_macros: identity
#[proc_macros::$0]
struct Foo; struct Foo;
#[foo::$0]
use self as this;
"#, "#,
expect![[r#""#]], expect![[r#"
at input_replace pub macro input_replace
at identity pub macro identity
"#]],
) )
} }
@ -61,6 +71,9 @@ fn with_existing_attr() {
at deny() at deny()
at forbid() at forbid()
at warn() at warn()
kw self
kw super
kw crate
"#]], "#]],
) )
} }
@ -90,6 +103,9 @@ fn attr_on_source_file() {
at recursion_limit = "" at recursion_limit = ""
at type_length_limit = at type_length_limit =
at windows_subsystem = "" at windows_subsystem = ""
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -113,6 +129,9 @@ fn attr_on_module() {
at no_mangle at no_mangle
at macro_use at macro_use
at path = "" at path = ""
kw self
kw super
kw crate
"#]], "#]],
); );
check( check(
@ -131,6 +150,9 @@ fn attr_on_module() {
at must_use at must_use
at no_mangle at no_mangle
at no_implicit_prelude at no_implicit_prelude
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -154,6 +176,9 @@ fn attr_on_macro_rules() {
at no_mangle at no_mangle
at macro_export at macro_export
at macro_use at macro_use
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -175,6 +200,9 @@ fn attr_on_macro_def() {
at doc(alias = "") at doc(alias = "")
at must_use at must_use
at no_mangle at no_mangle
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -197,6 +225,9 @@ fn attr_on_extern_crate() {
at must_use at must_use
at no_mangle at no_mangle
at macro_use at macro_use
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -218,6 +249,9 @@ fn attr_on_use() {
at doc(alias = "") at doc(alias = "")
at must_use at must_use
at no_mangle at no_mangle
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -239,6 +273,9 @@ fn attr_on_type_alias() {
at doc(alias = "") at doc(alias = "")
at must_use at must_use
at no_mangle at no_mangle
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -263,6 +300,9 @@ fn attr_on_struct() {
at derive() at derive()
at repr() at repr()
at non_exhaustive at non_exhaustive
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -287,6 +327,9 @@ fn attr_on_enum() {
at derive() at derive()
at repr() at repr()
at non_exhaustive at non_exhaustive
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -308,6 +351,9 @@ fn attr_on_const() {
at doc(alias = "") at doc(alias = "")
at must_use at must_use
at no_mangle at no_mangle
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -334,6 +380,9 @@ fn attr_on_static() {
at link_section = "" at link_section = ""
at global_allocator at global_allocator
at used at used
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -356,6 +405,9 @@ fn attr_on_trait() {
at must_use at must_use
at no_mangle at no_mangle
at must_use at must_use
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -378,6 +430,9 @@ fn attr_on_impl() {
at must_use at must_use
at no_mangle at no_mangle
at automatically_derived at automatically_derived
kw self
kw super
kw crate
"#]], "#]],
); );
check( check(
@ -395,6 +450,9 @@ fn attr_on_impl() {
at doc(alias = "") at doc(alias = "")
at must_use at must_use
at no_mangle at no_mangle
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -417,6 +475,9 @@ fn attr_on_extern_block() {
at must_use at must_use
at no_mangle at no_mangle
at link at link
kw self
kw super
kw crate
"#]], "#]],
); );
check( check(
@ -435,6 +496,9 @@ fn attr_on_extern_block() {
at must_use at must_use
at no_mangle at no_mangle
at link at link
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -451,6 +515,9 @@ fn attr_on_variant() {
at forbid() at forbid()
at warn() at warn()
at non_exhaustive at non_exhaustive
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -487,6 +554,9 @@ fn attr_on_fn() {
at target_feature = "" at target_feature = ""
at test at test
at track_caller at track_caller
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -503,6 +573,9 @@ fn attr_on_expr() {
at deny() at deny()
at forbid() at forbid()
at warn() at warn()
kw self
kw super
kw crate
"#]], "#]],
); );
} }
@ -548,6 +621,9 @@ fn attr_in_source_file_end() {
at track_caller at track_caller
at used at used
at warn() at warn()
kw self
kw super
kw crate
"#]], "#]],
); );
} }

View file

@ -1043,3 +1043,31 @@ enum Foo {
"#]], "#]],
) )
} }
#[test]
fn flyimport_attribute() {
check(
r#"
//- proc_macros:identity
#[ide$0]
struct Foo;
"#,
expect![[r#"
at identity (use proc_macros::identity) pub macro identity
"#]],
);
check_edit(
"identity",
r#"
//- proc_macros:identity
#[ide$0]
struct Foo;
"#,
r#"
use proc_macros::identity;
#[identity]
struct Foo;
"#,
);
}