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::resolution::TypeResolutionContext;
22use crate::ttype::shared::*;
23use crate::ttype::template::TemplateResult;
24use crate::ttype::template::inferred_type_replacer;
25use crate::ttype::union::TUnion;
26
27pub mod atomic;
28pub mod builder;
29pub mod cast;
30pub mod combination;
31pub mod combiner;
32pub mod comparator;
33pub mod error;
34pub mod expander;
35pub mod resolution;
36pub mod shared;
37pub mod template;
38pub mod union;
39
40/// A reference to a type in the type system, which can be either a union or an atomic type.
41#[derive(Clone, Copy, Debug)]
42pub enum TypeRef<'a> {
43    Union(&'a TUnion),
44    Atomic(&'a TAtomic),
45}
46
47/// A trait to be implemented by all types in the type system.
48pub trait TType {
49    /// Returns a vector of child type nodes that this type contains.
50    fn get_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
51        vec![]
52    }
53
54    /// Returns a vector of all child type nodes, including nested ones.
55    fn get_all_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
56        let mut child_nodes = self.get_child_nodes();
57        let mut all_child_nodes = vec![];
58
59        while let Some(child_node) = child_nodes.pop() {
60            let new_child_nodes = match child_node {
61                TypeRef::Union(union) => union.get_child_nodes(),
62                TypeRef::Atomic(atomic) => atomic.get_child_nodes(),
63            };
64
65            all_child_nodes.push(child_node);
66
67            child_nodes.extend(new_child_nodes);
68        }
69
70        all_child_nodes
71    }
72
73    /// Checks if this type can have intersection types (`&B&S`).
74    fn can_be_intersected(&self) -> bool {
75        false
76    }
77
78    /// Returns a slice of the additional intersection types (`&B&S`), if any. Contains boxed atomic types.
79    fn get_intersection_types(&self) -> Option<&[TAtomic]> {
80        None
81    }
82
83    /// Returns a mutable slice of the additional intersection types (`&B&S`), if any. Contains boxed atomic types.
84    fn get_intersection_types_mut(&mut self) -> Option<&mut Vec<TAtomic>> {
85        None
86    }
87
88    /// Checks if this type has intersection types.
89    fn has_intersection_types(&self) -> bool {
90        false
91    }
92
93    /// Adds an intersection type to this type.
94    ///
95    /// Returns `true` if the intersection type was added successfully,
96    ///  or `false` if this type does not support intersection types.
97    fn add_intersection_type(&mut self, _intersection_type: TAtomic) -> bool {
98        false
99    }
100
101    fn needs_population(&self) -> bool;
102
103    fn is_expandable(&self) -> bool;
104
105    /// Returns true if this type has complex structure that benefits from
106    /// multiline formatting when used as a generic parameter.
107    fn is_complex(&self) -> bool;
108
109    /// Return a human-readable atom for this type, which is
110    /// suitable for use in error messages or debugging.
111    ///
112    /// The resulting identifier must be unique for the type,
113    /// but it does not have to be globally unique.
114    fn get_id(&self) -> Atom;
115
116    fn get_pretty_id(&self) -> Atom {
117        self.get_pretty_id_with_indent(0)
118    }
119
120    fn get_pretty_id_with_indent(&self, indent: usize) -> Atom;
121}
122
123/// Implements the `TType` trait for `TypeRef`.
124impl<'a> TType for TypeRef<'a> {
125    fn get_child_nodes(&self) -> Vec<TypeRef<'a>> {
126        match self {
127            TypeRef::Union(ttype) => ttype.get_child_nodes(),
128            TypeRef::Atomic(ttype) => ttype.get_child_nodes(),
129        }
130    }
131
132    fn can_be_intersected(&self) -> bool {
133        match self {
134            TypeRef::Union(ttype) => ttype.can_be_intersected(),
135            TypeRef::Atomic(ttype) => ttype.can_be_intersected(),
136        }
137    }
138
139    fn get_intersection_types(&self) -> Option<&[TAtomic]> {
140        match self {
141            TypeRef::Union(ttype) => ttype.get_intersection_types(),
142            TypeRef::Atomic(ttype) => ttype.get_intersection_types(),
143        }
144    }
145
146    fn has_intersection_types(&self) -> bool {
147        match self {
148            TypeRef::Union(ttype) => ttype.has_intersection_types(),
149            TypeRef::Atomic(ttype) => ttype.has_intersection_types(),
150        }
151    }
152
153    fn needs_population(&self) -> bool {
154        match self {
155            TypeRef::Union(ttype) => ttype.needs_population(),
156            TypeRef::Atomic(ttype) => ttype.needs_population(),
157        }
158    }
159
160    fn is_expandable(&self) -> bool {
161        match self {
162            TypeRef::Union(ttype) => ttype.is_expandable(),
163            TypeRef::Atomic(ttype) => ttype.is_expandable(),
164        }
165    }
166
167    fn is_complex(&self) -> bool {
168        match self {
169            TypeRef::Union(ttype) => ttype.is_complex(),
170            TypeRef::Atomic(ttype) => ttype.is_complex(),
171        }
172    }
173
174    fn get_id(&self) -> Atom {
175        match self {
176            TypeRef::Union(ttype) => ttype.get_id(),
177            TypeRef::Atomic(ttype) => ttype.get_id(),
178        }
179    }
180
181    fn get_pretty_id_with_indent(&self, indent: usize) -> Atom {
182        match self {
183            TypeRef::Union(ttype) => ttype.get_pretty_id_with_indent(indent),
184            TypeRef::Atomic(ttype) => ttype.get_pretty_id_with_indent(indent),
185        }
186    }
187}
188
189impl<'a> From<&'a TUnion> for TypeRef<'a> {
190    fn from(reference: &'a TUnion) -> Self {
191        TypeRef::Union(reference)
192    }
193}
194
195impl<'a> From<&'a TAtomic> for TypeRef<'a> {
196    fn from(reference: &'a TAtomic) -> Self {
197        TypeRef::Atomic(reference)
198    }
199}
200
201/// Creates a `TUnion` from a `TInteger`, using a canonical static type where possible.
202///
203/// This function is a key optimization point. It checks if the provided `TInteger`
204/// matches a common, reusable form (like "any integer" or "a positive integer").
205/// If it does, it returns a zero-allocation `TUnion` that borrows a static,
206/// shared instance.
207///
208/// For specific literal values or ranges that do not have a canonical static
209/// representation, it falls back to creating a new, owned `TUnion`, which
210/// involves a heap allocation.
211pub fn get_union_from_integer(integer: &TInteger) -> TUnion {
212    if integer.is_unspecified() {
213        return get_int();
214    }
215
216    if integer.is_positive() {
217        return get_positive_int();
218    }
219
220    if integer.is_negative() {
221        return get_negative_int();
222    }
223
224    if integer.is_non_negative() {
225        return get_non_negative_int();
226    }
227
228    if integer.is_non_positive() {
229        return get_non_positive_int();
230    }
231
232    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::Integer(*integer))))
233}
234
235#[inline]
236pub fn wrap_atomic(tinner: TAtomic) -> TUnion {
237    TUnion::from_single(Cow::Owned(tinner))
238}
239
240#[inline]
241pub fn get_int() -> TUnion {
242    TUnion::from_single(Cow::Borrowed(INT_ATOMIC))
243}
244
245#[inline]
246pub fn get_positive_int() -> TUnion {
247    TUnion::from_single(Cow::Borrowed(POSITIVE_INT_ATOMIC))
248}
249
250#[inline]
251pub fn get_negative_int() -> TUnion {
252    TUnion::from_single(Cow::Borrowed(NEGATIVE_INT_ATOMIC))
253}
254
255#[inline]
256pub fn get_non_positive_int() -> TUnion {
257    TUnion::from_single(Cow::Borrowed(NON_POSITIVE_INT_ATOMIC))
258}
259
260#[inline]
261pub fn get_non_negative_int() -> TUnion {
262    TUnion::from_single(Cow::Borrowed(NON_NEGATIVE_INT_ATOMIC))
263}
264
265#[inline]
266pub fn get_int_range(from: Option<i64>, to: Option<i64>) -> TUnion {
267    let atomic = match (from, to) {
268        (Some(from), Some(to)) => TAtomic::Scalar(TScalar::Integer(TInteger::Range(from, to))),
269        (Some(from), None) => {
270            if 0 == from {
271                return get_non_negative_int();
272            }
273
274            if 1 == from {
275                return get_positive_int();
276            }
277
278            TAtomic::Scalar(TScalar::Integer(TInteger::From(from)))
279        }
280        (None, Some(to)) => {
281            if 0 == to {
282                return get_non_positive_int();
283            }
284
285            if -1 == to {
286                return get_negative_int();
287            }
288
289            TAtomic::Scalar(TScalar::Integer(TInteger::To(to)))
290        }
291        (None, None) => return get_int(),
292    };
293
294    TUnion::from_single(Cow::Owned(atomic))
295}
296
297/// Returns a zero-allocation `TUnion` for the type `-1|0|1`.
298#[inline]
299pub fn get_signum_result() -> TUnion {
300    TUnion::new(Cow::Borrowed(SIGNUM_RESULT_SLICE))
301}
302
303/// Returns a zero-allocation `TUnion` for the integer literal `1`.
304#[inline]
305pub fn get_one_int() -> TUnion {
306    TUnion::from_single(Cow::Borrowed(ONE_INT_ATOMIC))
307}
308
309/// Returns a zero-allocation `TUnion` for the integer literal `0`.
310#[inline]
311pub fn get_zero_int() -> TUnion {
312    TUnion::from_single(Cow::Borrowed(ZERO_INT_ATOMIC))
313}
314
315/// Returns a zero-allocation `TUnion` for the integer literal `-1`.
316#[inline]
317pub fn get_minus_one_int() -> TUnion {
318    TUnion::from_single(Cow::Borrowed(MINUS_ONE_INT_ATOMIC))
319}
320
321#[inline]
322pub fn get_literal_int(value: i64) -> TUnion {
323    if value == 0 {
324        return get_zero_int();
325    }
326
327    if value == 1 {
328        return get_one_int();
329    }
330
331    if value == -1 {
332        return get_minus_one_int();
333    }
334
335    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_int(value))))
336}
337
338#[inline]
339pub fn get_int_or_float() -> TUnion {
340    TUnion::new(Cow::Borrowed(INT_FLOAT_ATOMIC_SLICE))
341}
342
343#[inline]
344pub fn get_int_or_string() -> TUnion {
345    TUnion::new(Cow::Borrowed(INT_STRING_ATOMIC_SLICE))
346}
347
348#[inline]
349pub fn get_nullable_int() -> TUnion {
350    TUnion::new(Cow::Borrowed(NULL_INT_ATOMIC_SLICE))
351}
352
353#[inline]
354pub fn get_nullable_float() -> TUnion {
355    TUnion::new(Cow::Borrowed(NULL_FLOAT_ATOMIC_SLICE))
356}
357
358#[inline]
359pub fn get_nullable_object() -> TUnion {
360    TUnion::new(Cow::Borrowed(NULL_OBJECT_ATOMIC_SLICE))
361}
362
363#[inline]
364pub fn get_nullable_string() -> TUnion {
365    TUnion::new(Cow::Borrowed(NULL_STRING_ATOMIC_SLICE))
366}
367
368#[inline]
369pub fn get_string() -> TUnion {
370    TUnion::from_single(Cow::Borrowed(STRING_ATOMIC))
371}
372
373/// Returns a zero-allocation `TUnion` for a `string` with the specified properties.
374///
375/// This function maps all possible boolean property combinations to a canonical,
376/// static `TAtomic` instance, avoiding heap allocations for common string types.
377pub fn get_string_with_props(is_numeric: bool, is_truthy: bool, is_non_empty: bool, is_lowercase: bool) -> TUnion {
378    let atomic_ref = match (is_numeric, is_truthy, is_non_empty, is_lowercase) {
379        // is_numeric = true
380        (true, true, _, _) => NUMERIC_TRUTHY_STRING_ATOMIC,
381        (true, false, _, _) => NUMERIC_STRING_ATOMIC,
382        // is_numeric = false, is_truthy = true
383        (false, true, _, false) => TRUTHY_STRING_ATOMIC,
384        (false, true, _, true) => TRUTHY_LOWERCASE_STRING_ATOMIC,
385        // is_numeric = false, is_truthy = false
386        (false, false, false, false) => STRING_ATOMIC,
387        (false, false, false, true) => LOWERCASE_STRING_ATOMIC,
388        (false, false, true, false) => NON_EMPTY_STRING_ATOMIC,
389        (false, false, true, true) => NON_EMPTY_LOWERCASE_STRING_ATOMIC,
390    };
391
392    TUnion::from_single(Cow::Borrowed(atomic_ref))
393}
394
395#[inline]
396pub fn get_literal_class_string(value: Atom) -> TUnion {
397    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::literal(value)))))
398}
399
400#[inline]
401pub fn get_class_string() -> TUnion {
402    TUnion::from_single(Cow::Borrowed(CLASS_STRING_ATOMIC))
403}
404
405#[inline]
406pub fn get_class_string_of_type(constraint: TAtomic) -> TUnion {
407    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::class_string_of_type(
408        constraint,
409    )))))
410}
411
412#[inline]
413pub fn get_interface_string() -> TUnion {
414    TUnion::from_single(Cow::Borrowed(INTERFACE_STRING_ATOMIC))
415}
416
417#[inline]
418pub fn get_interface_string_of_type(constraint: TAtomic) -> TUnion {
419    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(
420        TClassLikeString::interface_string_of_type(constraint),
421    ))))
422}
423
424#[inline]
425pub fn get_enum_string() -> TUnion {
426    TUnion::from_single(Cow::Borrowed(ENUM_STRING_ATOMIC))
427}
428
429#[inline]
430pub fn get_enum_string_of_type(constraint: TAtomic) -> TUnion {
431    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::enum_string_of_type(
432        constraint,
433    )))))
434}
435
436#[inline]
437pub fn get_trait_string() -> TUnion {
438    TUnion::from_single(Cow::Borrowed(TRAIT_STRING_ATOMIC))
439}
440
441#[inline]
442pub fn get_trait_string_of_type(constraint: TAtomic) -> TUnion {
443    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::trait_string_of_type(
444        constraint,
445    )))))
446}
447
448#[inline]
449pub fn get_literal_string(value: Atom) -> TUnion {
450    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_string(value))))
451}
452
453#[inline]
454pub fn get_float() -> TUnion {
455    TUnion::from_single(Cow::Borrowed(FLOAT_ATOMIC))
456}
457
458#[inline]
459pub fn get_literal_float(v: f64) -> TUnion {
460    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_float(v))))
461}
462
463#[inline]
464pub fn get_mixed() -> TUnion {
465    TUnion::from_single(Cow::Borrowed(MIXED_ATOMIC))
466}
467
468#[inline]
469pub fn get_isset_from_mixed_mixed() -> TUnion {
470    TUnion::from_single(Cow::Borrowed(ISSET_FROM_LOOP_MIXED_ATOMIC))
471}
472
473pub fn get_mixed_maybe_from_loop(from_loop_isset: bool) -> TUnion {
474    if from_loop_isset { get_isset_from_mixed_mixed() } else { get_mixed() }
475}
476
477#[inline]
478pub fn get_never() -> TUnion {
479    TUnion::from_single(Cow::Borrowed(NEVER_ATOMIC))
480}
481
482#[inline]
483pub fn get_resource() -> TUnion {
484    TUnion::from_single(Cow::Borrowed(RESOURCE_ATOMIC))
485}
486
487#[inline]
488pub fn get_closed_resource() -> TUnion {
489    TUnion::from_single(Cow::Borrowed(CLOSED_RESOURCE_ATOMIC))
490}
491
492#[inline]
493pub fn get_open_resource() -> TUnion {
494    TUnion::from_single(Cow::Borrowed(OPEN_RESOURCE_ATOMIC))
495}
496
497#[inline]
498pub fn get_placeholder() -> TUnion {
499    TUnion::from_single(Cow::Borrowed(PLACEHOLDER_ATOMIC))
500}
501
502#[inline]
503pub fn get_void() -> TUnion {
504    TUnion::from_single(Cow::Borrowed(VOID_ATOMIC))
505}
506
507#[inline]
508pub fn get_null() -> TUnion {
509    TUnion::from_single(Cow::Borrowed(NULL_ATOMIC))
510}
511
512#[inline]
513pub fn get_arraykey() -> TUnion {
514    TUnion::from_single(Cow::Borrowed(ARRAYKEY_ATOMIC))
515}
516
517#[inline]
518pub fn get_bool() -> TUnion {
519    TUnion::from_single(Cow::Borrowed(BOOL_ATOMIC))
520}
521
522#[inline]
523pub fn get_false() -> TUnion {
524    TUnion::from_single(Cow::Borrowed(FALSE_ATOMIC))
525}
526
527#[inline]
528pub fn get_true() -> TUnion {
529    TUnion::from_single(Cow::Borrowed(TRUE_ATOMIC))
530}
531
532#[inline]
533pub fn get_object() -> TUnion {
534    TUnion::from_single(Cow::Borrowed(OBJECT_ATOMIC))
535}
536
537#[inline]
538pub fn get_numeric() -> TUnion {
539    TUnion::from_single(Cow::Borrowed(NUMERIC_ATOMIC))
540}
541
542#[inline]
543pub fn get_numeric_string() -> TUnion {
544    TUnion::from_single(Cow::Borrowed(NUMERIC_STRING_ATOMIC))
545}
546
547#[inline]
548pub fn get_lowercase_string() -> TUnion {
549    TUnion::from_single(Cow::Borrowed(LOWERCASE_STRING_ATOMIC))
550}
551
552#[inline]
553pub fn get_non_empty_lowercase_string() -> TUnion {
554    TUnion::from_single(Cow::Borrowed(NON_EMPTY_LOWERCASE_STRING_ATOMIC))
555}
556
557#[inline]
558pub fn get_non_empty_string() -> TUnion {
559    TUnion::from_single(Cow::Borrowed(NON_EMPTY_STRING_ATOMIC))
560}
561
562#[inline]
563pub fn get_empty_string() -> TUnion {
564    TUnion::from_single(Cow::Borrowed(&EMPTY_STRING_ATOMIC))
565}
566
567#[inline]
568pub fn get_truthy_string() -> TUnion {
569    TUnion::from_single(Cow::Borrowed(TRUTHY_STRING_ATOMIC))
570}
571
572#[inline]
573pub fn get_unspecified_literal_string() -> TUnion {
574    TUnion::from_single(Cow::Borrowed(UNSPECIFIED_LITERAL_STRING_ATOMIC))
575}
576
577#[inline]
578pub fn get_non_empty_unspecified_literal_string() -> TUnion {
579    TUnion::from_single(Cow::Borrowed(NON_EMPTY_UNSPECIFIED_LITERAL_STRING_ATOMIC))
580}
581
582#[inline]
583pub fn get_scalar() -> TUnion {
584    TUnion::from_single(Cow::Borrowed(SCALAR_ATOMIC))
585}
586
587#[inline]
588pub fn get_nullable_scalar() -> TUnion {
589    TUnion::new(Cow::Borrowed(NULL_SCALAR_ATOMIC_SLICE))
590}
591
592#[inline]
593pub fn get_mixed_iterable() -> TUnion {
594    TUnion::from_single(Cow::Borrowed(&MIXED_ITERABLE_ATOMIC))
595}
596
597#[inline]
598pub fn get_empty_keyed_array() -> TUnion {
599    TUnion::from_single(Cow::Borrowed(&EMPTY_KEYED_ARRAY_ATOMIC))
600}
601
602#[inline]
603pub fn get_mixed_list() -> TUnion {
604    get_list(get_mixed())
605}
606
607#[inline]
608pub fn get_mixed_keyed_array() -> TUnion {
609    get_keyed_array(get_arraykey(), get_mixed())
610}
611
612#[inline]
613pub fn get_mixed_callable() -> TUnion {
614    TUnion::from_single(Cow::Borrowed(&MIXED_CALLABLE_ATOMIC))
615}
616
617#[inline]
618pub fn get_mixed_closure() -> TUnion {
619    TUnion::from_single(Cow::Borrowed(&MIXED_CLOSURE_ATOMIC))
620}
621
622#[inline]
623pub fn get_named_object(name: Atom, type_resolution_context: Option<&TypeResolutionContext>) -> TUnion {
624    if let Some(type_resolution_context) = type_resolution_context
625        && let Some(defining_entities) = type_resolution_context.get_template_definition(&name)
626    {
627        return wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic {
628            kind: TClassLikeStringKind::Class,
629            parameter_name: name,
630            defining_entity: defining_entities[0].0,
631            constraint: Box::new((*(defining_entities[0].1.get_single())).clone()),
632        })));
633    }
634
635    wrap_atomic(TAtomic::Object(TObject::Named(TNamedObject::new(name))))
636}
637
638#[inline]
639pub fn get_iterable(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
640    wrap_atomic(TAtomic::Iterable(TIterable::new(Box::new(key_parameter), Box::new(value_parameter))))
641}
642
643#[inline]
644pub fn get_list(element_type: TUnion) -> TUnion {
645    wrap_atomic(TAtomic::Array(TArray::List(TList::new(Box::new(element_type)))))
646}
647
648#[inline]
649pub fn get_keyed_array(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
650    wrap_atomic(TAtomic::Array(TArray::Keyed(TKeyedArray::new_with_parameters(
651        Box::new(key_parameter),
652        Box::new(value_parameter),
653    ))))
654}
655
656#[inline]
657pub fn add_optional_union_type(base_type: TUnion, maybe_type: Option<&TUnion>, codebase: &CodebaseMetadata) -> TUnion {
658    if let Some(type_2) = maybe_type { add_union_type(base_type, type_2, codebase, false) } else { base_type }
659}
660
661#[inline]
662pub fn combine_optional_union_types(
663    type_1: Option<&TUnion>,
664    type_2: Option<&TUnion>,
665    codebase: &CodebaseMetadata,
666) -> TUnion {
667    match (type_1, type_2) {
668        (Some(type_1), Some(type_2)) => combine_union_types(type_1, type_2, codebase, false),
669        (Some(type_1), None) => type_1.clone(),
670        (None, Some(type_2)) => type_2.clone(),
671        (None, None) => get_mixed(),
672    }
673}
674
675#[inline]
676pub fn combine_union_types(
677    type_1: &TUnion,
678    type_2: &TUnion,
679    codebase: &CodebaseMetadata,
680    overwrite_empty_array: bool,
681) -> TUnion {
682    if type_1 == type_2 {
683        return type_1.clone();
684    }
685
686    let mut combined_type = if type_1.is_never() || type_1.is_never_template() {
687        type_2.clone()
688    } else if type_2.is_never() || type_2.is_never_template() {
689        type_1.clone()
690    } else if type_1.is_vanilla_mixed() && type_2.is_vanilla_mixed() {
691        get_mixed()
692    } else {
693        let mut all_atomic_types = type_1.types.clone().into_owned();
694        all_atomic_types.extend(type_2.types.clone().into_owned());
695
696        let mut result = TUnion::from_vec(combiner::combine(all_atomic_types, codebase, overwrite_empty_array));
697
698        if type_1.had_template && type_2.had_template {
699            result.had_template = true;
700        }
701
702        if type_1.reference_free && type_2.reference_free {
703            result.reference_free = true;
704        }
705
706        result
707    };
708
709    if type_1.possibly_undefined || type_2.possibly_undefined {
710        combined_type.possibly_undefined = true;
711    }
712
713    if type_1.possibly_undefined_from_try || type_2.possibly_undefined_from_try {
714        combined_type.possibly_undefined_from_try = true;
715    }
716
717    if type_1.ignore_falsable_issues || type_2.ignore_falsable_issues {
718        combined_type.ignore_falsable_issues = true;
719    }
720
721    combined_type
722}
723
724#[inline]
725pub fn add_union_type(
726    mut base_type: TUnion,
727    other_type: &TUnion,
728    codebase: &CodebaseMetadata,
729    overwrite_empty_array: bool,
730) -> TUnion {
731    if &base_type == other_type {
732        base_type.possibly_undefined |= other_type.possibly_undefined;
733        base_type.possibly_undefined_from_try |= other_type.possibly_undefined_from_try;
734        base_type.ignore_falsable_issues |= other_type.ignore_falsable_issues;
735        base_type.ignore_nullable_issues |= other_type.ignore_nullable_issues;
736
737        return base_type;
738    }
739
740    base_type.types = if base_type.is_vanilla_mixed() && other_type.is_vanilla_mixed() {
741        base_type.types
742    } else {
743        combine_union_types(&base_type, other_type, codebase, overwrite_empty_array).types
744    };
745
746    if !other_type.had_template {
747        base_type.had_template = false;
748    }
749
750    if !other_type.reference_free {
751        base_type.reference_free = false;
752    }
753
754    base_type.possibly_undefined |= other_type.possibly_undefined;
755    base_type.possibly_undefined_from_try |= other_type.possibly_undefined_from_try;
756    base_type.ignore_falsable_issues |= other_type.ignore_falsable_issues;
757    base_type.ignore_nullable_issues |= other_type.ignore_nullable_issues;
758
759    base_type
760}
761
762pub fn intersect_union_types(_type_1: &TUnion, _type_2: &TUnion, _codebase: &CodebaseMetadata) -> Option<TUnion> {
763    None
764}
765
766pub fn get_iterable_parameters(atomic: &TAtomic, codebase: &CodebaseMetadata) -> Option<(TUnion, TUnion)> {
767    if let Some(generator_parameters) = atomic.get_generator_parameters() {
768        return Some((generator_parameters.0, generator_parameters.1));
769    }
770
771    let parameters = 'parameters: {
772        match atomic {
773            TAtomic::Iterable(iterable) => Some((iterable.get_key_type().clone(), iterable.get_value_type().clone())),
774            TAtomic::Array(array_type) => Some(get_array_parameters(array_type, codebase)),
775            TAtomic::Object(object) => {
776                let name = object.get_name()?;
777                let traversable = atom("traversable");
778
779                let class_metadata = codebase.get_class_like(name)?;
780                if !codebase.is_instance_of(&class_metadata.name, &traversable) {
781                    break 'parameters None;
782                }
783
784                let traversable_metadata = codebase.get_class_like(&traversable)?;
785                let key_template = traversable_metadata.template_types.first().map(|(name, _)| name)?;
786                let value_template = traversable_metadata.template_types.get(1).map(|(name, _)| name)?;
787
788                let key_type = get_specialized_template_type(
789                    codebase,
790                    key_template,
791                    &traversable,
792                    class_metadata,
793                    object.get_type_parameters(),
794                )
795                .unwrap_or_else(get_mixed);
796
797                let value_type = get_specialized_template_type(
798                    codebase,
799                    value_template,
800                    &traversable,
801                    class_metadata,
802                    object.get_type_parameters(),
803                )
804                .unwrap_or_else(get_mixed);
805
806                Some((key_type, value_type))
807            }
808            _ => None,
809        }
810    };
811
812    if let Some((key_type, value_type)) = parameters {
813        return Some((key_type, value_type));
814    }
815
816    if let Some(intersection_types) = atomic.get_intersection_types() {
817        for intersection_type in intersection_types {
818            if let Some((key_type, value_type)) = get_iterable_parameters(intersection_type, codebase) {
819                return Some((key_type, value_type));
820            }
821        }
822    }
823
824    None
825}
826
827pub fn get_array_parameters(array_type: &TArray, codebase: &CodebaseMetadata) -> (TUnion, TUnion) {
828    match array_type {
829        TArray::Keyed(keyed_data) => {
830            let mut key_types = vec![];
831            let mut value_param;
832
833            if let Some((key_param, value_p)) = &keyed_data.parameters {
834                key_types.extend(key_param.types.clone().into_owned());
835                value_param = (**value_p).clone();
836            } else {
837                key_types.push(TAtomic::Never);
838                value_param = get_never();
839            }
840
841            if let Some(known_items) = &keyed_data.known_items {
842                for (key, (_, item_type)) in known_items {
843                    key_types.push(key.to_atomic());
844                    value_param = add_union_type(value_param, item_type, codebase, false);
845                }
846            }
847
848            let combined_key_types = combiner::combine(key_types, codebase, false);
849            let key_param_union = TUnion::from_vec(combined_key_types);
850
851            (key_param_union, value_param)
852        }
853        TArray::List(list_data) => {
854            let mut key_types = vec![];
855            let mut value_type = (*list_data.element_type).clone();
856
857            if let Some(known_elements) = &list_data.known_elements {
858                for (key_idx, (_, element_type)) in known_elements {
859                    key_types.push(TAtomic::Scalar(TScalar::literal_int(*key_idx as i64)));
860
861                    value_type = combine_union_types(element_type, &value_type, codebase, false);
862                }
863            }
864
865            if key_types.is_empty() || !value_type.is_never() {
866                if value_type.is_never() {
867                    key_types.push(TAtomic::Never);
868                } else {
869                    key_types.push(TAtomic::Scalar(TScalar::Integer(TInteger::non_negative())));
870                }
871            }
872
873            let key_type = TUnion::from_vec(combiner::combine(key_types, codebase, false));
874
875            (key_type, value_type)
876        }
877    }
878}
879
880pub fn get_iterable_value_parameter(atomic: &TAtomic, codebase: &CodebaseMetadata) -> Option<TUnion> {
881    if let Some(generator_parameters) = atomic.get_generator_parameters() {
882        return Some(generator_parameters.1);
883    }
884
885    let parameter = match atomic {
886        TAtomic::Iterable(iterable) => Some(iterable.get_value_type().clone()),
887        TAtomic::Array(array_type) => Some(get_array_value_parameter(array_type, codebase)),
888        TAtomic::Object(object) => {
889            let name = object.get_name()?;
890            let traversable = atom("traversable");
891
892            let class_metadata = codebase.get_class_like(name)?;
893            if !codebase.is_instance_of(&class_metadata.name, &traversable) {
894                return None;
895            }
896
897            let traversable_metadata = codebase.get_class_like(&traversable)?;
898            let value_template = traversable_metadata.template_types.get(1).map(|(name, _)| name)?;
899
900            get_specialized_template_type(
901                codebase,
902                value_template,
903                &traversable,
904                class_metadata,
905                object.get_type_parameters(),
906            )
907        }
908        _ => None,
909    };
910
911    if let Some(value_param) = parameter {
912        return Some(value_param);
913    }
914
915    if let Some(intersection_types) = atomic.get_intersection_types() {
916        for intersection_type in intersection_types {
917            if let Some(value_param) = get_iterable_value_parameter(intersection_type, codebase) {
918                return Some(value_param);
919            }
920        }
921    }
922
923    None
924}
925
926pub fn get_array_value_parameter(array_type: &TArray, codebase: &CodebaseMetadata) -> TUnion {
927    match array_type {
928        TArray::Keyed(keyed_data) => {
929            let mut value_param;
930
931            if let Some((_, value_p)) = &keyed_data.parameters {
932                value_param = (**value_p).clone();
933            } else {
934                value_param = get_never();
935            }
936
937            if let Some(known_items) = &keyed_data.known_items {
938                for (_, item_type) in known_items.values() {
939                    value_param = combine_union_types(item_type, &value_param, codebase, false);
940                }
941            }
942
943            value_param
944        }
945        TArray::List(list_data) => {
946            let mut value_param = (*list_data.element_type).clone();
947
948            if let Some(known_elements) = &list_data.known_elements {
949                for (_, element_type) in known_elements.values() {
950                    value_param = combine_union_types(element_type, &value_param, codebase, false);
951                }
952            }
953
954            value_param
955        }
956    }
957}
958
959/// Resolves a generic template from an ancestor class in the context of a descendant class.
960///
961/// This function correctly traverses the pre-calculated inheritance map to determine the
962/// concrete type of a template parameter.
963pub fn get_specialized_template_type(
964    codebase: &CodebaseMetadata,
965    template_name: &Atom,
966    template_defining_class_id: &Atom,
967    instantiated_class_metadata: &ClassLikeMetadata,
968    instantiated_type_parameters: Option<&[TUnion]>,
969) -> Option<TUnion> {
970    let defining_class_metadata = codebase.get_class_like(template_defining_class_id)?;
971
972    if defining_class_metadata.name == instantiated_class_metadata.name {
973        let index = instantiated_class_metadata.get_template_index_for_name(template_name)?;
974
975        let Some(instantiated_type_parameters) = instantiated_type_parameters else {
976            let type_map = instantiated_class_metadata.get_template_type(template_name)?;
977
978            return type_map.first().map(|(_, constraint)| constraint).cloned();
979        };
980
981        return instantiated_type_parameters.get(index).cloned();
982    }
983
984    let defining_template_type = defining_class_metadata.get_template_type(template_name)?;
985    let template_union = TUnion::from_vec(
986        defining_template_type
987            .iter()
988            .map(|(defining_entity, constraint)| {
989                TAtomic::GenericParameter(TGenericParameter {
990                    parameter_name: *template_name,
991                    defining_entity: *defining_entity,
992                    constraint: Box::new(constraint.clone()),
993                    intersection_types: None,
994                })
995            })
996            .collect::<Vec<_>>(),
997    );
998
999    let mut template_result = TemplateResult::default();
1000    for (defining_class, type_parameters_map) in &instantiated_class_metadata.template_extended_parameters {
1001        for (parameter_name, parameter_type) in type_parameters_map {
1002            template_result.add_lower_bound(
1003                *parameter_name,
1004                GenericParent::ClassLike(*defining_class),
1005                parameter_type.clone(),
1006            );
1007        }
1008    }
1009
1010    let mut template_type = inferred_type_replacer::replace(&template_union, &template_result, codebase);
1011    if let Some(type_parameters) = instantiated_type_parameters {
1012        let mut template_result = TemplateResult::default();
1013        for (i, parameter_type) in type_parameters.iter().enumerate() {
1014            if let Some(parameter_name) = instantiated_class_metadata.get_template_name_for_index(i) {
1015                template_result.add_lower_bound(
1016                    parameter_name,
1017                    GenericParent::ClassLike(instantiated_class_metadata.name),
1018                    parameter_type.clone(),
1019                );
1020            }
1021        }
1022
1023        if !template_result.lower_bounds.is_empty() {
1024            template_type = inferred_type_replacer::replace(&template_type, &template_result, codebase);
1025        }
1026    }
1027
1028    Some(template_type)
1029}