Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit eb7a4f2

Browse files
committed
minor: Add some basic docs for spans/hygiene handling
1 parent 9efa23c commit eb7a4f2

File tree

5 files changed

+184
-129
lines changed

5 files changed

+184
-129
lines changed

crates/hir-expand/src/db.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use either::Either;
55
use limit::Limit;
66
use mbe::{syntax_node_to_token_tree, ValueResult};
77
use rustc_hash::FxHashSet;
8-
use span::SyntaxContextId;
8+
use span::{SyntaxContextData, SyntaxContextId};
99
use syntax::{
1010
ast::{self, HasAttrs},
1111
AstNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T,
@@ -19,10 +19,7 @@ use crate::{
1919
builtin_fn_macro::EagerExpander,
2020
declarative::DeclarativeMacroExpander,
2121
fixup::{self, reverse_fixups, SyntaxFixupUndoInfo},
22-
hygiene::{
23-
span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt,
24-
SyntaxContextData,
25-
},
22+
hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt},
2623
proc_macro::ProcMacros,
2724
span_map::{RealSpanMap, SpanMap, SpanMapRef},
2825
tt, AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander,

crates/hir-expand/src/hygiene.rs

Lines changed: 45 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,34 @@
1-
//! This modules handles hygiene information.
1+
//! Machinery for hygienic macros.
22
//!
3-
//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at
4-
//! this moment, this is horribly incomplete and handles only `$crate`.
5-
6-
// FIXME: Consider moving this into the span crate.
3+
//! Inspired by Matthew Flatt et al., “Macros That Work Together: Compile-Time Bindings, Partial
4+
//! Expansion, and Definition Contexts,” *Journal of Functional Programming* 22, no. 2
5+
//! (March 1, 2012): 181–216, <https://doi.org/10.1017/S0956796812000093>.
6+
//!
7+
//! Also see https://rustc-dev-guide.rust-lang.org/macro-expansion.html#hygiene-and-hierarchies
8+
//!
9+
//! # The Expansion Order Hierarchy
10+
//!
11+
//! `ExpnData` in rustc, rust-analyzer's version is [`MacroCallLoc`]. Traversing the hierarchy
12+
//! upwards can be achieved by walking up [`MacroCallLoc::kind`]'s contained file id, as
13+
//! [`MacroFile`]s are interned [`MacroCallLoc`]s.
14+
//!
15+
//! # The Macro Definition Hierarchy
16+
//!
17+
//! `SyntaxContextData` in rustc and rust-analyzer. Basically the same in both.
18+
//!
19+
//! # The Call-site Hierarchy
20+
//!
21+
//! `ExpnData::call_site` in rustc, [`MacroCallLoc::call_site`] in rust-analyzer.
22+
// FIXME: Move this into the span crate? Not quite possible today as that depends on `MacroCallLoc`
23+
// which contains a bunch of unrelated things
724

825
use std::iter;
926

10-
use base_db::salsa::{self, InternValue};
11-
use span::{MacroCallId, Span, SyntaxContextId};
27+
use span::{MacroCallId, Span, SyntaxContextData, SyntaxContextId};
1228

1329
use crate::db::{ExpandDatabase, InternSyntaxContextQuery};
1430

15-
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
16-
pub struct SyntaxContextData {
17-
pub outer_expn: Option<MacroCallId>,
18-
pub outer_transparency: Transparency,
19-
pub parent: SyntaxContextId,
20-
/// This context, but with all transparent and semi-transparent expansions filtered away.
21-
pub opaque: SyntaxContextId,
22-
/// This context, but with all transparent expansions filtered away.
23-
pub opaque_and_semitransparent: SyntaxContextId,
24-
}
25-
26-
impl InternValue for SyntaxContextData {
27-
type Key = (SyntaxContextId, Option<MacroCallId>, Transparency);
28-
29-
fn into_key(&self) -> Self::Key {
30-
(self.parent, self.outer_expn, self.outer_transparency)
31-
}
32-
}
33-
34-
impl std::fmt::Debug for SyntaxContextData {
35-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36-
f.debug_struct("SyntaxContextData")
37-
.field("outer_expn", &self.outer_expn)
38-
.field("outer_transparency", &self.outer_transparency)
39-
.field("parent", &self.parent)
40-
.field("opaque", &self.opaque)
41-
.field("opaque_and_semitransparent", &self.opaque_and_semitransparent)
42-
.finish()
43-
}
44-
}
45-
46-
impl SyntaxContextData {
47-
pub fn root() -> Self {
48-
SyntaxContextData {
49-
outer_expn: None,
50-
outer_transparency: Transparency::Opaque,
51-
parent: SyntaxContextId::ROOT,
52-
opaque: SyntaxContextId::ROOT,
53-
opaque_and_semitransparent: SyntaxContextId::ROOT,
54-
}
55-
}
56-
57-
pub fn fancy_debug(
58-
self,
59-
self_id: SyntaxContextId,
60-
db: &dyn ExpandDatabase,
61-
f: &mut std::fmt::Formatter<'_>,
62-
) -> std::fmt::Result {
63-
write!(f, "#{self_id} parent: #{}, outer_mark: (", self.parent)?;
64-
match self.outer_expn {
65-
Some(id) => {
66-
write!(f, "{:?}::{{{{expn{:?}}}}}", db.lookup_intern_macro_call(id).krate, id)?
67-
}
68-
None => write!(f, "root")?,
69-
}
70-
write!(f, ", {:?})", self.outer_transparency)
71-
}
72-
}
73-
74-
/// A property of a macro expansion that determines how identifiers
75-
/// produced by that expansion are resolved.
76-
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
77-
pub enum Transparency {
78-
/// Identifier produced by a transparent expansion is always resolved at call-site.
79-
/// Call-site spans in procedural macros, hygiene opt-out in `macro` should use this.
80-
Transparent,
81-
/// Identifier produced by a semi-transparent expansion may be resolved
82-
/// either at call-site or at definition-site.
83-
/// If it's a local variable, label or `$crate` then it's resolved at def-site.
84-
/// Otherwise it's resolved at call-site.
85-
/// `macro_rules` macros behave like this, built-in macros currently behave like this too,
86-
/// but that's an implementation detail.
87-
SemiTransparent,
88-
/// Identifier produced by an opaque expansion is always resolved at definition-site.
89-
/// Def-site spans in procedural macros, identifiers from `macro` by default use this.
90-
Opaque,
91-
}
31+
pub use span::Transparency;
9232

9333
pub fn span_with_def_site_ctxt(db: &dyn ExpandDatabase, span: Span, expn_id: MacroCallId) -> Span {
9434
span_with_ctxt_from_mark(db, span, expn_id, Transparency::Opaque)
@@ -157,6 +97,8 @@ fn apply_mark_internal(
15797
call_id: Option<MacroCallId>,
15898
transparency: Transparency,
15999
) -> SyntaxContextId {
100+
use base_db::salsa;
101+
160102
let syntax_context_data = db.lookup_intern_syntax_context(ctxt);
161103
let mut opaque = syntax_context_data.opaque;
162104
let mut opaque_and_semitransparent = syntax_context_data.opaque_and_semitransparent;
@@ -199,6 +141,7 @@ fn apply_mark_internal(
199141
opaque_and_semitransparent,
200142
})
201143
}
144+
202145
pub trait SyntaxContextExt {
203146
fn normalize_to_macro_rules(self, db: &dyn ExpandDatabase) -> Self;
204147
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 {
277220

278221
impl<'a> std::fmt::Debug for SyntaxContextDebug<'a> {
279222
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280-
self.2.fancy_debug(self.1, self.0, f)
223+
fancy_debug(self.2, self.1, self.0, f)
281224
}
282225
}
226+
227+
pub fn fancy_debug(
228+
this: &SyntaxContextData,
229+
self_id: SyntaxContextId,
230+
db: &dyn ExpandDatabase,
231+
f: &mut std::fmt::Formatter<'_>,
232+
) -> std::fmt::Result {
233+
write!(f, "#{self_id} parent: #{}, outer_mark: (", this.parent)?;
234+
match this.outer_expn {
235+
Some(id) => {
236+
write!(f, "{:?}::{{{{expn{:?}}}}}", db.lookup_intern_macro_call(id).krate, id)?
237+
}
238+
None => write!(f, "root")?,
239+
}
240+
write!(f, ", {:?})", this.outer_transparency)
241+
}
242+
283243
stdx::format_to!(s, "{:?}\n", SyntaxContextDebug(db, e.key, &e.value.unwrap()));
284244
}
285245
s

crates/hir-expand/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use std::{fmt, hash::Hash};
3232

3333
use base_db::{salsa::impl_intern_value_trivial, CrateId, Edition, FileId};
3434
use either::Either;
35-
use span::{FileRange, HirFileIdRepr, Span, SyntaxContextId};
35+
use span::{FileRange, HirFileIdRepr, Span, SyntaxContextData, SyntaxContextId};
3636
use syntax::{
3737
ast::{self, AstNode},
3838
SyntaxNode, SyntaxToken, TextRange, TextSize,
@@ -44,7 +44,6 @@ use crate::{
4444
builtin_derive_macro::BuiltinDeriveExpander,
4545
builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander},
4646
db::{ExpandDatabase, TokenExpander},
47-
hygiene::SyntaxContextData,
4847
mod_path::ModPath,
4948
proc_macro::{CustomProcMacroExpander, ProcMacroKind},
5049
span_map::{ExpansionSpanMap, SpanMap},

crates/span/src/hygiene.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//! Machinery for hygienic macros.
2+
//!
3+
//! Inspired by Matthew Flatt et al., “Macros That Work Together: Compile-Time Bindings, Partial
4+
//! Expansion, and Definition Contexts,” *Journal of Functional Programming* 22, no. 2
5+
//! (March 1, 2012): 181–216, <https://doi.org/10.1017/S0956796812000093>.
6+
//!
7+
//! Also see https://rustc-dev-guide.rust-lang.org/macro-expansion.html#hygiene-and-hierarchies
8+
//!
9+
//! # The Expansion Order Hierarchy
10+
//!
11+
//! `ExpnData` in rustc, rust-analyzer's version is [`MacroCallLoc`]. Traversing the hierarchy
12+
//! upwards can be achieved by walking up [`MacroCallLoc::kind`]'s contained file id, as
13+
//! [`MacroFile`]s are interned [`MacroCallLoc`]s.
14+
//!
15+
//! # The Macro Definition Hierarchy
16+
//!
17+
//! `SyntaxContextData` in rustc and rust-analyzer. Basically the same in both.
18+
//!
19+
//! # The Call-site Hierarchy
20+
//!
21+
//! `ExpnData::call_site` in rustc, [`MacroCallLoc::call_site`] in rust-analyzer.
22+
use std::fmt;
23+
24+
use salsa::{InternId, InternValue};
25+
26+
use crate::MacroCallId;
27+
28+
/// Interned [`SyntaxContextData`].
29+
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
30+
pub struct SyntaxContextId(InternId);
31+
32+
impl salsa::InternKey for SyntaxContextId {
33+
fn from_intern_id(v: salsa::InternId) -> Self {
34+
SyntaxContextId(v)
35+
}
36+
fn as_intern_id(&self) -> salsa::InternId {
37+
self.0
38+
}
39+
}
40+
41+
impl fmt::Display for SyntaxContextId {
42+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43+
write!(f, "{}", self.0.as_u32())
44+
}
45+
}
46+
47+
impl SyntaxContextId {
48+
/// The root context, which is the parent of all other contexts. All [`FileId`]s have this context.
49+
pub const ROOT: Self = SyntaxContextId(unsafe { InternId::new_unchecked(0) });
50+
51+
pub fn is_root(self) -> bool {
52+
self == Self::ROOT
53+
}
54+
55+
/// Deconstruct a `SyntaxContextId` into a raw `u32`.
56+
/// This should only be used for deserialization purposes for the proc-macro server.
57+
pub fn into_u32(self) -> u32 {
58+
self.0.as_u32()
59+
}
60+
61+
/// Constructs a `SyntaxContextId` from a raw `u32`.
62+
/// This should only be used for serialization purposes for the proc-macro server.
63+
pub fn from_u32(u32: u32) -> Self {
64+
Self(InternId::from(u32))
65+
}
66+
}
67+
68+
/// A syntax context describes a hierarchy tracking order of macro definitions.
69+
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
70+
pub struct SyntaxContextData {
71+
pub outer_expn: Option<MacroCallId>,
72+
pub outer_transparency: Transparency,
73+
pub parent: SyntaxContextId,
74+
/// This context, but with all transparent and semi-transparent expansions filtered away.
75+
pub opaque: SyntaxContextId,
76+
/// This context, but with all transparent expansions filtered away.
77+
pub opaque_and_semitransparent: SyntaxContextId,
78+
}
79+
80+
impl InternValue for SyntaxContextData {
81+
type Key = (SyntaxContextId, Option<MacroCallId>, Transparency);
82+
83+
fn into_key(&self) -> Self::Key {
84+
(self.parent, self.outer_expn, self.outer_transparency)
85+
}
86+
}
87+
88+
impl std::fmt::Debug for SyntaxContextData {
89+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90+
f.debug_struct("SyntaxContextData")
91+
.field("outer_expn", &self.outer_expn)
92+
.field("outer_transparency", &self.outer_transparency)
93+
.field("parent", &self.parent)
94+
.field("opaque", &self.opaque)
95+
.field("opaque_and_semitransparent", &self.opaque_and_semitransparent)
96+
.finish()
97+
}
98+
}
99+
100+
impl SyntaxContextData {
101+
pub fn root() -> Self {
102+
SyntaxContextData {
103+
outer_expn: None,
104+
outer_transparency: Transparency::Opaque,
105+
parent: SyntaxContextId::ROOT,
106+
opaque: SyntaxContextId::ROOT,
107+
opaque_and_semitransparent: SyntaxContextId::ROOT,
108+
}
109+
}
110+
}
111+
112+
/// A property of a macro expansion that determines how identifiers
113+
/// produced by that expansion are resolved.
114+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
115+
pub enum Transparency {
116+
/// Identifier produced by a transparent expansion is always resolved at call-site.
117+
/// Call-site spans in procedural macros, hygiene opt-out in `macro` should use this.
118+
Transparent,
119+
/// Identifier produced by a semi-transparent expansion may be resolved
120+
/// either at call-site or at definition-site.
121+
/// If it's a local variable, label or `$crate` then it's resolved at def-site.
122+
/// Otherwise it's resolved at call-site.
123+
/// `macro_rules` macros behave like this, built-in macros currently behave like this too,
124+
/// but that's an implementation detail.
125+
SemiTransparent,
126+
/// Identifier produced by an opaque expansion is always resolved at definition-site.
127+
/// Def-site spans in procedural macros, identifiers from `macro` by default use this.
128+
Opaque,
129+
}

crates/span/src/lib.rs

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@ use std::fmt::{self, Write};
33

44
use salsa::InternId;
55

6+
mod hygiene;
67
mod map;
78

8-
pub use crate::map::{RealSpanMap, SpanMap};
9+
pub use self::{
10+
hygiene::{SyntaxContextData, SyntaxContextId, Transparency},
11+
map::{RealSpanMap, SpanMap},
12+
};
13+
914
pub use syntax::{TextRange, TextSize};
1015
pub use vfs::FileId;
1116

@@ -23,7 +28,7 @@ pub struct FileRange {
2328

2429
pub type ErasedFileAstId = la_arena::Idx<syntax::SyntaxNodePtr>;
2530

26-
// The first inde is always the root node's AstId
31+
// The first index is always the root node's AstId
2732
pub const ROOT_ERASED_FILE_AST_ID: ErasedFileAstId =
2833
la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(0));
2934

@@ -68,41 +73,6 @@ impl fmt::Display for Span {
6873
}
6974
}
7075

71-
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
72-
pub struct SyntaxContextId(InternId);
73-
74-
impl salsa::InternKey for SyntaxContextId {
75-
fn from_intern_id(v: salsa::InternId) -> Self {
76-
SyntaxContextId(v)
77-
}
78-
fn as_intern_id(&self) -> salsa::InternId {
79-
self.0
80-
}
81-
}
82-
83-
impl fmt::Display for SyntaxContextId {
84-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85-
write!(f, "{}", self.0.as_u32())
86-
}
87-
}
88-
89-
// inherent trait impls please tyvm
90-
impl SyntaxContextId {
91-
pub const ROOT: Self = SyntaxContextId(unsafe { InternId::new_unchecked(0) });
92-
93-
pub fn is_root(self) -> bool {
94-
self == Self::ROOT
95-
}
96-
97-
pub fn into_u32(self) -> u32 {
98-
self.0.as_u32()
99-
}
100-
101-
pub fn from_u32(u32: u32) -> Self {
102-
Self(InternId::from(u32))
103-
}
104-
}
105-
10676
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
10777
pub struct SpanAnchor {
10878
pub file_id: FileId,

0 commit comments

Comments
 (0)