Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #18604 from ChayimFriedman2/complete-helpers
feat: Complete derive helper attributes
Lukas Wirth 2024-12-04
parent ab652f7 · parent 890d155 · commit 99c9a99
-rw-r--r--crates/hir/src/semantics.rs16
-rw-r--r--crates/ide-completion/src/completions/attribute.rs13
-rw-r--r--crates/ide-completion/src/context.rs5
-rw-r--r--crates/ide-completion/src/context/analysis.rs17
-rw-r--r--crates/ide-completion/src/tests/attribute.rs64
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<Vec<(Symbol, Symbol)>> {
+ 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<Vec<(Macro, MacroFileId)>> {
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<hir::Macro>;
pub(crate) struct AttrCtx {
pub(crate) kind: AttrKind,
pub(crate) annotated_item_kind: Option<SyntaxKind>,
+ 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
@@ -9,6 +9,70 @@ fn check(ra_fixture: &str, expect: Expect) {
}
#[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(
r#"