Skip to content

Commit

Permalink
Correctly resolve variables and labels from before macro definition i…
Browse files Browse the repository at this point in the history
…n 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.
  • Loading branch information
ChayimFriedman2 committed Oct 22, 2024
1 parent 8adcbdc commit 345d698
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 50 deletions.
6 changes: 3 additions & 3 deletions crates/hir-def/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use crate::{

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

impl HygieneId {
pub const ROOT: Self = Self(span::SyntaxContextId::ROOT);
Expand All @@ -44,7 +44,7 @@ impl HygieneId {
Self(ctx)
}

fn is_root(self) -> bool {
pub(crate) fn is_root(self) -> bool {
self.0.is_root()
}
}
Expand Down Expand Up @@ -420,7 +420,7 @@ impl Body {
self.walk_exprs_in_pat(*pat, &mut f);
}
Statement::Expr { expr: expression, .. } => f(*expression),
Statement::Item => (),
Statement::Item(_) => (),
}
}
if let &Some(expr) = tail {
Expand Down
138 changes: 109 additions & 29 deletions crates/hir-def/src/body/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use either::Either;
use hir_expand::{
name::{AsName, Name},
span_map::{ExpansionSpanMap, SpanMap},
InFile,
InFile, MacroDefId,
};
use intern::{sym, Interned, Symbol};
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -39,16 +39,16 @@ use crate::{
FormatPlaceholder, FormatSign, FormatTrait,
},
Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind,
Expr, ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, OffsetOf, Pat,
PatId, RecordFieldPat, RecordLitField, Statement,
Expr, ExprId, Item, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability,
OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
},
item_scope::BuiltinShadowMode,
lang_item::LangItem,
lower::LowerCtx,
nameres::{DefMap, MacroSubNs},
path::{GenericArgs, Path},
type_ref::{Mutability, Rawness, TypeRef},
AdtId, BlockId, BlockLoc, ConstBlockLoc, DefWithBodyId, ModuleDefId, UnresolvedMacro,
AdtId, BlockId, BlockLoc, ConstBlockLoc, DefWithBodyId, MacroId, ModuleDefId, UnresolvedMacro,
};

type FxIndexSet<K> = indexmap::IndexSet<K, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
Expand Down Expand Up @@ -88,6 +88,7 @@ pub(super) fn lower(
current_binding_owner: None,
awaitable_context: None,
current_span_map: span_map,
current_block_legacy_macro_defs_count: FxHashMap::default(),
}
.collect(params, body, is_async_fn)
}
Expand All @@ -104,6 +105,10 @@ struct ExprCollector<'a> {

is_lowering_coroutine: bool,

/// Legacy (`macro_rules!`) macros can have multiple definitions and shadow each other,
/// and we need to find the current definition. So we track the number of definitions we saw.
current_block_legacy_macro_defs_count: FxHashMap<Name, usize>,

current_span_map: Option<Arc<ExpansionSpanMap>>,

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

impl LabelRib {
fn new(kind: RibKind) -> Self {
LabelRib { kind, label: None }
}
fn new_normal(label: (Name, LabelId, HygieneId)) -> Self {
LabelRib { kind: RibKind::Normal, label: Some(label) }
LabelRib { kind }
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
enum RibKind {
Normal,
Normal(Name, LabelId, HygieneId),
Closure,
Constant,
MacroDef(Box<MacroDefId>),
}

impl RibKind {
/// This rib forbids referring to labels defined in upwards ribs.
fn is_label_barrier(self) -> bool {
fn is_label_barrier(&self) -> bool {
match self {
RibKind::Normal => false,
RibKind::Normal(..) | RibKind::MacroDef(_) => false,
RibKind::Closure | RibKind::Constant => true,
}
}
Expand Down Expand Up @@ -1350,10 +1351,46 @@ impl ExprCollector<'_> {
statements.push(Statement::Expr { expr, has_semi });
}
}
ast::Stmt::Item(_item) => statements.push(Statement::Item),
ast::Stmt::Item(ast::Item::MacroDef(macro_)) => {
let Some(name) = macro_.name() else {
statements.push(Statement::Item(Item::Other));
return;
};
let name = name.as_name();
let macro_id = self.def_map.modules[DefMap::ROOT].scope.get(&name).take_macros();
self.collect_macro_def(statements, macro_id);
}
ast::Stmt::Item(ast::Item::MacroRules(macro_)) => {
let Some(name) = macro_.name() else {
statements.push(Statement::Item(Item::Other));
return;
};
let name = name.as_name();
let macro_defs_count =
self.current_block_legacy_macro_defs_count.entry(name.clone()).or_insert(0);
let macro_id = self.def_map.modules[DefMap::ROOT]
.scope
.get_legacy_macro(&name)
.and_then(|it| it.get(*macro_defs_count))
.copied();
*macro_defs_count += 1;
self.collect_macro_def(statements, macro_id);
}
ast::Stmt::Item(_item) => statements.push(Statement::Item(Item::Other)),
}
}

fn collect_macro_def(&mut self, statements: &mut Vec<Statement>, macro_id: Option<MacroId>) {
let Some(macro_id) = macro_id else {
never!("def map should have macro definition, but it doesn't");
statements.push(Statement::Item(Item::Other));
return;
};
let macro_id = self.db.macro_def(macro_id);
statements.push(Statement::Item(Item::MacroDef(Box::new(macro_id))));
self.label_ribs.push(LabelRib::new(RibKind::MacroDef(Box::new(macro_id))));
}

fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId {
self.collect_block_(block, |id, statements, tail| Expr::Block {
id,
Expand Down Expand Up @@ -1399,6 +1436,7 @@ impl ExprCollector<'_> {
};
let prev_def_map = mem::replace(&mut self.def_map, def_map);
let prev_local_module = mem::replace(&mut self.expander.module, module);
let prev_legacy_macros_count = mem::take(&mut self.current_block_legacy_macro_defs_count);

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

self.def_map = prev_def_map;
self.expander.module = prev_local_module;
self.current_block_legacy_macro_defs_count = prev_legacy_macros_count;
expr_id
}

Expand Down Expand Up @@ -1780,21 +1819,51 @@ impl ExprCollector<'_> {
lifetime: Option<ast::Lifetime>,
) -> Result<Option<LabelId>, BodyDiagnostic> {
let Some(lifetime) = lifetime else { return Ok(None) };
let hygiene = self.hygiene_id_for(lifetime.syntax().text_range().start());
let (mut hygiene_id, mut hygiene_info) = match &self.current_span_map {
None => (HygieneId::ROOT, None),
Some(span_map) => {
let span = span_map.span_at(lifetime.syntax().text_range().start());
let ctx = self.db.lookup_intern_syntax_context(span.ctx);
let hygiene_id = HygieneId::new(ctx.opaque_and_semitransparent);
let hygiene_info = ctx.outer_expn.map(|expansion| {
let expansion = self.db.lookup_intern_macro_call(expansion);
(ctx.parent, expansion.def)
});
(hygiene_id, hygiene_info)
}
};
let name = Name::new_lifetime(&lifetime);

for (rib_idx, rib) in self.label_ribs.iter().enumerate().rev() {
if let Some((label_name, id, label_hygiene)) = &rib.label {
if *label_name == name && *label_hygiene == hygiene {
return if self.is_label_valid_from_rib(rib_idx) {
Ok(Some(*id))
} else {
Err(BodyDiagnostic::UnreachableLabel {
name,
node: self.expander.in_file(AstPtr::new(&lifetime)),
})
};
match &rib.kind {
RibKind::Normal(label_name, id, label_hygiene) => {
if *label_name == name && *label_hygiene == hygiene_id {
return if self.is_label_valid_from_rib(rib_idx) {
Ok(Some(*id))
} else {
Err(BodyDiagnostic::UnreachableLabel {
name,
node: self.expander.in_file(AstPtr::new(&lifetime)),
})
};
}
}
RibKind::MacroDef(macro_id) => {
if let Some((parent_ctx, label_macro_id)) = hygiene_info {
if label_macro_id == **macro_id {
// A macro is allowed to refer to labels from before its declaration.
// Therefore, if we got to the rib of its declaration, give up its hygiene
// and use its parent expansion.
let parent_ctx = self.db.lookup_intern_syntax_context(parent_ctx);
hygiene_id = HygieneId::new(parent_ctx.opaque_and_semitransparent);
hygiene_info = parent_ctx.outer_expn.map(|expansion| {
let expansion = self.db.lookup_intern_macro_call(expansion);
(parent_ctx.parent, expansion.def)
});
}
}
}
_ => {}
}
}

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

fn pop_label_rib(&mut self) {
// We need to pop all macro defs, plus one rib.
while let Some(LabelRib { kind: RibKind::MacroDef(_) }) = self.label_ribs.pop() {
// Do nothing.
}
}

fn with_label_rib<T>(&mut self, kind: RibKind, f: impl FnOnce(&mut Self) -> T) -> T {
self.label_ribs.push(LabelRib::new(kind));
let res = f(self);
self.label_ribs.pop();
self.pop_label_rib();
res
}

Expand All @@ -1821,9 +1897,13 @@ impl ExprCollector<'_> {
hygiene: HygieneId,
f: impl FnOnce(&mut Self) -> T,
) -> T {
self.label_ribs.push(LabelRib::new_normal((self.body[label].name.clone(), label, hygiene)));
self.label_ribs.push(LabelRib::new(RibKind::Normal(
self.body[label].name.clone(),
label,
hygiene,
)));
let res = f(self);
self.label_ribs.pop();
self.pop_label_rib();
res
}

Expand Down
2 changes: 1 addition & 1 deletion crates/hir-def/src/body/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ impl Printer<'_> {
}
wln!(self);
}
Statement::Item => (),
Statement::Item(_) => (),
}
}

Expand Down
30 changes: 27 additions & 3 deletions crates/hir-def/src/body/scope.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//! Name resolution for expressions.
use hir_expand::name::Name;
use hir_expand::{name::Name, MacroDefId};
use la_arena::{Arena, ArenaMap, Idx, IdxRange, RawIdx};
use triomphe::Arc;

use crate::{
body::{Body, HygieneId},
db::DefDatabase,
hir::{Binding, BindingId, Expr, ExprId, LabelId, Pat, PatId, Statement},
hir::{Binding, BindingId, Expr, ExprId, Item, LabelId, Pat, PatId, Statement},
BlockId, ConstBlockId, DefWithBodyId,
};

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

Expand All @@ -67,6 +69,11 @@ impl ExprScopes {
self.scopes[scope].block
}

/// If `scope` refers to a macro def scope, returns the corresponding `MacroId`.
pub fn macro_def(&self, scope: ScopeId) -> Option<&Box<MacroDefId>> {
self.scopes[scope].macro_def.as_ref()
}

/// If `scope` refers to a labeled expression scope, returns the corresponding `Label`.
pub fn label(&self, scope: ScopeId) -> Option<(LabelId, Name)> {
self.scopes[scope].label.clone()
Expand Down Expand Up @@ -119,6 +126,7 @@ impl ExprScopes {
parent: None,
block: None,
label: None,
macro_def: None,
entries: empty_entries(self.scope_entries.len()),
})
}
Expand All @@ -128,6 +136,7 @@ impl ExprScopes {
parent: Some(parent),
block: None,
label: None,
macro_def: None,
entries: empty_entries(self.scope_entries.len()),
})
}
Expand All @@ -137,6 +146,7 @@ impl ExprScopes {
parent: Some(parent),
block: None,
label,
macro_def: None,
entries: empty_entries(self.scope_entries.len()),
})
}
Expand All @@ -151,6 +161,17 @@ impl ExprScopes {
parent: Some(parent),
block,
label,
macro_def: None,
entries: empty_entries(self.scope_entries.len()),
})
}

fn new_macro_def_scope(&mut self, parent: ScopeId, macro_id: Box<MacroDefId>) -> ScopeId {
self.scopes.alloc(ScopeData {
parent: Some(parent),
block: None,
label: None,
macro_def: Some(macro_id),
entries: empty_entries(self.scope_entries.len()),
})
}
Expand Down Expand Up @@ -217,7 +238,10 @@ fn compute_block_scopes(
Statement::Expr { expr, .. } => {
compute_expr_scopes(*expr, body, scopes, scope, resolve_const_block);
}
Statement::Item => (),
Statement::Item(Item::MacroDef(macro_id)) => {
*scope = scopes.new_macro_def_scope(*scope, macro_id.clone());
}
Statement::Item(Item::Other) => (),
}
}
if let Some(expr) = tail {
Expand Down
12 changes: 8 additions & 4 deletions crates/hir-def/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub mod type_ref;

use std::fmt;

use hir_expand::name::Name;
use hir_expand::{name::Name, MacroDefId};
use intern::{Interned, Symbol};
use la_arena::{Idx, RawIdx};
use rustc_apfloat::ieee::{Half as f16, Quad as f128};
Expand Down Expand Up @@ -492,9 +492,13 @@ pub enum Statement {
expr: ExprId,
has_semi: bool,
},
// At the moment, we only use this to figure out if a return expression
// is really the last statement of a block. See #16566
Item,
Item(Item),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Item {
MacroDef(Box<MacroDefId>),
Other,
}

/// Explicit binding annotations given in the HIR for a binding. Note
Expand Down
Loading

0 comments on commit 345d698

Please sign in to comment.