From 890d155ffeb3ac05ea8364c32995a3f782f32a44 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Dec 2024 01:34:52 +0200 Subject: [PATCH] Complete derive helper attributes Only their names, anything can go inside. --- crates/hir/src/semantics.rs | 16 +++++ .../src/completions/attribute.rs | 13 +++- crates/ide-completion/src/context.rs | 5 +- crates/ide-completion/src/context/analysis.rs | 17 ++++- crates/ide-completion/src/tests/attribute.rs | 64 +++++++++++++++++++ 5 files changed, 111 insertions(+), 4 deletions(-) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 46766fcc5b..65470d061b 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -510,6 +510,22 @@ impl<'db> SemanticsImpl<'db> { self.with_ctx(|ctx| ctx.has_derives(adt)) } + pub fn derive_helpers_in_scope(&self, adt: &ast::Adt) -> Option> { + let sa = self.analyze_no_infer(adt.syntax())?; + let id = self.db.ast_id_map(sa.file_id).ast_id(adt); + let result = sa + .resolver + .def_map() + .derive_helpers_in_scope(InFile::new(sa.file_id, id))? + .iter() + .map(|(name, macro_, _)| { + let macro_name = Macro::from(*macro_).name(self.db).symbol().clone(); + (name.symbol().clone(), macro_name) + }) + .collect(); + Some(result) + } + pub fn derive_helper(&self, attr: &ast::Attr) -> Option> { let adt = attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it { ast::Item::Struct(it) => Some(ast::Adt::Struct(it)), diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs index d0b489c4e8..cf5427bae3 100644 --- a/crates/ide-completion/src/completions/attribute.rs +++ b/crates/ide-completion/src/completions/attribute.rs @@ -86,10 +86,21 @@ pub(crate) fn complete_attribute_path( acc: &mut Completions, ctx: &CompletionContext<'_>, path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx, - &AttrCtx { kind, annotated_item_kind }: &AttrCtx, + &AttrCtx { kind, annotated_item_kind, ref derive_helpers }: &AttrCtx, ) { let is_inner = kind == AttrKind::Inner; + for (derive_helper, derive_name) in derive_helpers { + let mut item = CompletionItem::new( + SymbolKind::Attribute, + ctx.source_range(), + derive_helper.as_str(), + ctx.edition, + ); + item.detail(format!("derive helper of `{derive_name}`")); + item.add_to(acc, ctx.db); + } + match qualified { Qualified::With { resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))), diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index efbee39a2d..5b8d1c30a2 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -7,8 +7,8 @@ mod tests; use std::{iter, ops::ControlFlow}; use hir::{ - HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, - TypeInfo, + HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, + Symbol, Type, TypeInfo, }; use ide_db::{ base_db::SourceDatabase, famous_defs::FamousDefs, helpers::is_editable_crate, FilePosition, @@ -133,6 +133,7 @@ pub(crate) type ExistingDerives = FxHashSet; pub(crate) struct AttrCtx { pub(crate) kind: AttrKind, pub(crate) annotated_item_kind: Option, + pub(crate) derive_helpers: Vec<(Symbol, Symbol)>, } #[derive(Debug, PartialEq, Eq)] diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 468ad81ad2..a4e018b180 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -1129,7 +1129,22 @@ fn classify_name_ref( let is_trailing_outer_attr = kind != AttrKind::Inner && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next).is_none(); let annotated_item_kind = if is_trailing_outer_attr { None } else { Some(attached.kind()) }; - Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } }) + let derive_helpers = annotated_item_kind + .filter(|kind| { + matches!( + kind, + SyntaxKind::STRUCT + | SyntaxKind::ENUM + | SyntaxKind::UNION + | SyntaxKind::VARIANT + | SyntaxKind::TUPLE_FIELD + | SyntaxKind::RECORD_FIELD + ) + }) + .and_then(|_| nameref.as_ref()?.syntax().ancestors().find_map(ast::Adt::cast)) + .and_then(|adt| sema.derive_helpers_in_scope(&adt)) + .unwrap_or_default(); + Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind, derive_helpers } }) }; // Infer the path kind diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs index 45679355b4..1443ebc6c0 100644 --- a/crates/ide-completion/src/tests/attribute.rs +++ b/crates/ide-completion/src/tests/attribute.rs @@ -8,6 +8,70 @@ fn check(ra_fixture: &str, expect: Expect) { expect.assert_eq(&actual); } +#[test] +fn derive_helpers() { + check( + r#" +//- /mac.rs crate:mac +#![crate_type = "proc-macro"] + +#[proc_macro_derive(MyDerive, attributes(my_cool_helper_attribute))] +pub fn my_derive() {} + +//- /lib.rs crate:lib deps:mac +#[rustc_builtin_macro] +pub macro derive($item:item) {} + +#[derive(mac::MyDerive)] +pub struct Foo(#[m$0] i32); +"#, + expect![[r#" + at allow(…) + at automatically_derived + at cfg(…) + at cfg_attr(…) + at cold + at deny(…) + at deprecated + at derive macro derive + at derive(…) + at doc = "…" + at doc(alias = "…") + at doc(hidden) + at expect(…) + at export_name = "…" + at forbid(…) + at global_allocator + at ignore = "…" + at inline + at link + at link_name = "…" + at link_section = "…" + at macro_export + at macro_use + at must_use + at my_cool_helper_attribute derive helper of `MyDerive` + at no_mangle + at non_exhaustive + at panic_handler + at path = "…" + at proc_macro + at proc_macro_attribute + at proc_macro_derive(…) + at repr(…) + at should_panic + at target_feature(enable = "…") + at test + at track_caller + at used + at warn(…) + md mac + kw crate:: + kw self:: + "#]], + ) +} + #[test] fn proc_macros() { check(