Skip to content

Commit 4e60db2

Browse files
committed
feat: Downmap tokens inside derive helpers
1 parent 7ba94a8 commit 4e60db2

File tree

9 files changed

+151
-75
lines changed

9 files changed

+151
-75
lines changed

crates/hir-def/src/item_scope.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,14 @@ pub struct ItemScope {
6666
attr_macros: FxHashMap<AstId<ast::Item>, MacroCallId>,
6767
/// The derive macro invocations in this scope, keyed by the owner item over the actual derive attributes
6868
/// paired with the derive macro invocations for the specific attribute.
69-
derive_macros: FxHashMap<
70-
AstId<ast::Adt>,
71-
SmallVec<[(AttrId, MacroCallId, SmallVec<[Option<MacroCallId>; 1]>); 1]>,
72-
>,
69+
derive_macros: FxHashMap<AstId<ast::Adt>, SmallVec<[DeriveMacroInvocation; 1]>>,
70+
}
71+
72+
#[derive(Debug, PartialEq, Eq)]
73+
struct DeriveMacroInvocation {
74+
attr_id: AttrId,
75+
attr_call_id: MacroCallId,
76+
derive_call_ids: SmallVec<[Option<MacroCallId>; 1]>,
7377
}
7478

7579
pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| {
@@ -210,12 +214,14 @@ impl ItemScope {
210214
&mut self,
211215
adt: AstId<ast::Adt>,
212216
call: MacroCallId,
213-
attr_id: AttrId,
217+
id: AttrId,
214218
idx: usize,
215219
) {
216220
if let Some(derives) = self.derive_macros.get_mut(&adt) {
217-
if let Some((.., invocs)) = derives.iter_mut().find(|&&mut (id, ..)| id == attr_id) {
218-
invocs[idx] = Some(call);
221+
if let Some(DeriveMacroInvocation { derive_call_ids, .. }) =
222+
derives.iter_mut().find(|&&mut DeriveMacroInvocation { attr_id, .. }| id == attr_id)
223+
{
224+
derive_call_ids[idx] = Some(call);
219225
}
220226
}
221227
}
@@ -227,10 +233,14 @@ impl ItemScope {
227233
&mut self,
228234
adt: AstId<ast::Adt>,
229235
attr_id: AttrId,
230-
call_id: MacroCallId,
236+
attr_call_id: MacroCallId,
231237
len: usize,
232238
) {
233-
self.derive_macros.entry(adt).or_default().push((attr_id, call_id, smallvec![None; len]));
239+
self.derive_macros.entry(adt).or_default().push(DeriveMacroInvocation {
240+
attr_id,
241+
attr_call_id,
242+
derive_call_ids: smallvec![None; len],
243+
});
234244
}
235245

236246
pub(crate) fn derive_macro_invocs(
@@ -242,7 +252,12 @@ impl ItemScope {
242252
),
243253
> + '_ {
244254
self.derive_macros.iter().map(|(k, v)| {
245-
(*k, v.iter().map(|&(attr_id, call_id, ref invocs)| (attr_id, call_id, &**invocs)))
255+
(
256+
*k,
257+
v.iter().map(|DeriveMacroInvocation { attr_id, attr_call_id, derive_call_ids }| {
258+
(*attr_id, *attr_call_id, &**derive_call_ids)
259+
}),
260+
)
246261
})
247262
}
248263

crates/hir-def/src/nameres.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ mod proc_macro;
5757
#[cfg(test)]
5858
mod tests;
5959

60-
use std::{cmp::Ord, sync::Arc};
60+
use std::{ops::Deref, sync::Arc};
6161

6262
use base_db::{CrateId, Edition, FileId};
63-
use hir_expand::{name::Name, InFile, MacroDefId};
63+
use hir_expand::{name::Name, InFile, MacroCallId, MacroDefId};
6464
use itertools::Itertools;
6565
use la_arena::Arena;
6666
use profile::Count;
@@ -106,6 +106,9 @@ pub struct DefMap {
106106
fn_proc_macro_mapping: FxHashMap<FunctionId, ProcMacroId>,
107107
/// The error that occurred when failing to load the proc-macro dll.
108108
proc_macro_loading_error: Option<Box<str>>,
109+
/// Tracks which custom derives are in scope for an item, to allow resolution of derive helper
110+
/// attributes.
111+
derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<(Name, MacroCallId)>>,
109112

110113
/// Custom attributes registered with `#![register_attr]`.
111114
registered_attrs: Vec<SmolStr>,
@@ -275,6 +278,7 @@ impl DefMap {
275278
exported_derives: FxHashMap::default(),
276279
fn_proc_macro_mapping: FxHashMap::default(),
277280
proc_macro_loading_error: None,
281+
derive_helpers_in_scope: FxHashMap::default(),
278282
prelude: None,
279283
root,
280284
modules,
@@ -294,19 +298,27 @@ impl DefMap {
294298
pub fn modules(&self) -> impl Iterator<Item = (LocalModuleId, &ModuleData)> + '_ {
295299
self.modules.iter()
296300
}
301+
302+
pub fn derive_helpers_in_scope(&self, id: AstId<ast::Adt>) -> Option<&[(Name, MacroCallId)]> {
303+
self.derive_helpers_in_scope.get(&id.map(|it| it.upcast())).map(Deref::deref)
304+
}
305+
297306
pub fn registered_tools(&self) -> &[SmolStr] {
298307
&self.registered_tools
299308
}
309+
300310
pub fn registered_attrs(&self) -> &[SmolStr] {
301311
&self.registered_attrs
302312
}
313+
303314
pub fn root(&self) -> LocalModuleId {
304315
self.root
305316
}
306317

307318
pub fn fn_as_proc_macro(&self, id: FunctionId) -> Option<ProcMacroId> {
308319
self.fn_proc_macro_mapping.get(&id).copied()
309320
}
321+
310322
pub fn proc_macro_loading_error(&self) -> Option<&str> {
311323
self.proc_macro_loading_error.as_deref()
312324
}
@@ -467,6 +479,7 @@ impl DefMap {
467479
registered_attrs,
468480
registered_tools,
469481
fn_proc_macro_mapping,
482+
derive_helpers_in_scope,
470483
proc_macro_loading_error: _,
471484
block: _,
472485
edition: _,
@@ -483,6 +496,7 @@ impl DefMap {
483496
registered_attrs.shrink_to_fit();
484497
registered_tools.shrink_to_fit();
485498
fn_proc_macro_mapping.shrink_to_fit();
499+
derive_helpers_in_scope.shrink_to_fit();
486500
for (_, module) in modules.iter_mut() {
487501
module.children.shrink_to_fit();
488502
module.scope.shrink_to_fit();

crates/hir-def/src/nameres/collector.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: T
110110
proc_macros,
111111
from_glob_import: Default::default(),
112112
skip_attrs: Default::default(),
113-
derive_helpers_in_scope: Default::default(),
114113
is_proc_macro,
115114
};
116115
if tree_id.is_block() {
@@ -258,9 +257,6 @@ struct DefCollector<'a> {
258257
/// This also stores the attributes to skip when we resolve derive helpers and non-macro
259258
/// non-builtin attributes in general.
260259
skip_attrs: FxHashMap<InFile<ModItem>, AttrId>,
261-
/// Tracks which custom derives are in scope for an item, to allow resolution of derive helper
262-
/// attributes.
263-
derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<Name>>,
264260
}
265261

266262
impl DefCollector<'_> {
@@ -1132,8 +1128,8 @@ impl DefCollector<'_> {
11321128
};
11331129

11341130
if let Some(ident) = path.as_ident() {
1135-
if let Some(helpers) = self.derive_helpers_in_scope.get(&ast_id) {
1136-
if helpers.contains(ident) {
1131+
if let Some(helpers) = self.def_map.derive_helpers_in_scope.get(&ast_id) {
1132+
if helpers.iter().any(|(it, _)| it == ident) {
11371133
cov_mark::hit!(resolved_derive_helper);
11381134
// Resolved to derive helper. Collect the item's attributes again,
11391135
// starting after the derive helper.
@@ -1322,10 +1318,11 @@ impl DefCollector<'_> {
13221318
if loc.def.krate != self.def_map.krate {
13231319
let def_map = self.db.crate_def_map(loc.def.krate);
13241320
if let Some(helpers) = def_map.exported_derives.get(&loc.def) {
1325-
self.derive_helpers_in_scope
1321+
self.def_map
1322+
.derive_helpers_in_scope
13261323
.entry(ast_id.map(|it| it.upcast()))
13271324
.or_default()
1328-
.extend(helpers.iter().cloned());
1325+
.extend(helpers.iter().cloned().zip(std::iter::repeat(macro_call_id)));
13291326
}
13301327
}
13311328
}
@@ -2140,7 +2137,6 @@ mod tests {
21402137
proc_macros: Default::default(),
21412138
from_glob_import: Default::default(),
21422139
skip_attrs: Default::default(),
2143-
derive_helpers_in_scope: Default::default(),
21442140
is_proc_macro: false,
21452141
};
21462142
collector.seed_with_top_level();

crates/hir-def/src/resolver.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,10 +448,14 @@ impl Resolver {
448448
}
449449

450450
pub fn krate(&self) -> CrateId {
451+
self.def_map().krate()
452+
}
453+
454+
pub fn def_map(&self) -> &DefMap {
451455
self.scopes
452456
.get(0)
453457
.and_then(|scope| match scope {
454-
Scope::ModuleScope(m) => Some(m.def_map.krate()),
458+
Scope::ModuleScope(m) => Some(&m.def_map),
455459
_ => None,
456460
})
457461
.expect("module scope invariant violated")

crates/hir/src/semantics.rs

Lines changed: 82 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,8 @@ impl<'db> SemanticsImpl<'db> {
733733
Some(it) => it,
734734
None => return,
735735
};
736+
let def_map = sa.resolver.def_map();
737+
736738
let mut stack: SmallVec<[_; 4]> = smallvec![InFile::new(sa.file_id, token)];
737739
let mut cache = self.expansion_info_cache.borrow_mut();
738740
let mut mcache = self.macro_call_cache.borrow_mut();
@@ -764,7 +766,7 @@ impl<'db> SemanticsImpl<'db> {
764766
while let Some(token) = stack.pop() {
765767
self.db.unwind_if_cancelled();
766768
let was_not_remapped = (|| {
767-
// are we inside an attribute macro call
769+
// First expand into attribute invocations
768770
let containing_attribute_macro_call = self.with_ctx(|ctx| {
769771
token.value.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
770772
if item.attrs().next().is_none() {
@@ -784,53 +786,19 @@ impl<'db> SemanticsImpl<'db> {
784786
);
785787
}
786788

787-
// or are we inside a function-like macro call
788-
if let Some(tt) =
789-
// FIXME replace map.while_some with take_while once stable
790-
token
791-
.value
792-
.parent_ancestors()
793-
.map(ast::TokenTree::cast)
794-
.while_some()
795-
.last()
796-
{
797-
let parent = tt.syntax().parent()?;
798-
// check for derive attribute here
799-
let macro_call = match_ast! {
800-
match parent {
801-
ast::MacroCall(mcall) => mcall,
802-
// attribute we failed expansion for earlier, this might be a derive invocation
803-
// so try downmapping the token into the pseudo derive expansion
804-
// see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
805-
ast::Meta(meta) => {
806-
let attr = meta.parent_attr()?;
807-
let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
808-
let call_id = self.with_ctx(|ctx| {
809-
let (_, call_id, _) = ctx.attr_to_derive_macro_call(
810-
token.with_value(&adt),
811-
token.with_value(attr),
812-
)?;
813-
Some(call_id)
814-
})?;
815-
let file_id = call_id.as_file();
816-
return process_expansion_for_token(
817-
&mut stack,
818-
file_id,
819-
Some(adt.into()),
820-
token.as_ref(),
821-
);
822-
},
823-
_ => return None,
824-
}
825-
};
789+
// Then check for token trees, that means we are either in a function-like macro or
790+
// secondary attribute inputs
791+
let tt = token.value.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
792+
let parent = tt.syntax().parent()?;
826793

827-
if tt.left_delimiter_token().map_or(false, |it| it == token.value) {
828-
return None;
829-
}
830-
if tt.right_delimiter_token().map_or(false, |it| it == token.value) {
831-
return None;
832-
}
794+
if tt.left_delimiter_token().map_or(false, |it| it == token.value) {
795+
return None;
796+
}
797+
if tt.right_delimiter_token().map_or(false, |it| it == token.value) {
798+
return None;
799+
}
833800

801+
if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
834802
let mcall = token.with_value(macro_call);
835803
let file_id = match mcache.get(&mcall) {
836804
Some(&it) => it,
@@ -840,11 +808,75 @@ impl<'db> SemanticsImpl<'db> {
840808
it
841809
}
842810
};
843-
return process_expansion_for_token(&mut stack, file_id, None, token.as_ref());
811+
process_expansion_for_token(&mut stack, file_id, None, token.as_ref())
812+
} else if let Some(meta) = ast::Meta::cast(parent.clone()) {
813+
// attribute we failed expansion for earlier, this might be a derive invocation
814+
// or derive helper attribute
815+
let attr = meta.parent_attr()?;
816+
817+
let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast) {
818+
// this might be a derive, or a derive helper on an ADT
819+
let derive_call = self.with_ctx(|ctx| {
820+
// so try downmapping the token into the pseudo derive expansion
821+
// see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
822+
ctx.attr_to_derive_macro_call(
823+
token.with_value(&adt),
824+
token.with_value(attr.clone()),
825+
)
826+
.map(|(_, call_id, _)| call_id)
827+
});
828+
829+
match derive_call {
830+
Some(call_id) => {
831+
// resolved to a derive
832+
let file_id = call_id.as_file();
833+
return process_expansion_for_token(
834+
&mut stack,
835+
file_id,
836+
Some(adt.into()),
837+
token.as_ref(),
838+
);
839+
}
840+
None => Some(adt),
841+
}
842+
} else {
843+
// Otherwise this could be a derive helper on a variant or field
844+
if let Some(field) = attr.syntax().parent().and_then(ast::RecordField::cast)
845+
{
846+
field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
847+
} else if let Some(field) =
848+
attr.syntax().parent().and_then(ast::TupleField::cast)
849+
{
850+
field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
851+
} else if let Some(variant) =
852+
attr.syntax().parent().and_then(ast::Variant::cast)
853+
{
854+
variant.syntax().ancestors().nth(2).and_then(ast::Adt::cast)
855+
} else {
856+
None
857+
}
858+
}?;
859+
860+
// Not an attribute, nor a derive, so it's either a builtin or a derive helper
861+
// Try to resolve to a derive helper and downmap
862+
let attr_name = attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
863+
let id = self.db.ast_id_map(token.file_id).ast_id(&adt);
864+
let helpers =
865+
def_map.derive_helpers_in_scope(InFile::new(token.file_id, id))?;
866+
let item = Some(adt.into());
867+
let mut res = None;
868+
for (_, derive) in helpers.iter().filter(|(helper, _)| *helper == attr_name) {
869+
res = res.or(process_expansion_for_token(
870+
&mut stack,
871+
derive.as_file(),
872+
item.clone(),
873+
token.as_ref(),
874+
));
875+
}
876+
res
877+
} else {
878+
None
844879
}
845-
846-
// outside of a macro invocation so this is a "final" token
847-
None
848880
})()
849881
.is_none();
850882

crates/hir/src/semantics/source_to_def.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ impl SourceToDefCtx<'_, '_> {
247247
map[keys::ATTR_MACRO_CALL].get(&src.value).copied()
248248
}
249249

250+
/// (AttrId, derive attribute call id, derive call ids)
250251
pub(super) fn attr_to_derive_macro_call(
251252
&mut self,
252253
item: InFile<&ast::Adt>,
@@ -257,6 +258,7 @@ impl SourceToDefCtx<'_, '_> {
257258
.get(&src.value)
258259
.map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids))
259260
}
261+
260262
pub(super) fn has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool {
261263
self.dyn_map(adt).as_ref().map_or(false, |map| !map[keys::DERIVE_MACRO_CALL].is_empty())
262264
}

0 commit comments

Comments
 (0)