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",
"test-fixture",
"test-utils",
"text-size",
"tracing",
"triomphe",
"tt",

View file

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

View file

@ -33,6 +33,22 @@ use crate::{
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.).
#[derive(Debug, Eq, PartialEq)]
pub struct Body {
@ -55,6 +71,22 @@ pub struct Body {
pub body_expr: ExprId,
/// Block expressions in this body that may contain inner items.
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>;
@ -107,10 +139,11 @@ pub struct BodySourceMap {
field_map_back: FxHashMap<ExprId, FieldSource>,
pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
// FIXME: Make this a sane struct.
template_map: Option<
Box<(
// format_args!
FxHashMap<ExprId, Vec<(syntax::TextRange, Name)>>,
FxHashMap<ExprId, (HygieneId, Vec<(syntax::TextRange, Name)>)>,
// asm!
FxHashMap<ExprId, Vec<Vec<(syntax::TextRange, usize)>>>,
)>,
@ -268,6 +301,9 @@ impl Body {
pats,
bindings,
binding_owners,
binding_hygiene,
expr_hygiene,
pat_hygiene,
} = self;
block_scopes.shrink_to_fit();
exprs.shrink_to_fit();
@ -275,6 +311,9 @@ impl Body {
pats.shrink_to_fit();
bindings.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)) {
@ -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 {
@ -481,6 +539,9 @@ impl Default for Body {
block_scopes: Default::default(),
binding_owners: 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(
&self,
node: InFile<&ast::FormatArgsExpr>,
) -> Option<&[(syntax::TextRange, Name)]> {
) -> Option<(HygieneId, &[(syntax::TextRange, Name)])> {
let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
self.template_map
.as_ref()?
.0
.get(&self.expr_map.get(&src)?.as_expr()?)
.map(std::ops::Deref::deref)
let (hygiene, names) =
self.template_map.as_ref()?.0.get(&self.expr_map.get(&src)?.as_expr()?)?;
Some((*hygiene, &**names))
}
pub fn asm_template_args(
@ -649,13 +708,4 @@ impl BodySourceMap {
diagnostics.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 hir_expand::{
name::{AsName, Name},
span_map::{ExpansionSpanMap, SpanMap},
InFile,
};
use intern::{sym, Interned, Symbol};
@ -22,10 +23,11 @@ use syntax::{
},
AstNode, AstPtr, AstToken as _, SyntaxNodePtr,
};
use text_size::TextSize;
use triomphe::Arc;
use crate::{
body::{Body, BodyDiagnostic, BodySourceMap, ExprPtr, LabelPtr, PatPtr},
body::{Body, BodyDiagnostic, BodySourceMap, ExprPtr, HygieneId, LabelPtr, PatPtr},
builtin_type::BuiltinUint,
data::adt::StructKind,
db::DefDatabase,
@ -60,6 +62,17 @@ pub(super) fn lower(
krate: CrateId,
is_async_fn: bool,
) -> (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 {
db,
owner,
@ -74,6 +87,7 @@ pub(super) fn lower(
label_ribs: Vec::new(),
current_binding_owner: None,
awaitable_context: None,
current_span_map: span_map,
}
.collect(params, body, is_async_fn)
}
@ -90,6 +104,8 @@ struct ExprCollector<'a> {
is_lowering_coroutine: bool,
current_span_map: Option<Arc<ExpansionSpanMap>>,
current_try_block_label: Option<LabelId>,
// points to the expression that a try expression will target (replaces current_try_block_label)
// catch_scope: Option<ExprId>,
@ -109,14 +125,14 @@ struct ExprCollector<'a> {
struct LabelRib {
kind: RibKind,
// Once we handle macro hygiene this will need to be a map
label: Option<(Name, LabelId)>,
label: Option<(Name, LabelId, HygieneId)>,
}
impl LabelRib {
fn new(kind: RibKind) -> Self {
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) }
}
}
@ -145,7 +161,7 @@ enum Awaitable {
#[derive(Debug, Default)]
struct BindingList {
map: FxHashMap<Name, BindingId>,
map: FxHashMap<(Name, HygieneId), BindingId>,
is_used: FxHashMap<BindingId, bool>,
reject_new: bool,
}
@ -155,9 +171,16 @@ impl BindingList {
&mut self,
ec: &mut ExprCollector<'_>,
name: Name,
hygiene: HygieneId,
mode: BindingAnnotation,
) -> 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 {
ec.body.bindings[id].problems = Some(BindingProblems::BoundInconsistently);
}
@ -211,6 +234,13 @@ impl ExprCollector<'_> {
Name::new_symbol_root(sym::self_.clone()),
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.source_map.self_param = Some(self.expander.in_file(AstPtr::new(&self_param)));
}
@ -288,13 +318,14 @@ impl ExprCollector<'_> {
})
}
Some(ast::BlockModifier::Label(label)) => {
let label = self.collect_label(label);
self.with_labeled_rib(label, |this| {
let label_hygiene = self.hygiene_id_for(label.syntax().text_range().start());
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 {
id,
statements,
tail,
label: Some(label),
label: Some(label_id),
})
})
}
@ -336,9 +367,14 @@ impl ExprCollector<'_> {
None => self.collect_block(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());
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::ForExpr(e) => self.collect_for_loop(syntax_ptr, e),
@ -398,12 +434,15 @@ impl ExprCollector<'_> {
self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr)
}
ast::Expr::PathExpr(e) => {
let path = e
.path()
.and_then(|path| self.expander.parse_path(self.db, path))
.map(Expr::Path)
.unwrap_or(Expr::Missing);
self.alloc_expr(path, syntax_ptr)
let (path, hygiene) = self
.collect_expr_path(&e)
.map(|(path, hygiene)| (Expr::Path(path), hygiene))
.unwrap_or((Expr::Missing, HygieneId::ROOT));
let expr_id = self.alloc_expr(path, syntax_ptr);
if !hygiene.is_root() {
self.body.expr_hygiene.insert(expr_id, hygiene);
}
expr_id
}
ast::Expr::ContinueExpr(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 {
match 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)
}
ast::Expr::PathExpr(e) => {
let path = Box::new(self.expander.parse_path(self.db, e.path()?)?);
self.alloc_pat_from_expr(Pat::Path(path), syntax_ptr)
let (path, hygiene) = self
.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) => {
let e = e.macro_call()?;
@ -889,7 +953,7 @@ impl ExprCollector<'_> {
let old_label = self.current_try_block_label.replace(label);
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 block = this.collect_block_(e, |id, statements, tail| {
btail = tail;
@ -933,7 +997,9 @@ impl ExprCollector<'_> {
/// 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.
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());
// Labels can also be used in the condition expression, like this:
@ -950,9 +1016,9 @@ impl ExprCollector<'_> {
// }
// ```
let condition = match label {
Some(label) => {
self.with_labeled_rib(label, |this| this.collect_expr_opt(e.condition()))
}
Some((label_hygiene, label)) => self.with_labeled_rib(label, label_hygiene, |this| {
this.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) },
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:
@ -1005,7 +1071,9 @@ impl ExprCollector<'_> {
args: Box::new([self.collect_pat_top(e.pat())]),
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 {
pat: self.alloc_pat_desugared(some_pat),
guard: None,
@ -1037,7 +1105,8 @@ impl ExprCollector<'_> {
},
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_pat = self.alloc_pat_desugared(Pat::Bind { id: iter_binding, subpat: None });
self.add_definition_to_binding(iter_binding, iter_pat);
@ -1194,7 +1263,14 @@ impl ExprCollector<'_> {
// 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()));
self.current_span_map = old_span_map;
self.ast_id_map = prev_ast_id_map;
self.expander.exit(mark);
id
@ -1357,11 +1433,13 @@ impl ExprCollector<'_> {
fn collect_labelled_block_opt(
&mut self,
label: Option<LabelId>,
label: Option<(HygieneId, LabelId)>,
expr: Option<ast::BlockExpr>,
) -> ExprId {
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),
}
}
@ -1379,6 +1457,10 @@ impl ExprCollector<'_> {
let pattern = match &pat {
ast::Pat::IdentPat(bp) => {
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 =
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
_ => {
let id = binding_list.find(self, name, annotation);
let id = binding_list.find(self, name, hygiene, annotation);
(Some(id), Pat::Bind { id, subpat })
}
}
} else {
let id = binding_list.find(self, name, annotation);
let id = binding_list.find(self, name, hygiene, annotation);
(Some(id), Pat::Bind { id, subpat })
};
@ -1698,11 +1780,12 @@ 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 name = Name::new_lifetime(&lifetime);
for (rib_idx, rib) in self.label_ribs.iter().enumerate().rev() {
if let Some((label_name, id)) = &rib.label {
if *label_name == name {
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 {
@ -1732,8 +1815,13 @@ impl ExprCollector<'_> {
res
}
fn with_labeled_rib<T>(&mut self, label: LabelId, f: impl FnOnce(&mut Self) -> T) -> T {
self.label_ribs.push(LabelRib::new_normal((self.body[label].name.clone(), label)));
fn with_labeled_rib<T>(
&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);
self.label_ribs.pop();
res
@ -1741,12 +1829,12 @@ impl ExprCollector<'_> {
fn with_opt_labeled_rib<T>(
&mut self,
label: Option<LabelId>,
label: Option<(HygieneId, LabelId)>,
f: impl FnOnce(&mut Self) -> T,
) -> T {
match label {
None => f(self),
Some(label) => self.with_labeled_rib(label, f),
Some((hygiene, label)) => self.with_labeled_rib(label, hygiene, f),
}
}
// endregion: labels
@ -1795,28 +1883,39 @@ impl ExprCollector<'_> {
_ => None,
});
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)) => {
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,
fmt_snippet,
args,
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| {
if let Some(span) = span {
mappings.push((span, name))
}
},
call_ctx,
)
);
(fmt, hygiene)
}
None => FormatArgs {
template: Default::default(),
arguments: args.finish(),
orphans: Default::default(),
},
None => (
FormatArgs {
template: Default::default(),
arguments: args.finish(),
orphans: Default::default(),
},
HygieneId::ROOT,
),
};
// Create a list of all _unique_ (argument, format trait) combinations.
@ -1963,7 +2062,11 @@ impl ExprCollector<'_> {
},
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
}
@ -2264,6 +2367,17 @@ impl ExprCollector<'_> {
self.awaitable_context = orig;
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 {

View file

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

View file

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

View file

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

View file

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

View file

@ -289,10 +289,12 @@ impl ExprValidator {
match &self.body[scrutinee_expr] {
Expr::UnaryOp { op: UnaryOp::Deref, .. } => false,
Expr::Path(path) => {
let value_or_partial = self
.owner
.resolver(db.upcast())
.resolve_path_in_value_ns_fully(db.upcast(), path);
let value_or_partial =
self.owner.resolver(db.upcast()).resolve_path_in_value_ns_fully(
db.upcast(),
path,
self.body.expr_path_hygiene(scrutinee_expr),
);
value_or_partial.map_or(true, |v| !matches!(v, ValueNs::StaticId(_)))
}
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 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 {
let static_data = db.static_data(id);
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 hir_def::{
body::Body,
body::{Body, HygieneId},
builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
data::{ConstData, StaticData},
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 (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 {
ValueNs::EnumVariantId(var) => {
let substs = ctx.substs_from_path(path, var.into(), true);

View file

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

View file

@ -201,7 +201,11 @@ impl InferenceContext<'_> {
Expr::Path(Path::Normal { type_anchor: Some(_), .. }) => false,
Expr::Path(path) => self
.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(_))),
Expr::Underscore => true,
Expr::UnaryOp { op: UnaryOp::Deref, .. } => true,

View file

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

View file

@ -6,6 +6,7 @@ use base_db::CrateId;
use chalk_ir::{cast::Cast, Mutability};
use either::Either;
use hir_def::{
body::HygieneId,
builtin_type::BuiltinType,
data::adt::{StructFlags, VariantData},
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(
db.upcast(),
&hir_def::path::Path::from_known_path_with_no_generic(path![std::fmt::format]),
HygieneId::ROOT,
) else {
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 chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind};
use hir_def::{
body::Body,
body::{Body, HygieneId},
data::adt::{StructKind, VariantData},
hir::{
ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ExprId, LabelId, Literal,
@ -446,9 +446,10 @@ impl<'ctx> MirLowerCtx<'ctx> {
} else {
let resolver_guard =
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
.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(|| {
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);
let pr = self
.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)?;
match pr {
ResolveValueResult::ValueNs(v, _) => {

View file

@ -137,7 +137,9 @@ impl MirLowerCtx<'_> {
Expr::Path(p) => {
let resolver_guard =
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);
let Some(pr) = resolved else {
return try_rvalue(self);

View file

@ -351,9 +351,10 @@ impl MirLowerCtx<'_> {
None => {
let unresolved_name =
|| MirLowerError::unresolved_path(self.db, p, self.edition());
let hygiene = self.body.pat_path_hygiene(pattern);
let pr = self
.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)?;
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::{
db::HirDatabase,
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,
ConstParam, Crate, DeriveHelper, Enum, Field, Function, HasSource, HirFileId, Impl, InFile,
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
/// 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 path = Path::from_src(&ctx, path.clone())?;
resolve_hir_path(self.db, &self.resolver, &path)
let path = Path::from_src(&ctx, ast_path.clone())?;
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

View file

@ -16,7 +16,7 @@ use either::Either;
use hir_def::{
body::{
scope::{ExprScopes, ScopeId},
Body, BodySourceMap,
Body, BodySourceMap, HygieneId,
},
hir::{BindingId, ExprId, ExprOrPatId, Pat, PatId},
lang_item::LangItem,
@ -562,7 +562,8 @@ impl SourceAnalyzer {
let expr = ast::Expr::from(record_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() {
None
} else {
@ -571,7 +572,11 @@ impl SourceAnalyzer {
PathKind::Plain,
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(Local { binding_id, parent: self.resolver.body_owner()? })
}
@ -627,7 +632,7 @@ impl SourceAnalyzer {
Pat::Path(path) => path,
_ => return None,
};
let res = resolve_hir_path(db, &self.resolver, path)?;
let res = resolve_hir_path(db, &self.resolver, path, HygieneId::ROOT)?;
match res {
PathResolution::Def(def) => Some(def),
_ => None,
@ -818,7 +823,13 @@ impl SourceAnalyzer {
if parent().map_or(false, |it| ast::Visibility::can_cast(it.kind())) {
resolve_hir_path_qualifier(db, &self.resolver, &hir_path)
} 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>,
offset: TextSize,
) -> 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)| {
(
*range,
@ -956,6 +967,7 @@ impl SourceAnalyzer {
PathKind::Plain,
Some(name.clone()),
)),
hygiene,
),
)
})
@ -982,22 +994,22 @@ impl SourceAnalyzer {
db: &'a dyn HirDatabase,
format_args: InFile<&ast::FormatArgsExpr>,
) -> Option<impl Iterator<Item = (TextRange, Option<PathResolution>)> + 'a> {
Some(self.body_source_map()?.implicit_format_args(format_args)?.iter().map(
move |(range, name)| {
(
*range,
resolve_hir_value_path(
db,
&self.resolver,
self.resolver.body_owner(),
&Path::from_known_path_with_no_generic(ModPath::from_segments(
PathKind::Plain,
Some(name.clone()),
)),
),
)
},
))
let (hygiene, names) = self.body_source_map()?.implicit_format_args(format_args)?;
Some(names.iter().map(move |(range, name)| {
(
*range,
resolve_hir_value_path(
db,
&self.resolver,
self.resolver.body_owner(),
&Path::from_known_path_with_no_generic(ModPath::from_segments(
PathKind::Plain,
Some(name.clone()),
)),
hygiene,
),
)
}))
}
pub(crate) fn as_asm_parts(
@ -1143,8 +1155,9 @@ pub(crate) fn resolve_hir_path(
db: &dyn HirDatabase,
resolver: &Resolver,
path: &Path,
hygiene: HygieneId,
) -> Option<PathResolution> {
resolve_hir_path_(db, resolver, path, false)
resolve_hir_path_(db, resolver, path, false, hygiene)
}
#[inline]
@ -1164,6 +1177,7 @@ fn resolve_hir_path_(
resolver: &Resolver,
path: &Path,
prefer_value_ns: bool,
hygiene: HygieneId,
) -> Option<PathResolution> {
let types = || {
let (ty, unresolved) = match path.type_anchor() {
@ -1229,7 +1243,7 @@ fn resolve_hir_path_(
};
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 = || {
resolver
@ -1254,8 +1268,9 @@ fn resolve_hir_value_path(
resolver: &Resolver,
body_owner: Option<DefWithBodyId>,
path: &Path,
hygiene: HygieneId,
) -> 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 {
ValueNs::LocalBinding(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()))
})
}
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 {}
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);
}
}
"#,
);
}