Skip to main content

mago_codex/ttype/
mod.rs

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