Skip to main content

mago_codex/ttype/
mod.rs

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