Skip to main content

mir_types/
atomic.rs

1use std::hash::Hash;
2use std::sync::Arc;
3
4use indexmap::IndexMap;
5use serde::{Deserialize, Serialize};
6
7use crate::symbol::Name;
8use crate::Type;
9
10// ---------------------------------------------------------------------------
11// FnParam — used inside callable/closure atomics
12// ---------------------------------------------------------------------------
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct FnParam {
16    pub name: Name,
17    /// Parameter type stored as SimpleType for compact representation.
18    /// Most params are simple scalars (string, int, etc.) and fit inline.
19    pub ty: Option<crate::compact::SimpleType>,
20    /// `@param-out` / `@psalm-param-out` writeback type. When `Some`, the
21    /// variable passed for this by-ref param is set to this type after the call
22    /// instead of the declared in-type (`ty`). Preserved through `TClosure` so
23    /// first-class callables honour out-param annotations.
24    #[serde(default)]
25    pub out_ty: Option<crate::compact::SimpleType>,
26    /// Default value stored as SimpleType. Usually None or a simple scalar.
27    pub default: Option<crate::compact::SimpleType>,
28    pub is_variadic: bool,
29    pub is_byref: bool,
30    pub is_optional: bool,
31}
32
33// ---------------------------------------------------------------------------
34// Variance — covariance / contravariance for template type parameters
35// ---------------------------------------------------------------------------
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
38pub enum Variance {
39    /// Default: exact type match required (no `@template-covariant` / `@template-contravariant`).
40    #[default]
41    Invariant,
42    /// `@template-covariant T` — `C<Sub>` is assignable to `C<Super>`.
43    Covariant,
44    /// `@template-contravariant T` — `C<Super>` is assignable to `C<Sub>`.
45    Contravariant,
46}
47
48// ---------------------------------------------------------------------------
49// TemplateParam — `@template T` / `@template T of Bound`
50// ---------------------------------------------------------------------------
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub struct TemplateParam {
54    pub name: Name,
55    pub bound: Option<Type>,
56    pub variance: Variance,
57}
58
59// ---------------------------------------------------------------------------
60// KeyedProperty — entry in TKeyedArray
61// ---------------------------------------------------------------------------
62
63#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64pub enum ArrayKey {
65    String(Arc<str>),
66    Int(i64),
67}
68
69impl PartialOrd for ArrayKey {
70    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
71        Some(self.cmp(other))
72    }
73}
74
75impl Ord for ArrayKey {
76    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
77        match (self, other) {
78            (ArrayKey::Int(a), ArrayKey::Int(b)) => a.cmp(b),
79            (ArrayKey::String(a), ArrayKey::String(b)) => a.cmp(b),
80            // Int < String
81            (ArrayKey::Int(_), ArrayKey::String(_)) => std::cmp::Ordering::Less,
82            (ArrayKey::String(_), ArrayKey::Int(_)) => std::cmp::Ordering::Greater,
83        }
84    }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
88pub struct KeyedProperty {
89    pub ty: Type,
90    pub optional: bool,
91}
92
93type KeyedMap = IndexMap<ArrayKey, KeyedProperty, rustc_hash::FxBuildHasher>;
94
95/// Entries of an `array{...}` shape, in declaration order. Remembers its
96/// order-independent hash, since a shared shape is hashed again whenever a
97/// type holding it is interned; any mutable access forgets it.
98#[derive(Default, Serialize, Deserialize)]
99#[serde(transparent)]
100pub struct KeyedProperties {
101    map: KeyedMap,
102    #[serde(skip)]
103    hash: std::sync::OnceLock<u64>,
104}
105
106impl KeyedProperties {
107    pub fn with_capacity_and_hasher(n: usize, hasher: rustc_hash::FxBuildHasher) -> Self {
108        KeyedMap::with_capacity_and_hasher(n, hasher).into()
109    }
110
111    /// Hash of the entries, independent of their order (equality ignores it too).
112    pub fn entries_hash(&self) -> u64 {
113        *self.hash.get_or_init(|| {
114            self.map.iter().fold(0u64, |combined, (k, v)| {
115                let mut entry_hasher = rustc_hash::FxHasher::default();
116                k.hash(&mut entry_hasher);
117                v.hash(&mut entry_hasher);
118                combined.wrapping_add(std::hash::Hasher::finish(&entry_hasher))
119            })
120        })
121    }
122}
123
124impl From<KeyedMap> for KeyedProperties {
125    fn from(map: KeyedMap) -> Self {
126        KeyedProperties {
127            map,
128            hash: std::sync::OnceLock::new(),
129        }
130    }
131}
132
133impl Clone for KeyedProperties {
134    fn clone(&self) -> Self {
135        KeyedProperties {
136            map: self.map.clone(),
137            hash: self.hash.clone(),
138        }
139    }
140}
141
142impl std::fmt::Debug for KeyedProperties {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        self.map.fmt(f)
145    }
146}
147
148impl PartialEq for KeyedProperties {
149    fn eq(&self, other: &Self) -> bool {
150        // Remembered hashes that differ settle it without visiting the entries.
151        if let (Some(a), Some(b)) = (self.hash.get(), other.hash.get()) {
152            if a != b {
153                return false;
154            }
155        }
156        self.map == other.map
157    }
158}
159
160impl Eq for KeyedProperties {}
161
162impl std::ops::Deref for KeyedProperties {
163    type Target = KeyedMap;
164    fn deref(&self) -> &KeyedMap {
165        &self.map
166    }
167}
168
169impl std::ops::DerefMut for KeyedProperties {
170    fn deref_mut(&mut self) -> &mut KeyedMap {
171        self.hash = std::sync::OnceLock::new();
172        &mut self.map
173    }
174}
175
176impl FromIterator<(ArrayKey, KeyedProperty)> for KeyedProperties {
177    fn from_iter<I: IntoIterator<Item = (ArrayKey, KeyedProperty)>>(iter: I) -> Self {
178        KeyedMap::from_iter(iter).into()
179    }
180}
181
182impl Extend<(ArrayKey, KeyedProperty)> for KeyedProperties {
183    fn extend<I: IntoIterator<Item = (ArrayKey, KeyedProperty)>>(&mut self, iter: I) {
184        std::ops::DerefMut::deref_mut(self).extend(iter);
185    }
186}
187
188impl IntoIterator for KeyedProperties {
189    type Item = (ArrayKey, KeyedProperty);
190    type IntoIter = indexmap::map::IntoIter<ArrayKey, KeyedProperty>;
191    fn into_iter(self) -> Self::IntoIter {
192        self.map.into_iter()
193    }
194}
195
196impl<'a> IntoIterator for &'a KeyedProperties {
197    type Item = (&'a ArrayKey, &'a KeyedProperty);
198    type IntoIter = indexmap::map::Iter<'a, ArrayKey, KeyedProperty>;
199    fn into_iter(self) -> Self::IntoIter {
200        self.map.iter()
201    }
202}
203
204impl<'a> IntoIterator for &'a mut KeyedProperties {
205    type Item = (&'a ArrayKey, &'a mut KeyedProperty);
206    type IntoIter = indexmap::map::IterMut<'a, ArrayKey, KeyedProperty>;
207    fn into_iter(self) -> Self::IntoIter {
208        std::ops::DerefMut::deref_mut(self).iter_mut()
209    }
210}
211
212// ---------------------------------------------------------------------------
213// Boxed variant payloads — keep `Atomic` (and `Type`, which inlines two) small
214// ---------------------------------------------------------------------------
215
216/// Payload of [`Atomic::TClosure`].
217#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
218pub struct ClosureData {
219    pub params: Box<[FnParam]>,
220    pub return_type: Type,
221    pub this_type: Option<Type>,
222    /// `pure-Closure(...)`, or a closure literal whose body is checked as pure.
223    pub is_pure: bool,
224    /// A `static` closure literal, which has no `$this` to mutate.
225    pub is_static: bool,
226    /// A first-class callable of a named function or method, so surplus arguments are reported.
227    #[serde(default)]
228    pub rejects_surplus_args: bool,
229}
230
231/// Payload of [`Atomic::TConditional`].
232#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
233pub struct ConditionalData {
234    /// The parameter name being tested (without `$`), e.g. `"classOrInterface"`.
235    /// `None` for conditionals that were not parsed from a `$param is` form.
236    pub param_name: Option<Name>,
237    pub subject: Type,
238    pub if_true: Type,
239    pub if_false: Type,
240}
241
242// ---------------------------------------------------------------------------
243// Atomic — every distinct PHP type variant
244// ---------------------------------------------------------------------------
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
247pub enum Atomic {
248    // --- Scalars ---
249    /// `string`
250    TString,
251    /// `"hello"` — a specific string literal
252    TLiteralString(Arc<str>),
253    /// `callable-string` — a string containing a callable name
254    TCallableString,
255    /// `class-string` or `class-string<T>`
256    TClassString(Option<Name>),
257    /// `non-empty-string`
258    TNonEmptyString,
259    /// `numeric-string`
260    TNumericString,
261
262    /// `int`
263    TInt,
264    /// `42` — a specific integer literal
265    TLiteralInt(i64),
266    /// `int<min, max>` — bounded integer range
267    TIntRange { min: Option<i64>, max: Option<i64> },
268    /// `positive-int`
269    TPositiveInt,
270    /// `negative-int`
271    TNegativeInt,
272    /// `non-negative-int`
273    TNonNegativeInt,
274
275    /// `float`
276    TFloat,
277    /// `float` that is always integral (whole-number value, no fractional part).
278    /// Returned by `floor()`, `ceil()`, and zero-precision `round()`. Can be passed to `int`
279    /// parameters without `ImplicitFloatToIntCast` because the conversion is lossless.
280    TIntegralFloat,
281    /// `3.14` — a specific float literal
282    TLiteralFloat(i64, i64), // stored as (int_bits, frac_bits) to be PartialEq+Hash-friendly
283    // We use ordered_float or just store as ordered pair for equality purposes.
284    /// `bool`
285    TBool,
286    /// `true`
287    TTrue,
288    /// `false`
289    TFalse,
290
291    /// `null`
292    TNull,
293
294    // --- Bottom / top ---
295    /// `void` — return-only; can't be used as a value
296    TVoid,
297    /// `never` — function that never returns (throws or infinite loop)
298    TNever,
299    /// `mixed` — top type; accepts anything
300    TMixed,
301    /// `scalar` — int | float | string | bool
302    TScalar,
303    /// `numeric` — int | float | numeric-string
304    TNumeric,
305
306    // --- Objects ---
307    /// `object` — any object
308    TObject,
309    /// `ClassName` / `ClassName<T1, T2>` — specific named class/interface
310    TNamedObject {
311        fqcn: Name,
312        /// Resolved generic type arguments (e.g. `Collection<int>`)
313        type_params: Arc<[Type]>,
314    },
315    /// `static` — late static binding type; resolved to calling class at call site
316    TStaticObject { fqcn: Name },
317    /// `self` — the class in whose body the type appears
318    TSelf { fqcn: Name },
319    /// `parent` — the parent class
320    TParent { fqcn: Name },
321
322    // --- Callables ---
323    /// `callable` or `callable(T): R`
324    TCallable {
325        /// Thin box: an inline `Vec` or boxed slice here, next to `is_pure`,
326        /// would make this the largest variant and grow every `Atomic` (and
327        /// `Type` inlines two of them).
328        params: Option<Box<Vec<FnParam>>>,
329        return_type: Option<Box<Type>>,
330        /// `pure-callable`.
331        is_pure: bool,
332    },
333    /// `Closure` or `Closure(T): R` — more specific than TCallable.
334    /// Payload boxed to keep `Atomic` at 32 bytes (see [`ClosureData`]).
335    TClosure { data: Box<ClosureData> },
336
337    // --- Arrays ---
338    /// `array` or `array<K, V>`
339    TArray { key: Box<Type>, value: Box<Type> },
340    /// `list<T>` — integer-keyed sequential array (keys 0, 1, 2, …)
341    TList { value: Box<Type> },
342    /// `non-empty-array<K, V>`
343    TNonEmptyArray { key: Box<Type>, value: Box<Type> },
344    /// `non-empty-list<T>`
345    TNonEmptyList { value: Box<Type> },
346    /// `array{key: T, ...}` — shape / keyed array
347    TKeyedArray {
348        /// Shared so cloning a shape copies a pointer and equal clones compare by address.
349        properties: Arc<KeyedProperties>,
350        /// If true, additional keys beyond the declared ones may exist
351        is_open: bool,
352        /// If true, the shape represents a list (integer keys only)
353        is_list: bool,
354    },
355
356    // --- Generics / meta-types ---
357    /// `T` — a template type parameter
358    TTemplateParam {
359        name: Name,
360        as_type: Box<Type>,
361        /// The entity (class or function FQN) that declared this template
362        defining_entity: Name,
363    },
364    /// `key-of<T>` — preserved when the operand cannot yet be reduced.
365    TKeyOf { target: Box<Type> },
366    /// `value-of<T>` — preserved when the operand cannot yet be reduced.
367    TValueOf { target: Box<Type> },
368    /// `($param is TypeName ? A : B)` — conditional type.
369    /// Payload boxed to keep `Atomic` at 32 bytes (see [`ConditionalData`]).
370    TConditional { data: Box<ConditionalData> },
371
372    // --- Special object strings ---
373    /// `interface-string` or `interface-string<T>`
374    TInterfaceString(Option<Name>),
375    /// `enum-string`
376    TEnumString,
377    /// `trait-string`
378    TTraitString,
379
380    // --- Enum cases ---
381    /// `EnumName::CaseName` — a specific enum case literal
382    TLiteralEnumCase { enum_fqcn: Name, case_name: Name },
383
384    // --- Intersection ---
385    /// `A&B&C` — PHP 8.1+ pure intersection type
386    TIntersection { parts: Arc<[Type]> },
387
388    /// `object{prop: T, ...}` — an object with these public properties.
389    TObjectShape { properties: Arc<KeyedProperties> },
390}
391
392impl Atomic {
393    /// Whether this atomic type can ever evaluate to a falsy value.
394    pub fn can_be_falsy(&self) -> bool {
395        match self {
396            Atomic::TNull
397            | Atomic::TFalse
398            | Atomic::TBool
399            | Atomic::TNever
400            | Atomic::TLiteralInt(0)
401            | Atomic::TLiteralFloat(0, 0)
402            | Atomic::TInt
403            | Atomic::TFloat
404            | Atomic::TNumeric
405            | Atomic::TScalar
406            | Atomic::TMixed
407            | Atomic::TString
408            | Atomic::TNonEmptyString  // "0" is both non-empty and falsy in PHP
409            | Atomic::TNumericString   // "0" is a valid numeric-string and is falsy
410            | Atomic::TKeyOf { .. }
411            | Atomic::TValueOf { .. }
412            | Atomic::TArray { .. }
413            | Atomic::TList { .. } => true,
414            // Non-empty collections always have at least one element — never falsy in PHP.
415            // TNonEmptyArray and TNonEmptyList are intentionally excluded here.
416
417            Atomic::TLiteralString(s) => s.as_ref().is_empty() || s.as_ref() == "0",
418
419            Atomic::TKeyedArray { properties, .. } => properties.is_empty(),
420
421            // An int range can be falsy (== 0) when 0 is within the bounds.
422            Atomic::TIntRange { min, max } => {
423                min.is_none_or(|lo| lo <= 0) && max.is_none_or(|hi| hi >= 0)
424            }
425            // non-negative-int includes 0
426            Atomic::TNonNegativeInt => true,
427
428            _ => false,
429        }
430    }
431
432    /// Whether this atomic type can ever evaluate to a truthy value.
433    pub fn can_be_truthy(&self) -> bool {
434        match self {
435            Atomic::TNever
436            | Atomic::TVoid
437            | Atomic::TNull
438            | Atomic::TFalse
439            | Atomic::TLiteralInt(0)
440            | Atomic::TLiteralFloat(0, 0) => false,
441            Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => false,
442            // int<0, 0> always equals zero — never truthy.
443            Atomic::TIntRange {
444                min: Some(0),
445                max: Some(0),
446            } => false,
447            // array{} — a closed empty keyed array — is always falsy.
448            Atomic::TKeyedArray {
449                properties,
450                is_open,
451                ..
452            } if !is_open && properties.is_empty() => false,
453            _ => true,
454        }
455    }
456
457    /// Whether this atomic represents a numeric type (int, float, or numeric-string).
458    pub fn is_numeric(&self) -> bool {
459        matches!(
460            self,
461            Atomic::TInt
462                | Atomic::TLiteralInt(_)
463                | Atomic::TIntRange { .. }
464                | Atomic::TPositiveInt
465                | Atomic::TNegativeInt
466                | Atomic::TNonNegativeInt
467                | Atomic::TFloat
468                | Atomic::TIntegralFloat
469                | Atomic::TLiteralFloat(..)
470                | Atomic::TNumeric
471                | Atomic::TNumericString
472        )
473    }
474
475    /// Whether this atomic is an integer variant.
476    pub fn is_int(&self) -> bool {
477        matches!(
478            self,
479            Atomic::TInt
480                | Atomic::TLiteralInt(_)
481                | Atomic::TIntRange { .. }
482                | Atomic::TPositiveInt
483                | Atomic::TNegativeInt
484                | Atomic::TNonNegativeInt
485        )
486    }
487
488    /// Whether this atomic is a string variant.
489    pub fn is_string(&self) -> bool {
490        matches!(
491            self,
492            Atomic::TString
493                | Atomic::TLiteralString(_)
494                | Atomic::TCallableString
495                | Atomic::TClassString(_)
496                | Atomic::TNonEmptyString
497                | Atomic::TNumericString
498                | Atomic::TInterfaceString(_)
499                | Atomic::TEnumString
500                | Atomic::TTraitString
501        )
502    }
503
504    /// Whether this atomic is an array variant.
505    pub fn is_array(&self) -> bool {
506        matches!(
507            self,
508            Atomic::TArray { .. }
509                | Atomic::TList { .. }
510                | Atomic::TNonEmptyArray { .. }
511                | Atomic::TNonEmptyList { .. }
512                | Atomic::TKeyedArray { .. }
513        )
514    }
515
516    /// Whether this atomic is an object variant.
517    pub fn is_object(&self) -> bool {
518        matches!(
519            self,
520            Atomic::TObject
521                | Atomic::TObjectShape { .. }
522                | Atomic::TNamedObject { .. }
523                | Atomic::TStaticObject { .. }
524                | Atomic::TSelf { .. }
525                | Atomic::TParent { .. }
526                | Atomic::TIntersection { .. }
527        )
528    }
529
530    /// Whether this atomic can never be an object instance — so `clone`-ing it
531    /// is invalid. Conservative: ambiguous atomics (`mixed`, `callable`,
532    /// `never`, conditionals, template params, enum cases, intersections,
533    /// object types) all return `false` so they never trigger a false positive.
534    pub fn is_definitely_non_object(&self) -> bool {
535        matches!(
536            self,
537            Atomic::TString
538                | Atomic::TLiteralString(_)
539                | Atomic::TCallableString
540                | Atomic::TClassString(_)
541                | Atomic::TNonEmptyString
542                | Atomic::TNumericString
543                | Atomic::TInterfaceString(_)
544                | Atomic::TEnumString
545                | Atomic::TTraitString
546                | Atomic::TInt
547                | Atomic::TLiteralInt(_)
548                | Atomic::TIntRange { .. }
549                | Atomic::TPositiveInt
550                | Atomic::TNegativeInt
551                | Atomic::TNonNegativeInt
552                | Atomic::TFloat
553                | Atomic::TIntegralFloat
554                | Atomic::TLiteralFloat(..)
555                | Atomic::TBool
556                | Atomic::TTrue
557                | Atomic::TFalse
558                | Atomic::TNull
559                | Atomic::TVoid
560                | Atomic::TArray { .. }
561                | Atomic::TList { .. }
562                | Atomic::TNonEmptyArray { .. }
563                | Atomic::TNonEmptyList { .. }
564                | Atomic::TKeyedArray { .. }
565                | Atomic::TScalar
566                | Atomic::TNumeric
567        )
568    }
569
570    /// Whether this atomic is a callable variant.
571    ///
572    /// `TCallableString` is included: it denotes a string *known* to be a
573    /// valid callable, so `!is_callable()` must remove it from the type.
574    pub fn is_callable(&self) -> bool {
575        matches!(
576            self,
577            Atomic::TCallable { .. } | Atomic::TClosure { .. } | Atomic::TCallableString
578        )
579    }
580
581    /// Returns the FQCN if this is a named object type.
582    pub fn named_object_fqcn(&self) -> Option<&str> {
583        match self {
584            Atomic::TNamedObject { fqcn, .. }
585            | Atomic::TStaticObject { fqcn }
586            | Atomic::TSelf { fqcn }
587            | Atomic::TParent { fqcn } => Some(fqcn.as_ref()),
588            _ => None,
589        }
590    }
591
592    /// A human-readable name for this type (used in error messages).
593    pub fn type_name(&self) -> &'static str {
594        match self {
595            Atomic::TString
596            | Atomic::TLiteralString(_)
597            | Atomic::TNonEmptyString
598            | Atomic::TNumericString => "string",
599            Atomic::TCallableString => "callable-string",
600            Atomic::TClassString(_) => "class-string",
601            Atomic::TInt | Atomic::TLiteralInt(_) | Atomic::TIntRange { .. } => "int",
602            Atomic::TPositiveInt => "positive-int",
603            Atomic::TNegativeInt => "negative-int",
604            Atomic::TNonNegativeInt => "non-negative-int",
605            Atomic::TFloat | Atomic::TIntegralFloat | Atomic::TLiteralFloat(..) => "float",
606            Atomic::TBool => "bool",
607            Atomic::TTrue => "true",
608            Atomic::TFalse => "false",
609            Atomic::TNull => "null",
610            Atomic::TVoid => "void",
611            Atomic::TNever => "never",
612            Atomic::TMixed => "mixed",
613            Atomic::TScalar => "scalar",
614            Atomic::TNumeric => "numeric",
615            Atomic::TObject => "object",
616            Atomic::TNamedObject { .. } => "object",
617            Atomic::TStaticObject { .. } => "static",
618            Atomic::TSelf { .. } => "self",
619            Atomic::TParent { .. } => "parent",
620            Atomic::TCallable { .. } => "callable",
621            Atomic::TClosure { .. } => "Closure",
622            Atomic::TArray { .. } => "array",
623            Atomic::TList { .. } => "list",
624            Atomic::TNonEmptyArray { .. } => "non-empty-array",
625            Atomic::TNonEmptyList { .. } => "non-empty-list",
626            Atomic::TKeyedArray { .. } => "array",
627            Atomic::TTemplateParam { .. } => "template-param",
628            Atomic::TKeyOf { .. } => "key-of",
629            Atomic::TValueOf { .. } => "value-of",
630            Atomic::TConditional { .. } => "conditional-type",
631            Atomic::TInterfaceString(_) => "interface-string",
632            Atomic::TEnumString => "enum-string",
633            Atomic::TTraitString => "trait-string",
634            Atomic::TLiteralEnumCase { .. } => "enum-case",
635            Atomic::TIntersection { .. } => "intersection",
636            Atomic::TObjectShape { .. } => "object",
637        }
638    }
639}
640
641// ---------------------------------------------------------------------------
642// Hash impls
643// ---------------------------------------------------------------------------
644
645impl Hash for FnParam {
646    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
647        self.name.hash(state);
648        self.ty.hash(state);
649        self.out_ty.hash(state);
650        self.default.hash(state);
651        self.is_variadic.hash(state);
652        self.is_byref.hash(state);
653        self.is_optional.hash(state);
654    }
655}
656
657/// Discriminant tags for `Atomic` used in the manual `Hash` impl below.
658#[allow(non_camel_case_types, clippy::enum_variant_names)]
659#[repr(u8)]
660enum AtomicTag {
661    TString = 0,
662    TLiteralString,
663    TCallableString,
664    TClassString,
665    TNonEmptyString,
666    TNumericString,
667    TInt,
668    TLiteralInt,
669    TIntRange,
670    TPositiveInt,
671    TNegativeInt,
672    TNonNegativeInt,
673    TFloat,
674    TIntegralFloat,
675    TLiteralFloat,
676    TBool,
677    TTrue,
678    TFalse,
679    TNull,
680    TVoid,
681    TNever,
682    TMixed,
683    TScalar,
684    TNumeric,
685    TObject,
686    TNamedObject,
687    TStaticObject,
688    TSelf,
689    TParent,
690    TCallable,
691    TClosure,
692    TArray,
693    TList,
694    TNonEmptyArray,
695    TNonEmptyList,
696    TKeyedArray,
697    TTemplateParam,
698    TKeyOf,
699    TValueOf,
700    TConditional,
701    TInterfaceString,
702    TEnumString,
703    TTraitString,
704    TLiteralEnumCase,
705    TIntersection,
706    TObjectShape,
707}
708
709impl Hash for Atomic {
710    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
711        use AtomicTag as T;
712        match self {
713            // --- tag-only variants ---
714            Atomic::TString => (T::TString as u8).hash(state),
715            Atomic::TCallableString => (T::TCallableString as u8).hash(state),
716            Atomic::TNonEmptyString => (T::TNonEmptyString as u8).hash(state),
717            Atomic::TNumericString => (T::TNumericString as u8).hash(state),
718            Atomic::TInt => (T::TInt as u8).hash(state),
719            Atomic::TPositiveInt => (T::TPositiveInt as u8).hash(state),
720            Atomic::TNegativeInt => (T::TNegativeInt as u8).hash(state),
721            Atomic::TNonNegativeInt => (T::TNonNegativeInt as u8).hash(state),
722            Atomic::TFloat => (T::TFloat as u8).hash(state),
723            Atomic::TIntegralFloat => (T::TIntegralFloat as u8).hash(state),
724            Atomic::TBool => (T::TBool as u8).hash(state),
725            Atomic::TTrue => (T::TTrue as u8).hash(state),
726            Atomic::TFalse => (T::TFalse as u8).hash(state),
727            Atomic::TNull => (T::TNull as u8).hash(state),
728            Atomic::TVoid => (T::TVoid as u8).hash(state),
729            Atomic::TNever => (T::TNever as u8).hash(state),
730            Atomic::TMixed => (T::TMixed as u8).hash(state),
731            Atomic::TScalar => (T::TScalar as u8).hash(state),
732            Atomic::TNumeric => (T::TNumeric as u8).hash(state),
733            Atomic::TObject => (T::TObject as u8).hash(state),
734            Atomic::TEnumString => (T::TEnumString as u8).hash(state),
735            Atomic::TTraitString => (T::TTraitString as u8).hash(state),
736
737            // --- variants with fields ---
738            Atomic::TLiteralString(s) => {
739                (T::TLiteralString as u8).hash(state);
740                s.hash(state);
741            }
742            Atomic::TClassString(opt) => {
743                (T::TClassString as u8).hash(state);
744                opt.hash(state);
745            }
746            Atomic::TInterfaceString(opt) => {
747                (T::TInterfaceString as u8).hash(state);
748                opt.hash(state);
749            }
750            Atomic::TLiteralInt(n) => {
751                (T::TLiteralInt as u8).hash(state);
752                n.hash(state);
753            }
754            Atomic::TIntRange { min, max } => {
755                (T::TIntRange as u8).hash(state);
756                min.hash(state);
757                max.hash(state);
758            }
759            Atomic::TLiteralFloat(int_bits, frac_bits) => {
760                (T::TLiteralFloat as u8).hash(state);
761                int_bits.hash(state);
762                frac_bits.hash(state);
763            }
764            Atomic::TNamedObject { fqcn, type_params } => {
765                (T::TNamedObject as u8).hash(state);
766                fqcn.hash(state);
767                type_params.hash(state);
768            }
769            Atomic::TStaticObject { fqcn } => {
770                (T::TStaticObject as u8).hash(state);
771                fqcn.hash(state);
772            }
773            Atomic::TSelf { fqcn } => {
774                (T::TSelf as u8).hash(state);
775                fqcn.hash(state);
776            }
777            Atomic::TParent { fqcn } => {
778                (T::TParent as u8).hash(state);
779                fqcn.hash(state);
780            }
781            Atomic::TCallable {
782                params,
783                return_type,
784                is_pure,
785            } => {
786                (T::TCallable as u8).hash(state);
787                params.hash(state);
788                return_type.hash(state);
789                is_pure.hash(state);
790            }
791            Atomic::TClosure { data } => {
792                (T::TClosure as u8).hash(state);
793                data.hash(state);
794            }
795            Atomic::TArray { key, value } => {
796                (T::TArray as u8).hash(state);
797                key.hash(state);
798                value.hash(state);
799            }
800            Atomic::TList { value } => {
801                (T::TList as u8).hash(state);
802                value.hash(state);
803            }
804            Atomic::TNonEmptyArray { key, value } => {
805                (T::TNonEmptyArray as u8).hash(state);
806                key.hash(state);
807                value.hash(state);
808            }
809            Atomic::TNonEmptyList { value } => {
810                (T::TNonEmptyList as u8).hash(state);
811                value.hash(state);
812            }
813            Atomic::TObjectShape { properties } => {
814                (T::TObjectShape as u8).hash(state);
815                properties.entries_hash().hash(state);
816                properties.len().hash(state);
817            }
818            Atomic::TKeyedArray {
819                properties,
820                is_open,
821                is_list,
822            } => {
823                (T::TKeyedArray as u8).hash(state);
824                properties.entries_hash().hash(state);
825                properties.len().hash(state);
826                is_open.hash(state);
827                is_list.hash(state);
828            }
829            Atomic::TTemplateParam {
830                name,
831                as_type,
832                defining_entity,
833            } => {
834                (T::TTemplateParam as u8).hash(state);
835                name.hash(state);
836                as_type.hash(state);
837                defining_entity.hash(state);
838            }
839            Atomic::TKeyOf { target } => {
840                (T::TKeyOf as u8).hash(state);
841                target.hash(state);
842            }
843            Atomic::TValueOf { target } => {
844                (T::TValueOf as u8).hash(state);
845                target.hash(state);
846            }
847            Atomic::TConditional { data } => {
848                (T::TConditional as u8).hash(state);
849                data.hash(state);
850            }
851            Atomic::TLiteralEnumCase {
852                enum_fqcn,
853                case_name,
854            } => {
855                (T::TLiteralEnumCase as u8).hash(state);
856                enum_fqcn.hash(state);
857                case_name.hash(state);
858            }
859            Atomic::TIntersection { parts } => {
860                (T::TIntersection as u8).hash(state);
861                parts.hash(state);
862            }
863        }
864    }
865}