mago_codex/ttype/
mod.rs

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