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