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    /// Default value stored as SimpleType. Usually None or a simple scalar.
21    pub default: Option<crate::compact::SimpleType>,
22    pub is_variadic: bool,
23    pub is_byref: bool,
24    pub is_optional: bool,
25}
26
27// ---------------------------------------------------------------------------
28// Variance — covariance / contravariance for template type parameters
29// ---------------------------------------------------------------------------
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
32pub enum Variance {
33    /// Default: exact type match required (no `@template-covariant` / `@template-contravariant`).
34    #[default]
35    Invariant,
36    /// `@template-covariant T` — `C<Sub>` is assignable to `C<Super>`.
37    Covariant,
38    /// `@template-contravariant T` — `C<Super>` is assignable to `C<Sub>`.
39    Contravariant,
40}
41
42// ---------------------------------------------------------------------------
43// TemplateParam — `@template T` / `@template T of Bound`
44// ---------------------------------------------------------------------------
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
47pub struct TemplateParam {
48    pub name: Name,
49    pub bound: Option<Type>,
50    pub variance: Variance,
51}
52
53// ---------------------------------------------------------------------------
54// KeyedProperty — entry in TKeyedArray
55// ---------------------------------------------------------------------------
56
57#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
58pub enum ArrayKey {
59    String(Arc<str>),
60    Int(i64),
61}
62
63impl PartialOrd for ArrayKey {
64    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
65        Some(self.cmp(other))
66    }
67}
68
69impl Ord for ArrayKey {
70    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
71        match (self, other) {
72            (ArrayKey::Int(a), ArrayKey::Int(b)) => a.cmp(b),
73            (ArrayKey::String(a), ArrayKey::String(b)) => a.cmp(b),
74            // Int < String
75            (ArrayKey::Int(_), ArrayKey::String(_)) => std::cmp::Ordering::Less,
76            (ArrayKey::String(_), ArrayKey::Int(_)) => std::cmp::Ordering::Greater,
77        }
78    }
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
82pub struct KeyedProperty {
83    pub ty: Type,
84    pub optional: bool,
85}
86
87// ---------------------------------------------------------------------------
88// Atomic — every distinct PHP type variant
89// ---------------------------------------------------------------------------
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub enum Atomic {
93    // --- Scalars ---
94    /// `string`
95    TString,
96    /// `"hello"` — a specific string literal
97    TLiteralString(Arc<str>),
98    /// `callable-string` — a string containing a callable name
99    TCallableString,
100    /// `class-string` or `class-string<T>`
101    TClassString(Option<Name>),
102    /// `non-empty-string`
103    TNonEmptyString,
104    /// `numeric-string`
105    TNumericString,
106
107    /// `int`
108    TInt,
109    /// `42` — a specific integer literal
110    TLiteralInt(i64),
111    /// `int<min, max>` — bounded integer range
112    TIntRange { min: Option<i64>, max: Option<i64> },
113    /// `positive-int`
114    TPositiveInt,
115    /// `negative-int`
116    TNegativeInt,
117    /// `non-negative-int`
118    TNonNegativeInt,
119
120    /// `float`
121    TFloat,
122    /// `3.14` — a specific float literal
123    TLiteralFloat(i64, i64), // stored as (int_bits, frac_bits) to be PartialEq+Hash-friendly
124    // We use ordered_float or just store as ordered pair for equality purposes.
125    /// `bool`
126    TBool,
127    /// `true`
128    TTrue,
129    /// `false`
130    TFalse,
131
132    /// `null`
133    TNull,
134
135    // --- Bottom / top ---
136    /// `void` — return-only; can't be used as a value
137    TVoid,
138    /// `never` — function that never returns (throws or infinite loop)
139    TNever,
140    /// `mixed` — top type; accepts anything
141    TMixed,
142    /// `scalar` — int | float | string | bool
143    TScalar,
144    /// `numeric` — int | float | numeric-string
145    TNumeric,
146
147    // --- Objects ---
148    /// `object` — any object
149    TObject,
150    /// `ClassName` / `ClassName<T1, T2>` — specific named class/interface
151    TNamedObject {
152        fqcn: Name,
153        /// Resolved generic type arguments (e.g. `Collection<int>`)
154        type_params: Arc<[Type]>,
155    },
156    /// `static` — late static binding type; resolved to calling class at call site
157    TStaticObject { fqcn: Name },
158    /// `self` — the class in whose body the type appears
159    TSelf { fqcn: Name },
160    /// `parent` — the parent class
161    TParent { fqcn: Name },
162
163    // --- Callables ---
164    /// `callable` or `callable(T): R`
165    TCallable {
166        params: Option<Vec<FnParam>>,
167        return_type: Option<Box<Type>>,
168    },
169    /// `Closure` or `Closure(T): R` — more specific than TCallable
170    TClosure {
171        params: Vec<FnParam>,
172        return_type: Box<Type>,
173        this_type: Option<Box<Type>>,
174    },
175
176    // --- Arrays ---
177    /// `array` or `array<K, V>`
178    TArray { key: Box<Type>, value: Box<Type> },
179    /// `list<T>` — integer-keyed sequential array (keys 0, 1, 2, …)
180    TList { value: Box<Type> },
181    /// `non-empty-array<K, V>`
182    TNonEmptyArray { key: Box<Type>, value: Box<Type> },
183    /// `non-empty-list<T>`
184    TNonEmptyList { value: Box<Type> },
185    /// `array{key: T, ...}` — shape / keyed array
186    TKeyedArray {
187        properties: IndexMap<ArrayKey, KeyedProperty>,
188        /// If true, additional keys beyond the declared ones may exist
189        is_open: bool,
190        /// If true, the shape represents a list (integer keys only)
191        is_list: bool,
192    },
193
194    // --- Generics / meta-types ---
195    /// `T` — a template type parameter
196    TTemplateParam {
197        name: Name,
198        as_type: Box<Type>,
199        /// The entity (class or function FQN) that declared this template
200        defining_entity: Name,
201    },
202    /// `($param is TypeName ? A : B)` — conditional type
203    TConditional {
204        /// The parameter name being tested (without `$`), e.g. `"classOrInterface"`.
205        /// `None` for conditionals that were not parsed from a `$param is` form.
206        param_name: Option<Name>,
207        subject: Box<Type>,
208        if_true: Box<Type>,
209        if_false: Box<Type>,
210    },
211
212    // --- Special object strings ---
213    /// `interface-string`
214    TInterfaceString,
215    /// `enum-string`
216    TEnumString,
217    /// `trait-string`
218    TTraitString,
219
220    // --- Enum cases ---
221    /// `EnumName::CaseName` — a specific enum case literal
222    TLiteralEnumCase { enum_fqcn: Name, case_name: Name },
223
224    // --- Intersection ---
225    /// `A&B&C` — PHP 8.1+ pure intersection type
226    TIntersection { parts: Arc<[Type]> },
227}
228
229impl Atomic {
230    /// Whether this atomic type can ever evaluate to a falsy value.
231    pub fn can_be_falsy(&self) -> bool {
232        match self {
233            Atomic::TNull
234            | Atomic::TFalse
235            | Atomic::TBool
236            | Atomic::TNever
237            | Atomic::TLiteralInt(0)
238            | Atomic::TLiteralFloat(0, 0)
239            | Atomic::TInt
240            | Atomic::TFloat
241            | Atomic::TNumeric
242            | Atomic::TScalar
243            | Atomic::TMixed
244            | Atomic::TString
245            | Atomic::TNonEmptyString
246            | Atomic::TArray { .. }
247            | Atomic::TList { .. }
248            | Atomic::TNonEmptyArray { .. }
249            | Atomic::TNonEmptyList { .. } => true,
250
251            Atomic::TLiteralString(s) => s.as_ref().is_empty() || s.as_ref() == "0",
252
253            Atomic::TKeyedArray { properties, .. } => properties.is_empty(),
254
255            _ => false,
256        }
257    }
258
259    /// Whether this atomic type can ever evaluate to a truthy value.
260    pub fn can_be_truthy(&self) -> bool {
261        match self {
262            Atomic::TNever
263            | Atomic::TVoid
264            | Atomic::TNull
265            | Atomic::TFalse
266            | Atomic::TLiteralInt(0)
267            | Atomic::TLiteralFloat(0, 0) => false,
268            Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => false,
269            _ => true,
270        }
271    }
272
273    /// Whether this atomic represents a numeric type (int, float, or numeric-string).
274    pub fn is_numeric(&self) -> bool {
275        matches!(
276            self,
277            Atomic::TInt
278                | Atomic::TLiteralInt(_)
279                | Atomic::TIntRange { .. }
280                | Atomic::TPositiveInt
281                | Atomic::TNegativeInt
282                | Atomic::TNonNegativeInt
283                | Atomic::TFloat
284                | Atomic::TLiteralFloat(..)
285                | Atomic::TNumeric
286                | Atomic::TNumericString
287        )
288    }
289
290    /// Whether this atomic is an integer variant.
291    pub fn is_int(&self) -> bool {
292        matches!(
293            self,
294            Atomic::TInt
295                | Atomic::TLiteralInt(_)
296                | Atomic::TIntRange { .. }
297                | Atomic::TPositiveInt
298                | Atomic::TNegativeInt
299                | Atomic::TNonNegativeInt
300        )
301    }
302
303    /// Whether this atomic is a string variant.
304    pub fn is_string(&self) -> bool {
305        matches!(
306            self,
307            Atomic::TString
308                | Atomic::TLiteralString(_)
309                | Atomic::TCallableString
310                | Atomic::TClassString(_)
311                | Atomic::TNonEmptyString
312                | Atomic::TNumericString
313                | Atomic::TInterfaceString
314                | Atomic::TEnumString
315                | Atomic::TTraitString
316        )
317    }
318
319    /// Whether this atomic is an array variant.
320    pub fn is_array(&self) -> bool {
321        matches!(
322            self,
323            Atomic::TArray { .. }
324                | Atomic::TList { .. }
325                | Atomic::TNonEmptyArray { .. }
326                | Atomic::TNonEmptyList { .. }
327                | Atomic::TKeyedArray { .. }
328        )
329    }
330
331    /// Whether this atomic is an object variant.
332    pub fn is_object(&self) -> bool {
333        matches!(
334            self,
335            Atomic::TObject
336                | Atomic::TNamedObject { .. }
337                | Atomic::TStaticObject { .. }
338                | Atomic::TSelf { .. }
339                | Atomic::TParent { .. }
340                | Atomic::TIntersection { .. }
341        )
342    }
343
344    /// Whether this atomic is a callable variant.
345    pub fn is_callable(&self) -> bool {
346        matches!(self, Atomic::TCallable { .. } | Atomic::TClosure { .. })
347    }
348
349    /// Returns the FQCN if this is a named object type.
350    pub fn named_object_fqcn(&self) -> Option<&str> {
351        match self {
352            Atomic::TNamedObject { fqcn, .. }
353            | Atomic::TStaticObject { fqcn }
354            | Atomic::TSelf { fqcn }
355            | Atomic::TParent { fqcn } => Some(fqcn.as_ref()),
356            _ => None,
357        }
358    }
359
360    /// A human-readable name for this type (used in error messages).
361    pub fn type_name(&self) -> &'static str {
362        match self {
363            Atomic::TString
364            | Atomic::TLiteralString(_)
365            | Atomic::TNonEmptyString
366            | Atomic::TNumericString => "string",
367            Atomic::TCallableString => "callable-string",
368            Atomic::TClassString(_) => "class-string",
369            Atomic::TInt | Atomic::TLiteralInt(_) | Atomic::TIntRange { .. } => "int",
370            Atomic::TPositiveInt => "positive-int",
371            Atomic::TNegativeInt => "negative-int",
372            Atomic::TNonNegativeInt => "non-negative-int",
373            Atomic::TFloat | Atomic::TLiteralFloat(..) => "float",
374            Atomic::TBool => "bool",
375            Atomic::TTrue => "true",
376            Atomic::TFalse => "false",
377            Atomic::TNull => "null",
378            Atomic::TVoid => "void",
379            Atomic::TNever => "never",
380            Atomic::TMixed => "mixed",
381            Atomic::TScalar => "scalar",
382            Atomic::TNumeric => "numeric",
383            Atomic::TObject => "object",
384            Atomic::TNamedObject { .. } => "object",
385            Atomic::TStaticObject { .. } => "static",
386            Atomic::TSelf { .. } => "self",
387            Atomic::TParent { .. } => "parent",
388            Atomic::TCallable { .. } => "callable",
389            Atomic::TClosure { .. } => "Closure",
390            Atomic::TArray { .. } => "array",
391            Atomic::TList { .. } => "list",
392            Atomic::TNonEmptyArray { .. } => "non-empty-array",
393            Atomic::TNonEmptyList { .. } => "non-empty-list",
394            Atomic::TKeyedArray { .. } => "array",
395            Atomic::TTemplateParam { .. } => "template-param",
396            Atomic::TConditional { .. } => "conditional-type",
397            Atomic::TInterfaceString => "interface-string",
398            Atomic::TEnumString => "enum-string",
399            Atomic::TTraitString => "trait-string",
400            Atomic::TLiteralEnumCase { .. } => "enum-case",
401            Atomic::TIntersection { .. } => "intersection",
402        }
403    }
404}
405
406// ---------------------------------------------------------------------------
407// Hash impls
408// ---------------------------------------------------------------------------
409
410impl Hash for FnParam {
411    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
412        self.name.hash(state);
413        self.ty.hash(state);
414        self.default.hash(state);
415        self.is_variadic.hash(state);
416        self.is_byref.hash(state);
417        self.is_optional.hash(state);
418    }
419}
420
421/// Discriminant tags for `Atomic` used in the manual `Hash` impl below.
422#[allow(non_camel_case_types, clippy::enum_variant_names)]
423#[repr(u8)]
424enum AtomicTag {
425    TString = 0,
426    TLiteralString,
427    TCallableString,
428    TClassString,
429    TNonEmptyString,
430    TNumericString,
431    TInt,
432    TLiteralInt,
433    TIntRange,
434    TPositiveInt,
435    TNegativeInt,
436    TNonNegativeInt,
437    TFloat,
438    TLiteralFloat,
439    TBool,
440    TTrue,
441    TFalse,
442    TNull,
443    TVoid,
444    TNever,
445    TMixed,
446    TScalar,
447    TNumeric,
448    TObject,
449    TNamedObject,
450    TStaticObject,
451    TSelf,
452    TParent,
453    TCallable,
454    TClosure,
455    TArray,
456    TList,
457    TNonEmptyArray,
458    TNonEmptyList,
459    TKeyedArray,
460    TTemplateParam,
461    TConditional,
462    TInterfaceString,
463    TEnumString,
464    TTraitString,
465    TLiteralEnumCase,
466    TIntersection,
467}
468
469impl Hash for Atomic {
470    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
471        use AtomicTag as T;
472        match self {
473            // --- tag-only variants ---
474            Atomic::TString => (T::TString as u8).hash(state),
475            Atomic::TCallableString => (T::TCallableString as u8).hash(state),
476            Atomic::TNonEmptyString => (T::TNonEmptyString as u8).hash(state),
477            Atomic::TNumericString => (T::TNumericString as u8).hash(state),
478            Atomic::TInt => (T::TInt as u8).hash(state),
479            Atomic::TPositiveInt => (T::TPositiveInt as u8).hash(state),
480            Atomic::TNegativeInt => (T::TNegativeInt as u8).hash(state),
481            Atomic::TNonNegativeInt => (T::TNonNegativeInt as u8).hash(state),
482            Atomic::TFloat => (T::TFloat as u8).hash(state),
483            Atomic::TBool => (T::TBool as u8).hash(state),
484            Atomic::TTrue => (T::TTrue as u8).hash(state),
485            Atomic::TFalse => (T::TFalse as u8).hash(state),
486            Atomic::TNull => (T::TNull as u8).hash(state),
487            Atomic::TVoid => (T::TVoid as u8).hash(state),
488            Atomic::TNever => (T::TNever as u8).hash(state),
489            Atomic::TMixed => (T::TMixed as u8).hash(state),
490            Atomic::TScalar => (T::TScalar as u8).hash(state),
491            Atomic::TNumeric => (T::TNumeric as u8).hash(state),
492            Atomic::TObject => (T::TObject as u8).hash(state),
493            Atomic::TInterfaceString => (T::TInterfaceString as u8).hash(state),
494            Atomic::TEnumString => (T::TEnumString as u8).hash(state),
495            Atomic::TTraitString => (T::TTraitString as u8).hash(state),
496
497            // --- variants with fields ---
498            Atomic::TLiteralString(s) => {
499                (T::TLiteralString as u8).hash(state);
500                s.hash(state);
501            }
502            Atomic::TClassString(opt) => {
503                (T::TClassString as u8).hash(state);
504                opt.hash(state);
505            }
506            Atomic::TLiteralInt(n) => {
507                (T::TLiteralInt as u8).hash(state);
508                n.hash(state);
509            }
510            Atomic::TIntRange { min, max } => {
511                (T::TIntRange as u8).hash(state);
512                min.hash(state);
513                max.hash(state);
514            }
515            Atomic::TLiteralFloat(int_bits, frac_bits) => {
516                (T::TLiteralFloat as u8).hash(state);
517                int_bits.hash(state);
518                frac_bits.hash(state);
519            }
520            Atomic::TNamedObject { fqcn, type_params } => {
521                (T::TNamedObject as u8).hash(state);
522                fqcn.hash(state);
523                type_params.hash(state);
524            }
525            Atomic::TStaticObject { fqcn } => {
526                (T::TStaticObject as u8).hash(state);
527                fqcn.hash(state);
528            }
529            Atomic::TSelf { fqcn } => {
530                (T::TSelf as u8).hash(state);
531                fqcn.hash(state);
532            }
533            Atomic::TParent { fqcn } => {
534                (T::TParent as u8).hash(state);
535                fqcn.hash(state);
536            }
537            Atomic::TCallable {
538                params,
539                return_type,
540            } => {
541                (T::TCallable as u8).hash(state);
542                params.hash(state);
543                return_type.hash(state);
544            }
545            Atomic::TClosure {
546                params,
547                return_type,
548                this_type,
549            } => {
550                (T::TClosure as u8).hash(state);
551                params.hash(state);
552                return_type.hash(state);
553                this_type.hash(state);
554            }
555            Atomic::TArray { key, value } => {
556                (T::TArray as u8).hash(state);
557                key.hash(state);
558                value.hash(state);
559            }
560            Atomic::TList { value } => {
561                (T::TList as u8).hash(state);
562                value.hash(state);
563            }
564            Atomic::TNonEmptyArray { key, value } => {
565                (T::TNonEmptyArray as u8).hash(state);
566                key.hash(state);
567                value.hash(state);
568            }
569            Atomic::TNonEmptyList { value } => {
570                (T::TNonEmptyList as u8).hash(state);
571                value.hash(state);
572            }
573            Atomic::TKeyedArray {
574                properties,
575                is_open,
576                is_list,
577            } => {
578                (T::TKeyedArray as u8).hash(state);
579                // Sort by key before hashing so the hash is order-independent,
580                // consistent with PartialEq which uses IndexMap value equality.
581                let mut pairs: Vec<_> = properties.iter().collect();
582                pairs.sort_by_key(|(a, _)| *a);
583                for (k, v) in pairs {
584                    k.hash(state);
585                    v.hash(state);
586                }
587                is_open.hash(state);
588                is_list.hash(state);
589            }
590            Atomic::TTemplateParam {
591                name,
592                as_type,
593                defining_entity,
594            } => {
595                (T::TTemplateParam as u8).hash(state);
596                name.hash(state);
597                as_type.hash(state);
598                defining_entity.hash(state);
599            }
600            Atomic::TConditional {
601                param_name,
602                subject,
603                if_true,
604                if_false,
605            } => {
606                (T::TConditional as u8).hash(state);
607                param_name.hash(state);
608                subject.hash(state);
609                if_true.hash(state);
610                if_false.hash(state);
611            }
612            Atomic::TLiteralEnumCase {
613                enum_fqcn,
614                case_name,
615            } => {
616                (T::TLiteralEnumCase as u8).hash(state);
617                enum_fqcn.hash(state);
618                case_name.hash(state);
619            }
620            Atomic::TIntersection { parts } => {
621                (T::TIntersection as u8).hash(state);
622                parts.hash(state);
623            }
624        }
625    }
626}