Skip to main content

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