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