Skip to main content

mago_codex/ttype/
mod.rs

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