From eb7a4f293ef127f0f3d4d2061dc9374e3555902c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 1 Mar 2024 14:31:25 +0100 Subject: [PATCH] minor: Add some basic docs for spans/hygiene handling --- crates/hir-expand/src/db.rs | 7 +- crates/hir-expand/src/hygiene.rs | 130 +++++++++++-------------------- crates/hir-expand/src/lib.rs | 3 +- crates/span/src/hygiene.rs | 129 ++++++++++++++++++++++++++++++ crates/span/src/lib.rs | 44 ++--------- 5 files changed, 184 insertions(+), 129 deletions(-) create mode 100644 crates/span/src/hygiene.rs diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs index 7b62eaa028..6aea676459 100644 --- a/crates/hir-expand/src/db.rs +++ b/crates/hir-expand/src/db.rs @@ -5,7 +5,7 @@ use either::Either; use limit::Limit; use mbe::{syntax_node_to_token_tree, ValueResult}; use rustc_hash::FxHashSet; -use span::SyntaxContextId; +use span::{SyntaxContextData, SyntaxContextId}; use syntax::{ ast::{self, HasAttrs}, AstNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T, @@ -19,10 +19,7 @@ use crate::{ builtin_fn_macro::EagerExpander, declarative::DeclarativeMacroExpander, fixup::{self, reverse_fixups, SyntaxFixupUndoInfo}, - hygiene::{ - span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt, - SyntaxContextData, - }, + hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt}, proc_macro::ProcMacros, span_map::{RealSpanMap, SpanMap, SpanMapRef}, tt, AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, diff --git a/crates/hir-expand/src/hygiene.rs b/crates/hir-expand/src/hygiene.rs index 65b834d7a8..7f0174fa5a 100644 --- a/crates/hir-expand/src/hygiene.rs +++ b/crates/hir-expand/src/hygiene.rs @@ -1,94 +1,34 @@ -//! This modules handles hygiene information. +//! Machinery for hygienic macros. //! -//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at -//! this moment, this is horribly incomplete and handles only `$crate`. - -// FIXME: Consider moving this into the span crate. +//! Inspired by Matthew Flatt et al., “Macros That Work Together: Compile-Time Bindings, Partial +//! Expansion, and Definition Contexts,” *Journal of Functional Programming* 22, no. 2 +//! (March 1, 2012): 181–216, . +//! +//! Also see https://rustc-dev-guide.rust-lang.org/macro-expansion.html#hygiene-and-hierarchies +//! +//! # The Expansion Order Hierarchy +//! +//! `ExpnData` in rustc, rust-analyzer's version is [`MacroCallLoc`]. Traversing the hierarchy +//! upwards can be achieved by walking up [`MacroCallLoc::kind`]'s contained file id, as +//! [`MacroFile`]s are interned [`MacroCallLoc`]s. +//! +//! # The Macro Definition Hierarchy +//! +//! `SyntaxContextData` in rustc and rust-analyzer. Basically the same in both. +//! +//! # The Call-site Hierarchy +//! +//! `ExpnData::call_site` in rustc, [`MacroCallLoc::call_site`] in rust-analyzer. +// FIXME: Move this into the span crate? Not quite possible today as that depends on `MacroCallLoc` +// which contains a bunch of unrelated things use std::iter; -use base_db::salsa::{self, InternValue}; -use span::{MacroCallId, Span, SyntaxContextId}; +use span::{MacroCallId, Span, SyntaxContextData, SyntaxContextId}; use crate::db::{ExpandDatabase, InternSyntaxContextQuery}; -#[derive(Copy, Clone, Hash, PartialEq, Eq)] -pub struct SyntaxContextData { - pub outer_expn: Option, - 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 InternValue for SyntaxContextData { - type Key = (SyntaxContextId, Option, Transparency); - - fn into_key(&self) -> Self::Key { - (self.parent, self.outer_expn, self.outer_transparency) - } -} - -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() - } -} - -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, - } - } - - pub fn fancy_debug( - self, - self_id: SyntaxContextId, - db: &dyn ExpandDatabase, - 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")?, - } - write!(f, ", {:?})", self.outer_transparency) - } -} - -/// 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, -} +pub use span::Transparency; pub fn span_with_def_site_ctxt(db: &dyn ExpandDatabase, span: Span, expn_id: MacroCallId) -> Span { span_with_ctxt_from_mark(db, span, expn_id, Transparency::Opaque) @@ -157,6 +97,8 @@ fn apply_mark_internal( call_id: Option, transparency: Transparency, ) -> SyntaxContextId { + use base_db::salsa; + 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; @@ -199,6 +141,7 @@ fn apply_mark_internal( 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; @@ -277,9 +220,26 @@ pub(crate) fn dump_syntax_contexts(db: &dyn ExpandDatabase) -> String { impl<'a> std::fmt::Debug for SyntaxContextDebug<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.2.fancy_debug(self.1, self.0, f) + fancy_debug(self.2, self.1, self.0, f) } } + + pub fn fancy_debug( + this: &SyntaxContextData, + self_id: SyntaxContextId, + db: &dyn ExpandDatabase, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(f, "#{self_id} parent: #{}, outer_mark: (", this.parent)?; + match this.outer_expn { + Some(id) => { + write!(f, "{:?}::{{{{expn{:?}}}}}", db.lookup_intern_macro_call(id).krate, id)? + } + None => write!(f, "root")?, + } + write!(f, ", {:?})", this.outer_transparency) + } + stdx::format_to!(s, "{:?}\n", SyntaxContextDebug(db, e.key, &e.value.unwrap())); } s diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index 020ca75d80..e1a79e8b58 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -32,7 +32,7 @@ use std::{fmt, hash::Hash}; use base_db::{salsa::impl_intern_value_trivial, CrateId, Edition, FileId}; use either::Either; -use span::{FileRange, HirFileIdRepr, Span, SyntaxContextId}; +use span::{FileRange, HirFileIdRepr, Span, SyntaxContextData, SyntaxContextId}; use syntax::{ ast::{self, AstNode}, SyntaxNode, SyntaxToken, TextRange, TextSize, @@ -44,7 +44,6 @@ use crate::{ builtin_derive_macro::BuiltinDeriveExpander, builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, db::{ExpandDatabase, TokenExpander}, - hygiene::SyntaxContextData, mod_path::ModPath, proc_macro::{CustomProcMacroExpander, ProcMacroKind}, span_map::{ExpansionSpanMap, SpanMap}, diff --git a/crates/span/src/hygiene.rs b/crates/span/src/hygiene.rs new file mode 100644 index 0000000000..18da689922 --- /dev/null +++ b/crates/span/src/hygiene.rs @@ -0,0 +1,129 @@ +//! Machinery for hygienic macros. +//! +//! Inspired by Matthew Flatt et al., “Macros That Work Together: Compile-Time Bindings, Partial +//! Expansion, and Definition Contexts,” *Journal of Functional Programming* 22, no. 2 +//! (March 1, 2012): 181–216, . +//! +//! Also see https://rustc-dev-guide.rust-lang.org/macro-expansion.html#hygiene-and-hierarchies +//! +//! # The Expansion Order Hierarchy +//! +//! `ExpnData` in rustc, rust-analyzer's version is [`MacroCallLoc`]. Traversing the hierarchy +//! upwards can be achieved by walking up [`MacroCallLoc::kind`]'s contained file id, as +//! [`MacroFile`]s are interned [`MacroCallLoc`]s. +//! +//! # The Macro Definition Hierarchy +//! +//! `SyntaxContextData` in rustc and rust-analyzer. Basically the same in both. +//! +//! # The Call-site Hierarchy +//! +//! `ExpnData::call_site` in rustc, [`MacroCallLoc::call_site`] in rust-analyzer. +use std::fmt; + +use salsa::{InternId, InternValue}; + +use crate::MacroCallId; + +/// Interned [`SyntaxContextData`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SyntaxContextId(InternId); + +impl salsa::InternKey for SyntaxContextId { + fn from_intern_id(v: salsa::InternId) -> Self { + SyntaxContextId(v) + } + fn as_intern_id(&self) -> salsa::InternId { + self.0 + } +} + +impl fmt::Display for SyntaxContextId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.as_u32()) + } +} + +impl SyntaxContextId { + /// The root context, which is the parent of all other contexts. All [`FileId`]s have this context. + pub const ROOT: Self = SyntaxContextId(unsafe { InternId::new_unchecked(0) }); + + pub fn is_root(self) -> bool { + self == Self::ROOT + } + + /// Deconstruct a `SyntaxContextId` into a raw `u32`. + /// This should only be used for deserialization purposes for the proc-macro server. + pub fn into_u32(self) -> u32 { + self.0.as_u32() + } + + /// Constructs a `SyntaxContextId` from a raw `u32`. + /// This should only be used for serialization purposes for the proc-macro server. + pub fn from_u32(u32: u32) -> Self { + Self(InternId::from(u32)) + } +} + +/// A syntax context describes a hierarchy tracking order of macro definitions. +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct SyntaxContextData { + pub outer_expn: Option, + 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 InternValue for SyntaxContextData { + type Key = (SyntaxContextId, Option, Transparency); + + fn into_key(&self) -> Self::Key { + (self.parent, self.outer_expn, self.outer_transparency) + } +} + +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() + } +} + +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, + } + } +} + +/// 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, +} diff --git a/crates/span/src/lib.rs b/crates/span/src/lib.rs index 7763d75cc9..00f8264607 100644 --- a/crates/span/src/lib.rs +++ b/crates/span/src/lib.rs @@ -3,9 +3,14 @@ use std::fmt::{self, Write}; use salsa::InternId; +mod hygiene; mod map; -pub use crate::map::{RealSpanMap, SpanMap}; +pub use self::{ + hygiene::{SyntaxContextData, SyntaxContextId, Transparency}, + map::{RealSpanMap, SpanMap}, +}; + pub use syntax::{TextRange, TextSize}; pub use vfs::FileId; @@ -23,7 +28,7 @@ pub struct FileRange { pub type ErasedFileAstId = la_arena::Idx; -// The first inde is always the root node's AstId +// The first index is always the root node's AstId pub const ROOT_ERASED_FILE_AST_ID: ErasedFileAstId = la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(0)); @@ -68,41 +73,6 @@ impl fmt::Display for Span { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct SyntaxContextId(InternId); - -impl salsa::InternKey for SyntaxContextId { - fn from_intern_id(v: salsa::InternId) -> Self { - SyntaxContextId(v) - } - fn as_intern_id(&self) -> salsa::InternId { - self.0 - } -} - -impl fmt::Display for SyntaxContextId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0.as_u32()) - } -} - -// inherent trait impls please tyvm -impl SyntaxContextId { - pub const ROOT: Self = SyntaxContextId(unsafe { InternId::new_unchecked(0) }); - - pub fn is_root(self) -> bool { - self == Self::ROOT - } - - pub fn into_u32(self) -> u32 { - self.0.as_u32() - } - - pub fn from_u32(u32: u32) -> Self { - Self(InternId::from(u32)) - } -} - #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct SpanAnchor { pub file_id: FileId,