diff --git a/Cargo.lock b/Cargo.lock index fd569571b3..368e182895 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -556,6 +556,7 @@ dependencies = [ "syntax-bridge", "test-fixture", "test-utils", + "text-size", "tracing", "triomphe", "tt", diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml index c8ba5da449..375f18d9fe 100644 --- a/crates/hir-def/Cargo.toml +++ b/crates/hir-def/Cargo.toml @@ -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 diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index 684eaf1c3b..27fe5c87cc 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -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, + + /// 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, + /// 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, + /// A map from a destructuring assignment possible variable usages to their hygiene ID. + pat_hygiene: FxHashMap, } pub type ExprPtr = AstPtr; @@ -107,10 +139,11 @@ pub struct BodySourceMap { field_map_back: FxHashMap, pat_field_map_back: FxHashMap, + // FIXME: Make this a sane struct. template_map: Option< Box<( // format_args! - FxHashMap>, + FxHashMap)>, // asm! FxHashMap>>, )>, @@ -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::); - 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, Vec<(tt::TextRange, Name)>>, - FxHashMap, Vec>>, - )> { - self.template_map.as_deref() - } } diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 4b74028b83..cdc7b14171 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -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>, + current_try_block_label: Option, // points to the expression that a try expression will target (replaces current_try_block_label) // catch_scope: Option, @@ -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, + map: FxHashMap<(Name, HygieneId), BindingId>, is_used: FxHashMap, 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) -> 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 = ; _t }` /// to preserve drop semantics. We should probably do the same in future. fn collect_while_loop(&mut self, syntax_ptr: AstPtr, 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 in ` 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, + label: Option<(HygieneId, LabelId)>, expr: Option, ) -> 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, ) -> Result, 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(&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( + &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( &mut self, - label: Option, + 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) -> bool { diff --git a/crates/hir-def/src/body/scope.rs b/crates/hir-def/src/body/scope.rs index c6967961b3..7802bfe51a 100644 --- a/crates/hir-def/src/body/scope.rs +++ b/crates/hir-def/src/body/scope.rs @@ -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)); diff --git a/crates/hir-def/src/expander.rs b/crates/hir-def/src/expander.rs index 6d8b4445f7..6f200021ba 100644 --- a/crates/hir-def/src/expander.rs +++ b/crates/hir-def/src/expander.rs @@ -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 } diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index f0f2210ec2..cf93958ecb 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -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 { 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 { - 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, } diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs index 54313904a7..267d545833 100644 --- a/crates/hir-expand/src/name.rs +++ b/crates/hir-expand/src/name.rs @@ -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: (), } diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs index 6a85988844..9b0044f5c4 100644 --- a/crates/hir-ty/src/consteval.rs +++ b/crates/hir-ty/src/consteval.rs @@ -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 { - 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 { diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs index 75dce77831..92404e3a10 100644 --- a/crates/hir-ty/src/diagnostics/expr.rs +++ b/crates/hir-ty/src/diagnostics/expr.rs @@ -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) { diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs index 492262c7a4..c7f7fb7ad3 100644 --- a/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -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) { diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 6859c2d9c3..3cb0d89e01 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -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); diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 91ba2af85e..6fc82f6743 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -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 } diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 5c822fd22e..12b3ab671a 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -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, diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index e4841c7b15..44c496c054 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -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), diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index 7580f042ad..b7b565dece 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -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"); }; diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index a8a927c2c5..6343406b83 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -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, _) => { diff --git a/crates/hir-ty/src/mir/lower/as_place.rs b/crates/hir-ty/src/mir/lower/as_place.rs index 91086e8057..420f2aaff4 100644 --- a/crates/hir-ty/src/mir/lower/as_place.rs +++ b/crates/hir-ty/src/mir/lower/as_place.rs @@ -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); diff --git a/crates/hir-ty/src/mir/lower/pattern_matching.rs b/crates/hir-ty/src/mir/lower/pattern_matching.rs index 63bb336777..d985a6451b 100644 --- a/crates/hir-ty/src/mir/lower/pattern_matching.rs +++ b/crates/hir-ty/src/mir/lower/pattern_matching.rs @@ -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 ( diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index e6ef56b80d..7ea666986a 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -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 +} + "#, + ); +} diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index d34a0d8f51..860754d5e7 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -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 { + pub fn speculative_resolve(&self, ast_path: &ast::Path) -> Option { 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 diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 430b646e97..f5b57d57df 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -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)> { - 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)> + '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 { - 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 { 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, path: &Path, + hygiene: HygieneId, ) -> Option { - 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) +} diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 1d42a66995..363f852e0e 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -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); + } +} "#, ); }