Skip to main content

mago_codex/ttype/
mod.rs

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