mago_codex/ttype/
mod.rs

1use mago_interner::StringIdentifier;
2use mago_interner::ThreadedInterner;
3
4use crate::get_class_like;
5use crate::is_instance_of;
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::callable::TCallable;
14use crate::ttype::atomic::callable::TCallableSignature;
15use crate::ttype::atomic::generic::TGenericParameter;
16use crate::ttype::atomic::iterable::TIterable;
17use crate::ttype::atomic::mixed::TMixed;
18use crate::ttype::atomic::object::TObject;
19use crate::ttype::atomic::object::named::TNamedObject;
20use crate::ttype::atomic::resource::TResource;
21use crate::ttype::atomic::scalar::TScalar;
22use crate::ttype::atomic::scalar::class_like_string::TClassLikeString;
23use crate::ttype::atomic::scalar::class_like_string::TClassLikeStringKind;
24use crate::ttype::atomic::scalar::int::TInteger;
25use crate::ttype::atomic::scalar::string::TString;
26use crate::ttype::resolution::TypeResolutionContext;
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 template;
41pub mod union;
42
43/// A reference to a type in the type system, which can be either a union or an atomic type.
44#[derive(Clone, Copy, Debug)]
45pub enum TypeRef<'a> {
46    Union(&'a TUnion),
47    Atomic(&'a TAtomic),
48}
49
50/// A trait to be implemented by all types in the type system.
51pub trait TType {
52    /// Returns a vector of child type nodes that this type contains.
53    fn get_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
54        vec![]
55    }
56
57    /// Returns a vector of all child type nodes, including nested ones.
58    fn get_all_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
59        let mut child_nodes = self.get_child_nodes();
60        let mut all_child_nodes = vec![];
61
62        while let Some(child_node) = child_nodes.pop() {
63            let new_child_nodes = match child_node {
64                TypeRef::Union(union) => union.get_child_nodes(),
65                TypeRef::Atomic(atomic) => atomic.get_child_nodes(),
66            };
67
68            all_child_nodes.push(child_node);
69
70            child_nodes.extend(new_child_nodes);
71        }
72
73        all_child_nodes
74    }
75
76    /// Checks if this type can have intersection types (`&B&S`).
77    fn can_be_intersected(&self) -> bool {
78        false
79    }
80
81    /// Returns a slice of the additional intersection types (`&B&S`), if any. Contains boxed atomic types.
82    fn get_intersection_types(&self) -> Option<&[TAtomic]> {
83        None
84    }
85
86    /// Returns a mutable slice of the additional intersection types (`&B&S`), if any. Contains boxed atomic types.
87    fn get_intersection_types_mut(&mut self) -> Option<&mut Vec<TAtomic>> {
88        None
89    }
90
91    /// Checks if this type has intersection types.
92    fn has_intersection_types(&self) -> bool {
93        false
94    }
95
96    /// Adds an intersection type to this type.
97    ///
98    /// Returns `true` if the intersection type was added successfully,
99    ///  or `false` if this type does not support intersection types.
100    fn add_intersection_type(&mut self, _intersection_type: TAtomic) -> bool {
101        false
102    }
103
104    /// Return a human-readable identifier for this type, which is
105    /// suitable for use in error messages or debugging.
106    ///
107    /// The `interner` parameter is optional and can be used to resolve
108    /// string identifiers to their actual names.
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, interner: Option<&ThreadedInterner>) -> String;
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 get_id(&self, interner: Option<&ThreadedInterner>) -> String {
146        match self {
147            TypeRef::Union(ttype) => ttype.get_id(interner),
148            TypeRef::Atomic(ttype) => ttype.get_id(interner),
149        }
150    }
151}
152
153impl<'a> From<&'a TUnion> for TypeRef<'a> {
154    fn from(reference: &'a TUnion) -> Self {
155        TypeRef::Union(reference)
156    }
157}
158
159impl<'a> From<&'a TAtomic> for TypeRef<'a> {
160    fn from(reference: &'a TAtomic) -> Self {
161        TypeRef::Atomic(reference)
162    }
163}
164
165#[inline]
166pub fn wrap_atomic(tinner: TAtomic) -> TUnion {
167    TUnion::new(vec![tinner])
168}
169
170#[inline]
171pub fn get_int() -> TUnion {
172    wrap_atomic(TAtomic::Scalar(TScalar::int()))
173}
174
175#[inline]
176pub fn get_int_range(from: Option<i64>, to: Option<i64>) -> TUnion {
177    match (from, to) {
178        (Some(from), Some(to)) => wrap_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::Range(from, to)))),
179        (Some(from), None) => wrap_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::From(from)))),
180        (None, Some(to)) => wrap_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::To(to)))),
181        (None, None) => get_int(),
182    }
183}
184
185#[inline]
186pub fn get_literal_int(value: i64) -> TUnion {
187    wrap_atomic(TAtomic::Scalar(TScalar::literal_int(value)))
188}
189
190#[inline]
191pub fn get_string() -> TUnion {
192    wrap_atomic(TAtomic::Scalar(TScalar::string()))
193}
194
195pub fn get_string_with_props(is_numeric: bool, is_truthy: bool, is_non_empty: bool, is_lowercase: bool) -> TUnion {
196    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(
197        is_numeric,
198        is_truthy,
199        is_non_empty,
200        is_lowercase,
201    ))))
202}
203
204#[inline]
205pub fn get_literal_class_string(value: StringIdentifier) -> TUnion {
206    wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::literal(value))))
207}
208
209#[inline]
210pub fn get_class_string() -> TUnion {
211    wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::class_string())))
212}
213
214#[inline]
215pub fn get_class_string_of_type(constraint: TAtomic) -> TUnion {
216    wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::class_string_of_type(constraint))))
217}
218
219#[inline]
220pub fn get_interface_string() -> TUnion {
221    wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::interface_string())))
222}
223
224#[inline]
225pub fn get_interface_string_of_type(constraint: TAtomic) -> TUnion {
226    wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::interface_string_of_type(constraint))))
227}
228
229#[inline]
230pub fn get_enum_string() -> TUnion {
231    wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::enum_string())))
232}
233
234#[inline]
235pub fn get_enum_string_of_type(constraint: TAtomic) -> TUnion {
236    wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::enum_string_of_type(constraint))))
237}
238
239#[inline]
240pub fn get_trait_string() -> TUnion {
241    wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::trait_string())))
242}
243
244#[inline]
245pub fn get_trait_string_of_type(constraint: TAtomic) -> TUnion {
246    wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::trait_string_of_type(constraint))))
247}
248
249#[inline]
250pub fn get_literal_string(value: String) -> TUnion {
251    wrap_atomic(TAtomic::Scalar(TScalar::literal_string(value)))
252}
253
254#[inline]
255pub fn get_float() -> TUnion {
256    wrap_atomic(TAtomic::Scalar(TScalar::float()))
257}
258
259#[inline]
260pub fn get_literal_float(v: f64) -> TUnion {
261    wrap_atomic(TAtomic::Scalar(TScalar::literal_float(v)))
262}
263
264#[inline]
265pub fn get_mixed() -> TUnion {
266    wrap_atomic(TAtomic::Mixed(TMixed::new()))
267}
268
269pub fn get_mixed_maybe_from_loop(from_loop_isset: bool) -> TUnion {
270    wrap_atomic(TAtomic::Mixed(TMixed::maybe_isset_from_loop(from_loop_isset)))
271}
272
273#[inline]
274pub fn get_never() -> TUnion {
275    wrap_atomic(TAtomic::Never)
276}
277
278#[inline]
279pub fn get_resource() -> TUnion {
280    wrap_atomic(TAtomic::Resource(TResource::new(None)))
281}
282
283#[inline]
284pub fn get_closed_resource() -> TUnion {
285    wrap_atomic(TAtomic::Resource(TResource::new(Some(true))))
286}
287
288#[inline]
289pub fn get_open_resource() -> TUnion {
290    wrap_atomic(TAtomic::Resource(TResource::new(Some(false))))
291}
292
293#[inline]
294pub fn get_placeholder() -> TUnion {
295    wrap_atomic(TAtomic::Placeholder)
296}
297
298#[inline]
299pub fn get_void() -> TUnion {
300    wrap_atomic(TAtomic::Void)
301}
302
303#[inline]
304pub fn get_null() -> TUnion {
305    wrap_atomic(TAtomic::Null)
306}
307
308#[inline]
309pub fn get_arraykey() -> TUnion {
310    wrap_atomic(TAtomic::Scalar(TScalar::ArrayKey))
311}
312
313#[inline]
314pub fn get_bool() -> TUnion {
315    wrap_atomic(TAtomic::Scalar(TScalar::bool()))
316}
317
318#[inline]
319pub fn get_false() -> TUnion {
320    wrap_atomic(TAtomic::Scalar(TScalar::r#false()))
321}
322
323#[inline]
324pub fn get_true() -> TUnion {
325    wrap_atomic(TAtomic::Scalar(TScalar::r#true()))
326}
327
328#[inline]
329pub fn get_object() -> TUnion {
330    wrap_atomic(TAtomic::Object(TObject::Any))
331}
332
333#[inline]
334pub fn get_numeric() -> TUnion {
335    TUnion::new(vec![TAtomic::Scalar(TScalar::Numeric)])
336}
337
338#[inline]
339pub fn get_numeric_string() -> TUnion {
340    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(true, false, false, true))))
341}
342
343#[inline]
344pub fn get_lowercase_string() -> TUnion {
345    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(false, false, false, true))))
346}
347
348#[inline]
349pub fn get_non_empty_lowercase_string() -> TUnion {
350    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(false, false, true, true))))
351}
352
353#[inline]
354pub fn get_non_empty_string() -> TUnion {
355    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(false, false, true, false))))
356}
357
358#[inline]
359pub fn get_truthy_string() -> TUnion {
360    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(false, true, false, false))))
361}
362
363#[inline]
364pub fn get_unspecified_literal_string() -> TUnion {
365    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::unspecified_literal())))
366}
367
368#[inline]
369pub fn get_non_empty_unspecified_literal_string() -> TUnion {
370    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::unspecified_literal_with_props(false, false, true, false))))
371}
372
373#[inline]
374pub fn get_named_object(
375    interner: &ThreadedInterner,
376    name: StringIdentifier,
377    type_resolution_context: Option<&TypeResolutionContext>,
378) -> TUnion {
379    if let Some(type_resolution_context) = type_resolution_context {
380        let name_str = interner.lookup(&name);
381        if let Some(defining_entities) = type_resolution_context.get_template_definition(name_str) {
382            return wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic {
383                kind: TClassLikeStringKind::Class,
384                parameter_name: name,
385                defining_entity: defining_entities[0].0,
386                constraint: Box::new((*(defining_entities[0].1.get_single())).clone()),
387            })));
388        }
389    }
390
391    wrap_atomic(TAtomic::Object(TObject::Named(TNamedObject::new(name))))
392}
393
394#[inline]
395pub fn get_scalar() -> TUnion {
396    wrap_atomic(TAtomic::Scalar(TScalar::Generic))
397}
398
399#[inline]
400pub fn get_mixed_iterable() -> TUnion {
401    wrap_atomic(TAtomic::Iterable(TIterable::mixed()))
402}
403
404#[inline]
405pub fn get_iterable(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
406    wrap_atomic(TAtomic::Iterable(TIterable::new(Box::new(key_parameter), Box::new(value_parameter))))
407}
408
409#[inline]
410pub fn get_list(element_type: TUnion) -> TUnion {
411    wrap_atomic(TAtomic::Array(TArray::List(TList::new(Box::new(element_type)))))
412}
413
414#[inline]
415pub fn get_empty_keyed_array() -> TUnion {
416    wrap_atomic(TAtomic::Array(TArray::Keyed(TKeyedArray::new())))
417}
418
419#[inline]
420pub fn get_keyed_array(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
421    wrap_atomic(TAtomic::Array(TArray::Keyed(TKeyedArray::new_with_parameters(
422        Box::new(key_parameter),
423        Box::new(value_parameter),
424    ))))
425}
426
427#[inline]
428pub fn get_mixed_list() -> TUnion {
429    get_list(get_mixed())
430}
431
432#[inline]
433pub fn get_mixed_keyed_array() -> TUnion {
434    get_keyed_array(get_arraykey(), get_mixed())
435}
436
437#[inline]
438pub fn get_mixed_callable() -> TUnion {
439    wrap_atomic(TAtomic::Callable(TCallable::Signature(TCallableSignature::mixed(false))))
440}
441
442#[inline]
443pub fn get_mixed_closure() -> TUnion {
444    wrap_atomic(TAtomic::Callable(TCallable::Signature(TCallableSignature::mixed(true))))
445}
446
447#[inline]
448pub fn add_optional_union_type(
449    base_type: TUnion,
450    maybe_type: Option<&TUnion>,
451    codebase: &CodebaseMetadata,
452    interner: &ThreadedInterner,
453) -> TUnion {
454    if let Some(type_2) = maybe_type { add_union_type(base_type, type_2, codebase, interner, false) } else { base_type }
455}
456
457#[inline]
458pub fn combine_optional_union_types(
459    type_1: Option<&TUnion>,
460    type_2: Option<&TUnion>,
461    codebase: &CodebaseMetadata,
462    interner: &ThreadedInterner,
463) -> TUnion {
464    match (type_1, type_2) {
465        (Some(type_1), Some(type_2)) => combine_union_types(type_1, type_2, codebase, interner, false),
466        (Some(type_1), None) => type_1.clone(),
467        (None, Some(type_2)) => type_2.clone(),
468        (None, None) => get_mixed(),
469    }
470}
471
472#[inline]
473pub fn combine_union_types(
474    type_1: &TUnion,
475    type_2: &TUnion,
476    codebase: &CodebaseMetadata,
477    interner: &ThreadedInterner,
478    overwrite_empty_array: bool,
479) -> TUnion {
480    if type_1 == type_2 {
481        return type_1.clone();
482    }
483
484    let mut combined_type = if type_1.is_never() || type_1.is_never_template() {
485        type_2.clone()
486    } else if type_2.is_never() || type_2.is_never_template() {
487        type_1.clone()
488    } else if type_1.is_vanilla_mixed() && type_2.is_vanilla_mixed() {
489        get_mixed()
490    } else {
491        let mut all_atomic_types = type_1.types.clone();
492        all_atomic_types.extend(type_2.types.clone());
493
494        let mut result = TUnion::new(combiner::combine(all_atomic_types, codebase, interner, overwrite_empty_array));
495
496        if type_1.had_template && type_2.had_template {
497            result.had_template = true;
498        }
499
500        if type_1.reference_free && type_2.reference_free {
501            result.reference_free = true;
502        }
503
504        result
505    };
506
507    if type_1.possibly_undefined || type_2.possibly_undefined {
508        combined_type.possibly_undefined = true;
509    }
510
511    if type_1.possibly_undefined_from_try || type_2.possibly_undefined_from_try {
512        combined_type.possibly_undefined_from_try = true;
513    }
514
515    if type_1.ignore_falsable_issues || type_2.ignore_falsable_issues {
516        combined_type.ignore_falsable_issues = true;
517    }
518
519    combined_type
520}
521
522#[inline]
523pub fn add_union_type(
524    mut base_type: TUnion,
525    other_type: &TUnion,
526    codebase: &CodebaseMetadata,
527    interner: &ThreadedInterner,
528    overwrite_empty_array: bool,
529) -> TUnion {
530    if &base_type == other_type {
531        base_type.possibly_undefined |= other_type.possibly_undefined;
532        base_type.possibly_undefined_from_try |= other_type.possibly_undefined_from_try;
533        base_type.ignore_falsable_issues |= other_type.ignore_falsable_issues;
534        base_type.ignore_nullable_issues |= other_type.ignore_nullable_issues;
535
536        return base_type;
537    }
538
539    base_type.types = if base_type.is_vanilla_mixed() && other_type.is_vanilla_mixed() {
540        base_type.types
541    } else {
542        let mut all_atomic_types = base_type.types.clone();
543        all_atomic_types.extend(other_type.types.clone());
544
545        combiner::combine(all_atomic_types, codebase, interner, overwrite_empty_array)
546    };
547
548    if !other_type.had_template {
549        base_type.had_template = false;
550    }
551
552    if !other_type.reference_free {
553        base_type.reference_free = false;
554    }
555
556    base_type.possibly_undefined |= other_type.possibly_undefined;
557    base_type.possibly_undefined_from_try |= other_type.possibly_undefined_from_try;
558    base_type.ignore_falsable_issues |= other_type.ignore_falsable_issues;
559    base_type.ignore_nullable_issues |= other_type.ignore_nullable_issues;
560
561    base_type
562}
563
564pub fn intersect_union_types(_type_1: &TUnion, _type_2: &TUnion, _codebase: &CodebaseMetadata) -> Option<TUnion> {
565    None
566}
567
568pub fn get_iterable_parameters(
569    atomic: &TAtomic,
570    codebase: &CodebaseMetadata,
571    interner: &ThreadedInterner,
572) -> Option<(TUnion, TUnion)> {
573    if let Some(generator_parameters) = atomic.get_generator_parameters(interner) {
574        return Some((generator_parameters.0, generator_parameters.1));
575    }
576
577    let parameters = 'parameters: {
578        match atomic {
579            TAtomic::Iterable(iterable) => Some((iterable.get_key_type().clone(), iterable.get_value_type().clone())),
580            TAtomic::Array(array_type) => Some(get_array_parameters(array_type, codebase, interner)),
581            TAtomic::Object(object) => {
582                let name = object.get_name()?;
583                let traversable = interner.intern("traversable");
584
585                let class_metadata = get_class_like(codebase, interner, name)?;
586                if !is_instance_of(codebase, interner, &class_metadata.name, &traversable) {
587                    break 'parameters None;
588                }
589
590                let traversable_metadata = get_class_like(codebase, interner, &traversable)?;
591                let key_template = traversable_metadata.template_types.first().map(|(name, _)| name)?;
592                let value_template = traversable_metadata.template_types.get(1).map(|(name, _)| name)?;
593
594                let key_type = get_specialized_template_type(
595                    codebase,
596                    interner,
597                    key_template,
598                    &traversable,
599                    class_metadata,
600                    object.get_type_parameters(),
601                )
602                .unwrap_or_else(get_mixed);
603
604                let value_type = get_specialized_template_type(
605                    codebase,
606                    interner,
607                    value_template,
608                    &traversable,
609                    class_metadata,
610                    object.get_type_parameters(),
611                )
612                .unwrap_or_else(get_mixed);
613
614                Some((key_type, value_type))
615            }
616            _ => None,
617        }
618    };
619
620    if let Some((key_type, value_type)) = parameters {
621        return Some((key_type, value_type));
622    }
623
624    if let Some(intersection_types) = atomic.get_intersection_types() {
625        for intersection_type in intersection_types {
626            if let Some((key_type, value_type)) = get_iterable_parameters(intersection_type, codebase, interner) {
627                return Some((key_type, value_type));
628            }
629        }
630    }
631
632    None
633}
634
635pub fn get_array_parameters(
636    array_type: &TArray,
637    codebase: &CodebaseMetadata,
638    interner: &ThreadedInterner,
639) -> (TUnion, TUnion) {
640    match array_type {
641        TArray::Keyed(keyed_data) => {
642            let mut key_types = vec![];
643            let mut value_param;
644
645            if let Some((key_param, value_p)) = &keyed_data.parameters {
646                key_types.extend(key_param.types.clone());
647                value_param = (**value_p).clone();
648            } else {
649                key_types.push(TAtomic::Never);
650                value_param = get_never();
651            }
652
653            if let Some(known_items) = &keyed_data.known_items {
654                for (key, (_, item_type)) in known_items {
655                    key_types.push(key.to_atomic());
656                    value_param = add_union_type(value_param, item_type, codebase, interner, false);
657                }
658            }
659
660            let combined_key_types = combiner::combine(key_types, codebase, interner, false);
661            let key_param_union = TUnion::new(combined_key_types);
662
663            (key_param_union, value_param)
664        }
665        TArray::List(list_data) => {
666            let mut key_types = vec![];
667            let mut value_type = (*list_data.element_type).clone();
668
669            if let Some(known_elements) = &list_data.known_elements {
670                for (key_idx, (_, element_type)) in known_elements {
671                    key_types.push(TAtomic::Scalar(TScalar::literal_int(*key_idx as i64)));
672
673                    value_type = combine_union_types(element_type, &value_type, codebase, interner, false);
674                }
675            }
676
677            if key_types.is_empty() || !value_type.is_never() {
678                if value_type.is_never() {
679                    key_types.push(TAtomic::Never);
680                } else {
681                    key_types.push(TAtomic::Scalar(TScalar::Integer(TInteger::non_negative())));
682                }
683            }
684
685            let key_type = TUnion::new(combiner::combine(key_types, codebase, interner, false));
686
687            (key_type, value_type)
688        }
689    }
690}
691
692pub fn get_iterable_value_parameter(
693    atomic: &TAtomic,
694    codebase: &CodebaseMetadata,
695    interner: &ThreadedInterner,
696) -> Option<TUnion> {
697    if let Some(generator_parameters) = atomic.get_generator_parameters(interner) {
698        return Some(generator_parameters.1);
699    }
700
701    let parameter = match atomic {
702        TAtomic::Iterable(iterable) => Some(iterable.get_value_type().clone()),
703        TAtomic::Array(array_type) => Some(get_array_value_parameter(array_type, codebase, interner)),
704        TAtomic::Object(object) => {
705            let name = object.get_name()?;
706            let traversable = interner.intern("traversable");
707
708            let class_metadata = get_class_like(codebase, interner, name)?;
709            if !is_instance_of(codebase, interner, &class_metadata.name, &traversable) {
710                return None;
711            }
712
713            let traversable_metadata = get_class_like(codebase, interner, &traversable)?;
714            let value_template = traversable_metadata.template_types.get(1).map(|(name, _)| name)?;
715
716            get_specialized_template_type(
717                codebase,
718                interner,
719                value_template,
720                &traversable,
721                class_metadata,
722                object.get_type_parameters(),
723            )
724        }
725        _ => None,
726    };
727
728    if let Some(value_param) = parameter {
729        return Some(value_param);
730    }
731
732    if let Some(intersection_types) = atomic.get_intersection_types() {
733        for intersection_type in intersection_types {
734            if let Some(value_param) = get_iterable_value_parameter(intersection_type, codebase, interner) {
735                return Some(value_param);
736            }
737        }
738    }
739
740    None
741}
742
743pub fn get_array_value_parameter(
744    array_type: &TArray,
745    codebase: &CodebaseMetadata,
746    interner: &ThreadedInterner,
747) -> TUnion {
748    match array_type {
749        TArray::Keyed(keyed_data) => {
750            let mut value_param;
751
752            if let Some((_, value_p)) = &keyed_data.parameters {
753                value_param = (**value_p).clone();
754            } else {
755                value_param = get_never();
756            }
757
758            if let Some(known_items) = &keyed_data.known_items {
759                for (_, item_type) in known_items.values() {
760                    value_param = combine_union_types(item_type, &value_param, codebase, interner, false);
761                }
762            }
763
764            value_param
765        }
766        TArray::List(list_data) => {
767            let mut value_param = (*list_data.element_type).clone();
768
769            if let Some(known_elements) = &list_data.known_elements {
770                for (_, element_type) in known_elements.values() {
771                    value_param = combine_union_types(element_type, &value_param, codebase, interner, false);
772                }
773            }
774
775            value_param
776        }
777    }
778}
779
780/// Resolves a generic template from an ancestor class in the context of a descendant class.
781///
782/// This function correctly traverses the pre-calculated inheritance map to determine the
783/// concrete type of a template parameter.
784pub fn get_specialized_template_type(
785    codebase: &CodebaseMetadata,
786    interner: &ThreadedInterner,
787    template_name: &StringIdentifier,
788    template_defining_class_id: &StringIdentifier,
789    instantiated_class_metadata: &ClassLikeMetadata,
790    instantiated_type_parameters: Option<&[TUnion]>,
791) -> Option<TUnion> {
792    let defining_class_metadata = get_class_like(codebase, interner, template_defining_class_id)?;
793
794    if defining_class_metadata.name == instantiated_class_metadata.name {
795        let index = instantiated_class_metadata.get_template_index_for_name(template_name)?;
796
797        let Some(instantiated_type_parameters) = instantiated_type_parameters else {
798            let type_map = instantiated_class_metadata.get_template_type(template_name)?;
799
800            return type_map.first().map(|(_, constraint)| constraint).cloned();
801        };
802
803        return instantiated_type_parameters.get(index).cloned();
804    }
805
806    let defining_template_type = defining_class_metadata.get_template_type(template_name)?;
807    let template_union = TUnion::new(
808        defining_template_type
809            .iter()
810            .map(|(defining_entity, constraint)| {
811                TAtomic::GenericParameter(TGenericParameter {
812                    parameter_name: *template_name,
813                    defining_entity: *defining_entity,
814                    constraint: Box::new(constraint.clone()),
815                    intersection_types: None,
816                })
817            })
818            .collect::<Vec<_>>(),
819    );
820
821    let mut template_result = TemplateResult::default();
822    for (defining_class, type_parameters_map) in &instantiated_class_metadata.template_extended_parameters {
823        for (parameter_name, parameter_type) in type_parameters_map {
824            template_result.add_lower_bound(
825                *parameter_name,
826                GenericParent::ClassLike(*defining_class),
827                parameter_type.clone(),
828            );
829        }
830    }
831
832    let mut template_type = inferred_type_replacer::replace(&template_union, &template_result, codebase, interner);
833    if let Some(type_parameters) = instantiated_type_parameters {
834        let mut template_result = TemplateResult::default();
835        for (i, parameter_type) in type_parameters.iter().enumerate() {
836            if let Some(parameter_name) = instantiated_class_metadata.get_template_name_for_index(i) {
837                template_result.add_lower_bound(
838                    parameter_name,
839                    GenericParent::ClassLike(instantiated_class_metadata.name),
840                    parameter_type.clone(),
841                );
842            }
843        }
844
845        if !template_result.lower_bounds.is_empty() {
846            template_type = inferred_type_replacer::replace(&template_type, &template_result, codebase, interner);
847        }
848    }
849
850    Some(template_type)
851}