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 can never be an object instance — so `clone`-ing it
345    /// is invalid. Conservative: ambiguous atomics (`mixed`, `callable`,
346    /// `never`, conditionals, template params, enum cases, intersections,
347    /// object types) all return `false` so they never trigger a false positive.
348    pub fn is_definitely_non_object(&self) -> bool {
349        matches!(
350            self,
351            Atomic::TString
352                | Atomic::TLiteralString(_)
353                | Atomic::TCallableString
354                | Atomic::TClassString(_)
355                | Atomic::TNonEmptyString
356                | Atomic::TNumericString
357                | Atomic::TInterfaceString
358                | Atomic::TEnumString
359                | Atomic::TTraitString
360                | Atomic::TInt
361                | Atomic::TLiteralInt(_)
362                | Atomic::TIntRange { .. }
363                | Atomic::TPositiveInt
364                | Atomic::TNegativeInt
365                | Atomic::TNonNegativeInt
366                | Atomic::TFloat
367                | Atomic::TLiteralFloat(..)
368                | Atomic::TBool
369                | Atomic::TTrue
370                | Atomic::TFalse
371                | Atomic::TNull
372                | Atomic::TVoid
373                | Atomic::TArray { .. }
374                | Atomic::TList { .. }
375                | Atomic::TNonEmptyArray { .. }
376                | Atomic::TNonEmptyList { .. }
377                | Atomic::TKeyedArray { .. }
378                | Atomic::TScalar
379                | Atomic::TNumeric
380        )
381    }
382
383    /// Whether this atomic is a callable variant.
384    pub fn is_callable(&self) -> bool {
385        matches!(self, Atomic::TCallable { .. } | Atomic::TClosure { .. })
386    }
387
388    /// Returns the FQCN if this is a named object type.
389    pub fn named_object_fqcn(&self) -> Option<&str> {
390        match self {
391            Atomic::TNamedObject { fqcn, .. }
392            | Atomic::TStaticObject { fqcn }
393            | Atomic::TSelf { fqcn }
394            | Atomic::TParent { fqcn } => Some(fqcn.as_ref()),
395            _ => None,
396        }
397    }
398
399    /// A human-readable name for this type (used in error messages).
400    pub fn type_name(&self) -> &'static str {
401        match self {
402            Atomic::TString
403            | Atomic::TLiteralString(_)
404            | Atomic::TNonEmptyString
405            | Atomic::TNumericString => "string",
406            Atomic::TCallableString => "callable-string",
407            Atomic::TClassString(_) => "class-string",
408            Atomic::TInt | Atomic::TLiteralInt(_) | Atomic::TIntRange { .. } => "int",
409            Atomic::TPositiveInt => "positive-int",
410            Atomic::TNegativeInt => "negative-int",
411            Atomic::TNonNegativeInt => "non-negative-int",
412            Atomic::TFloat | Atomic::TLiteralFloat(..) => "float",
413            Atomic::TBool => "bool",
414            Atomic::TTrue => "true",
415            Atomic::TFalse => "false",
416            Atomic::TNull => "null",
417            Atomic::TVoid => "void",
418            Atomic::TNever => "never",
419            Atomic::TMixed => "mixed",
420            Atomic::TScalar => "scalar",
421            Atomic::TNumeric => "numeric",
422            Atomic::TObject => "object",
423            Atomic::TNamedObject { .. } => "object",
424            Atomic::TStaticObject { .. } => "static",
425            Atomic::TSelf { .. } => "self",
426            Atomic::TParent { .. } => "parent",
427            Atomic::TCallable { .. } => "callable",
428            Atomic::TClosure { .. } => "Closure",
429            Atomic::TArray { .. } => "array",
430            Atomic::TList { .. } => "list",
431            Atomic::TNonEmptyArray { .. } => "non-empty-array",
432            Atomic::TNonEmptyList { .. } => "non-empty-list",
433            Atomic::TKeyedArray { .. } => "array",
434            Atomic::TTemplateParam { .. } => "template-param",
435            Atomic::TConditional { .. } => "conditional-type",
436            Atomic::TInterfaceString => "interface-string",
437            Atomic::TEnumString => "enum-string",
438            Atomic::TTraitString => "trait-string",
439            Atomic::TLiteralEnumCase { .. } => "enum-case",
440            Atomic::TIntersection { .. } => "intersection",
441        }
442    }
443}
444
445// ---------------------------------------------------------------------------
446// Hash impls
447// ---------------------------------------------------------------------------
448
449impl Hash for FnParam {
450    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
451        self.name.hash(state);
452        self.ty.hash(state);
453        self.default.hash(state);
454        self.is_variadic.hash(state);
455        self.is_byref.hash(state);
456        self.is_optional.hash(state);
457    }
458}
459
460/// Discriminant tags for `Atomic` used in the manual `Hash` impl below.
461#[allow(non_camel_case_types, clippy::enum_variant_names)]
462#[repr(u8)]
463enum AtomicTag {
464    TString = 0,
465    TLiteralString,
466    TCallableString,
467    TClassString,
468    TNonEmptyString,
469    TNumericString,
470    TInt,
471    TLiteralInt,
472    TIntRange,
473    TPositiveInt,
474    TNegativeInt,
475    TNonNegativeInt,
476    TFloat,
477    TLiteralFloat,
478    TBool,
479    TTrue,
480    TFalse,
481    TNull,
482    TVoid,
483    TNever,
484    TMixed,
485    TScalar,
486    TNumeric,
487    TObject,
488    TNamedObject,
489    TStaticObject,
490    TSelf,
491    TParent,
492    TCallable,
493    TClosure,
494    TArray,
495    TList,
496    TNonEmptyArray,
497    TNonEmptyList,
498    TKeyedArray,
499    TTemplateParam,
500    TConditional,
501    TInterfaceString,
502    TEnumString,
503    TTraitString,
504    TLiteralEnumCase,
505    TIntersection,
506}
507
508impl Hash for Atomic {
509    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
510        use AtomicTag as T;
511        match self {
512            // --- tag-only variants ---
513            Atomic::TString => (T::TString as u8).hash(state),
514            Atomic::TCallableString => (T::TCallableString as u8).hash(state),
515            Atomic::TNonEmptyString => (T::TNonEmptyString as u8).hash(state),
516            Atomic::TNumericString => (T::TNumericString as u8).hash(state),
517            Atomic::TInt => (T::TInt as u8).hash(state),
518            Atomic::TPositiveInt => (T::TPositiveInt as u8).hash(state),
519            Atomic::TNegativeInt => (T::TNegativeInt as u8).hash(state),
520            Atomic::TNonNegativeInt => (T::TNonNegativeInt as u8).hash(state),
521            Atomic::TFloat => (T::TFloat as u8).hash(state),
522            Atomic::TBool => (T::TBool as u8).hash(state),
523            Atomic::TTrue => (T::TTrue as u8).hash(state),
524            Atomic::TFalse => (T::TFalse as u8).hash(state),
525            Atomic::TNull => (T::TNull as u8).hash(state),
526            Atomic::TVoid => (T::TVoid as u8).hash(state),
527            Atomic::TNever => (T::TNever as u8).hash(state),
528            Atomic::TMixed => (T::TMixed as u8).hash(state),
529            Atomic::TScalar => (T::TScalar as u8).hash(state),
530            Atomic::TNumeric => (T::TNumeric as u8).hash(state),
531            Atomic::TObject => (T::TObject as u8).hash(state),
532            Atomic::TInterfaceString => (T::TInterfaceString as u8).hash(state),
533            Atomic::TEnumString => (T::TEnumString as u8).hash(state),
534            Atomic::TTraitString => (T::TTraitString as u8).hash(state),
535
536            // --- variants with fields ---
537            Atomic::TLiteralString(s) => {
538                (T::TLiteralString as u8).hash(state);
539                s.hash(state);
540            }
541            Atomic::TClassString(opt) => {
542                (T::TClassString as u8).hash(state);
543                opt.hash(state);
544            }
545            Atomic::TLiteralInt(n) => {
546                (T::TLiteralInt as u8).hash(state);
547                n.hash(state);
548            }
549            Atomic::TIntRange { min, max } => {
550                (T::TIntRange as u8).hash(state);
551                min.hash(state);
552                max.hash(state);
553            }
554            Atomic::TLiteralFloat(int_bits, frac_bits) => {
555                (T::TLiteralFloat as u8).hash(state);
556                int_bits.hash(state);
557                frac_bits.hash(state);
558            }
559            Atomic::TNamedObject { fqcn, type_params } => {
560                (T::TNamedObject as u8).hash(state);
561                fqcn.hash(state);
562                type_params.hash(state);
563            }
564            Atomic::TStaticObject { fqcn } => {
565                (T::TStaticObject as u8).hash(state);
566                fqcn.hash(state);
567            }
568            Atomic::TSelf { fqcn } => {
569                (T::TSelf as u8).hash(state);
570                fqcn.hash(state);
571            }
572            Atomic::TParent { fqcn } => {
573                (T::TParent as u8).hash(state);
574                fqcn.hash(state);
575            }
576            Atomic::TCallable {
577                params,
578                return_type,
579            } => {
580                (T::TCallable as u8).hash(state);
581                params.hash(state);
582                return_type.hash(state);
583            }
584            Atomic::TClosure {
585                params,
586                return_type,
587                this_type,
588            } => {
589                (T::TClosure as u8).hash(state);
590                params.hash(state);
591                return_type.hash(state);
592                this_type.hash(state);
593            }
594            Atomic::TArray { key, value } => {
595                (T::TArray as u8).hash(state);
596                key.hash(state);
597                value.hash(state);
598            }
599            Atomic::TList { value } => {
600                (T::TList as u8).hash(state);
601                value.hash(state);
602            }
603            Atomic::TNonEmptyArray { key, value } => {
604                (T::TNonEmptyArray as u8).hash(state);
605                key.hash(state);
606                value.hash(state);
607            }
608            Atomic::TNonEmptyList { value } => {
609                (T::TNonEmptyList as u8).hash(state);
610                value.hash(state);
611            }
612            Atomic::TKeyedArray {
613                properties,
614                is_open,
615                is_list,
616            } => {
617                (T::TKeyedArray as u8).hash(state);
618                // Sort by key before hashing so the hash is order-independent,
619                // consistent with PartialEq which uses IndexMap value equality.
620                let mut pairs: Vec<_> = properties.iter().collect();
621                pairs.sort_by_key(|(a, _)| *a);
622                for (k, v) in pairs {
623                    k.hash(state);
624                    v.hash(state);
625                }
626                is_open.hash(state);
627                is_list.hash(state);
628            }
629            Atomic::TTemplateParam {
630                name,
631                as_type,
632                defining_entity,
633            } => {
634                (T::TTemplateParam as u8).hash(state);
635                name.hash(state);
636                as_type.hash(state);
637                defining_entity.hash(state);
638            }
639            Atomic::TConditional {
640                param_name,
641                subject,
642                if_true,
643                if_false,
644            } => {
645                (T::TConditional as u8).hash(state);
646                param_name.hash(state);
647                subject.hash(state);
648                if_true.hash(state);
649                if_false.hash(state);
650            }
651            Atomic::TLiteralEnumCase {
652                enum_fqcn,
653                case_name,
654            } => {
655                (T::TLiteralEnumCase as u8).hash(state);
656                enum_fqcn.hash(state);
657                case_name.hash(state);
658            }
659            Atomic::TIntersection { parts } => {
660                (T::TIntersection as u8).hash(state);
661                parts.hash(state);
662            }
663        }
664    }
665}