Skip to main content

mir_types/
atomic.rs

1use std::sync::Arc;
2
3use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5
6use crate::Union;
7
8// ---------------------------------------------------------------------------
9// FnParam — used inside callable/closure atomics
10// ---------------------------------------------------------------------------
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct FnParam {
14    pub name: Arc<str>,
15    pub ty: Option<Union>,
16    pub default: Option<Union>,
17    pub is_variadic: bool,
18    pub is_byref: bool,
19    pub is_optional: bool,
20}
21
22// ---------------------------------------------------------------------------
23// Variance — covariance / contravariance for template type parameters
24// ---------------------------------------------------------------------------
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
27pub enum Variance {
28    /// Default: exact type match required (no `@template-covariant` / `@template-contravariant`).
29    #[default]
30    Invariant,
31    /// `@template-covariant T` — `C<Sub>` is assignable to `C<Super>`.
32    Covariant,
33    /// `@template-contravariant T` — `C<Super>` is assignable to `C<Sub>`.
34    Contravariant,
35}
36
37// ---------------------------------------------------------------------------
38// TemplateParam — `@template T` / `@template T of Bound`
39// ---------------------------------------------------------------------------
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub struct TemplateParam {
43    pub name: Arc<str>,
44    pub bound: Option<Union>,
45    pub variance: Variance,
46}
47
48// ---------------------------------------------------------------------------
49// KeyedProperty — entry in TKeyedArray
50// ---------------------------------------------------------------------------
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub enum ArrayKey {
54    String(Arc<str>),
55    Int(i64),
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59pub struct KeyedProperty {
60    pub ty: Union,
61    pub optional: bool,
62}
63
64// ---------------------------------------------------------------------------
65// Atomic — every distinct PHP type variant
66// ---------------------------------------------------------------------------
67
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub enum Atomic {
70    // --- Scalars ---
71    /// `string`
72    TString,
73    /// `"hello"` — a specific string literal
74    TLiteralString(Arc<str>),
75    /// `class-string` or `class-string<T>`
76    TClassString(Option<Arc<str>>),
77    /// `non-empty-string`
78    TNonEmptyString,
79    /// `numeric-string`
80    TNumericString,
81
82    /// `int`
83    TInt,
84    /// `42` — a specific integer literal
85    TLiteralInt(i64),
86    /// `int<min, max>` — bounded integer range
87    TIntRange { min: Option<i64>, max: Option<i64> },
88    /// `positive-int`
89    TPositiveInt,
90    /// `negative-int`
91    TNegativeInt,
92    /// `non-negative-int`
93    TNonNegativeInt,
94
95    /// `float`
96    TFloat,
97    /// `3.14` — a specific float literal
98    TLiteralFloat(i64, i64), // stored as (int_bits, frac_bits) to be PartialEq+Hash-friendly
99    // We use ordered_float or just store as ordered pair for equality purposes.
100    /// `bool`
101    TBool,
102    /// `true`
103    TTrue,
104    /// `false`
105    TFalse,
106
107    /// `null`
108    TNull,
109
110    // --- Bottom / top ---
111    /// `void` — return-only; can't be used as a value
112    TVoid,
113    /// `never` — function that never returns (throws or infinite loop)
114    TNever,
115    /// `mixed` — top type; accepts anything
116    TMixed,
117    /// `scalar` — int | float | string | bool
118    TScalar,
119    /// `numeric` — int | float | numeric-string
120    TNumeric,
121
122    // --- Objects ---
123    /// `object` — any object
124    TObject,
125    /// `ClassName` / `ClassName<T1, T2>` — specific named class/interface
126    TNamedObject {
127        fqcn: Arc<str>,
128        /// Resolved generic type arguments (e.g. `Collection<int>`)
129        type_params: Vec<Union>,
130    },
131    /// `static` — late static binding type; resolved to calling class at call site
132    TStaticObject { fqcn: Arc<str> },
133    /// `self` — the class in whose body the type appears
134    TSelf { fqcn: Arc<str> },
135    /// `parent` — the parent class
136    TParent { fqcn: Arc<str> },
137
138    // --- Callables ---
139    /// `callable` or `callable(T): R`
140    TCallable {
141        params: Option<Vec<FnParam>>,
142        return_type: Option<Box<Union>>,
143    },
144    /// `Closure` or `Closure(T): R` — more specific than TCallable
145    TClosure {
146        params: Vec<FnParam>,
147        return_type: Box<Union>,
148        this_type: Option<Box<Union>>,
149    },
150
151    // --- Arrays ---
152    /// `array` or `array<K, V>`
153    TArray { key: Box<Union>, value: Box<Union> },
154    /// `list<T>` — integer-keyed sequential array (keys 0, 1, 2, …)
155    TList { value: Box<Union> },
156    /// `non-empty-array<K, V>`
157    TNonEmptyArray { key: Box<Union>, value: Box<Union> },
158    /// `non-empty-list<T>`
159    TNonEmptyList { value: Box<Union> },
160    /// `array{key: T, ...}` — shape / keyed array
161    TKeyedArray {
162        properties: IndexMap<ArrayKey, KeyedProperty>,
163        /// If true, additional keys beyond the declared ones may exist
164        is_open: bool,
165        /// If true, the shape represents a list (integer keys only)
166        is_list: bool,
167    },
168
169    // --- Generics / meta-types ---
170    /// `T` — a template type parameter
171    TTemplateParam {
172        name: Arc<str>,
173        as_type: Box<Union>,
174        /// The entity (class or function FQN) that declared this template
175        defining_entity: Arc<str>,
176    },
177    /// `(T is string ? A : B)` — conditional type
178    TConditional {
179        subject: Box<Union>,
180        if_true: Box<Union>,
181        if_false: Box<Union>,
182    },
183
184    // --- Special object strings ---
185    /// `interface-string`
186    TInterfaceString,
187    /// `enum-string`
188    TEnumString,
189    /// `trait-string`
190    TTraitString,
191}
192
193impl Atomic {
194    /// Whether this atomic type can ever evaluate to a falsy value.
195    pub fn can_be_falsy(&self) -> bool {
196        match self {
197            Atomic::TNull
198            | Atomic::TFalse
199            | Atomic::TBool
200            | Atomic::TNever
201            | Atomic::TLiteralInt(0)
202            | Atomic::TLiteralFloat(0, 0)
203            | Atomic::TInt
204            | Atomic::TFloat
205            | Atomic::TNumeric
206            | Atomic::TScalar
207            | Atomic::TMixed
208            | Atomic::TString
209            | Atomic::TNonEmptyString
210            | Atomic::TArray { .. }
211            | Atomic::TList { .. }
212            | Atomic::TNonEmptyArray { .. }
213            | Atomic::TNonEmptyList { .. } => true,
214
215            Atomic::TLiteralString(s) => s.as_ref().is_empty() || s.as_ref() == "0",
216
217            Atomic::TKeyedArray { properties, .. } => properties.is_empty(),
218
219            _ => false,
220        }
221    }
222
223    /// Whether this atomic type can ever evaluate to a truthy value.
224    pub fn can_be_truthy(&self) -> bool {
225        match self {
226            Atomic::TNever | Atomic::TVoid => false,
227            Atomic::TNull | Atomic::TFalse => false,
228            Atomic::TLiteralInt(0) => false,
229            Atomic::TLiteralFloat(0, 0) => false,
230            Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => false,
231            _ => true,
232        }
233    }
234
235    /// Whether this atomic represents a numeric type (int, float, or numeric-string).
236    pub fn is_numeric(&self) -> bool {
237        matches!(
238            self,
239            Atomic::TInt
240                | Atomic::TLiteralInt(_)
241                | Atomic::TIntRange { .. }
242                | Atomic::TPositiveInt
243                | Atomic::TNegativeInt
244                | Atomic::TNonNegativeInt
245                | Atomic::TFloat
246                | Atomic::TLiteralFloat(..)
247                | Atomic::TNumeric
248                | Atomic::TNumericString
249        )
250    }
251
252    /// Whether this atomic is an integer variant.
253    pub fn is_int(&self) -> bool {
254        matches!(
255            self,
256            Atomic::TInt
257                | Atomic::TLiteralInt(_)
258                | Atomic::TIntRange { .. }
259                | Atomic::TPositiveInt
260                | Atomic::TNegativeInt
261                | Atomic::TNonNegativeInt
262        )
263    }
264
265    /// Whether this atomic is a string variant.
266    pub fn is_string(&self) -> bool {
267        matches!(
268            self,
269            Atomic::TString
270                | Atomic::TLiteralString(_)
271                | Atomic::TClassString(_)
272                | Atomic::TNonEmptyString
273                | Atomic::TNumericString
274                | Atomic::TInterfaceString
275                | Atomic::TEnumString
276                | Atomic::TTraitString
277        )
278    }
279
280    /// Whether this atomic is an array variant.
281    pub fn is_array(&self) -> bool {
282        matches!(
283            self,
284            Atomic::TArray { .. }
285                | Atomic::TList { .. }
286                | Atomic::TNonEmptyArray { .. }
287                | Atomic::TNonEmptyList { .. }
288                | Atomic::TKeyedArray { .. }
289        )
290    }
291
292    /// Whether this atomic is an object variant.
293    pub fn is_object(&self) -> bool {
294        matches!(
295            self,
296            Atomic::TObject
297                | Atomic::TNamedObject { .. }
298                | Atomic::TStaticObject { .. }
299                | Atomic::TSelf { .. }
300                | Atomic::TParent { .. }
301        )
302    }
303
304    /// Whether this atomic is a callable variant.
305    pub fn is_callable(&self) -> bool {
306        matches!(self, Atomic::TCallable { .. } | Atomic::TClosure { .. })
307    }
308
309    /// Returns the FQCN if this is a named object type.
310    pub fn named_object_fqcn(&self) -> Option<&str> {
311        match self {
312            Atomic::TNamedObject { fqcn, .. }
313            | Atomic::TStaticObject { fqcn }
314            | Atomic::TSelf { fqcn }
315            | Atomic::TParent { fqcn } => Some(fqcn.as_ref()),
316            _ => None,
317        }
318    }
319
320    /// A human-readable name for this type (used in error messages).
321    pub fn type_name(&self) -> &'static str {
322        match self {
323            Atomic::TString
324            | Atomic::TLiteralString(_)
325            | Atomic::TNonEmptyString
326            | Atomic::TNumericString => "string",
327            Atomic::TClassString(_) => "class-string",
328            Atomic::TInt | Atomic::TLiteralInt(_) | Atomic::TIntRange { .. } => "int",
329            Atomic::TPositiveInt => "positive-int",
330            Atomic::TNegativeInt => "negative-int",
331            Atomic::TNonNegativeInt => "non-negative-int",
332            Atomic::TFloat | Atomic::TLiteralFloat(..) => "float",
333            Atomic::TBool => "bool",
334            Atomic::TTrue => "true",
335            Atomic::TFalse => "false",
336            Atomic::TNull => "null",
337            Atomic::TVoid => "void",
338            Atomic::TNever => "never",
339            Atomic::TMixed => "mixed",
340            Atomic::TScalar => "scalar",
341            Atomic::TNumeric => "numeric",
342            Atomic::TObject => "object",
343            Atomic::TNamedObject { .. } => "object",
344            Atomic::TStaticObject { .. } => "static",
345            Atomic::TSelf { .. } => "self",
346            Atomic::TParent { .. } => "parent",
347            Atomic::TCallable { .. } => "callable",
348            Atomic::TClosure { .. } => "Closure",
349            Atomic::TArray { .. } => "array",
350            Atomic::TList { .. } => "list",
351            Atomic::TNonEmptyArray { .. } => "non-empty-array",
352            Atomic::TNonEmptyList { .. } => "non-empty-list",
353            Atomic::TKeyedArray { .. } => "array",
354            Atomic::TTemplateParam { .. } => "template-param",
355            Atomic::TConditional { .. } => "conditional-type",
356            Atomic::TInterfaceString => "interface-string",
357            Atomic::TEnumString => "enum-string",
358            Atomic::TTraitString => "trait-string",
359        }
360    }
361}