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