]> git.lizzy.rs Git - rust.git/commitdiff
feat: Enable flyimport completions for attributes
authorLukas Wirth <lukastw97@gmail.com>
Sun, 5 Dec 2021 14:57:28 +0000 (15:57 +0100)
committerLukas Wirth <lukastw97@gmail.com>
Sun, 5 Dec 2021 14:57:28 +0000 (15:57 +0100)
crates/hir/src/lib.rs
crates/ide_completion/src/completions/flyimport.rs
crates/ide_completion/src/completions/qualified_path.rs
crates/ide_completion/src/completions/unqualified_path.rs
crates/ide_completion/src/context.rs
crates/ide_completion/src/render/macro_.rs
crates/ide_completion/src/tests/attribute.rs
crates/ide_completion/src/tests/flyimport.rs

index 4d758c7df7610b01e61e91c0eb220d71e0e858db..fe8a8018dbec007653226bbfdd36feb1adebe612 100644 (file)
@@ -1716,6 +1716,10 @@ pub fn is_fn_like(&self) -> bool {
             MacroKind::Attr | MacroKind::Derive => false,
         }
     }
+
+    pub fn is_attr(&self) -> bool {
+        matches!(self.kind(), MacroKind::Attr)
+    }
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
index 784a1a0637c017e21ee0e4d15c924db911ca7a42..446a808de84384656c572ba51d9b3fc48a777a0c 100644 (file)
@@ -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)
index a2662d2932bd999c3bcf7cdd8b06ddf4cd8e753b..4abf7374b3b2a66140a990f6dbac9761cfe88fef 100644 (file)
@@ -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,
index a84f0b334b59b32ec1847afd5820cdd8f6a3ba88..414c1d961bbf3991d640c47118f6e6dd535e315f 100644 (file)
@@ -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;
+        }
         _ => (),
     }
 
index d9ddd9f2aeb6da4edd9a78b282e6716a731a8591..b3ce1f8e9c4bca8ec94143ec897d642b5051996f 100644 (file)
@@ -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 @@ pub(crate) fn after_if(&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!(
                 self.prev_sibling,
                 Some(ImmediatePrevSibling::Attribute | ImmediatePrevSibling::Visibility)
@@ -241,8 +241,7 @@ pub(crate) fn is_path_disallowed(&self) -> bool {
             || matches!(
                 self.completion_location,
                 Some(
-                    ImmediateLocation::Attribute(_)
-                        | ImmediateLocation::ModDeclaration(_)
+                    ImmediateLocation::ModDeclaration(_)
                         | ImmediateLocation::RecordPat(_)
                         | ImmediateLocation::RecordExpr(_)
                         | ImmediateLocation::Rename
@@ -274,6 +273,10 @@ pub(crate) fn path_qual(&self) -> Option<&ast::Path> {
         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.
     pub(crate) fn is_visible<I>(&self, item: &I) -> bool
     where
@@ -785,6 +788,7 @@ fn classify_name_ref(
                 match parent {
                     ast::PathType(_it) => Some(PathKind::Type),
                     ast::PathExpr(_it) => Some(PathKind::Expr),
+                    ast::Meta(_it) => Some(PathKind::Attr),
                     _ => None,
                 }
             };
index 090bb5a71514c8358a5bce6951ed7bbbd1e678a4..22fb1f4825193cb23f0558cc81f268d29d136b35 100644 (file)
@@ -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 @@ fn new(ctx: RenderContext<'a>, name: hir::Name, macro_: hir::MacroDef) -> MacroR
         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 @@ fn render(self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> {
         } 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 @@ fn needs_bang(&self) -> bool {
     }
 
     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 @@ fn banged_name(&self) -> SmolStr {
     }
 
     fn detail(&self) -> Option<String> {
-        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)
     }
 }
 
index 8141fab299e3dc1d6678714cd8231b34e79a7020..c3dce61e7d4ea6a648faeae3b9df3fcd4b4589ac 100644 (file)
@@ -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 @@ fn doesnt_complete_items() {
             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
         "#]],
     );
 }
index 18880a67aa6ffcbb348db13ef9d51f71ed7458ef..ff46dda5e239e9a0ebd56a8069bb21f198067038 100644 (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;
+"#,
+    );
+}