Skip to content

Commit 6e10a9f

Browse files
Merge #2479
2479: Add expansion infrastructure for derive macros r=matklad a=flodiebold I thought I'd experiment a bit with attribute macro/derive expansion, and here's what I've got so far. It has dummy implementations of the Copy / Clone derives, to show that the approach works; it doesn't add any attribute macro support, but I think that fits into the architecture. Basically, during raw item collection, we look at the attributes and generate macro calls for them if necessary. Currently I only do this for derives, and just add the derive macro calls as separate calls next to the item. I think for derives, it's important that they don't obscure the actual item, since they can't actually change it (e.g. sending the item token tree through macro expansion unnecessarily might make completion within it more complicated). Attribute macros would have to be recognized at that stage and replace the item (i.e., the raw item collector will just emit an attribute macro call, and not the item). I think when we implement this, we should try to recognize known inert attributes, so that we don't do macro expansion unnecessarily; anything that isn't known needs to be treated as a possible attribute macro call (since the raw item collector can't resolve the macro yet). There's basically no name resolution for attribute macros implemented, I just hardcoded the built-in derives. In the future, the built-ins should work within the normal name resolution infrastructure; the problem there is that the builtin stubs in `std` use macros 2.0, which we don't support yet (and adding support is outside the scope of this). One aspect that I don't really have a solution for, but I don't know how important it is, is removing the attribute itself from its input. I'm pretty sure rustc leaves out the attribute macro from the input, but to do that, we'd have to create a completely new syntax node. I guess we could do it when / after converting to a token tree. Co-authored-by: Florian Diebold <[email protected]>
2 parents 217a6fa + 1069704 commit 6e10a9f

File tree

20 files changed

+632
-81
lines changed

20 files changed

+632
-81
lines changed

crates/ra_hir/src/code_model/src.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ impl HasSource for TypeAlias {
105105
impl HasSource for MacroDef {
106106
type Ast = ast::MacroCall;
107107
fn source(self, db: &impl DefDatabase) -> InFile<ast::MacroCall> {
108-
InFile { file_id: self.id.ast_id.file_id, value: self.id.ast_id.to_node(db) }
108+
InFile {
109+
file_id: self.id.ast_id.expect("MacroDef without ast_id").file_id,
110+
value: self.id.ast_id.expect("MacroDef without ast_id").to_node(db),
111+
}
109112
}
110113
}
111114
impl HasSource for ImplBlock {

crates/ra_hir/src/from_source.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ impl FromSource for MacroDef {
9393

9494
let module_src = ModuleSource::from_child_node(db, src.as_ref().map(|it| it.syntax()));
9595
let module = Module::from_definition(db, InFile::new(src.file_id, module_src))?;
96-
let krate = module.krate().crate_id();
96+
let krate = Some(module.krate().crate_id());
9797

98-
let ast_id = AstId::new(src.file_id, db.ast_id_map(src.file_id).ast_id(&src.value));
98+
let ast_id = Some(AstId::new(src.file_id, db.ast_id_map(src.file_id).ast_id(&src.value)));
9999

100100
let id: MacroDefId = MacroDefId { krate, ast_id, kind };
101101
Some(MacroDef { id })

crates/ra_hir/src/source_binder.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ use hir_def::{
2020
AssocItemId, DefWithBodyId,
2121
};
2222
use hir_expand::{
23-
hygiene::Hygiene, name::AsName, AstId, HirFileId, InFile, MacroCallId, MacroFileKind,
23+
hygiene::Hygiene, name::AsName, AstId, HirFileId, InFile, MacroCallId, MacroCallKind,
24+
MacroFileKind,
2425
};
2526
use ra_syntax::{
2627
ast::{self, AstNode},
@@ -456,7 +457,7 @@ impl SourceAnalyzer {
456457
db.ast_id_map(macro_call.file_id).ast_id(macro_call.value),
457458
);
458459
Some(Expansion {
459-
macro_call_id: def.as_call_id(db, ast_id),
460+
macro_call_id: def.as_call_id(db, MacroCallKind::FnLike(ast_id)),
460461
macro_file_kind: to_macro_file_kind(macro_call.value),
461462
})
462463
}

crates/ra_hir_def/src/attr.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ impl Attrs {
6161
AdtId::UnionId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db),
6262
},
6363
AttrDefId::TraitId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db),
64-
AttrDefId::MacroDefId(it) => attrs_from_ast(it.ast_id, db),
64+
AttrDefId::MacroDefId(it) => {
65+
it.ast_id.map_or_else(Default::default, |ast_id| attrs_from_ast(ast_id, db))
66+
}
6567
AttrDefId::ImplId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db),
6668
AttrDefId::ConstId(it) => attrs_from_loc(it.lookup(db), db),
6769
AttrDefId::StaticId(it) => attrs_from_loc(it.lookup(db), db),

crates/ra_hir_def/src/body.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ pub mod scope;
66
use std::{ops::Index, sync::Arc};
77

88
use either::Either;
9-
use hir_expand::{hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId, MacroFileKind};
9+
use hir_expand::{
10+
hygiene::Hygiene, AstId, HirFileId, InFile, MacroCallKind, MacroDefId, MacroFileKind,
11+
};
1012
use ra_arena::{map::ArenaMap, Arena};
1113
use ra_syntax::{ast, AstNode, AstPtr};
1214
use rustc_hash::FxHashMap;
@@ -46,7 +48,7 @@ impl Expander {
4648

4749
if let Some(path) = macro_call.path().and_then(|path| self.parse_path(path)) {
4850
if let Some(def) = self.resolve_path_as_macro(db, &path) {
49-
let call_id = def.as_call_id(db, ast_id);
51+
let call_id = def.as_call_id(db, MacroCallKind::FnLike(ast_id));
5052
let file_id = call_id.as_file(MacroFileKind::Expr);
5153
if let Some(node) = db.parse_or_expand(file_id) {
5254
if let Some(expr) = ast::Expr::cast(node) {

crates/ra_hir_def/src/docs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ impl Documentation {
6060
docs_from_ast(&src.value[it.local_id])
6161
}
6262
AttrDefId::TraitId(it) => docs_from_ast(&it.source(db).value),
63-
AttrDefId::MacroDefId(it) => docs_from_ast(&it.ast_id.to_node(db)),
63+
AttrDefId::MacroDefId(it) => docs_from_ast(&it.ast_id?.to_node(db)),
6464
AttrDefId::ConstId(it) => docs_from_ast(&it.lookup(db).source(db).value),
6565
AttrDefId::StaticId(it) => docs_from_ast(&it.lookup(db).source(db).value),
6666
AttrDefId::FunctionId(it) => docs_from_ast(&it.lookup(db).source(db).value),

crates/ra_hir_def/src/nameres/collector.rs

+66-7
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
//! resolves imports and expands macros.
55
66
use hir_expand::{
7+
builtin_derive::find_builtin_derive,
78
builtin_macro::find_builtin_macro,
89
name::{self, AsName, Name},
9-
HirFileId, MacroCallId, MacroDefId, MacroDefKind, MacroFileKind,
10+
HirFileId, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, MacroFileKind,
1011
};
1112
use ra_cfg::CfgOptions;
1213
use ra_db::{CrateId, FileId};
@@ -58,6 +59,7 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C
5859
glob_imports: FxHashMap::default(),
5960
unresolved_imports: Vec::new(),
6061
unexpanded_macros: Vec::new(),
62+
unexpanded_attribute_macros: Vec::new(),
6163
mod_dirs: FxHashMap::default(),
6264
macro_stack_monitor: MacroStackMonitor::default(),
6365
poison_macros: FxHashSet::default(),
@@ -102,6 +104,7 @@ struct DefCollector<'a, DB> {
102104
glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, LocalImportId)>>,
103105
unresolved_imports: Vec<(LocalModuleId, LocalImportId, raw::ImportData)>,
104106
unexpanded_macros: Vec<(LocalModuleId, AstId<ast::MacroCall>, Path)>,
107+
unexpanded_attribute_macros: Vec<(LocalModuleId, AstId<ast::ModuleItem>, Path)>,
105108
mod_dirs: FxHashMap<LocalModuleId, ModDir>,
106109

107110
/// Some macro use `$tt:tt which mean we have to handle the macro perfectly
@@ -470,6 +473,8 @@ where
470473

471474
fn resolve_macros(&mut self) -> ReachedFixedPoint {
472475
let mut macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new());
476+
let mut attribute_macros =
477+
std::mem::replace(&mut self.unexpanded_attribute_macros, Vec::new());
473478
let mut resolved = Vec::new();
474479
let mut res = ReachedFixedPoint::Yes;
475480
macros.retain(|(module_id, ast_id, path)| {
@@ -482,7 +487,19 @@ where
482487
);
483488

484489
if let Some(def) = resolved_res.resolved_def.take_macros() {
485-
let call_id = def.as_call_id(self.db, *ast_id);
490+
let call_id = def.as_call_id(self.db, MacroCallKind::FnLike(*ast_id));
491+
resolved.push((*module_id, call_id, def));
492+
res = ReachedFixedPoint::No;
493+
return false;
494+
}
495+
496+
true
497+
});
498+
attribute_macros.retain(|(module_id, ast_id, path)| {
499+
let resolved_res = self.resolve_attribute_macro(path);
500+
501+
if let Some(def) = resolved_res {
502+
let call_id = def.as_call_id(self.db, MacroCallKind::Attr(*ast_id));
486503
resolved.push((*module_id, call_id, def));
487504
res = ReachedFixedPoint::No;
488505
return false;
@@ -492,6 +509,7 @@ where
492509
});
493510

494511
self.unexpanded_macros = macros;
512+
self.unexpanded_attribute_macros = attribute_macros;
495513

496514
for (module_id, macro_call_id, macro_def_id) in resolved {
497515
self.collect_macro_expansion(module_id, macro_call_id, macro_def_id);
@@ -500,6 +518,20 @@ where
500518
res
501519
}
502520

521+
fn resolve_attribute_macro(&self, path: &Path) -> Option<MacroDefId> {
522+
// FIXME this is currently super hacky, just enough to support the
523+
// built-in derives
524+
if let Some(name) = path.as_ident() {
525+
// FIXME this should actually be handled with the normal name
526+
// resolution; the std lib defines built-in stubs for the derives,
527+
// but these are new-style `macro`s, which we don't support yet
528+
if let Some(def_id) = find_builtin_derive(name) {
529+
return Some(def_id);
530+
}
531+
}
532+
None
533+
}
534+
503535
fn collect_macro_expansion(
504536
&mut self,
505537
module_id: LocalModuleId,
@@ -587,7 +619,9 @@ where
587619
.def_collector
588620
.unresolved_imports
589621
.push((self.module_id, import_id, self.raw_items[import_id].clone())),
590-
raw::RawItemKind::Def(def) => self.define_def(&self.raw_items[def]),
622+
raw::RawItemKind::Def(def) => {
623+
self.define_def(&self.raw_items[def], &item.attrs)
624+
}
591625
raw::RawItemKind::Macro(mac) => self.collect_macro(&self.raw_items[mac]),
592626
raw::RawItemKind::Impl(imp) => {
593627
let module = ModuleId {
@@ -682,10 +716,16 @@ where
682716
res
683717
}
684718

685-
fn define_def(&mut self, def: &raw::DefData) {
719+
fn define_def(&mut self, def: &raw::DefData, attrs: &Attrs) {
686720
let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id };
687721
let ctx = LocationCtx::new(self.def_collector.db, module, self.file_id);
688722

723+
// FIXME: check attrs to see if this is an attribute macro invocation;
724+
// in which case we don't add the invocation, just a single attribute
725+
// macro invocation
726+
727+
self.collect_derives(attrs, def);
728+
689729
let name = def.name.clone();
690730
let def: PerNs = match def.kind {
691731
raw::DefKind::Function(ast_id) => {
@@ -736,6 +776,23 @@ where
736776
self.def_collector.update(self.module_id, None, &[(name, resolution)])
737777
}
738778

779+
fn collect_derives(&mut self, attrs: &Attrs, def: &raw::DefData) {
780+
for derive_subtree in attrs.by_key("derive").tt_values() {
781+
// for #[derive(Copy, Clone)], `derive_subtree` is the `(Copy, Clone)` subtree
782+
for tt in &derive_subtree.token_trees {
783+
let ident = match &tt {
784+
tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => ident,
785+
tt::TokenTree::Leaf(tt::Leaf::Punct(_)) => continue, // , is ok
786+
_ => continue, // anything else would be an error (which we currently ignore)
787+
};
788+
let path = Path::from_tt_ident(ident);
789+
790+
let ast_id = AstId::new(self.file_id, def.kind.ast_id());
791+
self.def_collector.unexpanded_attribute_macros.push((self.module_id, ast_id, path));
792+
}
793+
}
794+
}
795+
739796
fn collect_macro(&mut self, mac: &raw::MacroData) {
740797
let ast_id = AstId::new(self.file_id, mac.ast_id);
741798

@@ -759,8 +816,8 @@ where
759816
if is_macro_rules(&mac.path) {
760817
if let Some(name) = &mac.name {
761818
let macro_id = MacroDefId {
762-
ast_id,
763-
krate: self.def_collector.def_map.krate,
819+
ast_id: Some(ast_id),
820+
krate: Some(self.def_collector.def_map.krate),
764821
kind: MacroDefKind::Declarative,
765822
};
766823
self.def_collector.define_macro(self.module_id, name.clone(), macro_id, mac.export);
@@ -773,7 +830,8 @@ where
773830
if let Some(macro_def) = mac.path.as_ident().and_then(|name| {
774831
self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name)
775832
}) {
776-
let macro_call_id = macro_def.as_call_id(self.def_collector.db, ast_id);
833+
let macro_call_id =
834+
macro_def.as_call_id(self.def_collector.db, MacroCallKind::FnLike(ast_id));
777835

778836
self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, macro_def);
779837
return;
@@ -829,6 +887,7 @@ mod tests {
829887
glob_imports: FxHashMap::default(),
830888
unresolved_imports: Vec::new(),
831889
unexpanded_macros: Vec::new(),
890+
unexpanded_attribute_macros: Vec::new(),
832891
mod_dirs: FxHashMap::default(),
833892
macro_stack_monitor: monitor,
834893
poison_macros: FxHashSet::default(),

crates/ra_hir_def/src/nameres/raw.rs

+15
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,21 @@ pub(super) enum DefKind {
184184
TypeAlias(FileAstId<ast::TypeAliasDef>),
185185
}
186186

187+
impl DefKind {
188+
pub fn ast_id(&self) -> FileAstId<ast::ModuleItem> {
189+
match self {
190+
DefKind::Function(it) => it.upcast(),
191+
DefKind::Struct(it) => it.upcast(),
192+
DefKind::Union(it) => it.upcast(),
193+
DefKind::Enum(it) => it.upcast(),
194+
DefKind::Const(it) => it.upcast(),
195+
DefKind::Static(it) => it.upcast(),
196+
DefKind::Trait(it) => it.upcast(),
197+
DefKind::TypeAlias(it) => it.upcast(),
198+
}
199+
}
200+
}
201+
187202
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188203
pub(super) struct Macro(RawId);
189204
impl_arena_id!(Macro);

crates/ra_hir_def/src/nameres/tests/macros.rs

+24
Original file line numberDiff line numberDiff line change
@@ -600,3 +600,27 @@ fn macro_dollar_crate_is_correct_in_indirect_deps() {
600600
⋮bar: t v
601601
"###);
602602
}
603+
604+
#[test]
605+
fn expand_derive() {
606+
let map = compute_crate_def_map(
607+
"
608+
//- /main.rs
609+
#[derive(Clone)]
610+
struct Foo;
611+
",
612+
);
613+
assert_eq!(map.modules[map.root].impls.len(), 1);
614+
}
615+
616+
#[test]
617+
fn expand_multiple_derive() {
618+
let map = compute_crate_def_map(
619+
"
620+
//- /main.rs
621+
#[derive(Copy, Clone)]
622+
struct Foo;
623+
",
624+
);
625+
assert_eq!(map.modules[map.root].impls.len(), 2);
626+
}

crates/ra_hir_def/src/path.rs

+5
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ impl Path {
199199
name_ref.as_name().into()
200200
}
201201

202+
/// Converts an `tt::Ident` into a single-identifier `Path`.
203+
pub(crate) fn from_tt_ident(ident: &tt::Ident) -> Path {
204+
ident.as_name().into()
205+
}
206+
202207
/// `true` is this path is a single identifier, like `foo`
203208
pub fn is_ident(&self) -> bool {
204209
self.kind == PathKind::Plain && self.segments.len() == 1

crates/ra_hir_expand/src/ast_id_map.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ impl<N: AstNode> Hash for FileAstId<N> {
3939
}
4040
}
4141

42+
impl<N: AstNode> FileAstId<N> {
43+
// Can't make this a From implementation because of coherence
44+
pub fn upcast<M: AstNode>(self) -> FileAstId<M>
45+
where
46+
M: From<N>,
47+
{
48+
FileAstId { raw: self.raw, _ty: PhantomData }
49+
}
50+
}
51+
4252
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
4353
struct ErasedFileAstId(RawId);
4454
impl_arena_id!(ErasedFileAstId);
@@ -53,7 +63,7 @@ impl AstIdMap {
5363
pub(crate) fn from_source(node: &SyntaxNode) -> AstIdMap {
5464
assert!(node.parent().is_none());
5565
let mut res = AstIdMap { arena: Arena::default() };
56-
// By walking the tree in bread-first order we make sure that parents
66+
// By walking the tree in breadth-first order we make sure that parents
5767
// get lower ids then children. That is, adding a new child does not
5868
// change parent's id. This means that, say, adding a new function to a
5969
// trait does not change ids of top-level items, which helps caching.

0 commit comments

Comments
 (0)