mago_codex/ttype/
mod.rs

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