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