Skip to content

Commit 4ac3dc1

Browse files
Correctly resolve variables and labels from before macro definition in macro expansion
E.g.: ```rust let v; macro_rules! m { () => { v }; } ``` This was an existing bug, but it was less severe because unless the variable was shadowed it would be correctly resolved. With hygiene however, without this fix the variable is never resolved.
1 parent 8adcbdc commit 4ac3dc1

File tree

12 files changed

+287
-50
lines changed

12 files changed

+287
-50
lines changed

crates/hir-def/src/body.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use crate::{
3535

3636
/// A wrapper around [`span::SyntaxContextId`] that is intended only for comparisons.
3737
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38-
pub struct HygieneId(span::SyntaxContextId);
38+
pub struct HygieneId(pub(crate) span::SyntaxContextId);
3939

4040
impl HygieneId {
4141
pub const ROOT: Self = Self(span::SyntaxContextId::ROOT);
@@ -44,7 +44,7 @@ impl HygieneId {
4444
Self(ctx)
4545
}
4646

47-
fn is_root(self) -> bool {
47+
pub(crate) fn is_root(self) -> bool {
4848
self.0.is_root()
4949
}
5050
}
@@ -420,7 +420,7 @@ impl Body {
420420
self.walk_exprs_in_pat(*pat, &mut f);
421421
}
422422
Statement::Expr { expr: expression, .. } => f(*expression),
423-
Statement::Item => (),
423+
Statement::Item(_) => (),
424424
}
425425
}
426426
if let &Some(expr) = tail {

crates/hir-def/src/body/lower.rs

Lines changed: 109 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use either::Either;
1010
use hir_expand::{
1111
name::{AsName, Name},
1212
span_map::{ExpansionSpanMap, SpanMap},
13-
InFile,
13+
InFile, MacroDefId,
1414
};
1515
use intern::{sym, Interned, Symbol};
1616
use rustc_hash::FxHashMap;
@@ -39,16 +39,16 @@ use crate::{
3939
FormatPlaceholder, FormatSign, FormatTrait,
4040
},
4141
Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind,
42-
Expr, ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, OffsetOf, Pat,
43-
PatId, RecordFieldPat, RecordLitField, Statement,
42+
Expr, ExprId, Item, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability,
43+
OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
4444
},
4545
item_scope::BuiltinShadowMode,
4646
lang_item::LangItem,
4747
lower::LowerCtx,
4848
nameres::{DefMap, MacroSubNs},
4949
path::{GenericArgs, Path},
5050
type_ref::{Mutability, Rawness, TypeRef},
51-
AdtId, BlockId, BlockLoc, ConstBlockLoc, DefWithBodyId, ModuleDefId, UnresolvedMacro,
51+
AdtId, BlockId, BlockLoc, ConstBlockLoc, DefWithBodyId, MacroId, ModuleDefId, UnresolvedMacro,
5252
};
5353

5454
type FxIndexSet<K> = indexmap::IndexSet<K, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
@@ -88,6 +88,7 @@ pub(super) fn lower(
8888
current_binding_owner: None,
8989
awaitable_context: None,
9090
current_span_map: span_map,
91+
current_block_legacy_macro_defs_count: FxHashMap::default(),
9192
}
9293
.collect(params, body, is_async_fn)
9394
}
@@ -104,6 +105,10 @@ struct ExprCollector<'a> {
104105

105106
is_lowering_coroutine: bool,
106107

108+
/// Legacy (`macro_rules!`) macros can have multiple definitions and shadow each other,
109+
/// and we need to find the current definition. So we track the number of definitions we saw.
110+
current_block_legacy_macro_defs_count: FxHashMap<Name, usize>,
111+
107112
current_span_map: Option<Arc<ExpansionSpanMap>>,
108113

109114
current_try_block_label: Option<LabelId>,
@@ -124,31 +129,27 @@ struct ExprCollector<'a> {
124129
#[derive(Clone, Debug)]
125130
struct LabelRib {
126131
kind: RibKind,
127-
// Once we handle macro hygiene this will need to be a map
128-
label: Option<(Name, LabelId, HygieneId)>,
129132
}
130133

131134
impl LabelRib {
132135
fn new(kind: RibKind) -> Self {
133-
LabelRib { kind, label: None }
134-
}
135-
fn new_normal(label: (Name, LabelId, HygieneId)) -> Self {
136-
LabelRib { kind: RibKind::Normal, label: Some(label) }
136+
LabelRib { kind }
137137
}
138138
}
139139

140-
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
140+
#[derive(Clone, Debug, PartialEq, Eq)]
141141
enum RibKind {
142-
Normal,
142+
Normal(Name, LabelId, HygieneId),
143143
Closure,
144144
Constant,
145+
MacroDef(Box<MacroDefId>),
145146
}
146147

147148
impl RibKind {
148149
/// This rib forbids referring to labels defined in upwards ribs.
149-
fn is_label_barrier(self) -> bool {
150+
fn is_label_barrier(&self) -> bool {
150151
match self {
151-
RibKind::Normal => false,
152+
RibKind::Normal(..) | RibKind::MacroDef(_) => false,
152153
RibKind::Closure | RibKind::Constant => true,
153154
}
154155
}
@@ -1350,10 +1351,46 @@ impl ExprCollector<'_> {
13501351
statements.push(Statement::Expr { expr, has_semi });
13511352
}
13521353
}
1353-
ast::Stmt::Item(_item) => statements.push(Statement::Item),
1354+
ast::Stmt::Item(ast::Item::MacroDef(macro_)) => {
1355+
let Some(name) = macro_.name() else {
1356+
statements.push(Statement::Item(Item::Other));
1357+
return;
1358+
};
1359+
let name = name.as_name();
1360+
let macro_id = self.def_map.modules[DefMap::ROOT].scope.get(&name).take_macros();
1361+
self.collect_macro_def(statements, macro_id);
1362+
}
1363+
ast::Stmt::Item(ast::Item::MacroRules(macro_)) => {
1364+
let Some(name) = macro_.name() else {
1365+
statements.push(Statement::Item(Item::Other));
1366+
return;
1367+
};
1368+
let name = name.as_name();
1369+
let macro_defs_count =
1370+
self.current_block_legacy_macro_defs_count.entry(name.clone()).or_insert(0);
1371+
let macro_id = self.def_map.modules[DefMap::ROOT]
1372+
.scope
1373+
.get_legacy_macro(&name)
1374+
.and_then(|it| it.get(*macro_defs_count))
1375+
.copied();
1376+
*macro_defs_count += 1;
1377+
self.collect_macro_def(statements, macro_id);
1378+
}
1379+
ast::Stmt::Item(_item) => statements.push(Statement::Item(Item::Other)),
13541380
}
13551381
}
13561382

1383+
fn collect_macro_def(&mut self, statements: &mut Vec<Statement>, macro_id: Option<MacroId>) {
1384+
let Some(macro_id) = macro_id else {
1385+
never!("def map should have macro definition, but it doesn't");
1386+
statements.push(Statement::Item(Item::Other));
1387+
return;
1388+
};
1389+
let macro_id = self.db.macro_def(macro_id);
1390+
statements.push(Statement::Item(Item::MacroDef(Box::new(macro_id))));
1391+
self.label_ribs.push(LabelRib::new(RibKind::MacroDef(Box::new(macro_id))));
1392+
}
1393+
13571394
fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId {
13581395
self.collect_block_(block, |id, statements, tail| Expr::Block {
13591396
id,
@@ -1399,6 +1436,7 @@ impl ExprCollector<'_> {
13991436
};
14001437
let prev_def_map = mem::replace(&mut self.def_map, def_map);
14011438
let prev_local_module = mem::replace(&mut self.expander.module, module);
1439+
let prev_legacy_macros_count = mem::take(&mut self.current_block_legacy_macro_defs_count);
14021440

14031441
let mut statements = Vec::new();
14041442
block.statements().for_each(|s| self.collect_stmt(&mut statements, s));
@@ -1421,6 +1459,7 @@ impl ExprCollector<'_> {
14211459

14221460
self.def_map = prev_def_map;
14231461
self.expander.module = prev_local_module;
1462+
self.current_block_legacy_macro_defs_count = prev_legacy_macros_count;
14241463
expr_id
14251464
}
14261465

@@ -1780,21 +1819,51 @@ impl ExprCollector<'_> {
17801819
lifetime: Option<ast::Lifetime>,
17811820
) -> Result<Option<LabelId>, BodyDiagnostic> {
17821821
let Some(lifetime) = lifetime else { return Ok(None) };
1783-
let hygiene = self.hygiene_id_for(lifetime.syntax().text_range().start());
1822+
let (mut hygiene_id, mut hygiene_info) = match &self.current_span_map {
1823+
None => (HygieneId::ROOT, None),
1824+
Some(span_map) => {
1825+
let span = span_map.span_at(lifetime.syntax().text_range().start());
1826+
let ctx = self.db.lookup_intern_syntax_context(span.ctx);
1827+
let hygiene_id = HygieneId::new(ctx.opaque_and_semitransparent);
1828+
let hygiene_info = ctx.outer_expn.map(|expansion| {
1829+
let expansion = self.db.lookup_intern_macro_call(expansion);
1830+
(ctx.parent, expansion.def)
1831+
});
1832+
(hygiene_id, hygiene_info)
1833+
}
1834+
};
17841835
let name = Name::new_lifetime(&lifetime);
17851836

17861837
for (rib_idx, rib) in self.label_ribs.iter().enumerate().rev() {
1787-
if let Some((label_name, id, label_hygiene)) = &rib.label {
1788-
if *label_name == name && *label_hygiene == hygiene {
1789-
return if self.is_label_valid_from_rib(rib_idx) {
1790-
Ok(Some(*id))
1791-
} else {
1792-
Err(BodyDiagnostic::UnreachableLabel {
1793-
name,
1794-
node: self.expander.in_file(AstPtr::new(&lifetime)),
1795-
})
1796-
};
1838+
match &rib.kind {
1839+
RibKind::Normal(label_name, id, label_hygiene) => {
1840+
if *label_name == name && *label_hygiene == hygiene_id {
1841+
return if self.is_label_valid_from_rib(rib_idx) {
1842+
Ok(Some(*id))
1843+
} else {
1844+
Err(BodyDiagnostic::UnreachableLabel {
1845+
name,
1846+
node: self.expander.in_file(AstPtr::new(&lifetime)),
1847+
})
1848+
};
1849+
}
1850+
}
1851+
RibKind::MacroDef(macro_id) => {
1852+
if let Some((parent_ctx, label_macro_id)) = hygiene_info {
1853+
if label_macro_id == **macro_id {
1854+
// A macro is allowed to refer to labels from before its declaration.
1855+
// Therefore, if we got to the rib of its declaration, give up its hygiene
1856+
// and use its parent expansion.
1857+
let parent_ctx = self.db.lookup_intern_syntax_context(parent_ctx);
1858+
hygiene_id = HygieneId::new(parent_ctx.opaque_and_semitransparent);
1859+
hygiene_info = parent_ctx.outer_expn.map(|expansion| {
1860+
let expansion = self.db.lookup_intern_macro_call(expansion);
1861+
(parent_ctx.parent, expansion.def)
1862+
});
1863+
}
1864+
}
17971865
}
1866+
_ => {}
17981867
}
17991868
}
18001869

@@ -1808,10 +1877,17 @@ impl ExprCollector<'_> {
18081877
!self.label_ribs[rib_index + 1..].iter().any(|rib| rib.kind.is_label_barrier())
18091878
}
18101879

1880+
fn pop_label_rib(&mut self) {
1881+
// We need to pop all macro defs, plus one rib.
1882+
while let Some(LabelRib { kind: RibKind::MacroDef(_) }) = self.label_ribs.pop() {
1883+
// Do nothing.
1884+
}
1885+
}
1886+
18111887
fn with_label_rib<T>(&mut self, kind: RibKind, f: impl FnOnce(&mut Self) -> T) -> T {
18121888
self.label_ribs.push(LabelRib::new(kind));
18131889
let res = f(self);
1814-
self.label_ribs.pop();
1890+
self.pop_label_rib();
18151891
res
18161892
}
18171893

@@ -1821,9 +1897,13 @@ impl ExprCollector<'_> {
18211897
hygiene: HygieneId,
18221898
f: impl FnOnce(&mut Self) -> T,
18231899
) -> T {
1824-
self.label_ribs.push(LabelRib::new_normal((self.body[label].name.clone(), label, hygiene)));
1900+
self.label_ribs.push(LabelRib::new(RibKind::Normal(
1901+
self.body[label].name.clone(),
1902+
label,
1903+
hygiene,
1904+
)));
18251905
let res = f(self);
1826-
self.label_ribs.pop();
1906+
self.pop_label_rib();
18271907
res
18281908
}
18291909

crates/hir-def/src/body/pretty.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ impl Printer<'_> {
753753
}
754754
wln!(self);
755755
}
756-
Statement::Item => (),
756+
Statement::Item(_) => (),
757757
}
758758
}
759759

crates/hir-def/src/body/scope.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
//! Name resolution for expressions.
2-
use hir_expand::name::Name;
2+
use hir_expand::{name::Name, MacroDefId};
33
use la_arena::{Arena, ArenaMap, Idx, IdxRange, RawIdx};
44
use triomphe::Arc;
55

66
use crate::{
77
body::{Body, HygieneId},
88
db::DefDatabase,
9-
hir::{Binding, BindingId, Expr, ExprId, LabelId, Pat, PatId, Statement},
9+
hir::{Binding, BindingId, Expr, ExprId, Item, LabelId, Pat, PatId, Statement},
1010
BlockId, ConstBlockId, DefWithBodyId,
1111
};
1212

@@ -45,6 +45,8 @@ pub struct ScopeData {
4545
parent: Option<ScopeId>,
4646
block: Option<BlockId>,
4747
label: Option<(LabelId, Name)>,
48+
// FIXME: We can compress this with an enum for this and `label`/`block` if memory usage matters.
49+
macro_def: Option<Box<MacroDefId>>,
4850
entries: IdxRange<ScopeEntry>,
4951
}
5052

@@ -67,6 +69,12 @@ impl ExprScopes {
6769
self.scopes[scope].block
6870
}
6971

72+
/// If `scope` refers to a macro def scope, returns the corresponding `MacroId`.
73+
#[allow(clippy::borrowed_box)] // If we return `&MacroDefId` we need to move it, this way we just clone the `Box`.
74+
pub fn macro_def(&self, scope: ScopeId) -> Option<&Box<MacroDefId>> {
75+
self.scopes[scope].macro_def.as_ref()
76+
}
77+
7078
/// If `scope` refers to a labeled expression scope, returns the corresponding `Label`.
7179
pub fn label(&self, scope: ScopeId) -> Option<(LabelId, Name)> {
7280
self.scopes[scope].label.clone()
@@ -119,6 +127,7 @@ impl ExprScopes {
119127
parent: None,
120128
block: None,
121129
label: None,
130+
macro_def: None,
122131
entries: empty_entries(self.scope_entries.len()),
123132
})
124133
}
@@ -128,6 +137,7 @@ impl ExprScopes {
128137
parent: Some(parent),
129138
block: None,
130139
label: None,
140+
macro_def: None,
131141
entries: empty_entries(self.scope_entries.len()),
132142
})
133143
}
@@ -137,6 +147,7 @@ impl ExprScopes {
137147
parent: Some(parent),
138148
block: None,
139149
label,
150+
macro_def: None,
140151
entries: empty_entries(self.scope_entries.len()),
141152
})
142153
}
@@ -151,6 +162,17 @@ impl ExprScopes {
151162
parent: Some(parent),
152163
block,
153164
label,
165+
macro_def: None,
166+
entries: empty_entries(self.scope_entries.len()),
167+
})
168+
}
169+
170+
fn new_macro_def_scope(&mut self, parent: ScopeId, macro_id: Box<MacroDefId>) -> ScopeId {
171+
self.scopes.alloc(ScopeData {
172+
parent: Some(parent),
173+
block: None,
174+
label: None,
175+
macro_def: Some(macro_id),
154176
entries: empty_entries(self.scope_entries.len()),
155177
})
156178
}
@@ -217,7 +239,10 @@ fn compute_block_scopes(
217239
Statement::Expr { expr, .. } => {
218240
compute_expr_scopes(*expr, body, scopes, scope, resolve_const_block);
219241
}
220-
Statement::Item => (),
242+
Statement::Item(Item::MacroDef(macro_id)) => {
243+
*scope = scopes.new_macro_def_scope(*scope, macro_id.clone());
244+
}
245+
Statement::Item(Item::Other) => (),
221246
}
222247
}
223248
if let Some(expr) = tail {

0 commit comments

Comments
 (0)