mago_codex/ttype/
mod.rs

1use std::borrow::Cow;
2
3use mago_atom::Atom;
4use mago_atom::atom;
5
6use crate::metadata::CodebaseMetadata;
7use crate::metadata::class_like::ClassLikeMetadata;
8use crate::misc::GenericParent;
9use crate::ttype::atomic::TAtomic;
10use crate::ttype::atomic::array::TArray;
11use crate::ttype::atomic::array::keyed::TKeyedArray;
12use crate::ttype::atomic::array::list::TList;
13use crate::ttype::atomic::generic::TGenericParameter;
14use crate::ttype::atomic::iterable::TIterable;
15use crate::ttype::atomic::object::TObject;
16use crate::ttype::atomic::object::named::TNamedObject;
17use crate::ttype::atomic::scalar::TScalar;
18use crate::ttype::atomic::scalar::class_like_string::TClassLikeString;
19use crate::ttype::atomic::scalar::class_like_string::TClassLikeStringKind;
20use crate::ttype::atomic::scalar::int::TInteger;
21use crate::ttype::atomic::scalar::string::TString;
22use crate::ttype::atomic::scalar::string::TStringLiteral;
23use crate::ttype::comparator::ComparisonResult;
24use crate::ttype::comparator::union_comparator;
25use crate::ttype::expander::TypeExpansionOptions;
26use crate::ttype::resolution::TypeResolutionContext;
27use crate::ttype::shared::*;
28use crate::ttype::template::TemplateResult;
29use crate::ttype::template::inferred_type_replacer;
30use crate::ttype::union::TUnion;
31
32pub mod atomic;
33pub mod builder;
34pub mod cast;
35pub mod combination;
36pub mod combiner;
37pub mod comparator;
38pub mod error;
39pub mod expander;
40pub mod resolution;
41pub mod shared;
42pub mod template;
43pub mod union;
44
45/// A reference to a type in the type system, which can be either a union or an atomic type.
46#[derive(Clone, Copy, Debug)]
47pub enum TypeRef<'a> {
48    Union(&'a TUnion),
49    Atomic(&'a TAtomic),
50}
51
52/// A trait to be implemented by all types in the type system.
53pub trait TType {
54    /// Returns a vector of child type nodes that this type contains.
55    fn get_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
56        vec![]
57    }
58
59    /// Returns a vector of all child type nodes, including nested ones.
60    fn get_all_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
61        let mut child_nodes = self.get_child_nodes();
62        let mut all_child_nodes = vec![];
63
64        while let Some(child_node) = child_nodes.pop() {
65            let new_child_nodes = match child_node {
66                TypeRef::Union(union) => union.get_child_nodes(),
67                TypeRef::Atomic(atomic) => atomic.get_child_nodes(),
68            };
69
70            all_child_nodes.push(child_node);
71
72            child_nodes.extend(new_child_nodes);
73        }
74
75        all_child_nodes
76    }
77
78    /// Checks if this type can have intersection types (`&B&S`).
79    fn can_be_intersected(&self) -> bool {
80        false
81    }
82
83    /// Returns a slice of the additional intersection types (`&B&S`), if any. Contains boxed atomic types.
84    fn get_intersection_types(&self) -> Option<&[TAtomic]> {
85        None
86    }
87
88    /// Returns a mutable slice of the additional intersection types (`&B&S`), if any. Contains boxed atomic types.
89    fn get_intersection_types_mut(&mut self) -> Option<&mut Vec<TAtomic>> {
90        None
91    }
92
93    /// Checks if this type has intersection types.
94    fn has_intersection_types(&self) -> bool {
95        false
96    }
97
98    /// Adds an intersection type to this type.
99    ///
100    /// Returns `true` if the intersection type was added successfully,
101    ///  or `false` if this type does not support intersection types.
102    fn add_intersection_type(&mut self, _intersection_type: TAtomic) -> bool {
103        false
104    }
105
106    fn needs_population(&self) -> bool;
107
108    fn is_expandable(&self) -> bool;
109
110    /// Returns true if this type has complex structure that benefits from
111    /// multiline formatting when used as a generic parameter.
112    fn is_complex(&self) -> bool;
113
114    /// Return a human-readable atom for this type, which is
115    /// suitable for use in error messages or debugging.
116    ///
117    /// The resulting identifier must be unique for the type,
118    /// but it does not have to be globally unique.
119    fn get_id(&self) -> Atom;
120
121    fn get_pretty_id(&self) -> Atom {
122        self.get_pretty_id_with_indent(0)
123    }
124
125    fn get_pretty_id_with_indent(&self, indent: usize) -> Atom;
126}
127
128/// Implements the `TType` trait for `TypeRef`.
129impl<'a> TType for TypeRef<'a> {
130    fn get_child_nodes(&self) -> Vec<TypeRef<'a>> {
131        match self {
132            TypeRef::Union(ttype) => ttype.get_child_nodes(),
133            TypeRef::Atomic(ttype) => ttype.get_child_nodes(),
134        }
135    }
136
137    fn can_be_intersected(&self) -> bool {
138        match self {
139            TypeRef::Union(ttype) => ttype.can_be_intersected(),
140            TypeRef::Atomic(ttype) => ttype.can_be_intersected(),
141        }
142    }
143
144    fn get_intersection_types(&self) -> Option<&[TAtomic]> {
145        match self {
146            TypeRef::Union(ttype) => ttype.get_intersection_types(),
147            TypeRef::Atomic(ttype) => ttype.get_intersection_types(),
148        }
149    }
150
151    fn has_intersection_types(&self) -> bool {
152        match self {
153            TypeRef::Union(ttype) => ttype.has_intersection_types(),
154            TypeRef::Atomic(ttype) => ttype.has_intersection_types(),
155        }
156    }
157
158    fn needs_population(&self) -> bool {
159        match self {
160            TypeRef::Union(ttype) => ttype.needs_population(),
161            TypeRef::Atomic(ttype) => ttype.needs_population(),
162        }
163    }
164
165    fn is_expandable(&self) -> bool {
166        match self {
167            TypeRef::Union(ttype) => ttype.is_expandable(),
168            TypeRef::Atomic(ttype) => ttype.is_expandable(),
169        }
170    }
171
172    fn is_complex(&self) -> bool {
173        match self {
174            TypeRef::Union(ttype) => ttype.is_complex(),
175            TypeRef::Atomic(ttype) => ttype.is_complex(),
176        }
177    }
178
179    fn get_id(&self) -> Atom {
180        match self {
181            TypeRef::Union(ttype) => ttype.get_id(),
182            TypeRef::Atomic(ttype) => ttype.get_id(),
183        }
184    }
185
186    fn get_pretty_id_with_indent(&self, indent: usize) -> Atom {
187        match self {
188            TypeRef::Union(ttype) => ttype.get_pretty_id_with_indent(indent),
189            TypeRef::Atomic(ttype) => ttype.get_pretty_id_with_indent(indent),
190        }
191    }
192}
193
194impl<'a> From<&'a TUnion> for TypeRef<'a> {
195    fn from(reference: &'a TUnion) -> Self {
196        TypeRef::Union(reference)
197    }
198}
199
200impl<'a> From<&'a TAtomic> for TypeRef<'a> {
201    fn from(reference: &'a TAtomic) -> Self {
202        TypeRef::Atomic(reference)
203    }
204}
205
206/// Creates a `TUnion` from a `TInteger`, using a canonical static type where possible.
207///
208/// This function is a key optimization point. It checks if the provided `TInteger`
209/// matches a common, reusable form (like "any integer" or "a positive integer").
210/// If it does, it returns a zero-allocation `TUnion` that borrows a static,
211/// shared instance.
212///
213/// For specific literal values or ranges that do not have a canonical static
214/// representation, it falls back to creating a new, owned `TUnion`, which
215/// involves a heap allocation.
216pub fn get_union_from_integer(integer: &TInteger) -> TUnion {
217    if integer.is_unspecified() {
218        return get_int();
219    }
220
221    if integer.is_positive() {
222        return get_positive_int();
223    }
224
225    if integer.is_negative() {
226        return get_negative_int();
227    }
228
229    if integer.is_non_negative() {
230        return get_non_negative_int();
231    }
232
233    if integer.is_non_positive() {
234        return get_non_positive_int();
235    }
236
237    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::Integer(*integer))))
238}
239
240#[inline]
241pub fn wrap_atomic(tinner: TAtomic) -> TUnion {
242    TUnion::from_single(Cow::Owned(tinner))
243}
244
245#[inline]
246pub fn get_int() -> TUnion {
247    TUnion::from_single(Cow::Borrowed(INT_ATOMIC))
248}
249
250#[inline]
251pub fn get_positive_int() -> TUnion {
252    TUnion::from_single(Cow::Borrowed(POSITIVE_INT_ATOMIC))
253}
254
255#[inline]
256pub fn get_negative_int() -> TUnion {
257    TUnion::from_single(Cow::Borrowed(NEGATIVE_INT_ATOMIC))
258}
259
260#[inline]
261pub fn get_non_positive_int() -> TUnion {
262    TUnion::from_single(Cow::Borrowed(NON_POSITIVE_INT_ATOMIC))
263}
264
265#[inline]
266pub fn get_non_negative_int() -> TUnion {
267    TUnion::from_single(Cow::Borrowed(NON_NEGATIVE_INT_ATOMIC))
268}
269
270#[inline]
271pub fn get_unspecified_literal_int() -> TUnion {
272    TUnion::from_single(Cow::Borrowed(UNSPECIFIED_LITERAL_INT_ATOMIC))
273}
274
275#[inline]
276pub fn get_unspecified_literal_float() -> TUnion {
277    TUnion::from_single(Cow::Borrowed(UNSPECIFIED_LITERAL_FLOAT_ATOMIC))
278}
279
280#[inline]
281pub fn get_int_range(from: Option<i64>, to: Option<i64>) -> TUnion {
282    let atomic = match (from, to) {
283        (Some(from), Some(to)) => TAtomic::Scalar(TScalar::Integer(TInteger::Range(from, to))),
284        (Some(from), None) => {
285            if 0 == from {
286                return get_non_negative_int();
287            }
288
289            if 1 == from {
290                return get_positive_int();
291            }
292
293            TAtomic::Scalar(TScalar::Integer(TInteger::From(from)))
294        }
295        (None, Some(to)) => {
296            if 0 == to {
297                return get_non_positive_int();
298            }
299
300            if -1 == to {
301                return get_negative_int();
302            }
303
304            TAtomic::Scalar(TScalar::Integer(TInteger::To(to)))
305        }
306        (None, None) => return get_int(),
307    };
308
309    TUnion::from_single(Cow::Owned(atomic))
310}
311
312/// Returns a zero-allocation `TUnion` for the type `-1|0|1`.
313#[inline]
314pub fn get_signum_result() -> TUnion {
315    TUnion::new(Cow::Borrowed(SIGNUM_RESULT_SLICE))
316}
317
318/// Returns a zero-allocation `TUnion` for the integer literal `1`.
319#[inline]
320pub fn get_one_int() -> TUnion {
321    TUnion::from_single(Cow::Borrowed(ONE_INT_ATOMIC))
322}
323
324/// Returns a zero-allocation `TUnion` for the integer literal `0`.
325#[inline]
326pub fn get_zero_int() -> TUnion {
327    TUnion::from_single(Cow::Borrowed(ZERO_INT_ATOMIC))
328}
329
330/// Returns a zero-allocation `TUnion` for the integer literal `-1`.
331#[inline]
332pub fn get_minus_one_int() -> TUnion {
333    TUnion::from_single(Cow::Borrowed(MINUS_ONE_INT_ATOMIC))
334}
335
336#[inline]
337pub fn get_literal_int(value: i64) -> TUnion {
338    if value == 0 {
339        return get_zero_int();
340    }
341
342    if value == 1 {
343        return get_one_int();
344    }
345
346    if value == -1 {
347        return get_minus_one_int();
348    }
349
350    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_int(value))))
351}
352
353#[inline]
354pub fn get_int_or_float() -> TUnion {
355    TUnion::new(Cow::Borrowed(INT_FLOAT_ATOMIC_SLICE))
356}
357
358#[inline]
359pub fn get_int_or_string() -> TUnion {
360    TUnion::new(Cow::Borrowed(INT_STRING_ATOMIC_SLICE))
361}
362
363#[inline]
364pub fn get_nullable_int() -> TUnion {
365    TUnion::new(Cow::Borrowed(NULL_INT_ATOMIC_SLICE))
366}
367
368#[inline]
369pub fn get_nullable_float() -> TUnion {
370    TUnion::new(Cow::Borrowed(NULL_FLOAT_ATOMIC_SLICE))
371}
372
373#[inline]
374pub fn get_nullable_object() -> TUnion {
375    TUnion::new(Cow::Borrowed(NULL_OBJECT_ATOMIC_SLICE))
376}
377
378#[inline]
379pub fn get_nullable_string() -> TUnion {
380    TUnion::new(Cow::Borrowed(NULL_STRING_ATOMIC_SLICE))
381}
382
383#[inline]
384pub fn get_string() -> TUnion {
385    TUnion::from_single(Cow::Borrowed(STRING_ATOMIC))
386}
387
388/// Returns a zero-allocation `TUnion` for a `string` with the specified properties.
389///
390/// This function maps all possible boolean property combinations to a canonical,
391/// static `TAtomic` instance, avoiding heap allocations for common string types.
392pub fn get_string_with_props(is_numeric: bool, is_truthy: bool, is_non_empty: bool, is_lowercase: bool) -> TUnion {
393    let atomic_ref = match (is_numeric, is_truthy, is_non_empty, is_lowercase) {
394        // is_numeric = true
395        (true, true, _, _) => NUMERIC_TRUTHY_STRING_ATOMIC,
396        (true, false, _, _) => NUMERIC_STRING_ATOMIC,
397        // is_numeric = false, is_truthy = true
398        (false, true, _, false) => TRUTHY_STRING_ATOMIC,
399        (false, true, _, true) => TRUTHY_LOWERCASE_STRING_ATOMIC,
400        // is_numeric = false, is_truthy = false
401        (false, false, false, false) => STRING_ATOMIC,
402        (false, false, false, true) => LOWERCASE_STRING_ATOMIC,
403        (false, false, true, false) => NON_EMPTY_STRING_ATOMIC,
404        (false, false, true, true) => NON_EMPTY_LOWERCASE_STRING_ATOMIC,
405    };
406
407    TUnion::from_single(Cow::Borrowed(atomic_ref))
408}
409
410#[inline]
411pub fn get_literal_class_string(value: Atom) -> TUnion {
412    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::literal(value)))))
413}
414
415#[inline]
416pub fn get_class_string() -> TUnion {
417    TUnion::from_single(Cow::Borrowed(CLASS_STRING_ATOMIC))
418}
419
420#[inline]
421pub fn get_class_string_of_type(constraint: TAtomic) -> TUnion {
422    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::class_string_of_type(
423        constraint,
424    )))))
425}
426
427#[inline]
428pub fn get_interface_string() -> TUnion {
429    TUnion::from_single(Cow::Borrowed(INTERFACE_STRING_ATOMIC))
430}
431
432#[inline]
433pub fn get_interface_string_of_type(constraint: TAtomic) -> TUnion {
434    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(
435        TClassLikeString::interface_string_of_type(constraint),
436    ))))
437}
438
439#[inline]
440pub fn get_enum_string() -> TUnion {
441    TUnion::from_single(Cow::Borrowed(ENUM_STRING_ATOMIC))
442}
443
444#[inline]
445pub fn get_enum_string_of_type(constraint: TAtomic) -> TUnion {
446    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::enum_string_of_type(
447        constraint,
448    )))))
449}
450
451#[inline]
452pub fn get_trait_string() -> TUnion {
453    TUnion::from_single(Cow::Borrowed(TRAIT_STRING_ATOMIC))
454}
455
456#[inline]
457pub fn get_trait_string_of_type(constraint: TAtomic) -> TUnion {
458    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::trait_string_of_type(
459        constraint,
460    )))))
461}
462
463#[inline]
464pub fn get_literal_string(value: Atom) -> TUnion {
465    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_string(value))))
466}
467
468#[inline]
469pub fn get_float() -> TUnion {
470    TUnion::from_single(Cow::Borrowed(FLOAT_ATOMIC))
471}
472
473#[inline]
474pub fn get_literal_float(v: f64) -> TUnion {
475    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_float(v))))
476}
477
478#[inline]
479pub fn get_mixed() -> TUnion {
480    TUnion::from_single(Cow::Borrowed(MIXED_ATOMIC))
481}
482
483#[inline]
484pub fn get_isset_from_mixed_mixed() -> TUnion {
485    TUnion::from_single(Cow::Borrowed(ISSET_FROM_LOOP_MIXED_ATOMIC))
486}
487
488pub fn get_mixed_maybe_from_loop(from_loop_isset: bool) -> TUnion {
489    if from_loop_isset { get_isset_from_mixed_mixed() } else { get_mixed() }
490}
491
492#[inline]
493pub fn get_never() -> TUnion {
494    TUnion::from_single(Cow::Borrowed(NEVER_ATOMIC))
495}
496
497#[inline]
498pub fn get_resource() -> TUnion {
499    TUnion::from_single(Cow::Borrowed(RESOURCE_ATOMIC))
500}
501
502#[inline]
503pub fn get_closed_resource() -> TUnion {
504    TUnion::from_single(Cow::Borrowed(CLOSED_RESOURCE_ATOMIC))
505}
506
507#[inline]
508pub fn get_open_resource() -> TUnion {
509    TUnion::from_single(Cow::Borrowed(OPEN_RESOURCE_ATOMIC))
510}
511
512#[inline]
513pub fn get_placeholder() -> TUnion {
514    TUnion::from_single(Cow::Borrowed(PLACEHOLDER_ATOMIC))
515}
516
517#[inline]
518pub fn get_void() -> TUnion {
519    TUnion::from_single(Cow::Borrowed(VOID_ATOMIC))
520}
521
522#[inline]
523pub fn get_null() -> TUnion {
524    TUnion::from_single(Cow::Borrowed(NULL_ATOMIC))
525}
526
527#[inline]
528pub fn get_undefined_null() -> TUnion {
529    let mut null = TUnion::from_single(Cow::Borrowed(NULL_ATOMIC));
530    null.set_possibly_undefined(true, None);
531    null
532}
533
534#[inline]
535pub fn get_arraykey() -> TUnion {
536    TUnion::from_single(Cow::Borrowed(ARRAYKEY_ATOMIC))
537}
538
539#[inline]
540pub fn get_bool() -> TUnion {
541    TUnion::from_single(Cow::Borrowed(BOOL_ATOMIC))
542}
543
544#[inline]
545pub fn get_false() -> TUnion {
546    TUnion::from_single(Cow::Borrowed(FALSE_ATOMIC))
547}
548
549#[inline]
550pub fn get_true() -> TUnion {
551    TUnion::from_single(Cow::Borrowed(TRUE_ATOMIC))
552}
553
554#[inline]
555pub fn get_object() -> TUnion {
556    TUnion::from_single(Cow::Borrowed(OBJECT_ATOMIC))
557}
558
559#[inline]
560pub fn get_numeric() -> TUnion {
561    TUnion::from_single(Cow::Borrowed(NUMERIC_ATOMIC))
562}
563
564#[inline]
565pub fn get_numeric_string() -> TUnion {
566    TUnion::from_single(Cow::Borrowed(NUMERIC_STRING_ATOMIC))
567}
568
569#[inline]
570pub fn get_lowercase_string() -> TUnion {
571    TUnion::from_single(Cow::Borrowed(LOWERCASE_STRING_ATOMIC))
572}
573
574#[inline]
575pub fn get_non_empty_lowercase_string() -> TUnion {
576    TUnion::from_single(Cow::Borrowed(NON_EMPTY_LOWERCASE_STRING_ATOMIC))
577}
578
579#[inline]
580pub fn get_non_empty_string() -> TUnion {
581    TUnion::from_single(Cow::Borrowed(NON_EMPTY_STRING_ATOMIC))
582}
583
584#[inline]
585pub fn get_empty_string() -> TUnion {
586    TUnion::from_single(Cow::Borrowed(&EMPTY_STRING_ATOMIC))
587}
588
589#[inline]
590pub fn get_truthy_string() -> TUnion {
591    TUnion::from_single(Cow::Borrowed(TRUTHY_STRING_ATOMIC))
592}
593
594#[inline]
595pub fn get_unspecified_literal_string() -> TUnion {
596    TUnion::from_single(Cow::Borrowed(UNSPECIFIED_LITERAL_STRING_ATOMIC))
597}
598
599#[inline]
600pub fn get_non_empty_unspecified_literal_string() -> TUnion {
601    TUnion::from_single(Cow::Borrowed(NON_EMPTY_UNSPECIFIED_LITERAL_STRING_ATOMIC))
602}
603
604#[inline]
605pub fn get_scalar() -> TUnion {
606    TUnion::from_single(Cow::Borrowed(SCALAR_ATOMIC))
607}
608
609#[inline]
610pub fn get_nullable_scalar() -> TUnion {
611    TUnion::new(Cow::Borrowed(NULL_SCALAR_ATOMIC_SLICE))
612}
613
614#[inline]
615pub fn get_mixed_iterable() -> TUnion {
616    TUnion::from_single(Cow::Borrowed(&MIXED_ITERABLE_ATOMIC))
617}
618
619#[inline]
620pub fn get_empty_keyed_array() -> TUnion {
621    TUnion::from_single(Cow::Borrowed(&EMPTY_KEYED_ARRAY_ATOMIC))
622}
623
624#[inline]
625pub fn get_mixed_list() -> TUnion {
626    get_list(get_mixed())
627}
628
629#[inline]
630pub fn get_mixed_keyed_array() -> TUnion {
631    get_keyed_array(get_arraykey(), get_mixed())
632}
633
634#[inline]
635pub fn get_mixed_callable() -> TUnion {
636    TUnion::from_single(Cow::Borrowed(&MIXED_CALLABLE_ATOMIC))
637}
638
639#[inline]
640pub fn get_mixed_closure() -> TUnion {
641    TUnion::from_single(Cow::Borrowed(&MIXED_CLOSURE_ATOMIC))
642}
643
644#[inline]
645pub fn get_named_object(name: Atom, type_resolution_context: Option<&TypeResolutionContext>) -> TUnion {
646    if let Some(type_resolution_context) = type_resolution_context
647        && let Some(defining_entities) = type_resolution_context.get_template_definition(&name)
648    {
649        return wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic {
650            kind: TClassLikeStringKind::Class,
651            parameter_name: name,
652            defining_entity: defining_entities[0].0,
653            constraint: Box::new((*(defining_entities[0].1.get_single())).clone()),
654        })));
655    }
656
657    wrap_atomic(TAtomic::Object(TObject::Named(TNamedObject::new(name))))
658}
659
660#[inline]
661pub fn get_iterable(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
662    wrap_atomic(TAtomic::Iterable(TIterable::new(Box::new(key_parameter), Box::new(value_parameter))))
663}
664
665#[inline]
666pub fn get_list(element_type: TUnion) -> TUnion {
667    wrap_atomic(TAtomic::Array(TArray::List(TList::new(Box::new(element_type)))))
668}
669
670#[inline]
671pub fn get_non_empty_list(element_type: TUnion) -> TUnion {
672    wrap_atomic(TAtomic::Array(TArray::List(TList::new_non_empty(Box::new(element_type)))))
673}
674
675#[inline]
676pub fn get_keyed_array(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
677    wrap_atomic(TAtomic::Array(TArray::Keyed(TKeyedArray::new_with_parameters(
678        Box::new(key_parameter),
679        Box::new(value_parameter),
680    ))))
681}
682
683#[inline]
684pub fn add_optional_union_type(base_type: TUnion, maybe_type: Option<&TUnion>, codebase: &CodebaseMetadata) -> TUnion {
685    if let Some(type_2) = maybe_type { add_union_type(base_type, type_2, codebase, false) } else { base_type }
686}
687
688#[inline]
689pub fn combine_optional_union_types(
690    type_1: Option<&TUnion>,
691    type_2: Option<&TUnion>,
692    codebase: &CodebaseMetadata,
693) -> TUnion {
694    match (type_1, type_2) {
695        (Some(type_1), Some(type_2)) => combine_union_types(type_1, type_2, codebase, false),
696        (Some(type_1), None) => type_1.clone(),
697        (None, Some(type_2)) => type_2.clone(),
698        (None, None) => get_mixed(),
699    }
700}
701
702#[inline]
703pub fn combine_union_types(
704    type_1: &TUnion,
705    type_2: &TUnion,
706    codebase: &CodebaseMetadata,
707    overwrite_empty_array: bool,
708) -> TUnion {
709    if type_1 == type_2 {
710        return type_1.clone();
711    }
712
713    let mut combined_type = if type_1.is_never() || type_1.is_never_template() {
714        type_2.clone()
715    } else if type_2.is_never() || type_2.is_never_template() {
716        type_1.clone()
717    } else if type_1.is_vanilla_mixed() && type_2.is_vanilla_mixed() {
718        get_mixed()
719    } else {
720        let mut all_atomic_types = type_1.types.clone().into_owned();
721        all_atomic_types.extend(type_2.types.clone().into_owned());
722
723        let mut result = TUnion::from_vec(combiner::combine(all_atomic_types, codebase, overwrite_empty_array));
724
725        if type_1.had_template() && type_2.had_template() {
726            result.set_had_template(true);
727        }
728
729        if type_1.reference_free() && type_2.reference_free() {
730            result.set_reference_free(true);
731        }
732
733        result
734    };
735
736    if type_1.possibly_undefined() || type_2.possibly_undefined() {
737        combined_type.set_possibly_undefined(true, None);
738    }
739
740    if type_1.possibly_undefined_from_try() || type_2.possibly_undefined_from_try() {
741        combined_type.set_possibly_undefined_from_try(true);
742    }
743
744    if type_1.ignore_falsable_issues() || type_2.ignore_falsable_issues() {
745        combined_type.set_ignore_falsable_issues(true);
746    }
747
748    combined_type
749}
750
751#[inline]
752pub fn add_union_type(
753    mut base_type: TUnion,
754    other_type: &TUnion,
755    codebase: &CodebaseMetadata,
756    overwrite_empty_array: bool,
757) -> TUnion {
758    if &base_type != other_type {
759        base_type.types = if base_type.is_vanilla_mixed() && other_type.is_vanilla_mixed() {
760            base_type.types
761        } else {
762            combine_union_types(&base_type, other_type, codebase, overwrite_empty_array).types
763        };
764
765        if !other_type.had_template() {
766            base_type.set_had_template(false);
767        }
768
769        if !other_type.reference_free() {
770            base_type.set_reference_free(false);
771        }
772    }
773
774    if other_type.possibly_undefined() {
775        base_type.set_possibly_undefined(true, None);
776    }
777    if other_type.possibly_undefined_from_try() {
778        base_type.set_possibly_undefined_from_try(true);
779    }
780    if other_type.ignore_falsable_issues() {
781        base_type.set_ignore_falsable_issues(true);
782    }
783    if other_type.ignore_nullable_issues() {
784        base_type.set_ignore_nullable_issues(true);
785    }
786
787    base_type
788}
789
790pub fn intersect_union_types(type_1: &TUnion, type_2: &TUnion, codebase: &CodebaseMetadata) -> Option<TUnion> {
791    if type_1 == type_2 {
792        return Some(type_1.clone());
793    }
794
795    if type_1.is_never() || type_2.is_never() {
796        return Some(get_never());
797    }
798
799    let mut intersection_performed = false;
800
801    if type_1.is_mixed() {
802        if type_2.is_mixed() {
803            return Some(get_mixed());
804        }
805
806        return Some(type_2.clone());
807    } else if type_2.is_mixed() {
808        return Some(type_1.clone());
809    }
810
811    let mut intersected_atomic_types = vec![];
812    for type_1_atomic in type_1.types.iter() {
813        for type_2_atomic in type_2.types.iter() {
814            if let Some(intersection_atomic) =
815                intersect_atomic_types(type_1_atomic, type_2_atomic, codebase, &mut intersection_performed)
816            {
817                intersected_atomic_types.push(intersection_atomic);
818            }
819        }
820    }
821
822    let mut combined_type: Option<TUnion> = None;
823    if !intersected_atomic_types.is_empty() {
824        let combined_vec = combiner::combine(intersected_atomic_types, codebase, false);
825        if !combined_vec.is_empty() {
826            combined_type = Some(TUnion::from_vec(combined_vec));
827        }
828    }
829
830    // If atomic-level intersection didn't yield a result, check for subtyping at the union level.
831    if !intersection_performed {
832        if union_comparator::is_contained_by(
833            codebase,
834            type_1,
835            type_2,
836            false,
837            false,
838            false,
839            &mut ComparisonResult::default(),
840        ) {
841            intersection_performed = true;
842            combined_type = Some(type_1.clone());
843        } else if union_comparator::is_contained_by(
844            codebase,
845            type_2,
846            type_1,
847            false,
848            false,
849            false,
850            &mut ComparisonResult::default(),
851        ) {
852            intersection_performed = true;
853            combined_type = Some(type_2.clone());
854        }
855    }
856
857    if let Some(mut final_type) = combined_type {
858        final_type.set_possibly_undefined(
859            type_1.possibly_undefined() && type_2.possibly_undefined(),
860            Some(type_1.possibly_undefined_from_try() && type_2.possibly_undefined_from_try()),
861        );
862        final_type.set_ignore_falsable_issues(type_1.ignore_falsable_issues() && type_2.ignore_falsable_issues());
863        final_type.set_ignore_nullable_issues(type_1.ignore_nullable_issues() && type_2.ignore_nullable_issues());
864
865        return Some(final_type);
866    }
867
868    if !intersection_performed && type_1.get_id() != type_2.get_id() {
869        return None;
870    }
871
872    None
873}
874
875/// This is the core logic used by `intersect_union_types`.
876fn intersect_atomic_types(
877    type_1: &TAtomic,
878    type_2: &TAtomic,
879    codebase: &CodebaseMetadata,
880    intersection_performed: &mut bool,
881) -> Option<TAtomic> {
882    if let (TAtomic::Scalar(TScalar::Integer(t1_int)), TAtomic::Scalar(TScalar::Integer(t2_int))) = (type_1, type_2) {
883        let (min1, max1) = t1_int.get_bounds();
884        let (min2, max2) = t2_int.get_bounds();
885
886        let new_min = match (min1, min2) {
887            (Some(m1), Some(m2)) => Some(m1.max(m2)),
888            (Some(m), None) | (None, Some(m)) => Some(m),
889            (None, None) => None,
890        };
891
892        let new_max = match (max1, max2) {
893            (Some(m1), Some(m2)) => Some(m1.min(m2)),
894            (Some(m), None) | (None, Some(m)) => Some(m),
895            (None, None) => None,
896        };
897
898        let intersected_int = if let (Some(min), Some(max)) = (new_min, new_max) {
899            if min > max {
900                return None;
901            }
902
903            if min == max { TInteger::Literal(min) } else { TInteger::Range(min, max) }
904        } else if let Some(min) = new_min {
905            TInteger::From(min)
906        } else if let Some(max) = new_max {
907            TInteger::To(max)
908        } else {
909            TInteger::Unspecified
910        };
911
912        *intersection_performed = true;
913        return Some(TAtomic::Scalar(TScalar::Integer(intersected_int)));
914    }
915
916    let t1_union = TUnion::from_atomic(type_1.clone());
917    let t2_union = TUnion::from_atomic(type_2.clone());
918
919    let mut narrower_type = None;
920    let mut wider_type = None;
921
922    if union_comparator::is_contained_by(
923        codebase,
924        &t2_union,
925        &t1_union,
926        false,
927        false,
928        false,
929        &mut ComparisonResult::default(),
930    ) {
931        narrower_type = Some(type_2);
932        wider_type = Some(type_1);
933    } else if union_comparator::is_contained_by(
934        codebase,
935        &t1_union,
936        &t2_union,
937        false,
938        false,
939        false,
940        &mut ComparisonResult::default(),
941    ) {
942        narrower_type = Some(type_1);
943        wider_type = Some(type_2);
944    }
945
946    if let (Some(narrower), Some(wider)) = (narrower_type, wider_type) {
947        *intersection_performed = true;
948        let mut result = narrower.clone();
949
950        if narrower.can_be_intersected() && wider.can_be_intersected() {
951            let mut wider_clone = wider.clone();
952            if let Some(types) = wider_clone.get_intersection_types_mut() {
953                types.clear();
954            }
955            result.add_intersection_type(wider_clone);
956
957            if let Some(wider_intersections) = wider.get_intersection_types() {
958                for i_type in wider_intersections {
959                    result.add_intersection_type(i_type.clone());
960                }
961            }
962        }
963        return Some(result);
964    }
965
966    if let (TAtomic::Scalar(TScalar::String(s1)), TAtomic::Scalar(TScalar::String(s2))) = (type_1, type_2) {
967        if let (Some(v1), Some(v2)) = (&s1.get_known_literal_value(), &s2.get_known_literal_value())
968            && v1 != v2
969        {
970            return None;
971        }
972
973        let combined = TAtomic::Scalar(TScalar::String(TString {
974            is_numeric: s1.is_numeric || s2.is_numeric,
975            is_truthy: s1.is_truthy || s2.is_truthy,
976            is_non_empty: s1.is_non_empty || s2.is_non_empty,
977            is_lowercase: s1.is_lowercase || s2.is_lowercase,
978            literal: if s1.is_literal_origin() && s2.is_literal_origin() {
979                Some(TStringLiteral::Unspecified)
980            } else {
981                None
982            },
983        }));
984        *intersection_performed = true;
985        return Some(combined);
986    }
987
988    if type_1.can_be_intersected() && type_2.can_be_intersected() {
989        if let (TAtomic::Object(TObject::Named(n1)), TAtomic::Object(TObject::Named(n2))) = (type_1, type_2)
990            && let (Some(c1), Some(c2)) = (codebase.get_class_like(&n1.name), codebase.get_class_like(&n2.name))
991            && !c1.kind.is_interface()
992            && !c1.kind.is_trait()
993            && !c2.kind.is_interface()
994            && !c2.kind.is_trait()
995        {
996            return None;
997        }
998
999        let mut result = type_1.clone();
1000        result.add_intersection_type(type_2.clone());
1001        if let Some(intersections) = type_2.get_intersection_types() {
1002            for i in intersections {
1003                result.add_intersection_type(i.clone());
1004            }
1005        }
1006
1007        *intersection_performed = true;
1008        return Some(result);
1009    }
1010
1011    None
1012}
1013
1014pub fn get_iterable_parameters(atomic: &TAtomic, codebase: &CodebaseMetadata) -> Option<(TUnion, TUnion)> {
1015    if let Some(generator_parameters) = atomic.get_generator_parameters() {
1016        let mut key_type = generator_parameters.0;
1017        let mut value_type = generator_parameters.1;
1018
1019        expander::expand_union(codebase, &mut key_type, &TypeExpansionOptions::default());
1020        expander::expand_union(codebase, &mut value_type, &TypeExpansionOptions::default());
1021
1022        return Some((key_type, value_type));
1023    }
1024
1025    let parameters = 'parameters: {
1026        match atomic {
1027            TAtomic::Iterable(iterable) => {
1028                let mut key_type = iterable.get_key_type().clone();
1029                let mut value_type = iterable.get_value_type().clone();
1030
1031                expander::expand_union(codebase, &mut key_type, &TypeExpansionOptions::default());
1032                expander::expand_union(codebase, &mut value_type, &TypeExpansionOptions::default());
1033
1034                Some((key_type, value_type))
1035            }
1036            TAtomic::Array(array_type) => {
1037                let (mut key_type, mut value_type) = get_array_parameters(array_type, codebase);
1038
1039                expander::expand_union(codebase, &mut key_type, &TypeExpansionOptions::default());
1040                expander::expand_union(codebase, &mut value_type, &TypeExpansionOptions::default());
1041
1042                Some((key_type, value_type))
1043            }
1044            TAtomic::Object(object) => {
1045                let name = object.get_name()?;
1046                let traversable = atom("traversable");
1047                let iterator = atom("iterator");
1048                let iterator_aggregate = atom("iteratoraggregate");
1049
1050                let class_metadata = codebase.get_class_like(name)?;
1051                if !codebase.is_instance_of(&class_metadata.name, &traversable) {
1052                    break 'parameters None;
1053                }
1054
1055                let is_iterator_interface = name == &iterator || name == &traversable || name == &iterator_aggregate;
1056                if !is_iterator_interface
1057                    && codebase.is_instance_of(&class_metadata.name, &iterator)
1058                    && let (Some(key_type), Some(value_type)) = (
1059                        get_iterator_method_return_type(codebase, name, "key"),
1060                        get_iterator_method_return_type(codebase, name, "current"),
1061                    )
1062                {
1063                    let contains_generic_param =
1064                        |t: &TUnion| t.types.iter().any(|atomic| atomic.is_generic_parameter());
1065
1066                    if !key_type.is_mixed()
1067                        && !value_type.is_mixed()
1068                        && !contains_generic_param(&key_type)
1069                        && !contains_generic_param(&value_type)
1070                    {
1071                        return Some((key_type, value_type));
1072                    }
1073                }
1074
1075                let traversable_metadata = codebase.get_class_like(&traversable)?;
1076                let key_template = traversable_metadata.template_types.first().map(|(name, _)| name)?;
1077                let value_template = traversable_metadata.template_types.get(1).map(|(name, _)| name)?;
1078
1079                let key_type = get_specialized_template_type(
1080                    codebase,
1081                    key_template,
1082                    &traversable,
1083                    class_metadata,
1084                    object.get_type_parameters(),
1085                )
1086                .unwrap_or_else(get_mixed);
1087
1088                let value_type = get_specialized_template_type(
1089                    codebase,
1090                    value_template,
1091                    &traversable,
1092                    class_metadata,
1093                    object.get_type_parameters(),
1094                )
1095                .unwrap_or_else(get_mixed);
1096
1097                Some((key_type, value_type))
1098            }
1099            _ => None,
1100        }
1101    };
1102
1103    if let Some((key_type, value_type)) = parameters {
1104        return Some((key_type, value_type));
1105    }
1106
1107    if let Some(intersection_types) = atomic.get_intersection_types() {
1108        for intersection_type in intersection_types {
1109            if let Some((key_type, value_type)) = get_iterable_parameters(intersection_type, codebase) {
1110                return Some((key_type, value_type));
1111            }
1112        }
1113    }
1114
1115    None
1116}
1117
1118pub fn get_array_parameters(array_type: &TArray, codebase: &CodebaseMetadata) -> (TUnion, TUnion) {
1119    match array_type {
1120        TArray::Keyed(keyed_data) => {
1121            let mut key_types = vec![];
1122            let mut value_param;
1123
1124            if let Some((key_param, value_p)) = &keyed_data.parameters {
1125                key_types.extend(key_param.types.clone().into_owned());
1126                value_param = (**value_p).clone();
1127            } else {
1128                key_types.push(TAtomic::Never);
1129                value_param = get_never();
1130            }
1131
1132            if let Some(known_items) = &keyed_data.known_items {
1133                for (key, (_, item_type)) in known_items {
1134                    key_types.push(key.to_atomic());
1135                    value_param = add_union_type(value_param, item_type, codebase, false);
1136                }
1137            }
1138
1139            let combined_key_types = combiner::combine(key_types, codebase, false);
1140            let key_param_union = TUnion::from_vec(combined_key_types);
1141
1142            (key_param_union, value_param)
1143        }
1144        TArray::List(list_data) => {
1145            let mut key_types = vec![];
1146            let mut value_type = (*list_data.element_type).clone();
1147
1148            if let Some(known_elements) = &list_data.known_elements {
1149                for (key_idx, (_, element_type)) in known_elements {
1150                    key_types.push(TAtomic::Scalar(TScalar::literal_int(*key_idx as i64)));
1151
1152                    value_type = combine_union_types(element_type, &value_type, codebase, false);
1153                }
1154            }
1155
1156            if key_types.is_empty() || !value_type.is_never() {
1157                if value_type.is_never() {
1158                    key_types.push(TAtomic::Never);
1159                } else {
1160                    key_types.push(TAtomic::Scalar(TScalar::Integer(TInteger::non_negative())));
1161                }
1162            }
1163
1164            let key_type = TUnion::from_vec(combiner::combine(key_types, codebase, false));
1165
1166            (key_type, value_type)
1167        }
1168    }
1169}
1170
1171pub fn get_iterable_value_parameter(atomic: &TAtomic, codebase: &CodebaseMetadata) -> Option<TUnion> {
1172    if let Some(generator_parameters) = atomic.get_generator_parameters() {
1173        return Some(generator_parameters.1);
1174    }
1175
1176    let parameter = match atomic {
1177        TAtomic::Iterable(iterable) => Some(iterable.get_value_type().clone()),
1178        TAtomic::Array(array_type) => Some(get_array_value_parameter(array_type, codebase)),
1179        TAtomic::Object(object) => {
1180            let name = object.get_name()?;
1181            let traversable = atom("traversable");
1182
1183            let class_metadata = codebase.get_class_like(name)?;
1184            if !codebase.is_instance_of(&class_metadata.name, &traversable) {
1185                return None;
1186            }
1187
1188            let traversable_metadata = codebase.get_class_like(&traversable)?;
1189            let value_template = traversable_metadata.template_types.get(1).map(|(name, _)| name)?;
1190
1191            get_specialized_template_type(
1192                codebase,
1193                value_template,
1194                &traversable,
1195                class_metadata,
1196                object.get_type_parameters(),
1197            )
1198        }
1199        _ => None,
1200    };
1201
1202    if let Some(value_param) = parameter {
1203        return Some(value_param);
1204    }
1205
1206    if let Some(intersection_types) = atomic.get_intersection_types() {
1207        for intersection_type in intersection_types {
1208            if let Some(value_param) = get_iterable_value_parameter(intersection_type, codebase) {
1209                return Some(value_param);
1210            }
1211        }
1212    }
1213
1214    None
1215}
1216
1217pub fn get_array_value_parameter(array_type: &TArray, codebase: &CodebaseMetadata) -> TUnion {
1218    match array_type {
1219        TArray::Keyed(keyed_data) => {
1220            let mut value_param;
1221
1222            if let Some((_, value_p)) = &keyed_data.parameters {
1223                value_param = (**value_p).clone();
1224            } else {
1225                value_param = get_never();
1226            }
1227
1228            if let Some(known_items) = &keyed_data.known_items {
1229                for (_, item_type) in known_items.values() {
1230                    value_param = combine_union_types(item_type, &value_param, codebase, false);
1231                }
1232            }
1233
1234            value_param
1235        }
1236        TArray::List(list_data) => {
1237            let mut value_param = (*list_data.element_type).clone();
1238
1239            if let Some(known_elements) = &list_data.known_elements {
1240                for (_, element_type) in known_elements.values() {
1241                    value_param = combine_union_types(element_type, &value_param, codebase, false);
1242                }
1243            }
1244
1245            value_param
1246        }
1247    }
1248}
1249
1250/// Resolves a generic template from an ancestor class in the context of a descendant class.
1251///
1252/// This function correctly traverses the pre-calculated inheritance map to determine the
1253/// concrete type of a template parameter.
1254pub fn get_specialized_template_type(
1255    codebase: &CodebaseMetadata,
1256    template_name: &Atom,
1257    template_defining_class_id: &Atom,
1258    instantiated_class_metadata: &ClassLikeMetadata,
1259    instantiated_type_parameters: Option<&[TUnion]>,
1260) -> Option<TUnion> {
1261    let defining_class_metadata = codebase.get_class_like(template_defining_class_id)?;
1262
1263    if defining_class_metadata.name == instantiated_class_metadata.name {
1264        let index = instantiated_class_metadata.get_template_index_for_name(template_name)?;
1265
1266        let Some(instantiated_type_parameters) = instantiated_type_parameters else {
1267            let type_map = instantiated_class_metadata.get_template_type(template_name)?;
1268            let mut result = type_map.first().map(|(_, constraint)| constraint).cloned()?;
1269
1270            expander::expand_union(codebase, &mut result, &TypeExpansionOptions::default());
1271
1272            return Some(result);
1273        };
1274
1275        let mut result = instantiated_type_parameters.get(index).cloned()?;
1276
1277        expander::expand_union(codebase, &mut result, &TypeExpansionOptions::default());
1278
1279        return Some(result);
1280    }
1281
1282    let defining_template_type = defining_class_metadata.get_template_type(template_name)?;
1283    let template_union = TUnion::from_vec(
1284        defining_template_type
1285            .iter()
1286            .map(|(defining_entity, constraint)| {
1287                TAtomic::GenericParameter(TGenericParameter {
1288                    parameter_name: *template_name,
1289                    defining_entity: *defining_entity,
1290                    constraint: Box::new(constraint.clone()),
1291                    intersection_types: None,
1292                })
1293            })
1294            .collect::<Vec<_>>(),
1295    );
1296
1297    let mut template_result = TemplateResult::default();
1298    for (defining_class, type_parameters_map) in &instantiated_class_metadata.template_extended_parameters {
1299        for (parameter_name, parameter_type) in type_parameters_map {
1300            template_result.add_lower_bound(
1301                *parameter_name,
1302                GenericParent::ClassLike(*defining_class),
1303                parameter_type.clone(),
1304            );
1305        }
1306    }
1307
1308    let mut template_type = inferred_type_replacer::replace(&template_union, &template_result, codebase);
1309    if let Some(type_parameters) = instantiated_type_parameters {
1310        let mut template_result = TemplateResult::default();
1311        for (i, parameter_type) in type_parameters.iter().enumerate() {
1312            if let Some(parameter_name) = instantiated_class_metadata.get_template_name_for_index(i) {
1313                template_result.add_lower_bound(
1314                    parameter_name,
1315                    GenericParent::ClassLike(instantiated_class_metadata.name),
1316                    parameter_type.clone(),
1317                );
1318            }
1319        }
1320
1321        if !template_result.lower_bounds.is_empty() {
1322            template_type = inferred_type_replacer::replace(&template_type, &template_result, codebase);
1323        }
1324    }
1325
1326    expander::expand_union(codebase, &mut template_type, &TypeExpansionOptions::default());
1327
1328    Some(template_type)
1329}
1330
1331fn get_iterator_method_return_type(
1332    codebase: &CodebaseMetadata,
1333    class_name: &Atom,
1334    method_name: &str,
1335) -> Option<TUnion> {
1336    let method = codebase.get_declaring_method(class_name, method_name)?;
1337    let return_type_meta = method.return_type_metadata.as_ref()?;
1338    let mut return_type = return_type_meta.type_union.clone();
1339    expander::expand_union(codebase, &mut return_type, &TypeExpansionOptions::default());
1340    Some(return_type)
1341}