Implement semitransparent hygiene

Or macro_rules hygiene, or mixed site hygiene. In other words, hygiene for variables and labels but not items.

The realization that made me implement this was that while "full" hygiene (aka. def site hygiene) is really hard for us to implement, and will likely involve intrusive changes and performance losses, since every `Name` will have to carry hygiene, mixed site hygiene is very local: it applies only to bodies, and we very well can save it in a side map with minor losses.

This fixes one diagnostic in r-a that was about `izip!()` using hygiene (yay!) but it introduces a huge number of others, because of #18262. Up until now this issue wasn't a major problem because it only affected few cases, but with hygiene identifiers referred by macros like that are not resolved at all. The next commit will fix that.
This commit is contained in:
Chayim Refael Friedman 2024-10-07 23:02:02 +03:00
parent c286786888
commit 8adcbdcc49
23 changed files with 394 additions and 124 deletions

1
Cargo.lock generated
View file

@ -556,6 +556,7 @@ dependencies = [
"syntax-bridge", "syntax-bridge",
"test-fixture", "test-fixture",
"test-utils", "test-utils",
"text-size",
"tracing", "tracing",
"triomphe", "triomphe",
"tt", "tt",

View file

@ -29,6 +29,7 @@ smallvec.workspace = true
hashbrown.workspace = true hashbrown.workspace = true
triomphe.workspace = true triomphe.workspace = true
rustc_apfloat = "0.2.0" rustc_apfloat = "0.2.0"
text-size.workspace = true
ra-ap-rustc_parse_format.workspace = true ra-ap-rustc_parse_format.workspace = true
ra-ap-rustc_abi.workspace = true ra-ap-rustc_abi.workspace = true

View file

@ -33,6 +33,22 @@ use crate::{
BlockId, DefWithBodyId, HasModule, Lookup, BlockId, DefWithBodyId, HasModule, Lookup,
}; };
/// A wrapper around [`span::SyntaxContextId`] that is intended only for comparisons.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HygieneId(span::SyntaxContextId);
impl HygieneId {
pub const ROOT: Self = Self(span::SyntaxContextId::ROOT);
pub fn new(ctx: span::SyntaxContextId) -> Self {
Self(ctx)
}
fn is_root(self) -> bool {
self.0.is_root()
}
}
/// The body of an item (function, const etc.). /// The body of an item (function, const etc.).
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct Body { pub struct Body {
@ -55,6 +71,22 @@ pub struct Body {
pub body_expr: ExprId, pub body_expr: ExprId,
/// Block expressions in this body that may contain inner items. /// Block expressions in this body that may contain inner items.
block_scopes: Vec<BlockId>, block_scopes: Vec<BlockId>,
/// A map from binding to its hygiene ID.
///
/// Bindings that don't come from macro expansion are not allocated to save space, so not all bindings appear here.
/// If a binding does not appear here it has `SyntaxContextId::ROOT`.
///
/// Note that this may not be the direct `SyntaxContextId` of the binding's expansion, because transparent
/// expansions are attributed to their parent expansion (recursively).
binding_hygiene: FxHashMap<BindingId, HygieneId>,
/// A map from an variable usages to their hygiene ID.
///
/// Expressions that can be recorded here are single segment path, although not all single segments path refer
/// to variables and have hygiene (some refer to items, we don't know at this stage).
expr_hygiene: FxHashMap<ExprId, HygieneId>,
/// A map from a destructuring assignment possible variable usages to their hygiene ID.
pat_hygiene: FxHashMap<PatId, HygieneId>,
} }
pub type ExprPtr = AstPtr<ast::Expr>; pub type ExprPtr = AstPtr<ast::Expr>;
@ -107,10 +139,11 @@ pub struct BodySourceMap {
field_map_back: FxHashMap<ExprId, FieldSource>, field_map_back: FxHashMap<ExprId, FieldSource>,
pat_field_map_back: FxHashMap<PatId, PatFieldSource>, pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
// FIXME: Make this a sane struct.
template_map: Option< template_map: Option<
Box<( Box<(
// format_args! // format_args!
FxHashMap<ExprId, Vec<(syntax::TextRange, Name)>>, FxHashMap<ExprId, (HygieneId, Vec<(syntax::TextRange, Name)>)>,
// asm! // asm!
FxHashMap<ExprId, Vec<Vec<(syntax::TextRange, usize)>>>, FxHashMap<ExprId, Vec<Vec<(syntax::TextRange, usize)>>>,
)>, )>,
@ -268,6 +301,9 @@ impl Body {
pats, pats,
bindings, bindings,
binding_owners, binding_owners,
binding_hygiene,
expr_hygiene,
pat_hygiene,
} = self; } = self;
block_scopes.shrink_to_fit(); block_scopes.shrink_to_fit();
exprs.shrink_to_fit(); exprs.shrink_to_fit();
@ -275,6 +311,9 @@ impl Body {
pats.shrink_to_fit(); pats.shrink_to_fit();
bindings.shrink_to_fit(); bindings.shrink_to_fit();
binding_owners.shrink_to_fit(); binding_owners.shrink_to_fit();
binding_hygiene.shrink_to_fit();
expr_hygiene.shrink_to_fit();
pat_hygiene.shrink_to_fit();
} }
pub fn walk_bindings_in_pat(&self, pat_id: PatId, mut f: impl FnMut(BindingId)) { pub fn walk_bindings_in_pat(&self, pat_id: PatId, mut f: impl FnMut(BindingId)) {
@ -467,6 +506,25 @@ impl Body {
} }
}); });
} }
fn binding_hygiene(&self, binding: BindingId) -> HygieneId {
self.binding_hygiene.get(&binding).copied().unwrap_or(HygieneId::ROOT)
}
pub fn expr_path_hygiene(&self, expr: ExprId) -> HygieneId {
self.expr_hygiene.get(&expr).copied().unwrap_or(HygieneId::ROOT)
}
pub fn pat_path_hygiene(&self, pat: PatId) -> HygieneId {
self.pat_hygiene.get(&pat).copied().unwrap_or(HygieneId::ROOT)
}
pub fn expr_or_pat_path_hygiene(&self, id: ExprOrPatId) -> HygieneId {
match id {
ExprOrPatId::ExprId(id) => self.expr_path_hygiene(id),
ExprOrPatId::PatId(id) => self.pat_path_hygiene(id),
}
}
} }
impl Default for Body { impl Default for Body {
@ -481,6 +539,9 @@ impl Default for Body {
block_scopes: Default::default(), block_scopes: Default::default(),
binding_owners: Default::default(), binding_owners: Default::default(),
self_param: Default::default(), self_param: Default::default(),
binding_hygiene: Default::default(),
expr_hygiene: Default::default(),
pat_hygiene: Default::default(),
} }
} }
} }
@ -594,13 +655,11 @@ impl BodySourceMap {
pub fn implicit_format_args( pub fn implicit_format_args(
&self, &self,
node: InFile<&ast::FormatArgsExpr>, node: InFile<&ast::FormatArgsExpr>,
) -> Option<&[(syntax::TextRange, Name)]> { ) -> Option<(HygieneId, &[(syntax::TextRange, Name)])> {
let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>); let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
self.template_map let (hygiene, names) =
.as_ref()? self.template_map.as_ref()?.0.get(&self.expr_map.get(&src)?.as_expr()?)?;
.0 Some((*hygiene, &**names))
.get(&self.expr_map.get(&src)?.as_expr()?)
.map(std::ops::Deref::deref)
} }
pub fn asm_template_args( pub fn asm_template_args(
@ -649,13 +708,4 @@ impl BodySourceMap {
diagnostics.shrink_to_fit(); diagnostics.shrink_to_fit();
binding_definitions.shrink_to_fit(); binding_definitions.shrink_to_fit();
} }
pub fn template_map(
&self,
) -> Option<&(
FxHashMap<Idx<Expr>, Vec<(tt::TextRange, Name)>>,
FxHashMap<Idx<Expr>, Vec<Vec<(tt::TextRange, usize)>>>,
)> {
self.template_map.as_deref()
}
} }

View file

@ -9,6 +9,7 @@ use base_db::CrateId;
use either::Either; use either::Either;
use hir_expand::{ use hir_expand::{
name::{AsName, Name}, name::{AsName, Name},
span_map::{ExpansionSpanMap, SpanMap},
InFile, InFile,
}; };
use intern::{sym, Interned, Symbol}; use intern::{sym, Interned, Symbol};
@ -22,10 +23,11 @@ use syntax::{
}, },
AstNode, AstPtr, AstToken as _, SyntaxNodePtr, AstNode, AstPtr, AstToken as _, SyntaxNodePtr,
}; };
use text_size::TextSize;
use triomphe::Arc; use triomphe::Arc;
use crate::{ use crate::{
body::{Body, BodyDiagnostic, BodySourceMap, ExprPtr, LabelPtr, PatPtr}, body::{Body, BodyDiagnostic, BodySourceMap, ExprPtr, HygieneId, LabelPtr, PatPtr},
builtin_type::BuiltinUint, builtin_type::BuiltinUint,
data::adt::StructKind, data::adt::StructKind,
db::DefDatabase, db::DefDatabase,
@ -60,6 +62,17 @@ pub(super) fn lower(
krate: CrateId, krate: CrateId,
is_async_fn: bool, is_async_fn: bool,
) -> (Body, BodySourceMap) { ) -> (Body, BodySourceMap) {
// We cannot leave the root span map empty and let any identifier from it be treated as root,
// because when inside nested macros `SyntaxContextId`s from the outer macro will be interleaved
// with the inner macro, and that will cause confusion because they won't be the same as `ROOT`
// even though they should be the same. Also, when the body comes from multiple expansions, their
// hygiene is different.
let span_map = expander.current_file_id().macro_file().map(|_| {
let SpanMap::ExpansionSpanMap(span_map) = expander.span_map(db) else {
panic!("in a macro file there should be `ExpansionSpanMap`");
};
Arc::clone(span_map)
});
ExprCollector { ExprCollector {
db, db,
owner, owner,
@ -74,6 +87,7 @@ pub(super) fn lower(
label_ribs: Vec::new(), label_ribs: Vec::new(),
current_binding_owner: None, current_binding_owner: None,
awaitable_context: None, awaitable_context: None,
current_span_map: span_map,
} }
.collect(params, body, is_async_fn) .collect(params, body, is_async_fn)
} }
@ -90,6 +104,8 @@ struct ExprCollector<'a> {
is_lowering_coroutine: bool, is_lowering_coroutine: bool,
current_span_map: Option<Arc<ExpansionSpanMap>>,
current_try_block_label: Option<LabelId>, current_try_block_label: Option<LabelId>,
// points to the expression that a try expression will target (replaces current_try_block_label) // points to the expression that a try expression will target (replaces current_try_block_label)
// catch_scope: Option<ExprId>, // catch_scope: Option<ExprId>,
@ -109,14 +125,14 @@ struct ExprCollector<'a> {
struct LabelRib { struct LabelRib {
kind: RibKind, kind: RibKind,
// Once we handle macro hygiene this will need to be a map // Once we handle macro hygiene this will need to be a map
label: Option<(Name, LabelId)>, label: Option<(Name, LabelId, HygieneId)>,
} }
impl LabelRib { impl LabelRib {
fn new(kind: RibKind) -> Self { fn new(kind: RibKind) -> Self {
LabelRib { kind, label: None } LabelRib { kind, label: None }
} }
fn new_normal(label: (Name, LabelId)) -> Self { fn new_normal(label: (Name, LabelId, HygieneId)) -> Self {
LabelRib { kind: RibKind::Normal, label: Some(label) } LabelRib { kind: RibKind::Normal, label: Some(label) }
} }
} }
@ -145,7 +161,7 @@ enum Awaitable {
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct BindingList { struct BindingList {
map: FxHashMap<Name, BindingId>, map: FxHashMap<(Name, HygieneId), BindingId>,
is_used: FxHashMap<BindingId, bool>, is_used: FxHashMap<BindingId, bool>,
reject_new: bool, reject_new: bool,
} }
@ -155,9 +171,16 @@ impl BindingList {
&mut self, &mut self,
ec: &mut ExprCollector<'_>, ec: &mut ExprCollector<'_>,
name: Name, name: Name,
hygiene: HygieneId,
mode: BindingAnnotation, mode: BindingAnnotation,
) -> BindingId { ) -> BindingId {
let id = *self.map.entry(name).or_insert_with_key(|n| ec.alloc_binding(n.clone(), mode)); let id = *self.map.entry((name, hygiene)).or_insert_with_key(|(name, _)| {
let id = ec.alloc_binding(name.clone(), mode);
if !hygiene.is_root() {
ec.body.binding_hygiene.insert(id, hygiene);
}
id
});
if ec.body.bindings[id].mode != mode { if ec.body.bindings[id].mode != mode {
ec.body.bindings[id].problems = Some(BindingProblems::BoundInconsistently); ec.body.bindings[id].problems = Some(BindingProblems::BoundInconsistently);
} }
@ -211,6 +234,13 @@ impl ExprCollector<'_> {
Name::new_symbol_root(sym::self_.clone()), Name::new_symbol_root(sym::self_.clone()),
BindingAnnotation::new(is_mutable, false), BindingAnnotation::new(is_mutable, false),
); );
let hygiene = self_param
.name()
.map(|name| self.hygiene_id_for(name.syntax().text_range().start()))
.unwrap_or(HygieneId::ROOT);
if !hygiene.is_root() {
self.body.binding_hygiene.insert(binding_id, hygiene);
}
self.body.self_param = Some(binding_id); self.body.self_param = Some(binding_id);
self.source_map.self_param = Some(self.expander.in_file(AstPtr::new(&self_param))); self.source_map.self_param = Some(self.expander.in_file(AstPtr::new(&self_param)));
} }
@ -288,13 +318,14 @@ impl ExprCollector<'_> {
}) })
} }
Some(ast::BlockModifier::Label(label)) => { Some(ast::BlockModifier::Label(label)) => {
let label = self.collect_label(label); let label_hygiene = self.hygiene_id_for(label.syntax().text_range().start());
self.with_labeled_rib(label, |this| { let label_id = self.collect_label(label);
self.with_labeled_rib(label_id, label_hygiene, |this| {
this.collect_block_(e, |id, statements, tail| Expr::Block { this.collect_block_(e, |id, statements, tail| Expr::Block {
id, id,
statements, statements,
tail, tail,
label: Some(label), label: Some(label_id),
}) })
}) })
} }
@ -336,9 +367,14 @@ impl ExprCollector<'_> {
None => self.collect_block(e), None => self.collect_block(e),
}, },
ast::Expr::LoopExpr(e) => { ast::Expr::LoopExpr(e) => {
let label = e.label().map(|label| self.collect_label(label)); let label = e.label().map(|label| {
(
self.hygiene_id_for(label.syntax().text_range().start()),
self.collect_label(label),
)
});
let body = self.collect_labelled_block_opt(label, e.loop_body()); let body = self.collect_labelled_block_opt(label, e.loop_body());
self.alloc_expr(Expr::Loop { body, label }, syntax_ptr) self.alloc_expr(Expr::Loop { body, label: label.map(|it| it.1) }, syntax_ptr)
} }
ast::Expr::WhileExpr(e) => self.collect_while_loop(syntax_ptr, e), ast::Expr::WhileExpr(e) => self.collect_while_loop(syntax_ptr, e),
ast::Expr::ForExpr(e) => self.collect_for_loop(syntax_ptr, e), ast::Expr::ForExpr(e) => self.collect_for_loop(syntax_ptr, e),
@ -398,12 +434,15 @@ impl ExprCollector<'_> {
self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr) self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr)
} }
ast::Expr::PathExpr(e) => { ast::Expr::PathExpr(e) => {
let path = e let (path, hygiene) = self
.path() .collect_expr_path(&e)
.and_then(|path| self.expander.parse_path(self.db, path)) .map(|(path, hygiene)| (Expr::Path(path), hygiene))
.map(Expr::Path) .unwrap_or((Expr::Missing, HygieneId::ROOT));
.unwrap_or(Expr::Missing); let expr_id = self.alloc_expr(path, syntax_ptr);
self.alloc_expr(path, syntax_ptr) if !hygiene.is_root() {
self.body.expr_hygiene.insert(expr_id, hygiene);
}
expr_id
} }
ast::Expr::ContinueExpr(e) => { ast::Expr::ContinueExpr(e) => {
let label = self.resolve_label(e.lifetime()).unwrap_or_else(|e| { let label = self.resolve_label(e.lifetime()).unwrap_or_else(|e| {
@ -677,6 +716,24 @@ impl ExprCollector<'_> {
}) })
} }
fn collect_expr_path(&mut self, e: &ast::PathExpr) -> Option<(Path, HygieneId)> {
e.path().and_then(|path| {
let path = self.expander.parse_path(self.db, path)?;
let Path::Normal { type_anchor, mod_path, generic_args } = &path else {
panic!("path parsing produced a non-normal path");
};
// Need to enable `mod_path.len() < 1` for `self`.
let may_be_variable =
type_anchor.is_none() && mod_path.len() <= 1 && generic_args.is_none();
let hygiene = if may_be_variable {
self.hygiene_id_for(e.syntax().text_range().start())
} else {
HygieneId::ROOT
};
Some((path, hygiene))
})
}
fn collect_expr_as_pat_opt(&mut self, expr: Option<ast::Expr>) -> PatId { fn collect_expr_as_pat_opt(&mut self, expr: Option<ast::Expr>) -> PatId {
match expr { match expr {
Some(expr) => self.collect_expr_as_pat(expr), Some(expr) => self.collect_expr_as_pat(expr),
@ -740,8 +797,15 @@ impl ExprCollector<'_> {
self.alloc_pat_from_expr(Pat::TupleStruct { path, args, ellipsis }, syntax_ptr) self.alloc_pat_from_expr(Pat::TupleStruct { path, args, ellipsis }, syntax_ptr)
} }
ast::Expr::PathExpr(e) => { ast::Expr::PathExpr(e) => {
let path = Box::new(self.expander.parse_path(self.db, e.path()?)?); let (path, hygiene) = self
self.alloc_pat_from_expr(Pat::Path(path), syntax_ptr) .collect_expr_path(e)
.map(|(path, hygiene)| (Pat::Path(Box::new(path)), hygiene))
.unwrap_or((Pat::Missing, HygieneId::ROOT));
let pat_id = self.alloc_pat_from_expr(path, syntax_ptr);
if !hygiene.is_root() {
self.body.pat_hygiene.insert(pat_id, hygiene);
}
pat_id
} }
ast::Expr::MacroExpr(e) => { ast::Expr::MacroExpr(e) => {
let e = e.macro_call()?; let e = e.macro_call()?;
@ -889,7 +953,7 @@ impl ExprCollector<'_> {
let old_label = self.current_try_block_label.replace(label); let old_label = self.current_try_block_label.replace(label);
let ptr = AstPtr::new(&e).upcast(); let ptr = AstPtr::new(&e).upcast();
let (btail, expr_id) = self.with_labeled_rib(label, |this| { let (btail, expr_id) = self.with_labeled_rib(label, HygieneId::ROOT, |this| {
let mut btail = None; let mut btail = None;
let block = this.collect_block_(e, |id, statements, tail| { let block = this.collect_block_(e, |id, statements, tail| {
btail = tail; btail = tail;
@ -933,7 +997,9 @@ impl ExprCollector<'_> {
/// FIXME: Rustc wraps the condition in a construct equivalent to `{ let _t = <cond>; _t }` /// FIXME: Rustc wraps the condition in a construct equivalent to `{ let _t = <cond>; _t }`
/// to preserve drop semantics. We should probably do the same in future. /// to preserve drop semantics. We should probably do the same in future.
fn collect_while_loop(&mut self, syntax_ptr: AstPtr<ast::Expr>, e: ast::WhileExpr) -> ExprId { fn collect_while_loop(&mut self, syntax_ptr: AstPtr<ast::Expr>, e: ast::WhileExpr) -> ExprId {
let label = e.label().map(|label| self.collect_label(label)); let label = e.label().map(|label| {
(self.hygiene_id_for(label.syntax().text_range().start()), self.collect_label(label))
});
let body = self.collect_labelled_block_opt(label, e.loop_body()); let body = self.collect_labelled_block_opt(label, e.loop_body());
// Labels can also be used in the condition expression, like this: // Labels can also be used in the condition expression, like this:
@ -950,9 +1016,9 @@ impl ExprCollector<'_> {
// } // }
// ``` // ```
let condition = match label { let condition = match label {
Some(label) => { Some((label_hygiene, label)) => self.with_labeled_rib(label, label_hygiene, |this| {
self.with_labeled_rib(label, |this| this.collect_expr_opt(e.condition())) this.collect_expr_opt(e.condition())
} }),
None => self.collect_expr_opt(e.condition()), None => self.collect_expr_opt(e.condition()),
}; };
@ -961,7 +1027,7 @@ impl ExprCollector<'_> {
Expr::If { condition, then_branch: body, else_branch: Some(break_expr) }, Expr::If { condition, then_branch: body, else_branch: Some(break_expr) },
syntax_ptr, syntax_ptr,
); );
self.alloc_expr(Expr::Loop { body: if_expr, label }, syntax_ptr) self.alloc_expr(Expr::Loop { body: if_expr, label: label.map(|it| it.1) }, syntax_ptr)
} }
/// Desugar `ast::ForExpr` from: `[opt_ident]: for <pat> in <head> <body>` into: /// Desugar `ast::ForExpr` from: `[opt_ident]: for <pat> in <head> <body>` into:
@ -1005,7 +1071,9 @@ impl ExprCollector<'_> {
args: Box::new([self.collect_pat_top(e.pat())]), args: Box::new([self.collect_pat_top(e.pat())]),
ellipsis: None, ellipsis: None,
}; };
let label = e.label().map(|label| self.collect_label(label)); let label = e.label().map(|label| {
(self.hygiene_id_for(label.syntax().text_range().start()), self.collect_label(label))
});
let some_arm = MatchArm { let some_arm = MatchArm {
pat: self.alloc_pat_desugared(some_pat), pat: self.alloc_pat_desugared(some_pat),
guard: None, guard: None,
@ -1037,7 +1105,8 @@ impl ExprCollector<'_> {
}, },
syntax_ptr, syntax_ptr,
); );
let loop_outer = self.alloc_expr(Expr::Loop { body: loop_inner, label }, syntax_ptr); let loop_outer = self
.alloc_expr(Expr::Loop { body: loop_inner, label: label.map(|it| it.1) }, syntax_ptr);
let iter_binding = self.alloc_binding(iter_name, BindingAnnotation::Mutable); let iter_binding = self.alloc_binding(iter_name, BindingAnnotation::Mutable);
let iter_pat = self.alloc_pat_desugared(Pat::Bind { id: iter_binding, subpat: None }); let iter_pat = self.alloc_pat_desugared(Pat::Bind { id: iter_binding, subpat: None });
self.add_definition_to_binding(iter_binding, iter_pat); self.add_definition_to_binding(iter_binding, iter_pat);
@ -1194,7 +1263,14 @@ impl ExprCollector<'_> {
// FIXME: Report parse errors here // FIXME: Report parse errors here
} }
let SpanMap::ExpansionSpanMap(new_span_map) = self.expander.span_map(self.db)
else {
panic!("just expanded a macro, ExpansionSpanMap should be available");
};
let old_span_map =
mem::replace(&mut self.current_span_map, Some(new_span_map.clone()));
let id = collector(self, Some(expansion.tree())); let id = collector(self, Some(expansion.tree()));
self.current_span_map = old_span_map;
self.ast_id_map = prev_ast_id_map; self.ast_id_map = prev_ast_id_map;
self.expander.exit(mark); self.expander.exit(mark);
id id
@ -1357,11 +1433,13 @@ impl ExprCollector<'_> {
fn collect_labelled_block_opt( fn collect_labelled_block_opt(
&mut self, &mut self,
label: Option<LabelId>, label: Option<(HygieneId, LabelId)>,
expr: Option<ast::BlockExpr>, expr: Option<ast::BlockExpr>,
) -> ExprId { ) -> ExprId {
match label { match label {
Some(label) => self.with_labeled_rib(label, |this| this.collect_block_opt(expr)), Some((hygiene, label)) => {
self.with_labeled_rib(label, hygiene, |this| this.collect_block_opt(expr))
}
None => self.collect_block_opt(expr), None => self.collect_block_opt(expr),
} }
} }
@ -1379,6 +1457,10 @@ impl ExprCollector<'_> {
let pattern = match &pat { let pattern = match &pat {
ast::Pat::IdentPat(bp) => { ast::Pat::IdentPat(bp) => {
let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing); let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
let hygiene = bp
.name()
.map(|name| self.hygiene_id_for(name.syntax().text_range().start()))
.unwrap_or(HygieneId::ROOT);
let annotation = let annotation =
BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some()); BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some());
@ -1414,12 +1496,12 @@ impl ExprCollector<'_> {
} }
// shadowing statics is an error as well, so we just ignore that case here // shadowing statics is an error as well, so we just ignore that case here
_ => { _ => {
let id = binding_list.find(self, name, annotation); let id = binding_list.find(self, name, hygiene, annotation);
(Some(id), Pat::Bind { id, subpat }) (Some(id), Pat::Bind { id, subpat })
} }
} }
} else { } else {
let id = binding_list.find(self, name, annotation); let id = binding_list.find(self, name, hygiene, annotation);
(Some(id), Pat::Bind { id, subpat }) (Some(id), Pat::Bind { id, subpat })
}; };
@ -1698,11 +1780,12 @@ impl ExprCollector<'_> {
lifetime: Option<ast::Lifetime>, lifetime: Option<ast::Lifetime>,
) -> Result<Option<LabelId>, BodyDiagnostic> { ) -> Result<Option<LabelId>, BodyDiagnostic> {
let Some(lifetime) = lifetime else { return Ok(None) }; let Some(lifetime) = lifetime else { return Ok(None) };
let hygiene = self.hygiene_id_for(lifetime.syntax().text_range().start());
let name = Name::new_lifetime(&lifetime); let name = Name::new_lifetime(&lifetime);
for (rib_idx, rib) in self.label_ribs.iter().enumerate().rev() { for (rib_idx, rib) in self.label_ribs.iter().enumerate().rev() {
if let Some((label_name, id)) = &rib.label { if let Some((label_name, id, label_hygiene)) = &rib.label {
if *label_name == name { if *label_name == name && *label_hygiene == hygiene {
return if self.is_label_valid_from_rib(rib_idx) { return if self.is_label_valid_from_rib(rib_idx) {
Ok(Some(*id)) Ok(Some(*id))
} else { } else {
@ -1732,8 +1815,13 @@ impl ExprCollector<'_> {
res res
} }
fn with_labeled_rib<T>(&mut self, label: LabelId, f: impl FnOnce(&mut Self) -> T) -> T { fn with_labeled_rib<T>(
self.label_ribs.push(LabelRib::new_normal((self.body[label].name.clone(), label))); &mut self,
label: LabelId,
hygiene: HygieneId,
f: impl FnOnce(&mut Self) -> T,
) -> T {
self.label_ribs.push(LabelRib::new_normal((self.body[label].name.clone(), label, hygiene)));
let res = f(self); let res = f(self);
self.label_ribs.pop(); self.label_ribs.pop();
res res
@ -1741,12 +1829,12 @@ impl ExprCollector<'_> {
fn with_opt_labeled_rib<T>( fn with_opt_labeled_rib<T>(
&mut self, &mut self,
label: Option<LabelId>, label: Option<(HygieneId, LabelId)>,
f: impl FnOnce(&mut Self) -> T, f: impl FnOnce(&mut Self) -> T,
) -> T { ) -> T {
match label { match label {
None => f(self), None => f(self),
Some(label) => self.with_labeled_rib(label, f), Some((hygiene, label)) => self.with_labeled_rib(label, hygiene, f),
} }
} }
// endregion: labels // endregion: labels
@ -1795,28 +1883,39 @@ impl ExprCollector<'_> {
_ => None, _ => None,
}); });
let mut mappings = vec![]; let mut mappings = vec![];
let fmt = match template.and_then(|it| self.expand_macros_to_string(it)) { let (fmt, hygiene) = match template.and_then(|it| self.expand_macros_to_string(it)) {
Some((s, is_direct_literal)) => { Some((s, is_direct_literal)) => {
let call_ctx = self.expander.syntax_context(); let call_ctx = self.expander.syntax_context();
format_args::parse( let hygiene = self.hygiene_id_for(s.syntax().text_range().start());
let fmt = format_args::parse(
&s, &s,
fmt_snippet, fmt_snippet,
args, args,
is_direct_literal, is_direct_literal,
|name| self.alloc_expr_desugared(Expr::Path(Path::from(name))), |name| {
let expr_id = self.alloc_expr_desugared(Expr::Path(Path::from(name)));
if !hygiene.is_root() {
self.body.expr_hygiene.insert(expr_id, hygiene);
}
expr_id
},
|name, span| { |name, span| {
if let Some(span) = span { if let Some(span) = span {
mappings.push((span, name)) mappings.push((span, name))
} }
}, },
call_ctx, call_ctx,
) );
(fmt, hygiene)
} }
None => FormatArgs { None => (
FormatArgs {
template: Default::default(), template: Default::default(),
arguments: args.finish(), arguments: args.finish(),
orphans: Default::default(), orphans: Default::default(),
}, },
HygieneId::ROOT,
),
}; };
// Create a list of all _unique_ (argument, format trait) combinations. // Create a list of all _unique_ (argument, format trait) combinations.
@ -1963,7 +2062,11 @@ impl ExprCollector<'_> {
}, },
syntax_ptr, syntax_ptr,
); );
self.source_map.template_map.get_or_insert_with(Default::default).0.insert(idx, mappings); self.source_map
.template_map
.get_or_insert_with(Default::default)
.0
.insert(idx, (hygiene, mappings));
idx idx
} }
@ -2264,6 +2367,17 @@ impl ExprCollector<'_> {
self.awaitable_context = orig; self.awaitable_context = orig;
res res
} }
/// If this returns `HygieneId::ROOT`, do not allocate to save space.
fn hygiene_id_for(&self, span_start: TextSize) -> HygieneId {
match &self.current_span_map {
None => HygieneId::ROOT,
Some(span_map) => {
let ctx = span_map.span_at(span_start).ctx;
HygieneId(self.db.lookup_intern_syntax_context(ctx).opaque_and_semitransparent)
}
}
}
} }
fn comma_follows_token(t: Option<syntax::SyntaxToken>) -> bool { fn comma_follows_token(t: Option<syntax::SyntaxToken>) -> bool {

View file

@ -4,7 +4,7 @@ use la_arena::{Arena, ArenaMap, Idx, IdxRange, RawIdx};
use triomphe::Arc; use triomphe::Arc;
use crate::{ use crate::{
body::Body, body::{Body, HygieneId},
db::DefDatabase, db::DefDatabase,
hir::{Binding, BindingId, Expr, ExprId, LabelId, Pat, PatId, Statement}, hir::{Binding, BindingId, Expr, ExprId, LabelId, Pat, PatId, Statement},
BlockId, ConstBlockId, DefWithBodyId, BlockId, ConstBlockId, DefWithBodyId,
@ -22,6 +22,7 @@ pub struct ExprScopes {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct ScopeEntry { pub struct ScopeEntry {
name: Name, name: Name,
hygiene: HygieneId,
binding: BindingId, binding: BindingId,
} }
@ -30,6 +31,10 @@ impl ScopeEntry {
&self.name &self.name
} }
pub(crate) fn hygiene(&self) -> HygieneId {
self.hygiene
}
pub fn binding(&self) -> BindingId { pub fn binding(&self) -> BindingId {
self.binding self.binding
} }
@ -102,7 +107,7 @@ impl ExprScopes {
}; };
let mut root = scopes.root_scope(); let mut root = scopes.root_scope();
if let Some(self_param) = body.self_param { if let Some(self_param) = body.self_param {
scopes.add_bindings(body, root, self_param); scopes.add_bindings(body, root, self_param, body.binding_hygiene(self_param));
} }
scopes.add_params_bindings(body, root, &body.params); scopes.add_params_bindings(body, root, &body.params);
compute_expr_scopes(body.body_expr, body, &mut scopes, &mut root, resolve_const_block); compute_expr_scopes(body.body_expr, body, &mut scopes, &mut root, resolve_const_block);
@ -150,17 +155,23 @@ impl ExprScopes {
}) })
} }
fn add_bindings(&mut self, body: &Body, scope: ScopeId, binding: BindingId) { fn add_bindings(
&mut self,
body: &Body,
scope: ScopeId,
binding: BindingId,
hygiene: HygieneId,
) {
let Binding { name, .. } = &body.bindings[binding]; let Binding { name, .. } = &body.bindings[binding];
let entry = self.scope_entries.alloc(ScopeEntry { name: name.clone(), binding }); let entry = self.scope_entries.alloc(ScopeEntry { name: name.clone(), binding, hygiene });
self.scopes[scope].entries = self.scopes[scope].entries =
IdxRange::new_inclusive(self.scopes[scope].entries.start()..=entry); IdxRange::new_inclusive(self.scopes[scope].entries.start()..=entry);
} }
fn add_pat_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) { fn add_pat_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
let pattern = &body[pat]; let pattern = &body[pat];
if let Pat::Bind { id, .. } = pattern { if let Pat::Bind { id, .. } = *pattern {
self.add_bindings(body, scope, *id); self.add_bindings(body, scope, id, body.binding_hygiene(id));
} }
pattern.walk_child_pats(|pat| self.add_pat_bindings(body, scope, pat)); pattern.walk_child_pats(|pat| self.add_pat_bindings(body, scope, pat));

View file

@ -49,6 +49,10 @@ impl Expander {
} }
} }
pub(crate) fn span_map(&self, db: &dyn DefDatabase) -> &SpanMap {
self.span_map.get_or_init(|| db.span_map(self.current_file_id))
}
pub fn krate(&self) -> CrateId { pub fn krate(&self) -> CrateId {
self.module.krate self.module.krate
} }

View file

@ -10,7 +10,10 @@ use smallvec::{smallvec, SmallVec};
use triomphe::Arc; use triomphe::Arc;
use crate::{ use crate::{
body::scope::{ExprScopes, ScopeId}, body::{
scope::{ExprScopes, ScopeId},
HygieneId,
},
builtin_type::BuiltinType, builtin_type::BuiltinType,
data::ExternCrateDeclData, data::ExternCrateDeclData,
db::DefDatabase, db::DefDatabase,
@ -257,6 +260,7 @@ impl Resolver {
&self, &self,
db: &dyn DefDatabase, db: &dyn DefDatabase,
path: &Path, path: &Path,
hygiene: HygieneId,
) -> Option<ResolveValueResult> { ) -> Option<ResolveValueResult> {
let path = match path { let path = match path {
Path::Normal { mod_path, .. } => mod_path, Path::Normal { mod_path, .. } => mod_path,
@ -303,11 +307,10 @@ impl Resolver {
for scope in self.scopes() { for scope in self.scopes() {
match scope { match scope {
Scope::ExprScope(scope) => { Scope::ExprScope(scope) => {
let entry = scope let entry =
.expr_scopes scope.expr_scopes.entries(scope.scope_id).iter().find(|entry| {
.entries(scope.scope_id) entry.name() == first_name && entry.hygiene() == hygiene
.iter() });
.find(|entry| entry.name() == first_name);
if let Some(e) = entry { if let Some(e) = entry {
return Some(ResolveValueResult::ValueNs( return Some(ResolveValueResult::ValueNs(
@ -393,8 +396,9 @@ impl Resolver {
&self, &self,
db: &dyn DefDatabase, db: &dyn DefDatabase,
path: &Path, path: &Path,
hygiene: HygieneId,
) -> Option<ValueNs> { ) -> Option<ValueNs> {
match self.resolve_path_in_value_ns(db, path)? { match self.resolve_path_in_value_ns(db, path, hygiene)? {
ResolveValueResult::ValueNs(it, _) => Some(it), ResolveValueResult::ValueNs(it, _) => Some(it),
ResolveValueResult::Partial(..) => None, ResolveValueResult::Partial(..) => None,
} }

View file

@ -18,6 +18,8 @@ use syntax::utils::is_raw_identifier;
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct Name { pub struct Name {
symbol: Symbol, symbol: Symbol,
// If you are making this carry actual hygiene, beware that the special handling for variables and labels
// in bodies can go.
ctx: (), ctx: (),
} }

View file

@ -3,7 +3,7 @@
use base_db::{ra_salsa::Cycle, CrateId}; use base_db::{ra_salsa::Cycle, CrateId};
use chalk_ir::{cast::Cast, BoundVar, DebruijnIndex}; use chalk_ir::{cast::Cast, BoundVar, DebruijnIndex};
use hir_def::{ use hir_def::{
body::Body, body::{Body, HygieneId},
hir::{Expr, ExprId}, hir::{Expr, ExprId},
path::Path, path::Path,
resolver::{Resolver, ValueNs}, resolver::{Resolver, ValueNs},
@ -80,7 +80,7 @@ pub(crate) fn path_to_const<'g>(
debruijn: DebruijnIndex, debruijn: DebruijnIndex,
expected_ty: Ty, expected_ty: Ty,
) -> Option<Const> { ) -> Option<Const> {
match resolver.resolve_path_in_value_ns_fully(db.upcast(), path) { match resolver.resolve_path_in_value_ns_fully(db.upcast(), path, HygieneId::ROOT) {
Some(ValueNs::GenericParam(p)) => { Some(ValueNs::GenericParam(p)) => {
let ty = db.const_param_ty(p); let ty = db.const_param_ty(p);
let value = match mode { let value = match mode {

View file

@ -289,10 +289,12 @@ impl ExprValidator {
match &self.body[scrutinee_expr] { match &self.body[scrutinee_expr] {
Expr::UnaryOp { op: UnaryOp::Deref, .. } => false, Expr::UnaryOp { op: UnaryOp::Deref, .. } => false,
Expr::Path(path) => { Expr::Path(path) => {
let value_or_partial = self let value_or_partial =
.owner self.owner.resolver(db.upcast()).resolve_path_in_value_ns_fully(
.resolver(db.upcast()) db.upcast(),
.resolve_path_in_value_ns_fully(db.upcast(), path); path,
self.body.expr_path_hygiene(scrutinee_expr),
);
value_or_partial.map_or(true, |v| !matches!(v, ValueNs::StaticId(_))) value_or_partial.map_or(true, |v| !matches!(v, ValueNs::StaticId(_)))
} }
Expr::Field { expr, .. } => match self.infer.type_of_expr[*expr].kind(Interner) { Expr::Field { expr, .. } => match self.infer.type_of_expr[*expr].kind(Interner) {

View file

@ -77,7 +77,8 @@ fn walk_unsafe(
) { ) {
let mut mark_unsafe_path = |path, node| { let mut mark_unsafe_path = |path, node| {
let g = resolver.update_to_inner_scope(db.upcast(), def, current); let g = resolver.update_to_inner_scope(db.upcast(), def, current);
let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path); let hygiene = body.expr_or_pat_path_hygiene(node);
let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path, hygiene);
if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial { if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
let static_data = db.static_data(id); let static_data = db.static_data(id);
if static_data.mutable || (static_data.is_extern && !static_data.has_safe_kw) { if static_data.mutable || (static_data.is_extern && !static_data.has_safe_kw) {

View file

@ -33,7 +33,7 @@ use chalk_ir::{
}; };
use either::Either; use either::Either;
use hir_def::{ use hir_def::{
body::Body, body::{Body, HygieneId},
builtin_type::{BuiltinInt, BuiltinType, BuiltinUint}, builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
data::{ConstData, StaticData}, data::{ConstData, StaticData},
hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, PatId}, hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, PatId},
@ -1398,7 +1398,7 @@ impl<'a> InferenceContext<'a> {
}; };
let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver, self.owner.into()); let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver, self.owner.into());
let (resolution, unresolved) = if value_ns { let (resolution, unresolved) = if value_ns {
match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path) { match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, HygieneId::ROOT) {
Some(ResolveValueResult::ValueNs(value, _)) => match value { Some(ResolveValueResult::ValueNs(value, _)) => match value {
ValueNs::EnumVariantId(var) => { ValueNs::EnumVariantId(var) => {
let substs = ctx.substs_from_path(path, var.into(), true); let substs = ctx.substs_from_path(path, var.into(), true);

View file

@ -514,8 +514,11 @@ impl InferenceContext<'_> {
if path.type_anchor().is_some() { if path.type_anchor().is_some() {
return None; return None;
} }
let result = self.resolver.resolve_path_in_value_ns_fully(self.db.upcast(), path).and_then( let hygiene = self.body.expr_or_pat_path_hygiene(id);
|result| match result { let result = self
.resolver
.resolve_path_in_value_ns_fully(self.db.upcast(), path, hygiene)
.and_then(|result| match result {
ValueNs::LocalBinding(binding) => { ValueNs::LocalBinding(binding) => {
let mir_span = match id { let mir_span = match id {
ExprOrPatId::ExprId(id) => MirSpan::ExprId(id), ExprOrPatId::ExprId(id) => MirSpan::ExprId(id),
@ -525,8 +528,7 @@ impl InferenceContext<'_> {
Some(HirPlace { local: binding, projections: Vec::new() }) Some(HirPlace { local: binding, projections: Vec::new() })
} }
_ => None, _ => None,
}, });
);
result result
} }

View file

@ -201,7 +201,11 @@ impl InferenceContext<'_> {
Expr::Path(Path::Normal { type_anchor: Some(_), .. }) => false, Expr::Path(Path::Normal { type_anchor: Some(_), .. }) => false,
Expr::Path(path) => self Expr::Path(path) => self
.resolver .resolver
.resolve_path_in_value_ns_fully(self.db.upcast(), path) .resolve_path_in_value_ns_fully(
self.db.upcast(),
path,
self.body.expr_path_hygiene(expr),
)
.map_or(true, |res| matches!(res, ValueNs::LocalBinding(_) | ValueNs::StaticId(_))), .map_or(true, |res| matches!(res, ValueNs::LocalBinding(_) | ValueNs::StaticId(_))),
Expr::Underscore => true, Expr::Underscore => true,
Expr::UnaryOp { op: UnaryOp::Deref, .. } => true, Expr::UnaryOp { op: UnaryOp::Deref, .. } => true,

View file

@ -164,9 +164,10 @@ impl InferenceContext<'_> {
let ty = self.table.normalize_associated_types_in(ty); let ty = self.table.normalize_associated_types_in(ty);
self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))? self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))?
} else { } else {
let hygiene = self.body.expr_or_pat_path_hygiene(id);
// FIXME: report error, unresolved first path segment // FIXME: report error, unresolved first path segment
let value_or_partial = let value_or_partial =
self.resolver.resolve_path_in_value_ns(self.db.upcast(), path)?; self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene)?;
match value_or_partial { match value_or_partial {
ResolveValueResult::ValueNs(it, _) => (it, None), ResolveValueResult::ValueNs(it, _) => (it, None),

View file

@ -6,6 +6,7 @@ use base_db::CrateId;
use chalk_ir::{cast::Cast, Mutability}; use chalk_ir::{cast::Cast, Mutability};
use either::Either; use either::Either;
use hir_def::{ use hir_def::{
body::HygieneId,
builtin_type::BuiltinType, builtin_type::BuiltinType,
data::adt::{StructFlags, VariantData}, data::adt::{StructFlags, VariantData},
lang_item::LangItem, lang_item::LangItem,
@ -2953,6 +2954,7 @@ pub fn render_const_using_debug_impl(
let Some(ValueNs::FunctionId(format_fn)) = resolver.resolve_path_in_value_ns_fully( let Some(ValueNs::FunctionId(format_fn)) = resolver.resolve_path_in_value_ns_fully(
db.upcast(), db.upcast(),
&hir_def::path::Path::from_known_path_with_no_generic(path![std::fmt::format]), &hir_def::path::Path::from_known_path_with_no_generic(path![std::fmt::format]),
HygieneId::ROOT,
) else { ) else {
not_supported!("std::fmt::format not found"); not_supported!("std::fmt::format not found");
}; };

View file

@ -5,7 +5,7 @@ use std::{fmt::Write, iter, mem};
use base_db::ra_salsa::Cycle; use base_db::ra_salsa::Cycle;
use chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind}; use chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind};
use hir_def::{ use hir_def::{
body::Body, body::{Body, HygieneId},
data::adt::{StructKind, VariantData}, data::adt::{StructKind, VariantData},
hir::{ hir::{
ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ExprId, LabelId, Literal, ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ExprId, LabelId, Literal,
@ -446,9 +446,10 @@ impl<'ctx> MirLowerCtx<'ctx> {
} else { } else {
let resolver_guard = let resolver_guard =
self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, expr_id); self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, expr_id);
let hygiene = self.body.expr_path_hygiene(expr_id);
let result = self let result = self
.resolver .resolver
.resolve_path_in_value_ns_fully(self.db.upcast(), p) .resolve_path_in_value_ns_fully(self.db.upcast(), p, hygiene)
.ok_or_else(|| { .ok_or_else(|| {
MirLowerError::unresolved_path(self.db, p, self.edition()) MirLowerError::unresolved_path(self.db, p, self.edition())
})?; })?;
@ -1361,7 +1362,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
|| MirLowerError::unresolved_path(self.db, c.as_ref(), edition); || MirLowerError::unresolved_path(self.db, c.as_ref(), edition);
let pr = self let pr = self
.resolver .resolver
.resolve_path_in_value_ns(self.db.upcast(), c.as_ref()) .resolve_path_in_value_ns(self.db.upcast(), c.as_ref(), HygieneId::ROOT)
.ok_or_else(unresolved_name)?; .ok_or_else(unresolved_name)?;
match pr { match pr {
ResolveValueResult::ValueNs(v, _) => { ResolveValueResult::ValueNs(v, _) => {

View file

@ -137,7 +137,9 @@ impl MirLowerCtx<'_> {
Expr::Path(p) => { Expr::Path(p) => {
let resolver_guard = let resolver_guard =
self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, expr_id); self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, expr_id);
let resolved = self.resolver.resolve_path_in_value_ns_fully(self.db.upcast(), p); let hygiene = self.body.expr_path_hygiene(expr_id);
let resolved =
self.resolver.resolve_path_in_value_ns_fully(self.db.upcast(), p, hygiene);
self.resolver.reset_to_guard(resolver_guard); self.resolver.reset_to_guard(resolver_guard);
let Some(pr) = resolved else { let Some(pr) = resolved else {
return try_rvalue(self); return try_rvalue(self);

View file

@ -351,9 +351,10 @@ impl MirLowerCtx<'_> {
None => { None => {
let unresolved_name = let unresolved_name =
|| MirLowerError::unresolved_path(self.db, p, self.edition()); || MirLowerError::unresolved_path(self.db, p, self.edition());
let hygiene = self.body.pat_path_hygiene(pattern);
let pr = self let pr = self
.resolver .resolver
.resolve_path_in_value_ns(self.db.upcast(), p) .resolve_path_in_value_ns(self.db.upcast(), p, hygiene)
.ok_or_else(unresolved_name)?; .ok_or_else(unresolved_name)?;
if let ( if let (

View file

@ -3720,3 +3720,20 @@ fn test() -> bool {
"#]], "#]],
); );
} }
#[test]
fn macro_semitransparent_hygiene() {
check_types(
r#"
macro_rules! m {
() => { let bar: i32; };
}
fn foo() {
let bar: bool;
m!();
bar;
// ^^^ bool
}
"#,
);
}

View file

@ -45,7 +45,7 @@ use syntax::{
use crate::{ use crate::{
db::HirDatabase, db::HirDatabase,
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
source_analyzer::{resolve_hir_path, SourceAnalyzer}, source_analyzer::{name_hygiene, resolve_hir_path, SourceAnalyzer},
Access, Adjust, Adjustment, Adt, AutoBorrow, BindingMode, BuiltinAttr, Callable, Const, Access, Adjust, Adjustment, Adt, AutoBorrow, BindingMode, BuiltinAttr, Callable, Const,
ConstParam, Crate, DeriveHelper, Enum, Field, Function, HasSource, HirFileId, Impl, InFile, ConstParam, Crate, DeriveHelper, Enum, Field, Function, HasSource, HirFileId, Impl, InFile,
InlineAsmOperand, ItemInNs, Label, LifetimeParam, Local, Macro, Module, ModuleDef, Name, InlineAsmOperand, ItemInNs, Label, LifetimeParam, Local, Macro, Module, ModuleDef, Name,
@ -1952,10 +1952,15 @@ impl SemanticsScope<'_> {
/// Resolve a path as-if it was written at the given scope. This is /// Resolve a path as-if it was written at the given scope. This is
/// necessary a heuristic, as it doesn't take hygiene into account. /// necessary a heuristic, as it doesn't take hygiene into account.
pub fn speculative_resolve(&self, path: &ast::Path) -> Option<PathResolution> { pub fn speculative_resolve(&self, ast_path: &ast::Path) -> Option<PathResolution> {
let ctx = LowerCtx::new(self.db.upcast(), self.file_id); let ctx = LowerCtx::new(self.db.upcast(), self.file_id);
let path = Path::from_src(&ctx, path.clone())?; let path = Path::from_src(&ctx, ast_path.clone())?;
resolve_hir_path(self.db, &self.resolver, &path) resolve_hir_path(
self.db,
&self.resolver,
&path,
name_hygiene(self.db, InFile::new(self.file_id, ast_path.syntax())),
)
} }
/// Iterates over associated types that may be specified after the given path (using /// Iterates over associated types that may be specified after the given path (using

View file

@ -16,7 +16,7 @@ use either::Either;
use hir_def::{ use hir_def::{
body::{ body::{
scope::{ExprScopes, ScopeId}, scope::{ExprScopes, ScopeId},
Body, BodySourceMap, Body, BodySourceMap, HygieneId,
}, },
hir::{BindingId, ExprId, ExprOrPatId, Pat, PatId}, hir::{BindingId, ExprId, ExprOrPatId, Pat, PatId},
lang_item::LangItem, lang_item::LangItem,
@ -562,7 +562,8 @@ impl SourceAnalyzer {
let expr = ast::Expr::from(record_expr); let expr = ast::Expr::from(record_expr);
let expr_id = self.body_source_map()?.node_expr(InFile::new(self.file_id, &expr))?; let expr_id = self.body_source_map()?.node_expr(InFile::new(self.file_id, &expr))?;
let local_name = field.field_name()?.as_name(); let ast_name = field.field_name()?;
let local_name = ast_name.as_name();
let local = if field.name_ref().is_some() { let local = if field.name_ref().is_some() {
None None
} else { } else {
@ -571,7 +572,11 @@ impl SourceAnalyzer {
PathKind::Plain, PathKind::Plain,
once(local_name.clone()), once(local_name.clone()),
)); ));
match self.resolver.resolve_path_in_value_ns_fully(db.upcast(), &path) { match self.resolver.resolve_path_in_value_ns_fully(
db.upcast(),
&path,
name_hygiene(db, InFile::new(self.file_id, ast_name.syntax())),
) {
Some(ValueNs::LocalBinding(binding_id)) => { Some(ValueNs::LocalBinding(binding_id)) => {
Some(Local { binding_id, parent: self.resolver.body_owner()? }) Some(Local { binding_id, parent: self.resolver.body_owner()? })
} }
@ -627,7 +632,7 @@ impl SourceAnalyzer {
Pat::Path(path) => path, Pat::Path(path) => path,
_ => return None, _ => return None,
}; };
let res = resolve_hir_path(db, &self.resolver, path)?; let res = resolve_hir_path(db, &self.resolver, path, HygieneId::ROOT)?;
match res { match res {
PathResolution::Def(def) => Some(def), PathResolution::Def(def) => Some(def),
_ => None, _ => None,
@ -818,7 +823,13 @@ impl SourceAnalyzer {
if parent().map_or(false, |it| ast::Visibility::can_cast(it.kind())) { if parent().map_or(false, |it| ast::Visibility::can_cast(it.kind())) {
resolve_hir_path_qualifier(db, &self.resolver, &hir_path) resolve_hir_path_qualifier(db, &self.resolver, &hir_path)
} else { } else {
resolve_hir_path_(db, &self.resolver, &hir_path, prefer_value_ns) resolve_hir_path_(
db,
&self.resolver,
&hir_path,
prefer_value_ns,
name_hygiene(db, InFile::new(self.file_id, path.syntax())),
)
} }
} }
@ -944,7 +955,7 @@ impl SourceAnalyzer {
format_args: InFile<&ast::FormatArgsExpr>, format_args: InFile<&ast::FormatArgsExpr>,
offset: TextSize, offset: TextSize,
) -> Option<(TextRange, Option<PathResolution>)> { ) -> Option<(TextRange, Option<PathResolution>)> {
let implicits = self.body_source_map()?.implicit_format_args(format_args)?; let (hygiene, implicits) = self.body_source_map()?.implicit_format_args(format_args)?;
implicits.iter().find(|(range, _)| range.contains_inclusive(offset)).map(|(range, name)| { implicits.iter().find(|(range, _)| range.contains_inclusive(offset)).map(|(range, name)| {
( (
*range, *range,
@ -956,6 +967,7 @@ impl SourceAnalyzer {
PathKind::Plain, PathKind::Plain,
Some(name.clone()), Some(name.clone()),
)), )),
hygiene,
), ),
) )
}) })
@ -982,8 +994,8 @@ impl SourceAnalyzer {
db: &'a dyn HirDatabase, db: &'a dyn HirDatabase,
format_args: InFile<&ast::FormatArgsExpr>, format_args: InFile<&ast::FormatArgsExpr>,
) -> Option<impl Iterator<Item = (TextRange, Option<PathResolution>)> + 'a> { ) -> Option<impl Iterator<Item = (TextRange, Option<PathResolution>)> + 'a> {
Some(self.body_source_map()?.implicit_format_args(format_args)?.iter().map( let (hygiene, names) = self.body_source_map()?.implicit_format_args(format_args)?;
move |(range, name)| { Some(names.iter().map(move |(range, name)| {
( (
*range, *range,
resolve_hir_value_path( resolve_hir_value_path(
@ -994,10 +1006,10 @@ impl SourceAnalyzer {
PathKind::Plain, PathKind::Plain,
Some(name.clone()), Some(name.clone()),
)), )),
hygiene,
), ),
) )
}, }))
))
} }
pub(crate) fn as_asm_parts( pub(crate) fn as_asm_parts(
@ -1143,8 +1155,9 @@ pub(crate) fn resolve_hir_path(
db: &dyn HirDatabase, db: &dyn HirDatabase,
resolver: &Resolver, resolver: &Resolver,
path: &Path, path: &Path,
hygiene: HygieneId,
) -> Option<PathResolution> { ) -> Option<PathResolution> {
resolve_hir_path_(db, resolver, path, false) resolve_hir_path_(db, resolver, path, false, hygiene)
} }
#[inline] #[inline]
@ -1164,6 +1177,7 @@ fn resolve_hir_path_(
resolver: &Resolver, resolver: &Resolver,
path: &Path, path: &Path,
prefer_value_ns: bool, prefer_value_ns: bool,
hygiene: HygieneId,
) -> Option<PathResolution> { ) -> Option<PathResolution> {
let types = || { let types = || {
let (ty, unresolved) = match path.type_anchor() { let (ty, unresolved) = match path.type_anchor() {
@ -1229,7 +1243,7 @@ fn resolve_hir_path_(
}; };
let body_owner = resolver.body_owner(); let body_owner = resolver.body_owner();
let values = || resolve_hir_value_path(db, resolver, body_owner, path); let values = || resolve_hir_value_path(db, resolver, body_owner, path, hygiene);
let items = || { let items = || {
resolver resolver
@ -1254,8 +1268,9 @@ fn resolve_hir_value_path(
resolver: &Resolver, resolver: &Resolver,
body_owner: Option<DefWithBodyId>, body_owner: Option<DefWithBodyId>,
path: &Path, path: &Path,
hygiene: HygieneId,
) -> Option<PathResolution> { ) -> Option<PathResolution> {
resolver.resolve_path_in_value_ns_fully(db.upcast(), path).and_then(|val| { resolver.resolve_path_in_value_ns_fully(db.upcast(), path, hygiene).and_then(|val| {
let res = match val { let res = match val {
ValueNs::LocalBinding(binding_id) => { ValueNs::LocalBinding(binding_id) => {
let var = Local { parent: body_owner?, binding_id }; let var = Local { parent: body_owner?, binding_id };
@ -1360,3 +1375,13 @@ fn resolve_hir_path_qualifier(
.map(|it| PathResolution::Def(it.into())) .map(|it| PathResolution::Def(it.into()))
}) })
} }
pub(crate) fn name_hygiene(db: &dyn HirDatabase, name: InFile<&SyntaxNode>) -> HygieneId {
let Some(macro_file) = name.file_id.macro_file() else {
return HygieneId::ROOT;
};
let span_map = db.expansion_span_map(macro_file);
let ctx = span_map.span_at(name.value.text_range().start()).ctx;
let ctx = db.lookup_intern_syntax_context(ctx);
HygieneId::new(ctx.opaque_and_semitransparent)
}

View file

@ -2999,6 +2999,26 @@ mod bar {
mod m {} mod m {}
use foo::m; use foo::m;
"#,
);
}
#[test]
fn macro_label_hygiene() {
check(
r#"
macro_rules! m {
($x:stmt) => {
'bar: loop { $x }
};
}
fn foo() {
'bar: loop {
// ^^^^
m!(continue 'bar$0);
}
}
"#, "#,
); );
} }