diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 4d758c7df7..fe8a8018db 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1716,6 +1716,10 @@ impl MacroDef { MacroKind::Attr | MacroKind::Derive => false, } } + + pub fn is_attr(&self) -> bool { + matches!(self.kind(), MacroKind::Attr) + } } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs index 784a1a0637..446a808de8 100644 --- a/crates/ide_completion/src/completions/flyimport.rs +++ b/crates/ide_completion/src/completions/flyimport.rs @@ -1,13 +1,14 @@ //! See [`import_on_the_fly`]. +use hir::ItemInNs; use ide_db::helpers::{ - import_assets::{ImportAssets, ImportCandidate}, + import_assets::{ImportAssets, ImportCandidate, LocatedImport}, insert_use::ImportScope, }; use itertools::Itertools; use syntax::{AstNode, SyntaxNode, T}; use crate::{ - context::CompletionContext, + context::{CompletionContext, PathKind}, render::{render_resolution_with_import, RenderContext}, ImportEdit, }; @@ -135,10 +136,35 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) &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( import_assets .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind) .into_iter() + .filter(ns_filter) .filter(|import| { !ctx.is_item_hidden(&import.item_to_import) && !ctx.is_item_hidden(&import.original_item) diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs index a2662d2932..4abf7374b3 100644 --- a/crates/ide_completion/src/completions/qualified_path.rs +++ b/crates/ide_completion/src/completions/qualified_path.rs @@ -2,6 +2,7 @@ use std::iter; +use hir::ScopeDef; use rustc_hash::FxHashSet; 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) => { if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { 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() { 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); } } @@ -44,22 +45,37 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon return; } 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(next) = current_module .path_to_root(ctx.db) .into_iter() - .take_while(|&it| it != resolved) + .take_while(|&it| it != module) .next() { 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; } + 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); for (name, def) in module_scope { 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 name_ref.syntax().text() == name.to_smol_str().as_str() { // 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 { // 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 - hir::ScopeDef::ModuleDef( + ScopeDef::ModuleDef( hir::ModuleDef::Function(_) | hir::ModuleDef::Variant(_) | hir::ModuleDef::Static(_), ) - | hir::ScopeDef::Local(_) => !ctx.expects_type(), + | ScopeDef::Local(_) => !ctx.expects_type(), // 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() } _ => true, diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs index a84f0b334b..414c1d961b 100644 --- a/crates/ide_completion/src/completions/unqualified_path.rs +++ b/crates/ide_completion/src/completions/unqualified_path.rs @@ -56,6 +56,19 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC }); 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; + } _ => (), } diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index d9ddd9f2ae..b3ce1f8e9c 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -30,10 +30,11 @@ pub(crate) enum PatternRefutability { Irrefutable, } -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub(super) enum PathKind { Expr, Type, + Attr, } #[derive(Debug)] @@ -232,8 +233,7 @@ impl<'a> CompletionContext<'a> { } 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!( self.prev_sibling, Some(ImmediatePrevSibling::Attribute | ImmediatePrevSibling::Visibility) @@ -241,8 +241,7 @@ impl<'a> CompletionContext<'a> { || matches!( self.completion_location, Some( - ImmediateLocation::Attribute(_) - | ImmediateLocation::ModDeclaration(_) + ImmediateLocation::ModDeclaration(_) | ImmediateLocation::RecordPat(_) | ImmediateLocation::RecordExpr(_) | ImmediateLocation::Rename @@ -274,6 +273,10 @@ impl<'a> CompletionContext<'a> { self.path_context.as_ref().and_then(|it| it.qualifier.as_ref()) } + pub(crate) fn path_kind(&self) -> Option { + self.path_context.as_ref().and_then(|it| it.kind) + } + /// Checks if an item is visible and not `doc(hidden)` at the completion site. pub(crate) fn is_visible(&self, item: &I) -> bool where @@ -785,6 +788,7 @@ impl<'a> CompletionContext<'a> { match parent { ast::PathType(_it) => Some(PathKind::Type), ast::PathExpr(_it) => Some(PathKind::Expr), + ast::Meta(_it) => Some(PathKind::Attr), _ => None, } }; diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs index 090bb5a715..22fb1f4825 100644 --- a/crates/ide_completion/src/render/macro_.rs +++ b/crates/ide_completion/src/render/macro_.rs @@ -1,8 +1,12 @@ //! Renderer for macro invocations. +use either::Either; use hir::HasSource; use ide_db::SymbolKind; -use syntax::{display::macro_label, SmolStr}; +use syntax::{ + display::{fn_as_proc_macro_label, macro_label}, + SmolStr, +}; use crate::{ context::CallKind, @@ -35,7 +39,8 @@ impl<'a> MacroRender<'a> { let name = name.to_smol_str(); let docs = ctx.docs(macro_); 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 } } @@ -47,15 +52,23 @@ impl<'a> MacroRender<'a> { } else { 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()); if let Some(import_to_add) = import_to_add { item.add_import(import_to_add); } - let needs_bang = !(self.ctx.completion.in_use_tree() - || matches!(self.ctx.completion.path_call_kind(), Some(CallKind::Mac))); + let needs_bang = self.macro_.is_fn_like() + && !(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(); match self.ctx.snippet_cap() { @@ -84,10 +97,10 @@ impl<'a> MacroRender<'a> { } fn label(&self) -> SmolStr { - if self.needs_bang() && self.ctx.snippet_cap().is_some() { - SmolStr::from_iter([&*self.name, "!", self.bra, "…", self.ket]) - } else if self.macro_.kind() == hir::MacroKind::Derive { + if !self.macro_.is_fn_like() { self.name.clone() + } else if self.needs_bang() && self.ctx.snippet_cap().is_some() { + SmolStr::from_iter([&*self.name, "!", self.bra, "…", self.ket]) } else { self.banged_name() } @@ -98,8 +111,11 @@ impl<'a> MacroRender<'a> { } fn detail(&self) -> Option { - let ast_node = self.macro_.source(self.ctx.db())?.value.left()?; - Some(macro_label(&ast_node)) + let detail = match self.macro_.source(self.ctx.db())?.value { + Either::Left(node) => macro_label(&node), + Either::Right(node) => fn_as_proc_macro_label(&node), + }; + Some(detail) } } diff --git a/crates/ide_completion/src/tests/attribute.rs b/crates/ide_completion/src/tests/attribute.rs index 8141fab299..c3dce61e7d 100644 --- a/crates/ide_completion/src/tests/attribute.rs +++ b/crates/ide_completion/src/tests/attribute.rs @@ -9,12 +9,12 @@ fn check(ra_fixture: &str, expect: Expect) { } #[test] -fn doesnt_complete_items() { +fn proc_macros() { check( r#" -struct Foo; +//- proc_macros: identity #[$0] -use self as this; +struct Foo; "#, expect![[r#" at allow(…) @@ -29,19 +29,29 @@ use self as this; at doc(alias = "…") at must_use at no_mangle + at derive(…) + at repr(…) + at non_exhaustive + kw self + kw super + kw crate + md proc_macros "#]], ) } #[test] -fn doesnt_complete_qualified() { +fn proc_macros_qualified() { check( r#" +//- proc_macros: identity +#[proc_macros::$0] 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 forbid(…) at warn(…) + kw self + kw super + kw crate "#]], ) } @@ -90,6 +103,9 @@ fn attr_on_source_file() { at recursion_limit = "…" at type_length_limit = … at windows_subsystem = "…" + kw self + kw super + kw crate "#]], ); } @@ -113,6 +129,9 @@ fn attr_on_module() { at no_mangle at macro_use at path = "…" + kw self + kw super + kw crate "#]], ); check( @@ -131,6 +150,9 @@ fn attr_on_module() { at must_use at no_mangle at no_implicit_prelude + kw self + kw super + kw crate "#]], ); } @@ -154,6 +176,9 @@ fn attr_on_macro_rules() { at no_mangle at macro_export at macro_use + kw self + kw super + kw crate "#]], ); } @@ -175,6 +200,9 @@ fn attr_on_macro_def() { at doc(alias = "…") at must_use at no_mangle + kw self + kw super + kw crate "#]], ); } @@ -197,6 +225,9 @@ fn attr_on_extern_crate() { at must_use at no_mangle at macro_use + kw self + kw super + kw crate "#]], ); } @@ -218,6 +249,9 @@ fn attr_on_use() { at doc(alias = "…") at must_use at no_mangle + kw self + kw super + kw crate "#]], ); } @@ -239,6 +273,9 @@ fn attr_on_type_alias() { at doc(alias = "…") at must_use at no_mangle + kw self + kw super + kw crate "#]], ); } @@ -263,6 +300,9 @@ fn attr_on_struct() { at derive(…) at repr(…) at non_exhaustive + kw self + kw super + kw crate "#]], ); } @@ -287,6 +327,9 @@ fn attr_on_enum() { at derive(…) at repr(…) at non_exhaustive + kw self + kw super + kw crate "#]], ); } @@ -308,6 +351,9 @@ fn attr_on_const() { at doc(alias = "…") at must_use at no_mangle + kw self + kw super + kw crate "#]], ); } @@ -334,6 +380,9 @@ fn attr_on_static() { at link_section = "…" at global_allocator at used + kw self + kw super + kw crate "#]], ); } @@ -356,6 +405,9 @@ fn attr_on_trait() { at must_use at no_mangle at must_use + kw self + kw super + kw crate "#]], ); } @@ -378,6 +430,9 @@ fn attr_on_impl() { at must_use at no_mangle at automatically_derived + kw self + kw super + kw crate "#]], ); check( @@ -395,6 +450,9 @@ fn attr_on_impl() { at doc(alias = "…") at must_use at no_mangle + kw self + kw super + kw crate "#]], ); } @@ -417,6 +475,9 @@ fn attr_on_extern_block() { at must_use at no_mangle at link + kw self + kw super + kw crate "#]], ); check( @@ -435,6 +496,9 @@ fn attr_on_extern_block() { at must_use at no_mangle at link + kw self + kw super + kw crate "#]], ); } @@ -451,6 +515,9 @@ fn attr_on_variant() { at forbid(…) at warn(…) at non_exhaustive + kw self + kw super + kw crate "#]], ); } @@ -487,6 +554,9 @@ fn attr_on_fn() { at target_feature = "…" at test at track_caller + kw self + kw super + kw crate "#]], ); } @@ -503,6 +573,9 @@ fn attr_on_expr() { at deny(…) at forbid(…) at warn(…) + kw self + kw super + kw crate "#]], ); } @@ -548,6 +621,9 @@ fn attr_in_source_file_end() { at track_caller at used at warn(…) + kw self + kw super + kw crate "#]], ); } diff --git a/crates/ide_completion/src/tests/flyimport.rs b/crates/ide_completion/src/tests/flyimport.rs index 18880a67aa..ff46dda5e2 100644 --- a/crates/ide_completion/src/tests/flyimport.rs +++ b/crates/ide_completion/src/tests/flyimport.rs @@ -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; +"#, + ); +}