Skip to main content

mago_codex/ttype/
mod.rs

1use std::borrow::Cow;
2use std::rc::Rc;
3use std::sync::Arc;
4
5use mago_atom::Atom;
6use mago_atom::atom;
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<'a> {
112    Union(&'a TUnion),
113    Atomic(&'a 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) -> Atom;
184
185    fn get_pretty_id(&self) -> Atom {
186        self.get_pretty_id_with_indent(0)
187    }
188
189    fn get_pretty_id_with_indent(&self, indent: usize) -> Atom;
190}
191
192/// Implements the `TType` trait for `TypeRef`.
193impl<'a> TType for TypeRef<'a> {
194    fn get_child_nodes(&self) -> Vec<TypeRef<'a>> {
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) -> Atom {
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) -> Atom {
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<'a> From<&'a TUnion> for TypeRef<'a> {
259    fn from(reference: &'a TUnion) -> Self {
260        TypeRef::Union(reference)
261    }
262}
263
264impl<'a> From<&'a TAtomic> for TypeRef<'a> {
265    fn from(reference: &'a 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]
488pub fn get_string_with_props(
489    is_numeric: bool,
490    is_truthy: bool,
491    is_non_empty: bool,
492    is_callable: bool,
493    casing: TStringCasing,
494) -> TUnion {
495    if is_callable {
496        return match casing {
497            TStringCasing::Lowercase => TUnion::from_single(Cow::Borrowed(LOWERCASE_CALLABLE_STRING_ATOMIC)),
498            TStringCasing::Uppercase => TUnion::from_single(Cow::Borrowed(UPPERCASE_CALLABLE_STRING_ATOMIC)),
499            TStringCasing::Unspecified => TUnion::from_single(Cow::Borrowed(CALLABLE_STRING_ATOMIC)),
500        };
501    }
502
503    let atomic_ref = match (is_numeric, is_truthy, is_non_empty, casing) {
504        // is_numeric = true
505        (true, true, _, _) => NUMERIC_TRUTHY_STRING_ATOMIC,
506        (true, false, _, _) => NUMERIC_STRING_ATOMIC,
507        // is_numeric = false, is_truthy = true
508        (false, true, _, TStringCasing::Unspecified) => TRUTHY_STRING_ATOMIC,
509        (false, true, _, TStringCasing::Uppercase) => TRUTHY_UPPERCASE_STRING_ATOMIC,
510        (false, true, _, TStringCasing::Lowercase) => TRUTHY_LOWERCASE_STRING_ATOMIC,
511        // is_numeric = false, is_truthy = false
512        (false, false, false, TStringCasing::Unspecified) => STRING_ATOMIC,
513        (false, false, false, TStringCasing::Uppercase) => UPPERCASE_STRING_ATOMIC,
514        (false, false, false, TStringCasing::Lowercase) => LOWERCASE_STRING_ATOMIC,
515        (false, false, true, TStringCasing::Unspecified) => NON_EMPTY_STRING_ATOMIC,
516        (false, false, true, TStringCasing::Uppercase) => NON_EMPTY_UPPERCASE_STRING_ATOMIC,
517        (false, false, true, TStringCasing::Lowercase) => NON_EMPTY_LOWERCASE_STRING_ATOMIC,
518    };
519
520    TUnion::from_single(Cow::Borrowed(atomic_ref))
521}
522
523#[inline]
524#[must_use]
525pub fn get_literal_class_string(value: Atom) -> TUnion {
526    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::literal(value)))))
527}
528
529#[inline]
530#[must_use]
531pub fn get_class_string() -> TUnion {
532    TUnion::from_single(Cow::Borrowed(CLASS_STRING_ATOMIC))
533}
534
535#[inline]
536#[must_use]
537pub fn get_class_string_of_type(constraint: TAtomic) -> TUnion {
538    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::class_string_of_type(
539        constraint,
540    )))))
541}
542
543#[inline]
544#[must_use]
545pub fn get_interface_string() -> TUnion {
546    TUnion::from_single(Cow::Borrowed(INTERFACE_STRING_ATOMIC))
547}
548
549#[inline]
550#[must_use]
551pub fn get_interface_string_of_type(constraint: TAtomic) -> TUnion {
552    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(
553        TClassLikeString::interface_string_of_type(constraint),
554    ))))
555}
556
557#[inline]
558#[must_use]
559pub fn get_enum_string() -> TUnion {
560    TUnion::from_single(Cow::Borrowed(ENUM_STRING_ATOMIC))
561}
562
563#[inline]
564#[must_use]
565pub fn get_enum_string_of_type(constraint: TAtomic) -> TUnion {
566    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::enum_string_of_type(
567        constraint,
568    )))))
569}
570
571#[inline]
572#[must_use]
573pub fn get_trait_string() -> TUnion {
574    TUnion::from_single(Cow::Borrowed(TRAIT_STRING_ATOMIC))
575}
576
577#[inline]
578#[must_use]
579pub fn get_trait_string_of_type(constraint: TAtomic) -> TUnion {
580    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::trait_string_of_type(
581        constraint,
582    )))))
583}
584
585#[inline]
586#[must_use]
587pub fn get_literal_string(value: Atom) -> TUnion {
588    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_string(value))))
589}
590
591#[inline]
592#[must_use]
593pub fn get_float() -> TUnion {
594    TUnion::from_single(Cow::Borrowed(FLOAT_ATOMIC))
595}
596
597#[inline]
598#[must_use]
599pub fn get_literal_float(v: f64) -> TUnion {
600    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_float(v))))
601}
602
603#[inline]
604#[must_use]
605pub fn get_mixed() -> TUnion {
606    TUnion::from_single(Cow::Borrowed(MIXED_ATOMIC))
607}
608
609#[inline]
610#[must_use]
611pub fn get_truthy_mixed() -> TUnion {
612    TUnion::from_single(Cow::Borrowed(TRUTHY_MIXED_ATOMIC))
613}
614
615#[inline]
616#[must_use]
617pub fn get_isset_from_mixed_mixed() -> TUnion {
618    TUnion::from_single(Cow::Borrowed(ISSET_FROM_LOOP_MIXED_ATOMIC))
619}
620
621#[must_use]
622pub fn get_mixed_maybe_from_loop(from_loop_isset: bool) -> TUnion {
623    if from_loop_isset { get_isset_from_mixed_mixed() } else { get_mixed() }
624}
625
626#[inline]
627#[must_use]
628pub fn get_never() -> TUnion {
629    TUnion::from_single(Cow::Borrowed(NEVER_ATOMIC))
630}
631
632#[inline]
633#[must_use]
634pub fn get_resource() -> TUnion {
635    TUnion::from_single(Cow::Borrowed(RESOURCE_ATOMIC))
636}
637
638#[inline]
639#[must_use]
640pub fn get_closed_resource() -> TUnion {
641    TUnion::from_single(Cow::Borrowed(CLOSED_RESOURCE_ATOMIC))
642}
643
644#[inline]
645#[must_use]
646pub fn get_open_resource() -> TUnion {
647    TUnion::from_single(Cow::Borrowed(OPEN_RESOURCE_ATOMIC))
648}
649
650#[inline]
651#[must_use]
652pub fn get_placeholder() -> TUnion {
653    TUnion::from_single(Cow::Borrowed(PLACEHOLDER_ATOMIC))
654}
655
656#[inline]
657#[must_use]
658pub fn get_void() -> TUnion {
659    TUnion::from_single(Cow::Borrowed(VOID_ATOMIC))
660}
661
662#[inline]
663#[must_use]
664pub fn get_null() -> TUnion {
665    TUnion::from_single(Cow::Borrowed(NULL_ATOMIC))
666}
667
668#[inline]
669#[must_use]
670pub fn get_undefined_null() -> TUnion {
671    let mut null = TUnion::from_single(Cow::Borrowed(NULL_ATOMIC));
672    null.set_possibly_undefined(true, None);
673    null
674}
675
676#[inline]
677#[must_use]
678pub fn get_arraykey() -> TUnion {
679    TUnion::from_single(Cow::Borrowed(ARRAYKEY_ATOMIC))
680}
681
682#[inline]
683#[must_use]
684pub fn get_bool() -> TUnion {
685    TUnion::from_single(Cow::Borrowed(BOOL_ATOMIC))
686}
687
688#[inline]
689#[must_use]
690pub fn get_false() -> TUnion {
691    TUnion::from_single(Cow::Borrowed(FALSE_ATOMIC))
692}
693
694#[inline]
695#[must_use]
696pub fn get_true() -> TUnion {
697    TUnion::from_single(Cow::Borrowed(TRUE_ATOMIC))
698}
699
700#[inline]
701#[must_use]
702pub fn get_object() -> TUnion {
703    TUnion::from_single(Cow::Borrowed(OBJECT_ATOMIC))
704}
705
706#[inline]
707#[must_use]
708pub fn get_numeric() -> TUnion {
709    TUnion::from_single(Cow::Borrowed(NUMERIC_ATOMIC))
710}
711
712#[inline]
713#[must_use]
714pub fn get_callable_string() -> TUnion {
715    TUnion::from_single(Cow::Borrowed(CALLABLE_STRING_ATOMIC))
716}
717
718pub fn get_numeric_string() -> TUnion {
719    TUnion::from_single(Cow::Borrowed(NUMERIC_STRING_ATOMIC))
720}
721
722#[inline]
723#[must_use]
724pub fn get_lowercase_string() -> TUnion {
725    TUnion::from_single(Cow::Borrowed(LOWERCASE_STRING_ATOMIC))
726}
727
728#[inline]
729#[must_use]
730pub fn get_non_empty_lowercase_string() -> TUnion {
731    TUnion::from_single(Cow::Borrowed(NON_EMPTY_LOWERCASE_STRING_ATOMIC))
732}
733
734#[inline]
735#[must_use]
736pub fn get_uppercase_string() -> TUnion {
737    TUnion::from_single(Cow::Borrowed(UPPERCASE_STRING_ATOMIC))
738}
739
740#[inline]
741#[must_use]
742pub fn get_non_empty_uppercase_string() -> TUnion {
743    TUnion::from_single(Cow::Borrowed(NON_EMPTY_UPPERCASE_STRING_ATOMIC))
744}
745
746#[inline]
747#[must_use]
748pub fn get_non_empty_string() -> TUnion {
749    TUnion::from_single(Cow::Borrowed(NON_EMPTY_STRING_ATOMIC))
750}
751
752#[inline]
753#[must_use]
754pub fn get_empty_string() -> TUnion {
755    TUnion::from_single(Cow::Borrowed(&EMPTY_STRING_ATOMIC))
756}
757
758#[inline]
759#[must_use]
760pub fn get_truthy_string() -> TUnion {
761    TUnion::from_single(Cow::Borrowed(TRUTHY_STRING_ATOMIC))
762}
763
764#[inline]
765#[must_use]
766pub fn get_unspecified_literal_string() -> TUnion {
767    TUnion::from_single(Cow::Borrowed(UNSPECIFIED_LITERAL_STRING_ATOMIC))
768}
769
770#[inline]
771#[must_use]
772pub fn get_non_empty_unspecified_literal_string() -> TUnion {
773    TUnion::from_single(Cow::Borrowed(NON_EMPTY_UNSPECIFIED_LITERAL_STRING_ATOMIC))
774}
775
776#[inline]
777#[must_use]
778pub fn get_scalar() -> TUnion {
779    TUnion::from_single(Cow::Borrowed(SCALAR_ATOMIC))
780}
781
782#[inline]
783#[must_use]
784pub fn get_nullable_scalar() -> TUnion {
785    TUnion::new(Cow::Borrowed(NULL_SCALAR_ATOMIC_SLICE))
786}
787
788#[inline]
789#[must_use]
790pub fn get_mixed_iterable() -> TUnion {
791    TUnion::from_single(Cow::Borrowed(&MIXED_ITERABLE_ATOMIC))
792}
793
794#[inline]
795#[must_use]
796pub fn get_empty_keyed_array() -> TUnion {
797    TUnion::from_single(Cow::Borrowed(&EMPTY_KEYED_ARRAY_ATOMIC))
798}
799
800#[inline]
801#[must_use]
802pub fn get_mixed_list() -> TUnion {
803    get_list(get_mixed())
804}
805
806#[inline]
807#[must_use]
808pub fn get_mixed_keyed_array() -> TUnion {
809    get_keyed_array(get_arraykey(), get_mixed())
810}
811
812#[inline]
813#[must_use]
814pub fn get_mixed_callable() -> TUnion {
815    TUnion::from_single(Cow::Borrowed(&MIXED_CALLABLE_ATOMIC))
816}
817
818#[inline]
819#[must_use]
820pub fn get_mixed_closure() -> TUnion {
821    TUnion::from_single(Cow::Borrowed(&MIXED_CLOSURE_ATOMIC))
822}
823
824#[inline]
825#[must_use]
826pub fn get_named_object(name: Atom, type_resolution_context: Option<&TypeResolutionContext>) -> TUnion {
827    if let Some(type_resolution_context) = type_resolution_context
828        && let Some(defining_entities) = type_resolution_context.get_template_definition(name)
829    {
830        let first = &defining_entities[0];
831        return wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic {
832            kind: TClassLikeStringKind::Class,
833            parameter_name: name,
834            defining_entity: first.defining_entity,
835            constraint: Arc::new((*(first.constraint.get_single())).clone()),
836        })));
837    }
838
839    wrap_atomic(TAtomic::Object(TObject::Named(TNamedObject::new(name))))
840}
841
842#[inline]
843#[must_use]
844pub fn get_iterable(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
845    wrap_atomic(TAtomic::Iterable(TIterable::new(Arc::new(key_parameter), Arc::new(value_parameter))))
846}
847
848#[inline]
849#[must_use]
850pub fn get_list(element_type: TUnion) -> TUnion {
851    wrap_atomic(TAtomic::Array(TArray::List(TList::new(Arc::new(element_type)))))
852}
853
854#[inline]
855#[must_use]
856pub fn get_non_empty_list(element_type: TUnion) -> TUnion {
857    wrap_atomic(TAtomic::Array(TArray::List(TList::new_non_empty(Arc::new(element_type)))))
858}
859
860#[inline]
861#[must_use]
862pub fn get_keyed_array(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
863    wrap_atomic(TAtomic::Array(TArray::Keyed(TKeyedArray::new_with_parameters(
864        Arc::new(key_parameter),
865        Arc::new(value_parameter),
866    ))))
867}
868
869#[inline]
870#[must_use]
871pub fn add_optional_union_type(base_type: TUnion, maybe_type: Option<&TUnion>, codebase: &CodebaseMetadata) -> TUnion {
872    if let Some(type_2) = maybe_type {
873        add_union_type(base_type, type_2, codebase, combiner::CombinerOptions::default())
874    } else {
875        base_type
876    }
877}
878
879/// Reference-counted variant of [`add_optional_union_type`].
880#[must_use]
881pub fn add_optional_union_type_rc(
882    base_type: &Rc<TUnion>,
883    maybe_type: Option<&TUnion>,
884    codebase: &CodebaseMetadata,
885) -> Rc<TUnion> {
886    match maybe_type {
887        Some(type_2) => {
888            Rc::new(add_union_type((**base_type).clone(), type_2, codebase, combiner::CombinerOptions::default()))
889        }
890        None => Rc::clone(base_type),
891    }
892}
893
894#[inline]
895#[must_use]
896pub fn combine_optional_union_types(
897    type_1: Option<&TUnion>,
898    type_2: Option<&TUnion>,
899    codebase: &CodebaseMetadata,
900) -> TUnion {
901    match (type_1, type_2) {
902        (Some(type_1), Some(type_2)) => {
903            combine_union_types(type_1, type_2, codebase, combiner::CombinerOptions::default())
904        }
905        (Some(type_1), None) => type_1.clone(),
906        (None, Some(type_2)) => type_2.clone(),
907        (None, None) => get_mixed(),
908    }
909}
910
911/// Reference-counted variant of [`combine_union_types`].
912#[inline]
913#[must_use]
914pub fn combine_union_types_rc(
915    type_1: &Rc<TUnion>,
916    type_2: &Rc<TUnion>,
917    codebase: &CodebaseMetadata,
918    options: combiner::CombinerOptions,
919) -> Rc<TUnion> {
920    if Rc::ptr_eq(type_1, type_2) {
921        return Rc::clone(type_1);
922    }
923
924    Rc::new(combine_union_types(type_1, type_2, codebase, options))
925}
926
927#[inline]
928#[must_use]
929pub fn combine_union_types(
930    type_1: &TUnion,
931    type_2: &TUnion,
932    codebase: &CodebaseMetadata,
933    options: combiner::CombinerOptions,
934) -> TUnion {
935    if type_1 == type_2 {
936        return type_1.clone();
937    }
938
939    let mut combined_type = if type_1.is_never() || type_1.is_never_template() {
940        type_2.clone()
941    } else if type_2.is_never() || type_2.is_never_template() {
942        type_1.clone()
943    } else if type_1.is_vanilla_mixed() && type_2.is_vanilla_mixed() {
944        get_mixed()
945    } else {
946        let mut all_atomic_types = type_1.types.to_vec();
947        all_atomic_types.extend(type_2.types.iter().cloned());
948
949        let mut result = TUnion::from_vec(combiner::combine(all_atomic_types, codebase, options));
950
951        if type_1.had_template() && type_2.had_template() {
952            result.set_had_template(true);
953        }
954
955        if type_1.reference_free() && type_2.reference_free() {
956            result.set_reference_free(true);
957        }
958
959        result
960    };
961
962    if type_1.possibly_undefined() || type_2.possibly_undefined() {
963        combined_type.set_possibly_undefined(true, None);
964    }
965
966    if type_1.possibly_undefined_from_try() || type_2.possibly_undefined_from_try() {
967        combined_type.set_possibly_undefined_from_try(true);
968    }
969
970    if type_1.ignore_falsable_issues() || type_2.ignore_falsable_issues() {
971        combined_type.set_ignore_falsable_issues(true);
972    }
973
974    combined_type
975}
976
977#[inline]
978#[must_use]
979pub fn add_union_type(
980    mut base_type: TUnion,
981    other_type: &TUnion,
982    codebase: &CodebaseMetadata,
983    options: combiner::CombinerOptions,
984) -> TUnion {
985    if &base_type != other_type {
986        base_type.types = if base_type.is_vanilla_mixed() && other_type.is_vanilla_mixed() {
987            base_type.types
988        } else {
989            combine_union_types(&base_type, other_type, codebase, options).types
990        };
991
992        if !other_type.had_template() {
993            base_type.set_had_template(false);
994        }
995
996        if !other_type.reference_free() {
997            base_type.set_reference_free(false);
998        }
999    }
1000
1001    if other_type.possibly_undefined() {
1002        base_type.set_possibly_undefined(true, None);
1003    }
1004    if other_type.possibly_undefined_from_try() {
1005        base_type.set_possibly_undefined_from_try(true);
1006    }
1007    if other_type.ignore_falsable_issues() {
1008        base_type.set_ignore_falsable_issues(true);
1009    }
1010    if other_type.ignore_nullable_issues() {
1011        base_type.set_ignore_nullable_issues(true);
1012    }
1013
1014    base_type
1015}
1016
1017#[must_use]
1018pub fn intersect_union_types(type_1: &TUnion, type_2: &TUnion, codebase: &CodebaseMetadata) -> Option<TUnion> {
1019    if type_1 == type_2 {
1020        return Some(type_1.clone());
1021    }
1022
1023    if type_1.is_never() || type_2.is_never() {
1024        return Some(get_never());
1025    }
1026
1027    let mut intersection_performed = false;
1028
1029    if type_1.is_mixed() {
1030        if type_2.is_mixed() {
1031            return Some(get_mixed());
1032        }
1033
1034        return Some(type_2.clone());
1035    } else if type_2.is_mixed() {
1036        return Some(type_1.clone());
1037    }
1038
1039    let mut intersected_atomic_types = vec![];
1040    for type_1_atomic in type_1.types.iter() {
1041        for type_2_atomic in type_2.types.iter() {
1042            if let Some(intersection_atomic) =
1043                intersect_atomic_types(type_1_atomic, type_2_atomic, codebase, &mut intersection_performed)
1044            {
1045                intersected_atomic_types.push(intersection_atomic);
1046            }
1047        }
1048    }
1049
1050    let mut combined_type: Option<TUnion> = None;
1051    if !intersected_atomic_types.is_empty() {
1052        let combined_vec = combiner::combine(intersected_atomic_types, codebase, combiner::CombinerOptions::default());
1053        if !combined_vec.is_empty() {
1054            combined_type = Some(TUnion::from_vec(combined_vec));
1055        }
1056    }
1057
1058    // If atomic-level intersection didn't yield a result, check for subtyping at the union level.
1059    if !intersection_performed {
1060        if union_comparator::is_contained_by(
1061            codebase,
1062            type_1,
1063            type_2,
1064            false,
1065            false,
1066            false,
1067            &mut ComparisonResult::default(),
1068        ) {
1069            intersection_performed = true;
1070            combined_type = Some(type_1.clone());
1071        } else if union_comparator::is_contained_by(
1072            codebase,
1073            type_2,
1074            type_1,
1075            false,
1076            false,
1077            false,
1078            &mut ComparisonResult::default(),
1079        ) {
1080            intersection_performed = true;
1081            combined_type = Some(type_2.clone());
1082        }
1083    }
1084
1085    if let Some(mut final_type) = combined_type {
1086        final_type.set_possibly_undefined(
1087            type_1.possibly_undefined() && type_2.possibly_undefined(),
1088            Some(type_1.possibly_undefined_from_try() && type_2.possibly_undefined_from_try()),
1089        );
1090        final_type.set_ignore_falsable_issues(type_1.ignore_falsable_issues() && type_2.ignore_falsable_issues());
1091        final_type.set_ignore_nullable_issues(type_1.ignore_nullable_issues() && type_2.ignore_nullable_issues());
1092
1093        return Some(final_type);
1094    }
1095
1096    if !intersection_performed && type_1.get_id() != type_2.get_id() {
1097        return None;
1098    }
1099
1100    None
1101}
1102
1103/// This is the core logic used by `intersect_union_types`.
1104fn intersect_atomic_types(
1105    type_1: &TAtomic,
1106    type_2: &TAtomic,
1107    codebase: &CodebaseMetadata,
1108    intersection_performed: &mut bool,
1109) -> Option<TAtomic> {
1110    if let (TAtomic::Scalar(TScalar::Integer(t1_int)), TAtomic::Scalar(TScalar::Integer(t2_int))) = (type_1, type_2) {
1111        let (min1, max1) = t1_int.get_bounds();
1112        let (min2, max2) = t2_int.get_bounds();
1113
1114        let new_min = match (min1, min2) {
1115            (Some(m1), Some(m2)) => Some(m1.max(m2)),
1116            (Some(m), None) | (None, Some(m)) => Some(m),
1117            (None, None) => None,
1118        };
1119
1120        let new_max = match (max1, max2) {
1121            (Some(m1), Some(m2)) => Some(m1.min(m2)),
1122            (Some(m), None) | (None, Some(m)) => Some(m),
1123            (None, None) => None,
1124        };
1125
1126        let intersected_int = if let (Some(min), Some(max)) = (new_min, new_max) {
1127            if min > max {
1128                return None;
1129            }
1130
1131            if min == max { TInteger::Literal(min) } else { TInteger::Range(min, max) }
1132        } else if let Some(min) = new_min {
1133            TInteger::From(min)
1134        } else if let Some(max) = new_max {
1135            TInteger::To(max)
1136        } else {
1137            TInteger::Unspecified
1138        };
1139
1140        *intersection_performed = true;
1141        return Some(TAtomic::Scalar(TScalar::Integer(intersected_int)));
1142    }
1143
1144    let t1_union = TUnion::from_atomic(type_1.clone());
1145    let t2_union = TUnion::from_atomic(type_2.clone());
1146
1147    let mut narrower_type = None;
1148    let mut wider_type = None;
1149
1150    if union_comparator::is_contained_by(
1151        codebase,
1152        &t2_union,
1153        &t1_union,
1154        false,
1155        false,
1156        false,
1157        &mut ComparisonResult::default(),
1158    ) {
1159        narrower_type = Some(type_2);
1160        wider_type = Some(type_1);
1161    } else if union_comparator::is_contained_by(
1162        codebase,
1163        &t1_union,
1164        &t2_union,
1165        false,
1166        false,
1167        false,
1168        &mut ComparisonResult::default(),
1169    ) {
1170        narrower_type = Some(type_1);
1171        wider_type = Some(type_2);
1172    }
1173
1174    if let (Some(narrower), Some(wider)) = (narrower_type, wider_type) {
1175        *intersection_performed = true;
1176        let mut result = narrower.clone();
1177
1178        if narrower.can_be_intersected() && wider.can_be_intersected() {
1179            let mut wider_clone = wider.clone();
1180            if let Some(types) = wider_clone.get_intersection_types_mut() {
1181                types.clear();
1182            }
1183            result.add_intersection_type(wider_clone);
1184
1185            if let Some(wider_intersections) = wider.get_intersection_types() {
1186                for i_type in wider_intersections {
1187                    result.add_intersection_type(i_type.clone());
1188                }
1189            }
1190        }
1191        return Some(result);
1192    }
1193
1194    if let (TAtomic::Scalar(TScalar::String(s1)), TAtomic::Scalar(TScalar::String(s2))) = (type_1, type_2) {
1195        if let (Some(v1), Some(v2)) = (&s1.get_known_literal_value(), &s2.get_known_literal_value())
1196            && v1 != v2
1197        {
1198            return None;
1199        }
1200
1201        let combined = TAtomic::Scalar(TScalar::String(TString {
1202            is_numeric: s1.is_numeric || s2.is_numeric,
1203            is_truthy: s1.is_truthy || s2.is_truthy,
1204            is_non_empty: s1.is_non_empty || s2.is_non_empty,
1205            is_callable: false,
1206            casing: match (s1.casing, s2.casing) {
1207                (TStringCasing::Lowercase, TStringCasing::Lowercase) => TStringCasing::Lowercase,
1208                (TStringCasing::Uppercase, TStringCasing::Uppercase) => TStringCasing::Uppercase,
1209                _ => TStringCasing::Unspecified,
1210            },
1211            literal: if s1.is_literal_origin() && s2.is_literal_origin() {
1212                Some(TStringLiteral::Unspecified)
1213            } else {
1214                None
1215            },
1216        }));
1217        *intersection_performed = true;
1218        return Some(combined);
1219    }
1220
1221    if type_1.can_be_intersected() && type_2.can_be_intersected() {
1222        if let (TAtomic::Object(TObject::Named(n1)), TAtomic::Object(TObject::Named(n2))) = (type_1, type_2)
1223            && let (Some(c1), Some(c2)) = (codebase.get_class_like(&n1.name), codebase.get_class_like(&n2.name))
1224            && !c1.kind.is_interface()
1225            && !c1.kind.is_trait()
1226            && !c2.kind.is_interface()
1227            && !c2.kind.is_trait()
1228        {
1229            return None;
1230        }
1231
1232        let mut result = type_1.clone();
1233        result.add_intersection_type(type_2.clone());
1234        if let Some(intersections) = type_2.get_intersection_types() {
1235            for i in intersections {
1236                result.add_intersection_type(i.clone());
1237            }
1238        }
1239
1240        *intersection_performed = true;
1241        return Some(result);
1242    }
1243
1244    None
1245}
1246
1247pub fn get_iterable_parameters(atomic: &TAtomic, codebase: &CodebaseMetadata) -> Option<(TUnion, TUnion)> {
1248    if let Some(generator_parameters) = atomic.get_generator_parameters() {
1249        let mut key_type = generator_parameters.0;
1250        let mut value_type = generator_parameters.1;
1251
1252        expander::expand_union(codebase, &mut key_type, &TypeExpansionOptions::default());
1253        expander::expand_union(codebase, &mut value_type, &TypeExpansionOptions::default());
1254
1255        return Some((key_type, value_type));
1256    }
1257
1258    let parameters = 'parameters: {
1259        match atomic {
1260            TAtomic::Iterable(iterable) => {
1261                let mut key_type = iterable.get_key_type().clone();
1262                let mut value_type = iterable.get_value_type().clone();
1263
1264                expander::expand_union(codebase, &mut key_type, &TypeExpansionOptions::default());
1265                expander::expand_union(codebase, &mut value_type, &TypeExpansionOptions::default());
1266
1267                Some((key_type, value_type))
1268            }
1269            TAtomic::Array(array_type) => {
1270                let (mut key_type, mut value_type) = get_array_parameters(array_type, codebase);
1271
1272                expander::expand_union(codebase, &mut key_type, &TypeExpansionOptions::default());
1273                expander::expand_union(codebase, &mut value_type, &TypeExpansionOptions::default());
1274
1275                Some((key_type, value_type))
1276            }
1277            TAtomic::Object(object) => {
1278                let name = object.get_name()?;
1279                let traversable = atom("traversable");
1280                let iterator = atom("iterator");
1281                let iterator_aggregate = atom("iteratoraggregate");
1282
1283                let class_metadata = codebase.get_class_like(&name)?;
1284                if !codebase.is_instance_of(&class_metadata.name, &traversable) {
1285                    break 'parameters None;
1286                }
1287
1288                let is_iterator_interface = name == iterator || name == traversable || name == iterator_aggregate;
1289                if !is_iterator_interface
1290                    && codebase.is_instance_of(&class_metadata.name, &iterator)
1291                    && let (Some(key_type), Some(value_type)) = (
1292                        get_iterator_method_return_type(codebase, name, "key"),
1293                        get_iterator_method_return_type(codebase, name, "current"),
1294                    )
1295                {
1296                    let contains_generic_param = |t: &TUnion| t.types.iter().any(atomic::TAtomic::is_generic_parameter);
1297
1298                    if !key_type.is_mixed()
1299                        && !value_type.is_mixed()
1300                        && !contains_generic_param(&key_type)
1301                        && !contains_generic_param(&value_type)
1302                    {
1303                        return Some((key_type, value_type));
1304                    }
1305                }
1306
1307                let traversable_metadata = codebase.get_class_like(&traversable)?;
1308                let key_template = traversable_metadata.template_types.get_index(0).map(|(name, _)| *name)?;
1309                let value_template = traversable_metadata.template_types.get_index(1).map(|(name, _)| *name)?;
1310
1311                let key_type = get_specialized_template_type(
1312                    codebase,
1313                    key_template,
1314                    traversable,
1315                    class_metadata,
1316                    object.get_type_parameters(),
1317                )
1318                .unwrap_or_else(get_mixed);
1319
1320                let value_type = get_specialized_template_type(
1321                    codebase,
1322                    value_template,
1323                    traversable,
1324                    class_metadata,
1325                    object.get_type_parameters(),
1326                )
1327                .unwrap_or_else(get_mixed);
1328
1329                Some((key_type, value_type))
1330            }
1331            _ => None,
1332        }
1333    };
1334
1335    if let Some((key_type, value_type)) = parameters {
1336        return Some((key_type, value_type));
1337    }
1338
1339    if let Some(intersection_types) = atomic.get_intersection_types() {
1340        for intersection_type in intersection_types {
1341            if let Some((key_type, value_type)) = get_iterable_parameters(intersection_type, codebase) {
1342                return Some((key_type, value_type));
1343            }
1344        }
1345    }
1346
1347    None
1348}
1349
1350#[must_use]
1351pub fn get_array_parameters(array_type: &TArray, codebase: &CodebaseMetadata) -> (TUnion, TUnion) {
1352    match array_type {
1353        TArray::Keyed(keyed_data) => {
1354            let mut key_types = vec![];
1355            let mut value_param;
1356
1357            if let Some((key_param, value_p)) = &keyed_data.parameters {
1358                key_types.extend(key_param.types.iter().cloned());
1359                value_param = (**value_p).clone();
1360            } else {
1361                key_types.push(TAtomic::Never);
1362                value_param = get_never();
1363            }
1364
1365            if let Some(known_items) = &keyed_data.known_items {
1366                for (key, (_, item_type)) in known_items {
1367                    key_types.push(key.to_atomic());
1368                    value_param =
1369                        add_union_type(value_param, item_type, codebase, combiner::CombinerOptions::default());
1370                }
1371            }
1372
1373            if key_types.is_empty() {
1374                key_types.push(TAtomic::Never);
1375            }
1376
1377            let combined_key_types = combiner::combine(key_types, codebase, combiner::CombinerOptions::default());
1378            let key_param_union = TUnion::from_vec(combined_key_types);
1379
1380            (key_param_union, value_param)
1381        }
1382        TArray::List(list_data) => {
1383            let mut key_types = vec![];
1384            let mut value_type = (*list_data.element_type).clone();
1385
1386            if let Some(known_elements) = &list_data.known_elements {
1387                for (key_idx, (_, element_type)) in known_elements {
1388                    key_types.push(TAtomic::Scalar(TScalar::literal_int(*key_idx as i64)));
1389
1390                    value_type =
1391                        combine_union_types(element_type, &value_type, codebase, combiner::CombinerOptions::default());
1392                }
1393            }
1394
1395            if key_types.is_empty() || !value_type.is_never() {
1396                if value_type.is_never() {
1397                    key_types.push(TAtomic::Never);
1398                } else {
1399                    key_types.push(TAtomic::Scalar(TScalar::Integer(TInteger::non_negative())));
1400                }
1401            }
1402
1403            let key_type =
1404                TUnion::from_vec(combiner::combine(key_types, codebase, combiner::CombinerOptions::default()));
1405
1406            (key_type, value_type)
1407        }
1408    }
1409}
1410
1411#[must_use]
1412pub fn get_iterable_value_parameter(atomic: &TAtomic, codebase: &CodebaseMetadata) -> Option<TUnion> {
1413    if let Some(generator_parameters) = atomic.get_generator_parameters() {
1414        return Some(generator_parameters.1);
1415    }
1416
1417    let parameter = match atomic {
1418        TAtomic::Iterable(iterable) => Some(iterable.get_value_type().clone()),
1419        TAtomic::Array(array_type) => Some(get_array_value_parameter(array_type, codebase)),
1420        TAtomic::Object(object) => {
1421            let name = object.get_name()?;
1422            let traversable = atom("traversable");
1423
1424            let class_metadata = codebase.get_class_like(&name)?;
1425            if !codebase.is_instance_of(&class_metadata.name, &traversable) {
1426                return None;
1427            }
1428
1429            let traversable_metadata = codebase.get_class_like(&traversable)?;
1430            let value_template = traversable_metadata.template_types.get_index(1).map(|(name, _)| *name)?;
1431
1432            get_specialized_template_type(
1433                codebase,
1434                value_template,
1435                traversable,
1436                class_metadata,
1437                object.get_type_parameters(),
1438            )
1439        }
1440        _ => None,
1441    };
1442
1443    if let Some(value_param) = parameter {
1444        return Some(value_param);
1445    }
1446
1447    if let Some(intersection_types) = atomic.get_intersection_types() {
1448        for intersection_type in intersection_types {
1449            if let Some(value_param) = get_iterable_value_parameter(intersection_type, codebase) {
1450                return Some(value_param);
1451            }
1452        }
1453    }
1454
1455    None
1456}
1457
1458#[must_use]
1459pub fn get_array_value_parameter(array_type: &TArray, codebase: &CodebaseMetadata) -> TUnion {
1460    match array_type {
1461        TArray::Keyed(keyed_data) => {
1462            let mut value_param;
1463
1464            if let Some((_, value_p)) = &keyed_data.parameters {
1465                value_param = (**value_p).clone();
1466            } else {
1467                value_param = get_never();
1468            }
1469
1470            if let Some(known_items) = &keyed_data.known_items {
1471                for (_, item_type) in known_items.values() {
1472                    value_param =
1473                        combine_union_types(item_type, &value_param, codebase, combiner::CombinerOptions::default());
1474                }
1475            }
1476
1477            value_param
1478        }
1479        TArray::List(list_data) => {
1480            let mut value_param = (*list_data.element_type).clone();
1481
1482            if let Some(known_elements) = &list_data.known_elements {
1483                for (_, element_type) in known_elements.values() {
1484                    value_param =
1485                        combine_union_types(element_type, &value_param, codebase, combiner::CombinerOptions::default());
1486                }
1487            }
1488
1489            value_param
1490        }
1491    }
1492}
1493
1494/// Resolves a generic template from an ancestor class in the context of a descendant class.
1495///
1496/// This function correctly traverses the pre-calculated inheritance map to determine the
1497/// concrete type of a template parameter.
1498#[must_use]
1499pub fn get_specialized_template_type(
1500    codebase: &CodebaseMetadata,
1501    template_name: Atom,
1502    template_defining_class_id: Atom,
1503    instantiated_class_metadata: &ClassLikeMetadata,
1504    instantiated_type_parameters: Option<&[TUnion]>,
1505) -> Option<TUnion> {
1506    let defining_class_metadata = codebase.get_class_like(&template_defining_class_id)?;
1507
1508    if defining_class_metadata.name == instantiated_class_metadata.name {
1509        let index = instantiated_class_metadata.get_template_index_for_name(template_name)?;
1510
1511        let Some(instantiated_type_parameters) = instantiated_type_parameters else {
1512            let template = instantiated_class_metadata.get_template_type(template_name)?;
1513            let mut result = template.constraint.clone();
1514
1515            expander::expand_union(codebase, &mut result, &TypeExpansionOptions::default());
1516
1517            return Some(result);
1518        };
1519
1520        let mut result = instantiated_type_parameters.get(index).cloned()?;
1521
1522        expander::expand_union(codebase, &mut result, &TypeExpansionOptions::default());
1523
1524        return Some(result);
1525    }
1526
1527    let template = defining_class_metadata.get_template_type(template_name)?;
1528    let template_union = wrap_atomic(TAtomic::GenericParameter(TGenericParameter {
1529        parameter_name: template_name,
1530        defining_entity: template.defining_entity,
1531        constraint: Arc::new(template.constraint.clone()),
1532        intersection_types: None,
1533    }));
1534
1535    let mut template_result = TemplateResult::default();
1536    for (defining_class, template_parameters) in &instantiated_class_metadata.template_extended_parameters {
1537        for (parameter_name, parameter_type) in template_parameters {
1538            template_result.add_lower_bound(
1539                *parameter_name,
1540                GenericParent::ClassLike(*defining_class),
1541                parameter_type.clone(),
1542            );
1543        }
1544    }
1545
1546    let mut template_type = inferred_type_replacer::replace(&template_union, &template_result, codebase);
1547    if let Some(type_parameters) = instantiated_type_parameters {
1548        let mut template_result = TemplateResult::default();
1549        for (i, parameter_type) in type_parameters.iter().enumerate() {
1550            if let Some(parameter_name) = instantiated_class_metadata.get_template_name_for_index(i) {
1551                template_result.add_lower_bound(
1552                    parameter_name,
1553                    GenericParent::ClassLike(instantiated_class_metadata.name),
1554                    parameter_type.clone(),
1555                );
1556            }
1557        }
1558
1559        if !template_result.lower_bounds.is_empty() {
1560            template_type = inferred_type_replacer::replace(&template_type, &template_result, codebase);
1561        }
1562    }
1563
1564    expander::expand_union(codebase, &mut template_type, &TypeExpansionOptions::default());
1565
1566    Some(template_type)
1567}
1568
1569fn get_iterator_method_return_type(codebase: &CodebaseMetadata, class_name: Atom, method_name: &str) -> Option<TUnion> {
1570    let method = codebase.get_declaring_method(&class_name, method_name)?;
1571    let return_type_meta = method.return_type_metadata.as_ref()?;
1572    let mut return_type = return_type_meta.type_union.clone();
1573    expander::expand_union(codebase, &mut return_type, &TypeExpansionOptions::default());
1574    Some(return_type)
1575}