1use std::fmt;
23
24use crate::Edition;
25
26#[cfg(feature = "salsa")]
28#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
29pub struct SyntaxContext(
30 u32,
34 std::marker::PhantomData<&'static salsa::plumbing::interned::Value<SyntaxContext>>,
35);
36
37#[cfg(feature = "salsa")]
38const _: () = {
39 use crate::MacroCallId;
40 use salsa::plumbing as zalsa_;
41 use salsa::plumbing::interned as zalsa_struct_;
42
43 #[derive(Clone, Eq, Debug)]
44 pub struct SyntaxContextData {
45 outer_expn: Option<MacroCallId>,
46 outer_transparency: Transparency,
47 edition: Edition,
48 parent: SyntaxContext,
49 opaque: SyntaxContext,
50 opaque_and_semitransparent: SyntaxContext,
51 }
52
53 impl PartialEq for SyntaxContextData {
54 fn eq(&self, other: &Self) -> bool {
55 self.outer_expn == other.outer_expn
56 && self.outer_transparency == other.outer_transparency
57 && self.edition == other.edition
58 && self.parent == other.parent
59 }
60 }
61
62 impl std::hash::Hash for SyntaxContextData {
63 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
64 self.outer_expn.hash(state);
65 self.outer_transparency.hash(state);
66 self.edition.hash(state);
67 self.parent.hash(state);
68 }
69 }
70 #[derive(Hash)]
73 struct StructKey<'db, T0, T1, T2, T3>(T0, T1, T2, T3, std::marker::PhantomData<&'db ()>);
74
75 impl<'db, T0, T1, T2, T3> zalsa_::interned::HashEqLike<StructKey<'db, T0, T1, T2, T3>>
76 for SyntaxContextData
77 where
78 Option<MacroCallId>: zalsa_::interned::HashEqLike<T0>,
79 Transparency: zalsa_::interned::HashEqLike<T1>,
80 Edition: zalsa_::interned::HashEqLike<T2>,
81 SyntaxContext: zalsa_::interned::HashEqLike<T3>,
82 {
83 fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
84 zalsa_::interned::HashEqLike::<T0>::hash(&self.outer_expn, &mut *h);
85 zalsa_::interned::HashEqLike::<T1>::hash(&self.outer_transparency, &mut *h);
86 zalsa_::interned::HashEqLike::<T2>::hash(&self.edition, &mut *h);
87 zalsa_::interned::HashEqLike::<T3>::hash(&self.parent, &mut *h);
88 }
89 fn eq(&self, data: &StructKey<'db, T0, T1, T2, T3>) -> bool {
90 zalsa_::interned::HashEqLike::<T0>::eq(&self.outer_expn, &data.0)
91 && zalsa_::interned::HashEqLike::<T1>::eq(&self.outer_transparency, &data.1)
92 && zalsa_::interned::HashEqLike::<T2>::eq(&self.edition, &data.2)
93 && zalsa_::interned::HashEqLike::<T3>::eq(&self.parent, &data.3)
94 }
95 }
96 impl zalsa_struct_::Configuration for SyntaxContext {
97 const LOCATION: salsa::plumbing::Location =
98 salsa::plumbing::Location { file: file!(), line: line!() };
99 const DEBUG_NAME: &'static str = "SyntaxContextData";
100 const REVISIONS: std::num::NonZeroUsize = std::num::NonZeroUsize::MAX;
101 type Fields<'a> = SyntaxContextData;
102 type Struct<'a> = SyntaxContext;
103 }
104 impl SyntaxContext {
105 pub fn ingredient<Db>(db: &Db) -> &zalsa_struct_::IngredientImpl<Self>
106 where
107 Db: ?Sized + zalsa_::Database,
108 {
109 static CACHE: zalsa_::IngredientCache<zalsa_struct_::IngredientImpl<SyntaxContext>> =
110 zalsa_::IngredientCache::new();
111 CACHE.get_or_create(db.zalsa(), || {
112 db.zalsa()
113 .lookup_jar_by_type::<zalsa_struct_::JarImpl<SyntaxContext>>()
114 .get_or_create()
115 })
116 }
117 }
118 impl zalsa_::AsId for SyntaxContext {
119 fn as_id(&self) -> salsa::Id {
120 self.as_salsa_id().expect("`SyntaxContext::as_id()` called on a root `SyntaxContext`")
121 }
122 }
123 impl zalsa_::FromId for SyntaxContext {
124 fn from_id(id: salsa::Id) -> Self {
125 Self::from_salsa_id(id)
126 }
127 }
128 unsafe impl Send for SyntaxContext {}
129
130 unsafe impl Sync for SyntaxContext {}
131
132 impl zalsa_::SalsaStructInDb for SyntaxContext {
133 type MemoIngredientMap = salsa::plumbing::MemoIngredientSingletonIndex;
134
135 fn lookup_or_create_ingredient_index(
136 zalsa: &salsa::plumbing::Zalsa,
137 ) -> salsa::plumbing::IngredientIndices {
138 zalsa
139 .lookup_jar_by_type::<zalsa_struct_::JarImpl<SyntaxContext>>()
140 .get_or_create()
141 .into()
142 }
143
144 #[inline]
145 fn cast(id: salsa::Id, type_id: std::any::TypeId) -> Option<Self> {
146 if type_id == std::any::TypeId::of::<SyntaxContext>() {
147 Some(<Self as salsa::plumbing::FromId>::from_id(id))
148 } else {
149 None
150 }
151 }
152 }
153
154 unsafe impl salsa::plumbing::Update for SyntaxContext {
155 unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
156 if unsafe { *old_pointer } != new_value {
157 unsafe { *old_pointer = new_value };
158 true
159 } else {
160 false
161 }
162 }
163 }
164 impl<'db> SyntaxContext {
165 pub fn new<
166 Db,
167 T0: zalsa_::interned::Lookup<Option<MacroCallId>> + std::hash::Hash,
168 T1: zalsa_::interned::Lookup<Transparency> + std::hash::Hash,
169 T2: zalsa_::interned::Lookup<Edition> + std::hash::Hash,
170 T3: zalsa_::interned::Lookup<SyntaxContext> + std::hash::Hash,
171 >(
172 db: &'db Db,
173 outer_expn: T0,
174 outer_transparency: T1,
175 edition: T2,
176 parent: T3,
177 opaque: impl FnOnce(SyntaxContext) -> SyntaxContext,
178 opaque_and_semitransparent: impl FnOnce(SyntaxContext) -> SyntaxContext,
179 ) -> Self
180 where
181 Db: ?Sized + salsa::Database,
182 Option<MacroCallId>: zalsa_::interned::HashEqLike<T0>,
183 Transparency: zalsa_::interned::HashEqLike<T1>,
184 Edition: zalsa_::interned::HashEqLike<T2>,
185 SyntaxContext: zalsa_::interned::HashEqLike<T3>,
186 {
187 SyntaxContext::ingredient(db).intern(
188 db.as_dyn_database(),
189 StructKey::<'db>(
190 outer_expn,
191 outer_transparency,
192 edition,
193 parent,
194 std::marker::PhantomData,
195 ),
196 |id, data| SyntaxContextData {
197 outer_expn: zalsa_::interned::Lookup::into_owned(data.0),
198 outer_transparency: zalsa_::interned::Lookup::into_owned(data.1),
199 edition: zalsa_::interned::Lookup::into_owned(data.2),
200 parent: zalsa_::interned::Lookup::into_owned(data.3),
201 opaque: opaque(zalsa_::FromId::from_id(id)),
202 opaque_and_semitransparent: opaque_and_semitransparent(
203 zalsa_::FromId::from_id(id),
204 ),
205 },
206 )
207 }
208
209 pub fn outer_expn<Db>(self, db: &'db Db) -> Option<MacroCallId>
215 where
216 Db: ?Sized + zalsa_::Database,
217 {
218 let id = self.as_salsa_id()?;
219 let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
220 fields.outer_expn
221 }
222
223 pub fn outer_transparency<Db>(self, db: &'db Db) -> Transparency
224 where
225 Db: ?Sized + zalsa_::Database,
226 {
227 let Some(id) = self.as_salsa_id() else { return Transparency::Opaque };
228 let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
229 fields.outer_transparency
230 }
231
232 pub fn edition<Db>(self, db: &'db Db) -> Edition
233 where
234 Db: ?Sized + zalsa_::Database,
235 {
236 match self.as_salsa_id() {
237 Some(id) => {
238 let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
239 fields.edition
240 }
241 None => Edition::from_u32(SyntaxContext::MAX_ID - self.into_u32()),
242 }
243 }
244
245 pub fn parent<Db>(self, db: &'db Db) -> SyntaxContext
246 where
247 Db: ?Sized + zalsa_::Database,
248 {
249 match self.as_salsa_id() {
250 Some(id) => {
251 let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
252 fields.parent
253 }
254 None => self,
255 }
256 }
257
258 pub fn opaque<Db>(self, db: &'db Db) -> SyntaxContext
260 where
261 Db: ?Sized + zalsa_::Database,
262 {
263 match self.as_salsa_id() {
264 Some(id) => {
265 let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
266 fields.opaque
267 }
268 None => self,
269 }
270 }
271
272 pub fn opaque_and_semitransparent<Db>(self, db: &'db Db) -> SyntaxContext
274 where
275 Db: ?Sized + zalsa_::Database,
276 {
277 match self.as_salsa_id() {
278 Some(id) => {
279 let fields = SyntaxContext::ingredient(db).data(db.as_dyn_database(), id);
280 fields.opaque_and_semitransparent
281 }
282 None => self,
283 }
284 }
285 }
286};
287
288impl SyntaxContext {
289 #[inline]
290 pub fn is_root(self) -> bool {
291 (SyntaxContext::MAX_ID - Edition::LATEST as u32) <= self.into_u32()
292 && self.into_u32() <= (SyntaxContext::MAX_ID - Edition::Edition2015 as u32)
293 }
294
295 #[inline]
296 pub fn remove_root_edition(&mut self) {
297 if self.is_root() {
298 *self = Self::root(Edition::Edition2015);
299 }
300 }
301
302 #[inline]
304 pub const fn root(edition: Edition) -> Self {
305 let edition = edition as u32;
306 unsafe { SyntaxContext::from_u32(SyntaxContext::MAX_ID - edition) }
308 }
309}
310
311#[cfg(feature = "salsa")]
312impl<'db> SyntaxContext {
313 const MAX_ID: u32 = salsa::Id::MAX_U32 - 1;
314
315 #[inline]
316 pub const fn into_u32(self) -> u32 {
317 self.0
318 }
319
320 #[inline]
324 pub const unsafe fn from_u32(u32: u32) -> Self {
325 Self(u32, std::marker::PhantomData)
327 }
328
329 #[inline]
330 fn as_salsa_id(self) -> Option<salsa::Id> {
331 if self.is_root() {
332 None
333 } else {
334 unsafe { Some(salsa::Id::from_index(self.0)) }
336 }
337 }
338
339 #[inline]
340 fn from_salsa_id(id: salsa::Id) -> Self {
341 unsafe { Self::from_u32(id.index()) }
343 }
344
345 #[inline]
346 pub fn outer_mark(
347 self,
348 db: &'db dyn salsa::Database,
349 ) -> (Option<crate::MacroCallId>, Transparency) {
350 (self.outer_expn(db), self.outer_transparency(db))
351 }
352
353 #[inline]
354 pub fn normalize_to_macros_2_0(self, db: &'db dyn salsa::Database) -> SyntaxContext {
355 self.opaque(db)
356 }
357
358 #[inline]
359 pub fn normalize_to_macro_rules(self, db: &'db dyn salsa::Database) -> SyntaxContext {
360 self.opaque_and_semitransparent(db)
361 }
362
363 pub fn is_opaque(self, db: &'db dyn salsa::Database) -> bool {
364 !self.is_root() && self.outer_transparency(db).is_opaque()
365 }
366
367 pub fn remove_mark(
368 &mut self,
369 db: &'db dyn salsa::Database,
370 ) -> (Option<crate::MacroCallId>, Transparency) {
371 let data = *self;
372 *self = data.parent(db);
373 (data.outer_expn(db), data.outer_transparency(db))
374 }
375
376 pub fn marks(
377 self,
378 db: &'db dyn salsa::Database,
379 ) -> impl Iterator<Item = (crate::MacroCallId, Transparency)> {
380 let mut marks = self.marks_rev(db).collect::<Vec<_>>();
381 marks.reverse();
382 marks.into_iter()
383 }
384
385 pub fn marks_rev(
386 self,
387 db: &'db dyn salsa::Database,
388 ) -> impl Iterator<Item = (crate::MacroCallId, Transparency)> {
389 std::iter::successors(Some(self), move |&mark| Some(mark.parent(db)))
390 .take_while(|&it| !it.is_root())
391 .map(|ctx| {
392 let mark = ctx.outer_mark(db);
393 (mark.0.unwrap(), mark.1)
396 })
397 }
398}
399#[cfg(not(feature = "salsa"))]
400#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
401pub struct SyntaxContext(u32);
402
403#[allow(dead_code)]
404const SALSA_MAX_ID_MIRROR: u32 = u32::MAX - 0xFF;
405#[cfg(feature = "salsa")]
406const _: () = assert!(salsa::Id::MAX_U32 == SALSA_MAX_ID_MIRROR);
407
408#[cfg(not(feature = "salsa"))]
409impl SyntaxContext {
410 const MAX_ID: u32 = SALSA_MAX_ID_MIRROR - 1;
411
412 pub const fn into_u32(self) -> u32 {
413 self.0
414 }
415
416 pub const unsafe fn from_u32(u32: u32) -> Self {
420 Self(u32)
421 }
422}
423
424#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
427pub enum Transparency {
428 Transparent,
431 SemiTransparent,
438 Opaque,
441}
442
443impl Transparency {
444 pub fn is_opaque(&self) -> bool {
448 matches!(self, Self::Opaque)
449 }
450}
451
452impl fmt::Display for SyntaxContext {
453 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454 if self.is_root() {
455 write!(f, "ROOT{}", Edition::from_u32(SyntaxContext::MAX_ID - self.into_u32()).number())
456 } else {
457 write!(f, "{}", self.into_u32())
458 }
459 }
460}
461
462impl std::fmt::Debug for SyntaxContext {
463 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
464 if f.alternate() {
465 fmt::Display::fmt(self, f)
466 } else {
467 f.debug_tuple("SyntaxContext").field(&self.0).finish()
468 }
469 }
470}