mago_codex/ttype/
mod.rs

1use std::borrow::Cow;
2
3use mago_atom::Atom;
4use mago_atom::atom;
5
6use crate::get_class_like;
7use crate::is_instance_of;
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::resolution::TypeResolutionContext;
24use crate::ttype::shared::*;
25use crate::ttype::template::TemplateResult;
26use crate::ttype::template::inferred_type_replacer;
27use crate::ttype::union::TUnion;
28
29pub mod atomic;
30pub mod builder;
31pub mod cast;
32pub mod combination;
33pub mod combiner;
34pub mod comparator;
35pub mod error;
36pub mod expander;
37pub mod resolution;
38pub mod shared;
39pub mod template;
40pub mod union;
41
42/// A reference to a type in the type system, which can be either a union or an atomic type.
43#[derive(Clone, Copy, Debug)]
44pub enum TypeRef<'a> {
45    Union(&'a TUnion),
46    Atomic(&'a TAtomic),
47}
48
49/// A trait to be implemented by all types in the type system.
50pub trait TType {
51    /// Returns a vector of child type nodes that this type contains.
52    fn get_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
53        vec![]
54    }
55
56    /// Returns a vector of all child type nodes, including nested ones.
57    fn get_all_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
58        let mut child_nodes = self.get_child_nodes();
59        let mut all_child_nodes = vec![];
60
61        while let Some(child_node) = child_nodes.pop() {
62            let new_child_nodes = match child_node {
63                TypeRef::Union(union) => union.get_child_nodes(),
64                TypeRef::Atomic(atomic) => atomic.get_child_nodes(),
65            };
66
67            all_child_nodes.push(child_node);
68
69            child_nodes.extend(new_child_nodes);
70        }
71
72        all_child_nodes
73    }
74
75    /// Checks if this type can have intersection types (`&B&S`).
76    fn can_be_intersected(&self) -> bool {
77        false
78    }
79
80    /// Returns a slice of the additional intersection types (`&B&S`), if any. Contains boxed atomic types.
81    fn get_intersection_types(&self) -> Option<&[TAtomic]> {
82        None
83    }
84
85    /// Returns a mutable slice of the additional intersection types (`&B&S`), if any. Contains boxed atomic types.
86    fn get_intersection_types_mut(&mut self) -> Option<&mut Vec<TAtomic>> {
87        None
88    }
89
90    /// Checks if this type has intersection types.
91    fn has_intersection_types(&self) -> bool {
92        false
93    }
94
95    /// Adds an intersection type to this type.
96    ///
97    /// Returns `true` if the intersection type was added successfully,
98    ///  or `false` if this type does not support intersection types.
99    fn add_intersection_type(&mut self, _intersection_type: TAtomic) -> bool {
100        false
101    }
102
103    fn needs_population(&self) -> bool;
104
105    fn is_expandable(&self) -> bool;
106
107    /// Return a human-readable atom for this type, which is
108    /// suitable for use in error messages or debugging.
109    ///
110    /// The resulting identifier must be unique for the type,
111    /// but it does not have to be globally unique.
112    fn get_id(&self) -> Atom;
113}
114
115/// Implements the `TType` trait for `TypeRef`.
116impl<'a> TType for TypeRef<'a> {
117    fn get_child_nodes(&self) -> Vec<TypeRef<'a>> {
118        match self {
119            TypeRef::Union(ttype) => ttype.get_child_nodes(),
120            TypeRef::Atomic(ttype) => ttype.get_child_nodes(),
121        }
122    }
123
124    fn can_be_intersected(&self) -> bool {
125        match self {
126            TypeRef::Union(ttype) => ttype.can_be_intersected(),
127            TypeRef::Atomic(ttype) => ttype.can_be_intersected(),
128        }
129    }
130
131    fn get_intersection_types(&self) -> Option<&[TAtomic]> {
132        match self {
133            TypeRef::Union(ttype) => ttype.get_intersection_types(),
134            TypeRef::Atomic(ttype) => ttype.get_intersection_types(),
135        }
136    }
137
138    fn has_intersection_types(&self) -> bool {
139        match self {
140            TypeRef::Union(ttype) => ttype.has_intersection_types(),
141            TypeRef::Atomic(ttype) => ttype.has_intersection_types(),
142        }
143    }
144
145    fn needs_population(&self) -> bool {
146        match self {
147            TypeRef::Union(ttype) => ttype.needs_population(),
148            TypeRef::Atomic(ttype) => ttype.needs_population(),
149        }
150    }
151
152    fn is_expandable(&self) -> bool {
153        match self {
154            TypeRef::Union(ttype) => ttype.is_expandable(),
155            TypeRef::Atomic(ttype) => ttype.is_expandable(),
156        }
157    }
158
159    fn get_id(&self) -> Atom {
160        match self {
161            TypeRef::Union(ttype) => ttype.get_id(),
162            TypeRef::Atomic(ttype) => ttype.get_id(),
163        }
164    }
165}
166
167impl<'a> From<&'a TUnion> for TypeRef<'a> {
168    fn from(reference: &'a TUnion) -> Self {
169        TypeRef::Union(reference)
170    }
171}
172
173impl<'a> From<&'a TAtomic> for TypeRef<'a> {
174    fn from(reference: &'a TAtomic) -> Self {
175        TypeRef::Atomic(reference)
176    }
177}
178
179/// Creates a `TUnion` from a `TInteger`, using a canonical static type where possible.
180///
181/// This function is a key optimization point. It checks if the provided `TInteger`
182/// matches a common, reusable form (like "any integer" or "a positive integer").
183/// If it does, it returns a zero-allocation `TUnion` that borrows a static,
184/// shared instance.
185///
186/// For specific literal values or ranges that do not have a canonical static
187/// representation, it falls back to creating a new, owned `TUnion`, which
188/// involves a heap allocation.
189pub fn get_union_from_integer(integer: &TInteger) -> TUnion {
190    if integer.is_unspecified() {
191        return get_int();
192    }
193
194    if integer.is_positive() {
195        return get_positive_int();
196    }
197
198    if integer.is_negative() {
199        return get_negative_int();
200    }
201
202    if integer.is_non_negative() {
203        return get_non_negative_int();
204    }
205
206    if integer.is_non_positive() {
207        return get_non_positive_int();
208    }
209
210    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::Integer(*integer))))
211}
212
213#[inline]
214pub fn wrap_atomic(tinner: TAtomic) -> TUnion {
215    TUnion::from_single(Cow::Owned(tinner))
216}
217
218#[inline]
219pub fn get_int() -> TUnion {
220    TUnion::from_single(Cow::Borrowed(INT_ATOMIC))
221}
222
223#[inline]
224pub fn get_positive_int() -> TUnion {
225    TUnion::from_single(Cow::Borrowed(POSITIVE_INT_ATOMIC))
226}
227
228#[inline]
229pub fn get_negative_int() -> TUnion {
230    TUnion::from_single(Cow::Borrowed(NEGATIVE_INT_ATOMIC))
231}
232
233#[inline]
234pub fn get_non_positive_int() -> TUnion {
235    TUnion::from_single(Cow::Borrowed(NON_POSITIVE_INT_ATOMIC))
236}
237
238#[inline]
239pub fn get_non_negative_int() -> TUnion {
240    TUnion::from_single(Cow::Borrowed(NON_NEGATIVE_INT_ATOMIC))
241}
242
243#[inline]
244pub fn get_int_range(from: Option<i64>, to: Option<i64>) -> TUnion {
245    let atomic = match (from, to) {
246        (Some(from), Some(to)) => TAtomic::Scalar(TScalar::Integer(TInteger::Range(from, to))),
247        (Some(from), None) => {
248            if 0 == from {
249                return get_non_negative_int();
250            }
251
252            if 1 == from {
253                return get_positive_int();
254            }
255
256            TAtomic::Scalar(TScalar::Integer(TInteger::From(from)))
257        }
258        (None, Some(to)) => {
259            if 0 == to {
260                return get_non_positive_int();
261            }
262
263            if -1 == to {
264                return get_negative_int();
265            }
266
267            TAtomic::Scalar(TScalar::Integer(TInteger::To(to)))
268        }
269        (None, None) => return get_int(),
270    };
271
272    TUnion::from_single(Cow::Owned(atomic))
273}
274
275/// Returns a zero-allocation `TUnion` for the type `-1|0|1`.
276#[inline]
277pub fn get_signum_result() -> TUnion {
278    TUnion::new(Cow::Borrowed(SIGNUM_RESULT_SLICE))
279}
280
281/// Returns a zero-allocation `TUnion` for the integer literal `1`.
282#[inline]
283pub fn get_one_int() -> TUnion {
284    TUnion::from_single(Cow::Borrowed(ONE_INT_ATOMIC))
285}
286
287/// Returns a zero-allocation `TUnion` for the integer literal `0`.
288#[inline]
289pub fn get_zero_int() -> TUnion {
290    TUnion::from_single(Cow::Borrowed(ZERO_INT_ATOMIC))
291}
292
293/// Returns a zero-allocation `TUnion` for the integer literal `-1`.
294#[inline]
295pub fn get_minus_one_int() -> TUnion {
296    TUnion::from_single(Cow::Borrowed(MINUS_ONE_INT_ATOMIC))
297}
298
299#[inline]
300pub fn get_literal_int(value: i64) -> TUnion {
301    if value == 0 {
302        return get_zero_int();
303    }
304
305    if value == 1 {
306        return get_one_int();
307    }
308
309    if value == -1 {
310        return get_minus_one_int();
311    }
312
313    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_int(value))))
314}
315
316#[inline]
317pub fn get_int_or_float() -> TUnion {
318    TUnion::new(Cow::Borrowed(INT_FLOAT_ATOMIC_SLICE))
319}
320
321#[inline]
322pub fn get_int_or_string() -> TUnion {
323    TUnion::new(Cow::Borrowed(INT_STRING_ATOMIC_SLICE))
324}
325
326#[inline]
327pub fn get_nullable_int() -> TUnion {
328    TUnion::new(Cow::Borrowed(NULL_INT_ATOMIC_SLICE))
329}
330
331#[inline]
332pub fn get_nullable_float() -> TUnion {
333    TUnion::new(Cow::Borrowed(NULL_FLOAT_ATOMIC_SLICE))
334}
335
336#[inline]
337pub fn get_nullable_object() -> TUnion {
338    TUnion::new(Cow::Borrowed(NULL_OBJECT_ATOMIC_SLICE))
339}
340
341#[inline]
342pub fn get_nullable_string() -> TUnion {
343    TUnion::new(Cow::Borrowed(NULL_STRING_ATOMIC_SLICE))
344}
345
346#[inline]
347pub fn get_string() -> TUnion {
348    TUnion::from_single(Cow::Borrowed(STRING_ATOMIC))
349}
350
351/// Returns a zero-allocation `TUnion` for a `string` with the specified properties.
352///
353/// This function maps all possible boolean property combinations to a canonical,
354/// static `TAtomic` instance, avoiding heap allocations for common string types.
355pub fn get_string_with_props(is_numeric: bool, is_truthy: bool, is_non_empty: bool, is_lowercase: bool) -> TUnion {
356    let atomic_ref = match (is_numeric, is_truthy, is_non_empty, is_lowercase) {
357        // is_numeric = true
358        (true, true, _, _) => NUMERIC_TRUTHY_STRING_ATOMIC,
359        (true, false, _, _) => NUMERIC_STRING_ATOMIC,
360        // is_numeric = false, is_truthy = true
361        (false, true, _, false) => TRUTHY_STRING_ATOMIC,
362        (false, true, _, true) => TRUTHY_LOWERCASE_STRING_ATOMIC,
363        // is_numeric = false, is_truthy = false
364        (false, false, false, false) => STRING_ATOMIC,
365        (false, false, false, true) => LOWERCASE_STRING_ATOMIC,
366        (false, false, true, false) => NON_EMPTY_STRING_ATOMIC,
367        (false, false, true, true) => NON_EMPTY_LOWERCASE_STRING_ATOMIC,
368    };
369
370    TUnion::from_single(Cow::Borrowed(atomic_ref))
371}
372
373#[inline]
374pub fn get_literal_class_string(value: Atom) -> TUnion {
375    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::literal(value)))))
376}
377
378#[inline]
379pub fn get_class_string() -> TUnion {
380    TUnion::from_single(Cow::Borrowed(CLASS_STRING_ATOMIC))
381}
382
383#[inline]
384pub fn get_class_string_of_type(constraint: TAtomic) -> TUnion {
385    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::class_string_of_type(
386        constraint,
387    )))))
388}
389
390#[inline]
391pub fn get_interface_string() -> TUnion {
392    TUnion::from_single(Cow::Borrowed(INTERFACE_STRING_ATOMIC))
393}
394
395#[inline]
396pub fn get_interface_string_of_type(constraint: TAtomic) -> TUnion {
397    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(
398        TClassLikeString::interface_string_of_type(constraint),
399    ))))
400}
401
402#[inline]
403pub fn get_enum_string() -> TUnion {
404    TUnion::from_single(Cow::Borrowed(ENUM_STRING_ATOMIC))
405}
406
407#[inline]
408pub fn get_enum_string_of_type(constraint: TAtomic) -> TUnion {
409    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::enum_string_of_type(
410        constraint,
411    )))))
412}
413
414#[inline]
415pub fn get_trait_string() -> TUnion {
416    TUnion::from_single(Cow::Borrowed(TRAIT_STRING_ATOMIC))
417}
418
419#[inline]
420pub fn get_trait_string_of_type(constraint: TAtomic) -> TUnion {
421    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::trait_string_of_type(
422        constraint,
423    )))))
424}
425
426#[inline]
427pub fn get_literal_string(value: Atom) -> TUnion {
428    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_string(value))))
429}
430
431#[inline]
432pub fn get_float() -> TUnion {
433    TUnion::from_single(Cow::Borrowed(FLOAT_ATOMIC))
434}
435
436#[inline]
437pub fn get_literal_float(v: f64) -> TUnion {
438    TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::literal_float(v))))
439}
440
441#[inline]
442pub fn get_mixed() -> TUnion {
443    TUnion::from_single(Cow::Borrowed(MIXED_ATOMIC))
444}
445
446#[inline]
447pub fn get_isset_from_mixed_mixed() -> TUnion {
448    TUnion::from_single(Cow::Borrowed(ISSET_FROM_LOOP_MIXED_ATOMIC))
449}
450
451pub fn get_mixed_maybe_from_loop(from_loop_isset: bool) -> TUnion {
452    if from_loop_isset { get_isset_from_mixed_mixed() } else { get_mixed() }
453}
454
455#[inline]
456pub fn get_never() -> TUnion {
457    TUnion::from_single(Cow::Borrowed(NEVER_ATOMIC))
458}
459
460#[inline]
461pub fn get_resource() -> TUnion {
462    TUnion::from_single(Cow::Borrowed(RESOURCE_ATOMIC))
463}
464
465#[inline]
466pub fn get_closed_resource() -> TUnion {
467    TUnion::from_single(Cow::Borrowed(CLOSED_RESOURCE_ATOMIC))
468}
469
470#[inline]
471pub fn get_open_resource() -> TUnion {
472    TUnion::from_single(Cow::Borrowed(OPEN_RESOURCE_ATOMIC))
473}
474
475#[inline]
476pub fn get_placeholder() -> TUnion {
477    TUnion::from_single(Cow::Borrowed(PLACEHOLDER_ATOMIC))
478}
479
480#[inline]
481pub fn get_void() -> TUnion {
482    TUnion::from_single(Cow::Borrowed(VOID_ATOMIC))
483}
484
485#[inline]
486pub fn get_null() -> TUnion {
487    TUnion::from_single(Cow::Borrowed(NULL_ATOMIC))
488}
489
490#[inline]
491pub fn get_arraykey() -> TUnion {
492    TUnion::from_single(Cow::Borrowed(ARRAYKEY_ATOMIC))
493}
494
495#[inline]
496pub fn get_bool() -> TUnion {
497    TUnion::from_single(Cow::Borrowed(BOOL_ATOMIC))
498}
499
500#[inline]
501pub fn get_false() -> TUnion {
502    TUnion::from_single(Cow::Borrowed(FALSE_ATOMIC))
503}
504
505#[inline]
506pub fn get_true() -> TUnion {
507    TUnion::from_single(Cow::Borrowed(TRUE_ATOMIC))
508}
509
510#[inline]
511pub fn get_object() -> TUnion {
512    TUnion::from_single(Cow::Borrowed(OBJECT_ATOMIC))
513}
514
515#[inline]
516pub fn get_numeric() -> TUnion {
517    TUnion::from_single(Cow::Borrowed(NUMERIC_ATOMIC))
518}
519
520#[inline]
521pub fn get_numeric_string() -> TUnion {
522    TUnion::from_single(Cow::Borrowed(NUMERIC_STRING_ATOMIC))
523}
524
525#[inline]
526pub fn get_lowercase_string() -> TUnion {
527    TUnion::from_single(Cow::Borrowed(LOWERCASE_STRING_ATOMIC))
528}
529
530#[inline]
531pub fn get_non_empty_lowercase_string() -> TUnion {
532    TUnion::from_single(Cow::Borrowed(NON_EMPTY_LOWERCASE_STRING_ATOMIC))
533}
534
535#[inline]
536pub fn get_non_empty_string() -> TUnion {
537    TUnion::from_single(Cow::Borrowed(NON_EMPTY_STRING_ATOMIC))
538}
539
540#[inline]
541pub fn get_empty_string() -> TUnion {
542    TUnion::from_single(Cow::Borrowed(&EMPTY_STRING_ATOMIC))
543}
544
545#[inline]
546pub fn get_truthy_string() -> TUnion {
547    TUnion::from_single(Cow::Borrowed(TRUTHY_STRING_ATOMIC))
548}
549
550#[inline]
551pub fn get_unspecified_literal_string() -> TUnion {
552    TUnion::from_single(Cow::Borrowed(UNSPECIFIED_LITERAL_STRING_ATOMIC))
553}
554
555#[inline]
556pub fn get_non_empty_unspecified_literal_string() -> TUnion {
557    TUnion::from_single(Cow::Borrowed(NON_EMPTY_UNSPECIFIED_LITERAL_STRING_ATOMIC))
558}
559
560#[inline]
561pub fn get_scalar() -> TUnion {
562    TUnion::from_single(Cow::Borrowed(SCALAR_ATOMIC))
563}
564
565#[inline]
566pub fn get_nullable_scalar() -> TUnion {
567    TUnion::new(Cow::Borrowed(NULL_SCALAR_ATOMIC_SLICE))
568}
569
570#[inline]
571pub fn get_mixed_iterable() -> TUnion {
572    TUnion::from_single(Cow::Borrowed(&MIXED_ITERABLE_ATOMIC))
573}
574
575#[inline]
576pub fn get_empty_keyed_array() -> TUnion {
577    TUnion::from_single(Cow::Borrowed(&EMPTY_KEYED_ARRAY_ATOMIC))
578}
579
580#[inline]
581pub fn get_mixed_list() -> TUnion {
582    get_list(get_mixed())
583}
584
585#[inline]
586pub fn get_mixed_keyed_array() -> TUnion {
587    get_keyed_array(get_arraykey(), get_mixed())
588}
589
590#[inline]
591pub fn get_mixed_callable() -> TUnion {
592    TUnion::from_single(Cow::Borrowed(&MIXED_CALLABLE_ATOMIC))
593}
594
595#[inline]
596pub fn get_mixed_closure() -> TUnion {
597    TUnion::from_single(Cow::Borrowed(&MIXED_CLOSURE_ATOMIC))
598}
599
600#[inline]
601pub fn get_named_object(name: Atom, type_resolution_context: Option<&TypeResolutionContext>) -> TUnion {
602    if let Some(type_resolution_context) = type_resolution_context
603        && let Some(defining_entities) = type_resolution_context.get_template_definition(&name)
604    {
605        return wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic {
606            kind: TClassLikeStringKind::Class,
607            parameter_name: name,
608            defining_entity: defining_entities[0].0,
609            constraint: Box::new((*(defining_entities[0].1.get_single())).clone()),
610        })));
611    }
612
613    wrap_atomic(TAtomic::Object(TObject::Named(TNamedObject::new(name))))
614}
615
616#[inline]
617pub fn get_iterable(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
618    wrap_atomic(TAtomic::Iterable(TIterable::new(Box::new(key_parameter), Box::new(value_parameter))))
619}
620
621#[inline]
622pub fn get_list(element_type: TUnion) -> TUnion {
623    wrap_atomic(TAtomic::Array(TArray::List(TList::new(Box::new(element_type)))))
624}
625
626#[inline]
627pub fn get_keyed_array(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
628    wrap_atomic(TAtomic::Array(TArray::Keyed(TKeyedArray::new_with_parameters(
629        Box::new(key_parameter),
630        Box::new(value_parameter),
631    ))))
632}
633
634#[inline]
635pub fn add_optional_union_type(base_type: TUnion, maybe_type: Option<&TUnion>, codebase: &CodebaseMetadata) -> TUnion {
636    if let Some(type_2) = maybe_type { add_union_type(base_type, type_2, codebase, false) } else { base_type }
637}
638
639#[inline]
640pub fn combine_optional_union_types(
641    type_1: Option<&TUnion>,
642    type_2: Option<&TUnion>,
643    codebase: &CodebaseMetadata,
644) -> TUnion {
645    match (type_1, type_2) {
646        (Some(type_1), Some(type_2)) => combine_union_types(type_1, type_2, codebase, false),
647        (Some(type_1), None) => type_1.clone(),
648        (None, Some(type_2)) => type_2.clone(),
649        (None, None) => get_mixed(),
650    }
651}
652
653#[inline]
654pub fn combine_union_types(
655    type_1: &TUnion,
656    type_2: &TUnion,
657    codebase: &CodebaseMetadata,
658    overwrite_empty_array: bool,
659) -> TUnion {
660    if type_1 == type_2 {
661        return type_1.clone();
662    }
663
664    let mut combined_type = if type_1.is_never() || type_1.is_never_template() {
665        type_2.clone()
666    } else if type_2.is_never() || type_2.is_never_template() {
667        type_1.clone()
668    } else if type_1.is_vanilla_mixed() && type_2.is_vanilla_mixed() {
669        get_mixed()
670    } else {
671        let mut all_atomic_types = type_1.types.clone().into_owned();
672        all_atomic_types.extend(type_2.types.clone().into_owned());
673
674        let mut result = TUnion::from_vec(combiner::combine(all_atomic_types, codebase, overwrite_empty_array));
675
676        if type_1.had_template && type_2.had_template {
677            result.had_template = true;
678        }
679
680        if type_1.reference_free && type_2.reference_free {
681            result.reference_free = true;
682        }
683
684        result
685    };
686
687    if type_1.possibly_undefined || type_2.possibly_undefined {
688        combined_type.possibly_undefined = true;
689    }
690
691    if type_1.possibly_undefined_from_try || type_2.possibly_undefined_from_try {
692        combined_type.possibly_undefined_from_try = true;
693    }
694
695    if type_1.ignore_falsable_issues || type_2.ignore_falsable_issues {
696        combined_type.ignore_falsable_issues = true;
697    }
698
699    combined_type
700}
701
702#[inline]
703pub fn add_union_type(
704    mut base_type: TUnion,
705    other_type: &TUnion,
706    codebase: &CodebaseMetadata,
707    overwrite_empty_array: bool,
708) -> TUnion {
709    if &base_type == other_type {
710        base_type.possibly_undefined |= other_type.possibly_undefined;
711        base_type.possibly_undefined_from_try |= other_type.possibly_undefined_from_try;
712        base_type.ignore_falsable_issues |= other_type.ignore_falsable_issues;
713        base_type.ignore_nullable_issues |= other_type.ignore_nullable_issues;
714
715        return base_type;
716    }
717
718    base_type.types = if base_type.is_vanilla_mixed() && other_type.is_vanilla_mixed() {
719        base_type.types
720    } else {
721        combine_union_types(&base_type, other_type, codebase, overwrite_empty_array).types
722    };
723
724    if !other_type.had_template {
725        base_type.had_template = false;
726    }
727
728    if !other_type.reference_free {
729        base_type.reference_free = false;
730    }
731
732    base_type.possibly_undefined |= other_type.possibly_undefined;
733    base_type.possibly_undefined_from_try |= other_type.possibly_undefined_from_try;
734    base_type.ignore_falsable_issues |= other_type.ignore_falsable_issues;
735    base_type.ignore_nullable_issues |= other_type.ignore_nullable_issues;
736
737    base_type
738}
739
740pub fn intersect_union_types(_type_1: &TUnion, _type_2: &TUnion, _codebase: &CodebaseMetadata) -> Option<TUnion> {
741    None
742}
743
744pub fn get_iterable_parameters(atomic: &TAtomic, codebase: &CodebaseMetadata) -> Option<(TUnion, TUnion)> {
745    if let Some(generator_parameters) = atomic.get_generator_parameters() {
746        return Some((generator_parameters.0, generator_parameters.1));
747    }
748
749    let parameters = 'parameters: {
750        match atomic {
751            TAtomic::Iterable(iterable) => Some((iterable.get_key_type().clone(), iterable.get_value_type().clone())),
752            TAtomic::Array(array_type) => Some(get_array_parameters(array_type, codebase)),
753            TAtomic::Object(object) => {
754                let name = object.get_name()?;
755                let traversable = atom("traversable");
756
757                let class_metadata = get_class_like(codebase, name)?;
758                if !is_instance_of(codebase, &class_metadata.name, &traversable) {
759                    break 'parameters None;
760                }
761
762                let traversable_metadata = get_class_like(codebase, &traversable)?;
763                let key_template = traversable_metadata.template_types.first().map(|(name, _)| name)?;
764                let value_template = traversable_metadata.template_types.get(1).map(|(name, _)| name)?;
765
766                let key_type = get_specialized_template_type(
767                    codebase,
768                    key_template,
769                    &traversable,
770                    class_metadata,
771                    object.get_type_parameters(),
772                )
773                .unwrap_or_else(get_mixed);
774
775                let value_type = get_specialized_template_type(
776                    codebase,
777                    value_template,
778                    &traversable,
779                    class_metadata,
780                    object.get_type_parameters(),
781                )
782                .unwrap_or_else(get_mixed);
783
784                Some((key_type, value_type))
785            }
786            _ => None,
787        }
788    };
789
790    if let Some((key_type, value_type)) = parameters {
791        return Some((key_type, value_type));
792    }
793
794    if let Some(intersection_types) = atomic.get_intersection_types() {
795        for intersection_type in intersection_types {
796            if let Some((key_type, value_type)) = get_iterable_parameters(intersection_type, codebase) {
797                return Some((key_type, value_type));
798            }
799        }
800    }
801
802    None
803}
804
805pub fn get_array_parameters(array_type: &TArray, codebase: &CodebaseMetadata) -> (TUnion, TUnion) {
806    match array_type {
807        TArray::Keyed(keyed_data) => {
808            let mut key_types = vec![];
809            let mut value_param;
810
811            if let Some((key_param, value_p)) = &keyed_data.parameters {
812                key_types.extend(key_param.types.clone().into_owned());
813                value_param = (**value_p).clone();
814            } else {
815                key_types.push(TAtomic::Never);
816                value_param = get_never();
817            }
818
819            if let Some(known_items) = &keyed_data.known_items {
820                for (key, (_, item_type)) in known_items {
821                    key_types.push(key.to_atomic());
822                    value_param = add_union_type(value_param, item_type, codebase, false);
823                }
824            }
825
826            let combined_key_types = combiner::combine(key_types, codebase, false);
827            let key_param_union = TUnion::from_vec(combined_key_types);
828
829            (key_param_union, value_param)
830        }
831        TArray::List(list_data) => {
832            let mut key_types = vec![];
833            let mut value_type = (*list_data.element_type).clone();
834
835            if let Some(known_elements) = &list_data.known_elements {
836                for (key_idx, (_, element_type)) in known_elements {
837                    key_types.push(TAtomic::Scalar(TScalar::literal_int(*key_idx as i64)));
838
839                    value_type = combine_union_types(element_type, &value_type, codebase, false);
840                }
841            }
842
843            if key_types.is_empty() || !value_type.is_never() {
844                if value_type.is_never() {
845                    key_types.push(TAtomic::Never);
846                } else {
847                    key_types.push(TAtomic::Scalar(TScalar::Integer(TInteger::non_negative())));
848                }
849            }
850
851            let key_type = TUnion::from_vec(combiner::combine(key_types, codebase, false));
852
853            (key_type, value_type)
854        }
855    }
856}
857
858pub fn get_iterable_value_parameter(atomic: &TAtomic, codebase: &CodebaseMetadata) -> Option<TUnion> {
859    if let Some(generator_parameters) = atomic.get_generator_parameters() {
860        return Some(generator_parameters.1);
861    }
862
863    let parameter = match atomic {
864        TAtomic::Iterable(iterable) => Some(iterable.get_value_type().clone()),
865        TAtomic::Array(array_type) => Some(get_array_value_parameter(array_type, codebase)),
866        TAtomic::Object(object) => {
867            let name = object.get_name()?;
868            let traversable = atom("traversable");
869
870            let class_metadata = get_class_like(codebase, name)?;
871            if !is_instance_of(codebase, &class_metadata.name, &traversable) {
872                return None;
873            }
874
875            let traversable_metadata = get_class_like(codebase, &traversable)?;
876            let value_template = traversable_metadata.template_types.get(1).map(|(name, _)| name)?;
877
878            get_specialized_template_type(
879                codebase,
880                value_template,
881                &traversable,
882                class_metadata,
883                object.get_type_parameters(),
884            )
885        }
886        _ => None,
887    };
888
889    if let Some(value_param) = parameter {
890        return Some(value_param);
891    }
892
893    if let Some(intersection_types) = atomic.get_intersection_types() {
894        for intersection_type in intersection_types {
895            if let Some(value_param) = get_iterable_value_parameter(intersection_type, codebase) {
896                return Some(value_param);
897            }
898        }
899    }
900
901    None
902}
903
904pub fn get_array_value_parameter(array_type: &TArray, codebase: &CodebaseMetadata) -> TUnion {
905    match array_type {
906        TArray::Keyed(keyed_data) => {
907            let mut value_param;
908
909            if let Some((_, value_p)) = &keyed_data.parameters {
910                value_param = (**value_p).clone();
911            } else {
912                value_param = get_never();
913            }
914
915            if let Some(known_items) = &keyed_data.known_items {
916                for (_, item_type) in known_items.values() {
917                    value_param = combine_union_types(item_type, &value_param, codebase, false);
918                }
919            }
920
921            value_param
922        }
923        TArray::List(list_data) => {
924            let mut value_param = (*list_data.element_type).clone();
925
926            if let Some(known_elements) = &list_data.known_elements {
927                for (_, element_type) in known_elements.values() {
928                    value_param = combine_union_types(element_type, &value_param, codebase, false);
929                }
930            }
931
932            value_param
933        }
934    }
935}
936
937/// Resolves a generic template from an ancestor class in the context of a descendant class.
938///
939/// This function correctly traverses the pre-calculated inheritance map to determine the
940/// concrete type of a template parameter.
941pub fn get_specialized_template_type(
942    codebase: &CodebaseMetadata,
943    template_name: &Atom,
944    template_defining_class_id: &Atom,
945    instantiated_class_metadata: &ClassLikeMetadata,
946    instantiated_type_parameters: Option<&[TUnion]>,
947) -> Option<TUnion> {
948    let defining_class_metadata = get_class_like(codebase, template_defining_class_id)?;
949
950    if defining_class_metadata.name == instantiated_class_metadata.name {
951        let index = instantiated_class_metadata.get_template_index_for_name(template_name)?;
952
953        let Some(instantiated_type_parameters) = instantiated_type_parameters else {
954            let type_map = instantiated_class_metadata.get_template_type(template_name)?;
955
956            return type_map.first().map(|(_, constraint)| constraint).cloned();
957        };
958
959        return instantiated_type_parameters.get(index).cloned();
960    }
961
962    let defining_template_type = defining_class_metadata.get_template_type(template_name)?;
963    let template_union = TUnion::from_vec(
964        defining_template_type
965            .iter()
966            .map(|(defining_entity, constraint)| {
967                TAtomic::GenericParameter(TGenericParameter {
968                    parameter_name: *template_name,
969                    defining_entity: *defining_entity,
970                    constraint: Box::new(constraint.clone()),
971                    intersection_types: None,
972                })
973            })
974            .collect::<Vec<_>>(),
975    );
976
977    let mut template_result = TemplateResult::default();
978    for (defining_class, type_parameters_map) in &instantiated_class_metadata.template_extended_parameters {
979        for (parameter_name, parameter_type) in type_parameters_map {
980            template_result.add_lower_bound(
981                *parameter_name,
982                GenericParent::ClassLike(*defining_class),
983                parameter_type.clone(),
984            );
985        }
986    }
987
988    let mut template_type = inferred_type_replacer::replace(&template_union, &template_result, codebase);
989    if let Some(type_parameters) = instantiated_type_parameters {
990        let mut template_result = TemplateResult::default();
991        for (i, parameter_type) in type_parameters.iter().enumerate() {
992            if let Some(parameter_name) = instantiated_class_metadata.get_template_name_for_index(i) {
993                template_result.add_lower_bound(
994                    parameter_name,
995                    GenericParent::ClassLike(instantiated_class_metadata.name),
996                    parameter_type.clone(),
997                );
998            }
999        }
1000
1001        if !template_result.lower_bounds.is_empty() {
1002            template_type = inferred_type_replacer::replace(&template_type, &template_result, codebase);
1003        }
1004    }
1005
1006    Some(template_type)
1007}