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