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.rs158
1 files changed, 62 insertions, 96 deletions
diff --git a/crates/hir-expand/src/hygiene.rs b/crates/hir-expand/src/hygiene.rs
index 65b834d7a8..ac2bab280d 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, <https://doi.org/10.1017/S0956796812000093>.
+//!
+//! 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<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 InternValue for SyntaxContextData {
- type Key = (SyntaxContextId, Option<MacroCallId>, 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)
@@ -122,7 +62,7 @@ pub(super) fn apply_mark(
transparency: Transparency,
) -> SyntaxContextId {
if transparency == Transparency::Opaque {
- return apply_mark_internal(db, ctxt, Some(call_id), transparency);
+ return apply_mark_internal(db, ctxt, call_id, transparency);
}
let call_site_ctxt = db.lookup_intern_macro_call(call_id).call_site.ctx;
@@ -133,7 +73,7 @@ pub(super) fn apply_mark(
};
if call_site_ctxt.is_root() {
- return apply_mark_internal(db, ctxt, Some(call_id), transparency);
+ return apply_mark_internal(db, ctxt, call_id, transparency);
}
// Otherwise, `expn_id` is a macros 1.0 definition and the call site is in a
@@ -148,15 +88,19 @@ pub(super) fn apply_mark(
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)
+ apply_mark_internal(db, call_site_ctxt, call_id, transparency)
}
fn apply_mark_internal(
db: &dyn ExpandDatabase,
ctxt: SyntaxContextId,
- call_id: Option<MacroCallId>,
+ call_id: MacroCallId,
transparency: Transparency,
) -> SyntaxContextId {
+ use base_db::salsa;
+
+ let call_id = Some(call_id);
+
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,13 +143,14 @@ 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;
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)>;
+ fn marks(self, db: &dyn ExpandDatabase) -> Vec<(MacroCallId, Transparency)>;
}
impl SyntaxContextExt for SyntaxContextId {
@@ -227,7 +172,7 @@ impl SyntaxContextExt for SyntaxContextId {
*self = data.parent;
(data.outer_expn, data.outer_transparency)
}
- fn marks(self, db: &dyn ExpandDatabase) -> Vec<(Option<MacroCallId>, Transparency)> {
+ fn marks(self, db: &dyn ExpandDatabase) -> Vec<(MacroCallId, Transparency)> {
let mut marks = marks_rev(self, db).collect::<Vec<_>>();
marks.reverse();
marks
@@ -238,11 +183,15 @@ impl SyntaxContextExt for SyntaxContextId {
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))
+) -> impl Iterator<Item = (MacroCallId, Transparency)> + '_ {
+ iter::successors(Some(ctxt), move |&mark| Some(mark.parent_ctxt(db)))
+ .take_while(|&it| !it.is_root())
+ .map(|ctx| {
+ let mark = ctx.outer_mark(db);
+ // We stop before taking the root expansion, as such we cannot encounter a `None` outer
+ // expansion, as only the ROOT has it.
+ (mark.0.unwrap(), mark.1)
+ })
}
pub(crate) fn dump_syntax_contexts(db: &dyn ExpandDatabase) -> String {
@@ -277,9 +226,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)
}
}
+
+ 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