Skip to content

Commit 34ebb30

Browse files
committed
Auto merge of #14610 - lowr:fix/hygiene-for-meta-item, r=Veykril
fix: Resolve `$crate` in derive paths Paths in derive meta item list may contain any kind of paths, including those that start with `$crate` generated by macros. We need to take hygiene into account when we lower paths in the list. This issue was identified while investigating #14607, though this patch doesn't fix the broken trait resolution.
2 parents 6f43a56 + 85e7654 commit 34ebb30

File tree

3 files changed

+76
-17
lines changed

3 files changed

+76
-17
lines changed

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

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use hir_expand::{
1414
builtin_attr_macro::find_builtin_attr,
1515
builtin_derive_macro::find_builtin_derive,
1616
builtin_fn_macro::find_builtin_macro,
17+
hygiene::Hygiene,
1718
name::{name, AsName, Name},
1819
proc_macro::ProcMacroExpander,
1920
ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallLoc,
@@ -122,6 +123,7 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: T
122123
from_glob_import: Default::default(),
123124
skip_attrs: Default::default(),
124125
is_proc_macro,
126+
hygienes: FxHashMap::default(),
125127
};
126128
if tree_id.is_block() {
127129
collector.seed_with_inner(tree_id);
@@ -269,6 +271,12 @@ struct DefCollector<'a> {
269271
/// This also stores the attributes to skip when we resolve derive helpers and non-macro
270272
/// non-builtin attributes in general.
271273
skip_attrs: FxHashMap<InFile<ModItem>, AttrId>,
274+
/// `Hygiene` cache, because `Hygiene` construction is expensive.
275+
///
276+
/// Almost all paths should have been lowered to `ModPath` during `ItemTree` construction.
277+
/// However, `DefCollector` still needs to lower paths in attributes, in particular those in
278+
/// derive meta item list.
279+
hygienes: FxHashMap<HirFileId, Hygiene>,
272280
}
273281

274282
impl DefCollector<'_> {
@@ -312,13 +320,15 @@ impl DefCollector<'_> {
312320
}
313321

314322
if *attr_name == hir_expand::name![feature] {
315-
let features =
316-
attr.parse_path_comma_token_tree().into_iter().flatten().filter_map(
317-
|feat| match feat.segments() {
318-
[name] => Some(name.to_smol_str()),
319-
_ => None,
320-
},
321-
);
323+
let hygiene = &Hygiene::new_unhygienic();
324+
let features = attr
325+
.parse_path_comma_token_tree(self.db.upcast(), hygiene)
326+
.into_iter()
327+
.flatten()
328+
.filter_map(|feat| match feat.segments() {
329+
[name] => Some(name.to_smol_str()),
330+
_ => None,
331+
});
322332
self.def_map.unstable_features.extend(features);
323333
}
324334

@@ -1224,7 +1234,19 @@ impl DefCollector<'_> {
12241234
};
12251235
let ast_id = ast_id.with_value(ast_adt_id);
12261236

1227-
match attr.parse_path_comma_token_tree() {
1237+
let extend_unhygenic;
1238+
let hygiene = if file_id.is_macro() {
1239+
self.hygienes
1240+
.entry(file_id)
1241+
.or_insert_with(|| Hygiene::new(self.db.upcast(), file_id))
1242+
} else {
1243+
// Avoid heap allocation (`Hygiene` embraces `Arc`) and hash map entry
1244+
// when we're in an oridinary (non-macro) file.
1245+
extend_unhygenic = Hygiene::new_unhygienic();
1246+
&extend_unhygenic
1247+
};
1248+
1249+
match attr.parse_path_comma_token_tree(self.db.upcast(), hygiene) {
12281250
Some(derive_macros) => {
12291251
let mut len = 0;
12301252
for (idx, path) in derive_macros.enumerate() {
@@ -2212,6 +2234,7 @@ mod tests {
22122234
from_glob_import: Default::default(),
22132235
skip_attrs: Default::default(),
22142236
is_proc_macro: false,
2237+
hygienes: FxHashMap::default(),
22152238
};
22162239
collector.seed_with_top_level();
22172240
collector.collect();

crates/hir-def/src/nameres/tests/macros.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,29 @@ pub struct bar;
664664
);
665665
}
666666

667+
#[test]
668+
fn macro_dollar_crate_is_correct_in_derive_meta() {
669+
let map = compute_crate_def_map(
670+
r#"
671+
//- minicore: derive, clone
672+
//- /main.rs crate:main deps:lib
673+
lib::foo!();
674+
675+
//- /lib.rs crate:lib
676+
#[macro_export]
677+
macro_rules! foo {
678+
() => {
679+
#[derive($crate::Clone)]
680+
struct S;
681+
}
682+
}
683+
684+
pub use core::clone::Clone;
685+
"#,
686+
);
687+
assert_eq!(map.modules[map.root].scope.impls().len(), 1);
688+
}
689+
667690
#[test]
668691
fn expand_derive() {
669692
let map = compute_crate_def_map(

crates/hir-expand/src/attrs.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ use syntax::{ast, match_ast, AstNode, SmolStr, SyntaxNode};
1212
use crate::{
1313
db::ExpandDatabase,
1414
hygiene::Hygiene,
15-
mod_path::{ModPath, PathKind},
16-
name::AsName,
15+
mod_path::ModPath,
1716
tt::{self, Subtree},
1817
InFile,
1918
};
@@ -267,7 +266,11 @@ impl Attr {
267266
}
268267

269268
/// Parses this attribute as a token tree consisting of comma separated paths.
270-
pub fn parse_path_comma_token_tree(&self) -> Option<impl Iterator<Item = ModPath> + '_> {
269+
pub fn parse_path_comma_token_tree<'a>(
270+
&'a self,
271+
db: &'a dyn ExpandDatabase,
272+
hygiene: &'a Hygiene,
273+
) -> Option<impl Iterator<Item = ModPath> + 'a> {
271274
let args = self.token_tree_value()?;
272275

273276
if args.delimiter.kind != DelimiterKind::Parenthesis {
@@ -276,15 +279,25 @@ impl Attr {
276279
let paths = args
277280
.token_trees
278281
.split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. }))))
279-
.filter_map(|tts| {
282+
.filter_map(move |tts| {
280283
if tts.is_empty() {
281284
return None;
282285
}
283-
let segments = tts.iter().filter_map(|tt| match tt {
284-
tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()),
285-
_ => None,
286-
});
287-
Some(ModPath::from_segments(PathKind::Plain, segments))
286+
// FIXME: This is necessarily a hack. It'd be nice if we could avoid allocation here.
287+
let subtree = tt::Subtree {
288+
delimiter: tt::Delimiter::unspecified(),
289+
token_trees: tts.into_iter().cloned().collect(),
290+
};
291+
let (parse, _) =
292+
mbe::token_tree_to_syntax_node(&subtree, mbe::TopEntryPoint::MetaItem);
293+
let meta = ast::Meta::cast(parse.syntax_node())?;
294+
// Only simple paths are allowed.
295+
if meta.eq_token().is_some() || meta.expr().is_some() || meta.token_tree().is_some()
296+
{
297+
return None;
298+
}
299+
let path = meta.path()?;
300+
ModPath::from_src(db, path, hygiene)
288301
});
289302

290303
Some(paths)

0 commit comments

Comments
 (0)