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