From d99b81f839ec2b7e4a0eb37c96b40e6c5b050b72 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 24 Aug 2021 16:33:52 +0200 Subject: [PATCH] Expand derive macros under cursor in `Expand Macro Recursively` --- crates/hir/src/semantics.rs | 16 ++++++++++++ crates/hir/src/semantics/source_to_def.rs | 9 +++++++ crates/hir_def/src/child_by_source.rs | 7 +++++ crates/hir_def/src/item_scope.rs | 22 ++++++++++++++-- crates/hir_def/src/keys.rs | 1 + crates/hir_def/src/nameres/collector.rs | 6 +++++ crates/ide/src/expand_macro.rs | 31 +++++++++++++++++++++++ 7 files changed, 90 insertions(+), 2 deletions(-) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 000daf76c2..b0435a4bac 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -148,6 +148,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.expand_attr_macro(item) } + pub fn expand_derive_macro(&self, derive: &ast::Attr) -> Option { + self.imp.expand_derive_macro(derive) + } + pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool { self.imp.is_attr_macro_call(item) } @@ -385,6 +389,18 @@ impl<'db> SemanticsImpl<'db> { Some(node) } + fn expand_derive_macro(&self, attr: &ast::Attr) -> Option { + let item = attr.syntax().parent().and_then(ast::Item::cast)?; + let sa = self.analyze(item.syntax()); + let item = InFile::new(sa.file_id, &item); + let src = InFile::new(sa.file_id, attr.clone()); + let macro_call_id = self.with_ctx(|ctx| ctx.attr_to_derive_macro_call(item, src))?; + let file_id = macro_call_id.as_file(); + let node = self.db.parse_or_expand(file_id)?; + self.cache(node.clone(), file_id); + Some(node) + } + fn is_attr_macro_call(&self, item: &ast::Item) -> bool { let sa = self.analyze(item.syntax()); let src = InFile::new(sa.file_id, item.clone()); diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 773eab7931..acf545816b 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -242,6 +242,15 @@ impl SourceToDefCtx<'_, '_> { map[keys::ATTR_MACRO].get(&src).copied() } + pub(super) fn attr_to_derive_macro_call( + &mut self, + item: InFile<&ast::Item>, + src: InFile, + ) -> Option { + let map = self.dyn_map(item)?; + map[keys::DERIVE_MACRO].get(&src).copied() + } + fn to_def( &mut self, src: InFile, diff --git a/crates/hir_def/src/child_by_source.rs b/crates/hir_def/src/child_by_source.rs index 1a2b47cd03..feb6a88060 100644 --- a/crates/hir_def/src/child_by_source.rs +++ b/crates/hir_def/src/child_by_source.rs @@ -6,6 +6,7 @@ use either::Either; use hir_expand::HirFileId; +use syntax::ast::AttrsOwner; use crate::{ db::DefDatabase, @@ -108,6 +109,12 @@ impl ChildBySource for ItemScope { let item = ast_id.with_value(ast_id.to_node(db.upcast())); res[keys::ATTR_MACRO].insert(item, call_id); }); + self.derive_macro_invocs().for_each(|(ast_id, (attr_id, call_id))| { + let item = ast_id.to_node(db.upcast()); + if let Some(attr) = item.attrs().nth(attr_id.ast_index as usize) { + res[keys::DERIVE_MACRO].insert(ast_id.with_value(attr), call_id); + } + }); fn add_module_def( db: &dyn DefDatabase, diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs index 3825911f1d..51586142e2 100644 --- a/crates/hir_def/src/item_scope.rs +++ b/crates/hir_def/src/item_scope.rs @@ -12,8 +12,8 @@ use stdx::format_to; use syntax::ast; use crate::{ - db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, ImplId, - LocalModuleId, MacroDefId, ModuleDefId, ModuleId, TraitId, + attr::AttrId, db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, + ConstId, ImplId, LocalModuleId, MacroDefId, ModuleDefId, ModuleId, TraitId, }; #[derive(Copy, Clone)] @@ -61,6 +61,7 @@ pub struct ItemScope { // be all resolved to the last one defined if shadowing happens. legacy_macros: FxHashMap, attr_macros: FxHashMap, MacroCallId>, + derive_macros: FxHashMap, (AttrId, MacroCallId)>, } pub(crate) static BUILTIN_SCOPE: Lazy> = Lazy::new(|| { @@ -182,6 +183,21 @@ impl ItemScope { self.attr_macros.iter().map(|(k, v)| (*k, *v)) } + pub(crate) fn add_derive_macro_invoc( + &mut self, + item: AstId, + call: MacroCallId, + attr_id: AttrId, + ) { + self.derive_macros.insert(item, (attr_id, call)); + } + + pub(crate) fn derive_macro_invocs( + &self, + ) -> impl Iterator, (AttrId, MacroCallId))> + '_ { + self.derive_macros.iter().map(|(k, v)| (*k, *v)) + } + pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option { self.unnamed_trait_imports.get(&tr).copied() } @@ -320,6 +336,7 @@ impl ItemScope { unnamed_trait_imports, legacy_macros, attr_macros, + derive_macros, } = self; types.shrink_to_fit(); values.shrink_to_fit(); @@ -331,6 +348,7 @@ impl ItemScope { unnamed_trait_imports.shrink_to_fit(); legacy_macros.shrink_to_fit(); attr_macros.shrink_to_fit(); + derive_macros.shrink_to_fit(); } } diff --git a/crates/hir_def/src/keys.rs b/crates/hir_def/src/keys.rs index 688cd9fcff..ef01593500 100644 --- a/crates/hir_def/src/keys.rs +++ b/crates/hir_def/src/keys.rs @@ -33,6 +33,7 @@ pub const CONST_PARAM: Key = Key::new(); pub const MACRO: Key = Key::new(); pub const ATTR_MACRO: Key = Key::new(); +pub const DERIVE_MACRO: Key = Key::new(); /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are /// equal if they point to exactly the same object. diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index f8b3c3949f..e9c33392f3 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs @@ -1047,6 +1047,12 @@ impl DefCollector<'_> { &resolver, ) { Ok(call_id) => { + self.def_map.modules[directive.module_id].scope.add_derive_macro_invoc( + ast_id.ast_id, + call_id, + *derive_attr, + ); + resolved.push((directive.module_id, call_id, directive.depth)); res = ReachedFixedPoint::No; return false; diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index 814d28e7b6..363b77967c 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs @@ -2,6 +2,7 @@ use std::iter; use hir::Semantics; use ide_db::{helpers::pick_best_token, RootDatabase}; +use itertools::Itertools; use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxKind::*, SyntaxNode, WalkEvent, T}; use crate::FilePosition; @@ -33,6 +34,18 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< let mut expanded = None; let mut name = None; for node in tok.ancestors() { + if let Some(attr) = ast::Attr::cast(node.clone()) { + if let Some((path, tt)) = attr.as_simple_call() { + if path == "derive" { + let mut tt = tt.syntax().children_with_tokens().skip(1).join(""); + tt.pop(); + name = Some(tt); + expanded = sema.expand_derive_macro(&attr); + break; + } + } + } + if let Some(item) = ast::Item::cast(node.clone()) { if let Some(def) = sema.resolve_attr_macro_call(&item) { name = def.name(db).map(|name| name.to_string()); @@ -325,4 +338,22 @@ fn main() { 0 "#]], ); } + + #[test] + fn macro_expand_derive() { + check( + r#" + +#[rustc_builtin_macro] +pub macro Clone {} + +#[derive(C$0lone)] +struct Foo {} +"#, + expect![[r#" + Clone + impl< >crate::clone::Clone for Foo< >{} + "#]], + ); + } }