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