about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--crates/hir-def/src/body/lower.rs2
-rw-r--r--crates/hir-def/src/item_tree.rs9
-rw-r--r--crates/hir-def/src/item_tree/lower.rs5
-rw-r--r--crates/hir-def/src/lib.rs3
-rw-r--r--crates/hir-def/src/lower.rs2
-rw-r--r--crates/hir-def/src/nameres.rs4
-rw-r--r--crates/hir-def/src/nameres/collector.rs3
-rw-r--r--crates/hir-expand/src/db.rs13
-rw-r--r--crates/hir-expand/src/files.rs37
-rw-r--r--crates/hir-expand/src/hygiene.rs158
-rw-r--r--crates/hir-expand/src/lib.rs7
-rw-r--r--crates/hir-expand/src/mod_path.rs2
-rw-r--r--crates/span/Cargo.toml3
-rw-r--r--crates/span/src/ast_id.rs (renamed from crates/hir-expand/src/ast_id_map.rs)44
-rw-r--r--crates/span/src/hygiene.rs130
-rw-r--r--crates/span/src/lib.rs52
17 files changed, 266 insertions, 210 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 92d5edea6f0..9acace2fb33 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1855,7 +1855,9 @@ dependencies = [
 name = "span"
 version = "0.0.0"
 dependencies = [
+ "hashbrown",
  "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-hash",
  "salsa",
  "stdx",
  "syntax",
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index 5dc5fedd230..ad8782d3d1e 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/crates/hir-def/src/body/lower.rs
@@ -6,7 +6,6 @@ use std::mem;
 use base_db::CrateId;
 use either::Either;
 use hir_expand::{
-    ast_id_map::AstIdMap,
     name::{name, AsName, Name},
     ExpandError, InFile,
 };
@@ -14,6 +13,7 @@ use intern::Interned;
 use profile::Count;
 use rustc_hash::FxHashMap;
 use smallvec::SmallVec;
+use span::AstIdMap;
 use syntax::{
     ast::{
         self, ArrayExprKind, AstChildren, BlockExpr, HasArgList, HasAttrs, HasLoopBody, HasName,
diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs
index bb36950f95a..c7cf611589b 100644
--- a/crates/hir-def/src/item_tree.rs
+++ b/crates/hir-def/src/item_tree.rs
@@ -47,18 +47,13 @@ use std::{
 use ast::{AstNode, StructKind};
 use base_db::CrateId;
 use either::Either;
-use hir_expand::{
-    ast_id_map::{AstIdNode, FileAstId},
-    attrs::RawAttrs,
-    name::Name,
-    ExpandTo, HirFileId, InFile,
-};
+use hir_expand::{attrs::RawAttrs, name::Name, ExpandTo, HirFileId, InFile};
 use intern::Interned;
 use la_arena::{Arena, Idx, IdxRange, RawIdx};
 use profile::Count;
 use rustc_hash::FxHashMap;
 use smallvec::SmallVec;
-use span::Span;
+use span::{AstIdNode, FileAstId, Span};
 use stdx::never;
 use syntax::{ast, match_ast, SyntaxKind};
 use triomphe::Arc;
diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs
index 37fdece8768..21cffafa952 100644
--- a/crates/hir-def/src/item_tree/lower.rs
+++ b/crates/hir-def/src/item_tree/lower.rs
@@ -2,10 +2,9 @@
 
 use std::collections::hash_map::Entry;
 
-use hir_expand::{
-    ast_id_map::AstIdMap, mod_path::path, name, name::AsName, span_map::SpanMapRef, HirFileId,
-};
+use hir_expand::{mod_path::path, name, name::AsName, span_map::SpanMapRef, HirFileId};
 use la_arena::Arena;
+use span::AstIdMap;
 use syntax::{
     ast::{self, HasModuleItem, HasName, HasTypeBounds, IsString},
     AstNode,
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index 5670ebfa17f..de3ab57a124 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -76,7 +76,6 @@ use base_db::{
     CrateId, Edition,
 };
 use hir_expand::{
-    ast_id_map::{AstIdNode, FileAstId},
     builtin_attr_macro::BuiltinAttrExpander,
     builtin_derive_macro::BuiltinDeriveExpander,
     builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander},
@@ -91,7 +90,7 @@ use hir_expand::{
 use item_tree::ExternBlock;
 use la_arena::Idx;
 use nameres::DefMap;
-use span::{FileId, Span};
+use span::{AstIdNode, FileAstId, FileId, Span};
 use stdx::impl_from;
 use syntax::{ast, AstNode};
 
diff --git a/crates/hir-def/src/lower.rs b/crates/hir-def/src/lower.rs
index 395b69d284f..2fa6acdf175 100644
--- a/crates/hir-def/src/lower.rs
+++ b/crates/hir-def/src/lower.rs
@@ -2,10 +2,10 @@
 use std::cell::OnceCell;
 
 use hir_expand::{
-    ast_id_map::{AstIdMap, AstIdNode},
     span_map::{SpanMap, SpanMapRef},
     AstId, HirFileId, InFile,
 };
+use span::{AstIdMap, AstIdNode};
 use syntax::ast;
 use triomphe::Arc;
 
diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs
index 1a827821156..270468ad0a6 100644
--- a/crates/hir-def/src/nameres.rs
+++ b/crates/hir-def/src/nameres.rs
@@ -61,13 +61,13 @@ use std::ops::Deref;
 
 use base_db::{CrateId, Edition, FileId};
 use hir_expand::{
-    ast_id_map::FileAstId, name::Name, proc_macro::ProcMacroKind, HirFileId, InFile, MacroCallId,
-    MacroDefId,
+    name::Name, proc_macro::ProcMacroKind, HirFileId, InFile, MacroCallId, MacroDefId,
 };
 use itertools::Itertools;
 use la_arena::Arena;
 use profile::Count;
 use rustc_hash::{FxHashMap, FxHashSet};
+use span::FileAstId;
 use stdx::format_to;
 use syntax::{ast, SmolStr};
 use triomphe::Arc;
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index 32825406505..538e735688b 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -9,7 +9,6 @@ use base_db::{CrateId, Dependency, Edition, FileId};
 use cfg::{CfgExpr, CfgOptions};
 use either::Either;
 use hir_expand::{
-    ast_id_map::FileAstId,
     attrs::{Attr, AttrId},
     builtin_attr_macro::{find_builtin_attr, BuiltinAttrExpander},
     builtin_derive_macro::find_builtin_derive,
@@ -23,7 +22,7 @@ use itertools::{izip, Itertools};
 use la_arena::Idx;
 use limit::Limit;
 use rustc_hash::{FxHashMap, FxHashSet};
-use span::{ErasedFileAstId, Span, SyntaxContextId};
+use span::{ErasedFileAstId, FileAstId, Span, SyntaxContextId};
 use stdx::always;
 use syntax::{ast, SmolStr};
 use triomphe::Arc;
diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs
index 7b62eaa0289..f1f0d8990f1 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::{AstIdMap, SyntaxContextData, SyntaxContextId};
 use syntax::{
     ast::{self, HasAttrs},
     AstNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T,
@@ -13,16 +13,12 @@ use syntax::{
 use triomphe::Arc;
 
 use crate::{
-    ast_id_map::AstIdMap,
     attrs::collect_attrs,
     builtin_attr_macro::pseudo_derive_attr_expansion,
     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,
@@ -61,7 +57,6 @@ pub trait ExpandDatabase: SourceDatabase {
     #[salsa::input]
     fn proc_macros(&self) -> Arc<ProcMacros>;
 
-    #[salsa::invoke(AstIdMap::new)]
     fn ast_id_map(&self, file_id: HirFileId) -> Arc<AstIdMap>;
 
     /// Main public API -- parses a hir file, not caring whether it's a real
@@ -256,6 +251,10 @@ pub fn expand_speculative(
     Some((node.syntax_node(), token))
 }
 
+fn ast_id_map(db: &dyn ExpandDatabase, file_id: span::HirFileId) -> triomphe::Arc<AstIdMap> {
+    triomphe::Arc::new(AstIdMap::from_source(&db.parse_or_expand(file_id)))
+}
+
 fn parse_or_expand(db: &dyn ExpandDatabase, file_id: HirFileId) -> SyntaxNode {
     match file_id.repr() {
         HirFileIdRepr::FileId(file_id) => db.parse(file_id).syntax_node(),
diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs
index 707daf04024..66ceb1b7d42 100644
--- a/crates/hir-expand/src/files.rs
+++ b/crates/hir-expand/src/files.rs
@@ -2,10 +2,16 @@
 use std::iter;
 
 use either::Either;
-use span::{FileId, FileRange, HirFileId, HirFileIdRepr, MacroFileId, SyntaxContextId};
-use syntax::{AstNode, SyntaxNode, SyntaxToken, TextRange, TextSize};
+use span::{
+    AstIdNode, ErasedFileAstId, FileAstId, FileId, FileRange, HirFileId, HirFileIdRepr,
+    MacroFileId, SyntaxContextId,
+};
+use syntax::{AstNode, AstPtr, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize};
 
-use crate::{db, map_node_range_up, span_for_offset, MacroFileIdExt};
+use crate::{
+    db::{self, ExpandDatabase},
+    map_node_range_up, span_for_offset, MacroFileIdExt,
+};
 
 /// `InFile<T>` stores a value of `T` inside a particular file/syntax tree.
 ///
@@ -23,6 +29,31 @@ pub type InFile<T> = InFileWrapper<HirFileId, T>;
 pub type InMacroFile<T> = InFileWrapper<MacroFileId, T>;
 pub type InRealFile<T> = InFileWrapper<FileId, T>;
 
+/// `AstId` points to an AST node in any file.
+///
+/// It is stable across reparses, and can be used as salsa key/value.
+pub type AstId<N> = crate::InFile<FileAstId<N>>;
+
+impl<N: AstIdNode> AstId<N> {
+    pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
+        self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))
+    }
+    pub fn to_in_file_node(&self, db: &dyn ExpandDatabase) -> crate::InFile<N> {
+        crate::InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)))
+    }
+    pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> AstPtr<N> {
+        db.ast_id_map(self.file_id).get(self.value)
+    }
+}
+
+pub type ErasedAstId = crate::InFile<ErasedFileAstId>;
+
+impl ErasedAstId {
+    pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> SyntaxNodePtr {
+        db.ast_id_map(self.file_id).get_erased(self.value)
+    }
+}
+
 impl<FileKind, T> InFileWrapper<FileKind, T> {
     pub fn new(file_id: FileKind, value: T) -> Self {
         Self { file_id, value }
diff --git a/crates/hir-expand/src/hygiene.rs b/crates/hir-expand/src/hygiene.rs
index 65b834d7a81..ac2bab280d5 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
diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs
index 020ca75d80c..42dc8c12d60 100644
--- a/crates/hir-expand/src/lib.rs
+++ b/crates/hir-expand/src/lib.rs
@@ -6,7 +6,6 @@
 
 #![warn(rust_2018_idioms, unused_lifetimes)]
 
-pub mod ast_id_map;
 pub mod attrs;
 pub mod builtin_attr_macro;
 pub mod builtin_derive_macro;
@@ -32,7 +31,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::{ErasedFileAstId, FileRange, HirFileIdRepr, Span, SyntaxContextData, SyntaxContextId};
 use syntax::{
     ast::{self, AstNode},
     SyntaxNode, SyntaxToken, TextRange, TextSize,
@@ -44,14 +43,12 @@ 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},
 };
 
-pub use crate::ast_id_map::{AstId, ErasedAstId, ErasedFileAstId};
-pub use crate::files::{InFile, InMacroFile, InRealFile};
+pub use crate::files::{AstId, ErasedAstId, InFile, InMacroFile, InRealFile};
 
 pub use mbe::ValueResult;
 pub use span::{HirFileId, MacroCallId, MacroFileId};
diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs
index 136b0935be2..0cf1fadec97 100644
--- a/crates/hir-expand/src/mod_path.rs
+++ b/crates/hir-expand/src/mod_path.rs
@@ -358,7 +358,7 @@ pub fn resolve_crate_root(db: &dyn ExpandDatabase, mut ctxt: SyntaxContextId) ->
         result_mark = Some(mark);
     }
 
-    result_mark.flatten().map(|call| db.lookup_intern_macro_call(call).def.krate)
+    result_mark.map(|call| db.lookup_intern_macro_call(call).def.krate)
 }
 
 pub use crate::name as __name;
diff --git a/crates/span/Cargo.toml b/crates/span/Cargo.toml
index 7093f3a691e..cbda91f0a59 100644
--- a/crates/span/Cargo.toml
+++ b/crates/span/Cargo.toml
@@ -12,7 +12,8 @@ authors.workspace = true
 [dependencies]
 la-arena.workspace = true
 salsa.workspace = true
-
+rustc-hash.workspace = true
+hashbrown.workspace = true
 
 # local deps
 vfs.workspace = true
diff --git a/crates/hir-expand/src/ast_id_map.rs b/crates/span/src/ast_id.rs
index ab582741f5b..2d98aa81e50 100644
--- a/crates/hir-expand/src/ast_id_map.rs
+++ b/crates/span/src/ast_id.rs
@@ -5,8 +5,6 @@
 //! item as an ID. That way, id's don't change unless the set of items itself
 //! changes.
 
-// FIXME: Consider moving this into the span crate
-
 use std::{
     any::type_name,
     fmt,
@@ -15,38 +13,12 @@ use std::{
 };
 
 use la_arena::{Arena, Idx, RawIdx};
-use profile::Count;
 use rustc_hash::FxHasher;
 use syntax::{ast, AstNode, AstPtr, SyntaxNode, SyntaxNodePtr};
 
-use crate::db::ExpandDatabase;
-
-pub use span::ErasedFileAstId;
-
-/// `AstId` points to an AST node in any file.
-///
-/// It is stable across reparses, and can be used as salsa key/value.
-pub type AstId<N> = crate::InFile<FileAstId<N>>;
-
-impl<N: AstIdNode> AstId<N> {
-    pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
-        self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))
-    }
-    pub fn to_in_file_node(&self, db: &dyn ExpandDatabase) -> crate::InFile<N> {
-        crate::InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)))
-    }
-    pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> AstPtr<N> {
-        db.ast_id_map(self.file_id).get(self.value)
-    }
-}
-
-pub type ErasedAstId = crate::InFile<ErasedFileAstId>;
-
-impl ErasedAstId {
-    pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> SyntaxNodePtr {
-        db.ast_id_map(self.file_id).get_erased(self.value)
-    }
-}
+/// See crates\hir-expand\src\ast_id_map.rs
+/// This is a type erased FileAstId.
+pub type ErasedFileAstId = la_arena::Idx<syntax::SyntaxNodePtr>;
 
 /// `AstId` points to an AST node in a specific file.
 pub struct FileAstId<N: AstIdNode> {
@@ -138,7 +110,6 @@ pub struct AstIdMap {
     arena: Arena<SyntaxNodePtr>,
     /// Reverse: map ptr to id.
     map: hashbrown::HashMap<Idx<SyntaxNodePtr>, (), ()>,
-    _c: Count<Self>,
 }
 
 impl fmt::Debug for AstIdMap {
@@ -155,14 +126,7 @@ impl PartialEq for AstIdMap {
 impl Eq for AstIdMap {}
 
 impl AstIdMap {
-    pub(crate) fn new(
-        db: &dyn ExpandDatabase,
-        file_id: span::HirFileId,
-    ) -> triomphe::Arc<AstIdMap> {
-        triomphe::Arc::new(AstIdMap::from_source(&db.parse_or_expand(file_id)))
-    }
-
-    fn from_source(node: &SyntaxNode) -> AstIdMap {
+    pub fn from_source(node: &SyntaxNode) -> AstIdMap {
         assert!(node.parent().is_none());
         let mut res = AstIdMap::default();
 
diff --git a/crates/span/src/hygiene.rs b/crates/span/src/hygiene.rs
new file mode 100644
index 00000000000..4f6d792201b
--- /dev/null
+++ b/crates/span/src/hygiene.rs
@@ -0,0 +1,130 @@
+//! 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, <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.
+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 {
+    /// Invariant: Only [`SyntaxContextId::ROOT`] has a [`None`] outer expansion.
+    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,
+        }
+    }
+}
+
+/// 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 7763d75cc92..0fe3275863d 100644
--- a/crates/span/src/lib.rs
+++ b/crates/span/src/lib.rs
@@ -3,9 +3,16 @@ use std::fmt::{self, Write};
 
 use salsa::InternId;
 
+mod ast_id;
+mod hygiene;
 mod map;
 
-pub use crate::map::{RealSpanMap, SpanMap};
+pub use self::{
+    ast_id::{AstIdMap, AstIdNode, ErasedFileAstId, FileAstId},
+    hygiene::{SyntaxContextData, SyntaxContextId, Transparency},
+    map::{RealSpanMap, SpanMap},
+};
+
 pub use syntax::{TextRange, TextSize};
 pub use vfs::FileId;
 
@@ -21,9 +28,10 @@ pub struct FileRange {
     pub range: TextRange,
 }
 
-pub type ErasedFileAstId = la_arena::Idx<syntax::SyntaxNodePtr>;
-
-// The first inde is always the root node's AstId
+// The first index is always the root node's AstId
+/// The root ast id always points to the encompassing file, using this in spans is discouraged as
+/// any range relative to it will be effectively absolute, ruining the entire point of anchored
+/// relative text ranges.
 pub const ROOT_ERASED_FILE_AST_ID: ErasedFileAstId =
     la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(0));
 
@@ -42,6 +50,7 @@ pub struct SpanData<Ctx> {
     /// We need the anchor for incrementality, as storing absolute ranges will require
     /// recomputation on every change in a file at all times.
     pub range: TextRange,
+    /// The anchor this span is relative to.
     pub anchor: SpanAnchor,
     /// The syntax context of the span.
     pub ctx: Ctx,
@@ -68,41 +77,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,