Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-expand/src/hygiene.rs')
-rw-r--r--crates/hir-expand/src/hygiene.rs423
1 files changed, 209 insertions, 214 deletions
diff --git a/crates/hir-expand/src/hygiene.rs b/crates/hir-expand/src/hygiene.rs
index ca65db1136..7b03709ace 100644
--- a/crates/hir-expand/src/hygiene.rs
+++ b/crates/hir-expand/src/hygiene.rs
@@ -2,252 +2,247 @@
//!
//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at
//! this moment, this is horribly incomplete and handles only `$crate`.
-use base_db::CrateId;
-use db::TokenExpander;
-use either::Either;
-use mbe::Origin;
-use syntax::{
- ast::{self, HasDocComments},
- AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize,
-};
-use triomphe::Arc;
-
-use crate::{
- db::{self, ExpandDatabase},
- fixup,
- name::{AsName, Name},
- HirFileId, InFile, MacroCallKind, MacroCallLoc, MacroDefKind, MacroFile,
-};
-
-#[derive(Clone, Debug)]
-pub struct Hygiene {
- frames: Option<HygieneFrames>,
+use std::iter;
+
+use base_db::span::{MacroCallId, SpanData, SyntaxContextId};
+
+use crate::db::ExpandDatabase;
+
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct SyntaxContextData {
+ pub outer_expn: Option<MacroCallId>,
+ pub outer_transparency: Transparency,
+ pub parent: SyntaxContextId,
+ /// This context, but with all transparent and semi-transparent expansions filtered away.
+ pub opaque: SyntaxContextId,
+ /// This context, but with all transparent expansions filtered away.
+ pub opaque_and_semitransparent: SyntaxContextId,
}
-impl Hygiene {
- pub fn new(db: &dyn ExpandDatabase, file_id: HirFileId) -> Hygiene {
- Hygiene { frames: Some(HygieneFrames::new(db, file_id)) }
+impl std::fmt::Debug for SyntaxContextData {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("SyntaxContextData")
+ .field("outer_expn", &self.outer_expn)
+ .field("outer_transparency", &self.outer_transparency)
+ .field("parent", &self.parent)
+ .field("opaque", &self.opaque)
+ .field("opaque_and_semitransparent", &self.opaque_and_semitransparent)
+ .finish()
}
+}
- pub fn new_unhygienic() -> Hygiene {
- Hygiene { frames: None }
+impl SyntaxContextData {
+ pub fn root() -> Self {
+ SyntaxContextData {
+ outer_expn: None,
+ outer_transparency: Transparency::Opaque,
+ parent: SyntaxContextId::ROOT,
+ opaque: SyntaxContextId::ROOT,
+ opaque_and_semitransparent: SyntaxContextId::ROOT,
+ }
}
- // FIXME: this should just return name
- pub fn name_ref_to_name(
- &self,
+ pub fn fancy_debug(
+ self,
+ self_id: SyntaxContextId,
db: &dyn ExpandDatabase,
- name_ref: ast::NameRef,
- ) -> Either<Name, CrateId> {
- if let Some(frames) = &self.frames {
- if name_ref.text() == "$crate" {
- if let Some(krate) = frames.root_crate(db, name_ref.syntax()) {
- return Either::Right(krate);
- }
+ f: &mut std::fmt::Formatter<'_>,
+ ) -> std::fmt::Result {
+ write!(f, "#{self_id} parent: #{}, outer_mark: (", self.parent)?;
+ match self.outer_expn {
+ Some(id) => {
+ write!(f, "{:?}::{{{{expn{:?}}}}}", db.lookup_intern_macro_call(id).krate, id)?
}
+ None => write!(f, "root")?,
}
-
- Either::Left(name_ref.as_name())
+ write!(f, ", {:?})", self.outer_transparency)
}
+}
- pub fn local_inner_macros(&self, db: &dyn ExpandDatabase, path: ast::Path) -> Option<CrateId> {
- let mut token = path.syntax().first_token()?.text_range();
- let frames = self.frames.as_ref()?;
- let mut current = &frames.0;
-
- loop {
- let (mapped, origin) = current.expansion.as_ref()?.map_ident_up(db, token)?;
- if origin == Origin::Def {
- return if current.local_inner {
- frames.root_crate(db, path.syntax())
- } else {
- None
- };
- }
- current = current.call_site.as_ref()?;
- token = mapped.value;
- }
- }
+/// A property of a macro expansion that determines how identifiers
+/// produced by that expansion are resolved.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
+pub enum Transparency {
+ /// Identifier produced by a transparent expansion is always resolved at call-site.
+ /// Call-site spans in procedural macros, hygiene opt-out in `macro` should use this.
+ Transparent,
+ /// Identifier produced by a semi-transparent expansion may be resolved
+ /// either at call-site or at definition-site.
+ /// If it's a local variable, label or `$crate` then it's resolved at def-site.
+ /// Otherwise it's resolved at call-site.
+ /// `macro_rules` macros behave like this, built-in macros currently behave like this too,
+ /// but that's an implementation detail.
+ SemiTransparent,
+ /// Identifier produced by an opaque expansion is always resolved at definition-site.
+ /// Def-site spans in procedural macros, identifiers from `macro` by default use this.
+ Opaque,
}
-#[derive(Clone, Debug)]
-struct HygieneFrames(Arc<HygieneFrame>);
+pub fn span_with_def_site_ctxt(
+ db: &dyn ExpandDatabase,
+ span: SpanData,
+ expn_id: MacroCallId,
+) -> SpanData {
+ span_with_ctxt_from_mark(db, span, expn_id, Transparency::Opaque)
+}
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct HygieneFrame {
- expansion: Option<HygieneInfo>,
+pub fn span_with_call_site_ctxt(
+ db: &dyn ExpandDatabase,
+ span: SpanData,
+ expn_id: MacroCallId,
+) -> SpanData {
+ span_with_ctxt_from_mark(db, span, expn_id, Transparency::Transparent)
+}
- // Indicate this is a local inner macro
- local_inner: bool,
- krate: Option<CrateId>,
+pub fn span_with_mixed_site_ctxt(
+ db: &dyn ExpandDatabase,
+ span: SpanData,
+ expn_id: MacroCallId,
+) -> SpanData {
+ span_with_ctxt_from_mark(db, span, expn_id, Transparency::SemiTransparent)
+}
- call_site: Option<Arc<HygieneFrame>>,
- def_site: Option<Arc<HygieneFrame>>,
+fn span_with_ctxt_from_mark(
+ db: &dyn ExpandDatabase,
+ span: SpanData,
+ expn_id: MacroCallId,
+ transparency: Transparency,
+) -> SpanData {
+ SpanData { ctx: apply_mark(db, SyntaxContextId::ROOT, expn_id, transparency), ..span }
}
-impl HygieneFrames {
- fn new(db: &dyn ExpandDatabase, file_id: HirFileId) -> Self {
- // Note that this intentionally avoids the `hygiene_frame` query to avoid blowing up memory
- // usage. The query is only helpful for nested `HygieneFrame`s as it avoids redundant work.
- HygieneFrames(Arc::new(HygieneFrame::new(db, file_id)))
+pub(super) fn apply_mark(
+ db: &dyn ExpandDatabase,
+ ctxt: SyntaxContextId,
+ call_id: MacroCallId,
+ transparency: Transparency,
+) -> SyntaxContextId {
+ if transparency == Transparency::Opaque {
+ return apply_mark_internal(db, ctxt, Some(call_id), transparency);
}
- fn root_crate(&self, db: &dyn ExpandDatabase, node: &SyntaxNode) -> Option<CrateId> {
- let mut token = node.first_token()?.text_range();
- let mut result = self.0.krate;
- let mut current = self.0.clone();
-
- while let Some((mapped, origin)) =
- current.expansion.as_ref().and_then(|it| it.map_ident_up(db, token))
- {
- result = current.krate;
-
- let site = match origin {
- Origin::Def => &current.def_site,
- Origin::Call => &current.call_site,
- };
+ let call_site_ctxt = db.lookup_intern_macro_call(call_id).call_site;
+ let mut call_site_ctxt = if transparency == Transparency::SemiTransparent {
+ call_site_ctxt.normalize_to_macros_2_0(db)
+ } else {
+ call_site_ctxt.normalize_to_macro_rules(db)
+ };
- let site = match site {
- None => break,
- Some(it) => it,
- };
-
- current = site.clone();
- token = mapped.value;
- }
+ if call_site_ctxt.is_root() {
+ return apply_mark_internal(db, ctxt, Some(call_id), transparency);
+ }
- result
+ // Otherwise, `expn_id` is a macros 1.0 definition and the call site is in a
+ // macros 2.0 expansion, i.e., a macros 1.0 invocation is in a macros 2.0 definition.
+ //
+ // In this case, the tokens from the macros 1.0 definition inherit the hygiene
+ // at their invocation. That is, we pretend that the macros 1.0 definition
+ // was defined at its invocation (i.e., inside the macros 2.0 definition)
+ // so that the macros 2.0 definition remains hygienic.
+ //
+ // See the example at `test/ui/hygiene/legacy_interaction.rs`.
+ for (call_id, transparency) in ctxt.marks(db) {
+ call_site_ctxt = apply_mark_internal(db, call_site_ctxt, call_id, transparency);
}
+ apply_mark_internal(db, call_site_ctxt, Some(call_id), transparency)
}
-#[derive(Debug, Clone, PartialEq, Eq)]
-struct HygieneInfo {
- file: MacroFile,
- /// The start offset of the `macro_rules!` arguments or attribute input.
- attr_input_or_mac_def_start: Option<InFile<TextSize>>,
+fn apply_mark_internal(
+ db: &dyn ExpandDatabase,
+ ctxt: SyntaxContextId,
+ call_id: Option<MacroCallId>,
+ transparency: Transparency,
+) -> SyntaxContextId {
+ let syntax_context_data = db.lookup_intern_syntax_context(ctxt);
+ let mut opaque = syntax_context_data.opaque;
+ let mut opaque_and_semitransparent = syntax_context_data.opaque_and_semitransparent;
+
+ if transparency >= Transparency::Opaque {
+ let parent = opaque;
+ let new_opaque = SyntaxContextId::SELF_REF;
+ // But we can't just grab the to be allocated ID either as that would not deduplicate
+ // things!
+ // So we need a new salsa store type here ...
+ opaque = db.intern_syntax_context(SyntaxContextData {
+ outer_expn: call_id,
+ outer_transparency: transparency,
+ parent,
+ opaque: new_opaque,
+ opaque_and_semitransparent: new_opaque,
+ });
+ }
+
+ if transparency >= Transparency::SemiTransparent {
+ let parent = opaque_and_semitransparent;
+ let new_opaque_and_semitransparent = SyntaxContextId::SELF_REF;
+ opaque_and_semitransparent = db.intern_syntax_context(SyntaxContextData {
+ outer_expn: call_id,
+ outer_transparency: transparency,
+ parent,
+ opaque,
+ opaque_and_semitransparent: new_opaque_and_semitransparent,
+ });
+ }
- macro_def: TokenExpander,
- macro_arg: Arc<(crate::tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>,
- macro_arg_shift: mbe::Shift,
- exp_map: Arc<mbe::TokenMap>,
+ let parent = ctxt;
+ db.intern_syntax_context(SyntaxContextData {
+ outer_expn: call_id,
+ outer_transparency: transparency,
+ parent,
+ opaque,
+ opaque_and_semitransparent,
+ })
+}
+pub trait SyntaxContextExt {
+ fn normalize_to_macro_rules(self, db: &dyn ExpandDatabase) -> Self;
+ fn normalize_to_macros_2_0(self, db: &dyn ExpandDatabase) -> Self;
+ fn parent_ctxt(self, db: &dyn ExpandDatabase) -> Self;
+ fn remove_mark(&mut self, db: &dyn ExpandDatabase) -> (Option<MacroCallId>, Transparency);
+ fn outer_mark(self, db: &dyn ExpandDatabase) -> (Option<MacroCallId>, Transparency);
+ fn marks(self, db: &dyn ExpandDatabase) -> Vec<(Option<MacroCallId>, Transparency)>;
}
-impl HygieneInfo {
- fn map_ident_up(
- &self,
- db: &dyn ExpandDatabase,
- token: TextRange,
- ) -> Option<(InFile<TextRange>, Origin)> {
- let token_id = self.exp_map.token_by_range(token)?;
- let (mut token_id, origin) = self.macro_def.map_id_up(token_id);
-
- let loc = db.lookup_intern_macro_call(self.file.macro_call_id);
-
- let (token_map, tt) = match &loc.kind {
- MacroCallKind::Attr { attr_args, .. } => match self.macro_arg_shift.unshift(token_id) {
- Some(unshifted) => {
- token_id = unshifted;
- (&attr_args.1, self.attr_input_or_mac_def_start?)
- }
- None => (&self.macro_arg.1, loc.kind.arg(db)?.map(|it| it.text_range().start())),
- },
- _ => match origin {
- mbe::Origin::Call => {
- (&self.macro_arg.1, loc.kind.arg(db)?.map(|it| it.text_range().start()))
- }
- mbe::Origin::Def => match (&self.macro_def, &self.attr_input_or_mac_def_start) {
- (TokenExpander::DeclarativeMacro(expander), Some(tt)) => {
- (&expander.def_site_token_map, *tt)
- }
- _ => panic!("`Origin::Def` used with non-`macro_rules!` macro"),
- },
- },
- };
-
- let range = token_map.first_range_by_token(token_id, SyntaxKind::IDENT)?;
- Some((tt.with_value(range + tt.value), origin))
+#[inline(always)]
+fn handle_self_ref(p: SyntaxContextId, n: SyntaxContextId) -> SyntaxContextId {
+ match n {
+ SyntaxContextId::SELF_REF => p,
+ _ => n,
}
}
-fn make_hygiene_info(
- db: &dyn ExpandDatabase,
- macro_file: MacroFile,
- loc: &MacroCallLoc,
-) -> HygieneInfo {
- let def = loc.def.ast_id().left().and_then(|id| {
- let def_tt = match id.to_node(db) {
- ast::Macro::MacroRules(mac) => mac.token_tree()?,
- ast::Macro::MacroDef(mac) => mac.body()?,
- };
- Some(InFile::new(id.file_id, def_tt))
- });
- let attr_input_or_mac_def = def.or_else(|| match loc.kind {
- MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
- let tt = ast_id
- .to_node(db)
- .doc_comments_and_attrs()
- .nth(invoc_attr_index.ast_index())
- .and_then(Either::left)?
- .token_tree()?;
- Some(InFile::new(ast_id.file_id, tt))
- }
- _ => None,
- });
-
- let macro_def = db.macro_expander(loc.def);
- let (_, exp_map) = db.parse_macro_expansion(macro_file).value;
- let macro_arg = db.macro_arg(macro_file.macro_call_id).value.unwrap_or_else(|| {
- Arc::new((
- tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: Vec::new() },
- Default::default(),
- Default::default(),
- ))
- });
-
- HygieneInfo {
- file: macro_file,
- attr_input_or_mac_def_start: attr_input_or_mac_def
- .map(|it| it.map(|tt| tt.syntax().text_range().start())),
- macro_arg_shift: mbe::Shift::new(&macro_arg.0),
- macro_arg,
- macro_def,
- exp_map,
+impl SyntaxContextExt for SyntaxContextId {
+ fn normalize_to_macro_rules(self, db: &dyn ExpandDatabase) -> Self {
+ handle_self_ref(self, db.lookup_intern_syntax_context(self).opaque_and_semitransparent)
+ }
+ fn normalize_to_macros_2_0(self, db: &dyn ExpandDatabase) -> Self {
+ handle_self_ref(self, db.lookup_intern_syntax_context(self).opaque)
+ }
+ fn parent_ctxt(self, db: &dyn ExpandDatabase) -> Self {
+ db.lookup_intern_syntax_context(self).parent
+ }
+ fn outer_mark(self, db: &dyn ExpandDatabase) -> (Option<MacroCallId>, Transparency) {
+ let data = db.lookup_intern_syntax_context(self);
+ (data.outer_expn, data.outer_transparency)
+ }
+ fn remove_mark(&mut self, db: &dyn ExpandDatabase) -> (Option<MacroCallId>, Transparency) {
+ let data = db.lookup_intern_syntax_context(*self);
+ *self = data.parent;
+ (data.outer_expn, data.outer_transparency)
+ }
+ fn marks(self, db: &dyn ExpandDatabase) -> Vec<(Option<MacroCallId>, Transparency)> {
+ let mut marks = marks_rev(self, db).collect::<Vec<_>>();
+ marks.reverse();
+ marks
}
}
-impl HygieneFrame {
- pub(crate) fn new(db: &dyn ExpandDatabase, file_id: HirFileId) -> HygieneFrame {
- let (info, krate, local_inner) = match file_id.macro_file() {
- None => (None, None, false),
- Some(macro_file) => {
- let loc = db.lookup_intern_macro_call(macro_file.macro_call_id);
- let info = Some((make_hygiene_info(db, macro_file, &loc), loc.kind.file_id()));
- match loc.def.kind {
- MacroDefKind::Declarative(_) => {
- (info, Some(loc.def.krate), loc.def.local_inner)
- }
- MacroDefKind::BuiltIn(..) => (info, Some(loc.def.krate), false),
- MacroDefKind::BuiltInAttr(..) => (info, None, false),
- MacroDefKind::BuiltInDerive(..) => (info, None, false),
- MacroDefKind::BuiltInEager(..) => (info, None, false),
- MacroDefKind::ProcMacro(..) => (info, None, false),
- }
- }
- };
-
- let Some((info, calling_file)) = info else {
- return HygieneFrame {
- expansion: None,
- local_inner,
- krate,
- call_site: None,
- def_site: None,
- };
- };
-
- let def_site = info.attr_input_or_mac_def_start.map(|it| db.hygiene_frame(it.file_id));
- let call_site = Some(db.hygiene_frame(calling_file));
-
- HygieneFrame { expansion: Some(info), local_inner, krate, call_site, def_site }
- }
+// FIXME: Make this a SyntaxContextExt method once we have RPIT
+pub fn marks_rev(
+ ctxt: SyntaxContextId,
+ db: &dyn ExpandDatabase,
+) -> impl Iterator<Item = (Option<MacroCallId>, Transparency)> + '_ {
+ iter::successors(Some(ctxt), move |&mark| {
+ Some(mark.parent_ctxt(db)).filter(|&it| it != SyntaxContextId::ROOT)
+ })
+ .map(|ctx| ctx.outer_mark(db))
}