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::vanilla()))
267}
268
269#[inline]
270pub fn get_mixed_any() -> TUnion {
271    wrap_atomic(TAtomic::Mixed(TMixed::any()))
272}
273
274pub fn get_mixed_maybe_from_loop(from_loop_isset: bool) -> TUnion {
275    wrap_atomic(TAtomic::Mixed(TMixed::maybe_isset_from_loop(from_loop_isset)))
276}
277
278#[inline]
279pub fn get_never() -> TUnion {
280    wrap_atomic(TAtomic::Never)
281}
282
283#[inline]
284pub fn get_resource() -> TUnion {
285    wrap_atomic(TAtomic::Resource(TResource::new(None)))
286}
287
288#[inline]
289pub fn get_closed_resource() -> TUnion {
290    wrap_atomic(TAtomic::Resource(TResource::new(Some(true))))
291}
292
293#[inline]
294pub fn get_open_resource() -> TUnion {
295    wrap_atomic(TAtomic::Resource(TResource::new(Some(false))))
296}
297
298#[inline]
299pub fn get_placeholder() -> TUnion {
300    wrap_atomic(TAtomic::Placeholder)
301}
302
303#[inline]
304pub fn get_void() -> TUnion {
305    wrap_atomic(TAtomic::Void)
306}
307
308#[inline]
309pub fn get_null() -> TUnion {
310    wrap_atomic(TAtomic::Null)
311}
312
313#[inline]
314pub fn get_arraykey() -> TUnion {
315    wrap_atomic(TAtomic::Scalar(TScalar::ArrayKey))
316}
317
318#[inline]
319pub fn get_bool() -> TUnion {
320    wrap_atomic(TAtomic::Scalar(TScalar::bool()))
321}
322
323#[inline]
324pub fn get_false() -> TUnion {
325    wrap_atomic(TAtomic::Scalar(TScalar::r#false()))
326}
327
328#[inline]
329pub fn get_true() -> TUnion {
330    wrap_atomic(TAtomic::Scalar(TScalar::r#true()))
331}
332
333#[inline]
334pub fn get_object() -> TUnion {
335    wrap_atomic(TAtomic::Object(TObject::Any))
336}
337
338#[inline]
339pub fn get_numeric() -> TUnion {
340    TUnion::new(vec![TAtomic::Scalar(TScalar::Numeric)])
341}
342
343#[inline]
344pub fn get_numeric_string() -> TUnion {
345    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(true, false, false, true))))
346}
347
348#[inline]
349pub fn get_lowercase_string() -> TUnion {
350    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(false, false, false, true))))
351}
352
353#[inline]
354pub fn get_non_empty_lowercase_string() -> TUnion {
355    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(false, false, true, true))))
356}
357
358#[inline]
359pub fn get_non_empty_string() -> TUnion {
360    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(false, false, true, false))))
361}
362
363#[inline]
364pub fn get_truthy_string() -> TUnion {
365    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::general_with_props(false, true, false, false))))
366}
367
368#[inline]
369pub fn get_unspecified_literal_string() -> TUnion {
370    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::unspecified_literal())))
371}
372
373#[inline]
374pub fn get_non_empty_unspecified_literal_string() -> TUnion {
375    wrap_atomic(TAtomic::Scalar(TScalar::String(TString::unspecified_literal_with_props(false, false, true, false))))
376}
377
378#[inline]
379pub fn get_named_object(
380    interner: &ThreadedInterner,
381    name: StringIdentifier,
382    type_resolution_context: Option<&TypeResolutionContext>,
383) -> TUnion {
384    if let Some(type_resolution_context) = type_resolution_context {
385        let name_str = interner.lookup(&name);
386        if let Some(defining_entities) = type_resolution_context.get_template_definition(name_str) {
387            return wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic {
388                kind: TClassLikeStringKind::Class,
389                parameter_name: name,
390                defining_entity: defining_entities[0].0,
391                constraint: Box::new((*(defining_entities[0].1.get_single())).clone()),
392            })));
393        }
394    }
395
396    wrap_atomic(TAtomic::Object(TObject::Named(TNamedObject::new(name))))
397}
398
399#[inline]
400pub fn get_scalar() -> TUnion {
401    wrap_atomic(TAtomic::Scalar(TScalar::Generic))
402}
403
404#[inline]
405pub fn get_mixed_iterable() -> TUnion {
406    wrap_atomic(TAtomic::Iterable(TIterable::mixed()))
407}
408
409#[inline]
410pub fn get_iterable(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
411    wrap_atomic(TAtomic::Iterable(TIterable::new(Box::new(key_parameter), Box::new(value_parameter))))
412}
413
414#[inline]
415pub fn get_list(element_type: TUnion) -> TUnion {
416    wrap_atomic(TAtomic::Array(TArray::List(TList::new(Box::new(element_type)))))
417}
418
419#[inline]
420pub fn get_empty_keyed_array() -> TUnion {
421    wrap_atomic(TAtomic::Array(TArray::Keyed(TKeyedArray::new())))
422}
423
424#[inline]
425pub fn get_keyed_array(key_parameter: TUnion, value_parameter: TUnion) -> TUnion {
426    wrap_atomic(TAtomic::Array(TArray::Keyed(TKeyedArray::new_with_parameters(
427        Box::new(key_parameter),
428        Box::new(value_parameter),
429    ))))
430}
431
432#[inline]
433pub fn get_mixed_list() -> TUnion {
434    get_list(get_mixed())
435}
436
437#[inline]
438pub fn get_mixed_keyed_array() -> TUnion {
439    get_keyed_array(get_arraykey(), get_mixed())
440}
441
442#[inline]
443pub fn get_mixed_callable() -> TUnion {
444    wrap_atomic(TAtomic::Callable(TCallable::Signature(TCallableSignature::mixed(false))))
445}
446
447#[inline]
448pub fn get_mixed_closure() -> TUnion {
449    wrap_atomic(TAtomic::Callable(TCallable::Signature(TCallableSignature::mixed(true))))
450}
451
452#[inline]
453pub fn add_optional_union_type(
454    base_type: TUnion,
455    maybe_type: Option<&TUnion>,
456    codebase: &CodebaseMetadata,
457    interner: &ThreadedInterner,
458) -> TUnion {
459    if let Some(type_2) = maybe_type { add_union_type(base_type, type_2, codebase, interner, false) } else { base_type }
460}
461
462#[inline]
463pub fn combine_optional_union_types(
464    type_1: Option<&TUnion>,
465    type_2: Option<&TUnion>,
466    codebase: &CodebaseMetadata,
467    interner: &ThreadedInterner,
468) -> TUnion {
469    match (type_1, type_2) {
470        (Some(type_1), Some(type_2)) => combine_union_types(type_1, type_2, codebase, interner, false),
471        (Some(type_1), None) => type_1.clone(),
472        (None, Some(type_2)) => type_2.clone(),
473        (None, None) => get_mixed_any(),
474    }
475}
476
477#[inline]
478pub fn combine_union_types(
479    type_1: &TUnion,
480    type_2: &TUnion,
481    codebase: &CodebaseMetadata,
482    interner: &ThreadedInterner,
483    overwrite_empty_array: bool,
484) -> TUnion {
485    if type_1 == type_2 {
486        return type_1.clone();
487    }
488
489    let mut combined_type = if type_1.is_never() || type_1.is_never_template() {
490        type_2.clone()
491    } else if type_2.is_never() || type_2.is_never_template() {
492        type_1.clone()
493    } else if type_1.is_vanilla_mixed() && type_2.is_vanilla_mixed() {
494        get_mixed()
495    } else {
496        let mut all_atomic_types = type_1.types.clone();
497        all_atomic_types.extend(type_2.types.clone());
498
499        let mut result = TUnion::new(combiner::combine(all_atomic_types, codebase, interner, overwrite_empty_array));
500
501        if type_1.had_template && type_2.had_template {
502            result.had_template = true;
503        }
504
505        if type_1.reference_free && type_2.reference_free {
506            result.reference_free = true;
507        }
508
509        result
510    };
511
512    if type_1.possibly_undefined || type_2.possibly_undefined {
513        combined_type.possibly_undefined = true;
514    }
515
516    if type_1.possibly_undefined_from_try || type_2.possibly_undefined_from_try {
517        combined_type.possibly_undefined_from_try = true;
518    }
519
520    if type_1.ignore_falsable_issues || type_2.ignore_falsable_issues {
521        combined_type.ignore_falsable_issues = true;
522    }
523
524    combined_type
525}
526
527#[inline]
528pub fn add_union_type(
529    mut base_type: TUnion,
530    other_type: &TUnion,
531    codebase: &CodebaseMetadata,
532    interner: &ThreadedInterner,
533    overwrite_empty_array: bool,
534) -> TUnion {
535    if &base_type == other_type {
536        base_type.possibly_undefined |= other_type.possibly_undefined;
537        base_type.possibly_undefined_from_try |= other_type.possibly_undefined_from_try;
538        base_type.ignore_falsable_issues |= other_type.ignore_falsable_issues;
539        base_type.ignore_nullable_issues |= other_type.ignore_nullable_issues;
540
541        return base_type;
542    }
543
544    base_type.types = if base_type.is_vanilla_mixed() && other_type.is_vanilla_mixed() {
545        base_type.types
546    } else {
547        let mut all_atomic_types = base_type.types.clone();
548        all_atomic_types.extend(other_type.types.clone());
549
550        combiner::combine(all_atomic_types, codebase, interner, overwrite_empty_array)
551    };
552
553    if !other_type.had_template {
554        base_type.had_template = false;
555    }
556
557    if !other_type.reference_free {
558        base_type.reference_free = false;
559    }
560
561    base_type.possibly_undefined |= other_type.possibly_undefined;
562    base_type.possibly_undefined_from_try |= other_type.possibly_undefined_from_try;
563    base_type.ignore_falsable_issues |= other_type.ignore_falsable_issues;
564    base_type.ignore_nullable_issues |= other_type.ignore_nullable_issues;
565
566    base_type
567}
568
569pub fn intersect_union_types(_type_1: &TUnion, _type_2: &TUnion, _codebase: &CodebaseMetadata) -> Option<TUnion> {
570    None
571}
572
573pub fn get_iterable_parameters(
574    atomic: &TAtomic,
575    codebase: &CodebaseMetadata,
576    interner: &ThreadedInterner,
577) -> Option<(TUnion, TUnion)> {
578    if let Some(generator_parameters) = atomic.get_generator_parameters(interner) {
579        return Some((generator_parameters.0, generator_parameters.1));
580    }
581
582    let parameters = 'parameters: {
583        match atomic {
584            TAtomic::Iterable(iterable) => Some((iterable.get_key_type().clone(), iterable.get_value_type().clone())),
585            TAtomic::Array(array_type) => Some(get_array_parameters(array_type, codebase, interner)),
586            TAtomic::Object(object) => {
587                let name = object.get_name()?;
588                let traversable = interner.intern("traversable");
589
590                let class_metadata = get_class_like(codebase, interner, name)?;
591                if !is_instance_of(codebase, interner, &class_metadata.name, &traversable) {
592                    break 'parameters None;
593                }
594
595                let traversable_metadata = get_class_like(codebase, interner, &traversable)?;
596                let key_template = traversable_metadata.template_types.first().map(|(name, _)| name)?;
597                let value_template = traversable_metadata.template_types.get(1).map(|(name, _)| name)?;
598
599                let key_type = get_specialized_template_type(
600                    codebase,
601                    interner,
602                    key_template,
603                    &traversable,
604                    class_metadata,
605                    object.get_type_parameters(),
606                )
607                .unwrap_or_else(get_mixed);
608
609                let value_type = get_specialized_template_type(
610                    codebase,
611                    interner,
612                    value_template,
613                    &traversable,
614                    class_metadata,
615                    object.get_type_parameters(),
616                )
617                .unwrap_or_else(get_mixed);
618
619                Some((key_type, value_type))
620            }
621            _ => None,
622        }
623    };
624
625    if let Some((key_type, value_type)) = parameters {
626        return Some((key_type, value_type));
627    }
628
629    if let Some(intersection_types) = atomic.get_intersection_types() {
630        for intersection_type in intersection_types {
631            if let Some((key_type, value_type)) = get_iterable_parameters(intersection_type, codebase, interner) {
632                return Some((key_type, value_type));
633            }
634        }
635    }
636
637    None
638}
639
640pub fn get_array_parameters(
641    array_type: &TArray,
642    codebase: &CodebaseMetadata,
643    interner: &ThreadedInterner,
644) -> (TUnion, TUnion) {
645    match array_type {
646        TArray::Keyed(keyed_data) => {
647            let mut key_types = vec![];
648            let mut value_param;
649
650            if let Some((key_param, value_p)) = &keyed_data.parameters {
651                key_types.extend(key_param.types.clone());
652                value_param = (**value_p).clone();
653            } else {
654                key_types.push(TAtomic::Never);
655                value_param = get_never();
656            }
657
658            if let Some(known_items) = &keyed_data.known_items {
659                for (key, (_, item_type)) in known_items {
660                    key_types.push(key.to_atomic());
661                    value_param = add_union_type(value_param, item_type, codebase, interner, false);
662                }
663            }
664
665            let combined_key_types = combiner::combine(key_types, codebase, interner, false);
666            let key_param_union = TUnion::new(combined_key_types);
667
668            (key_param_union, value_param)
669        }
670        TArray::List(list_data) => {
671            let mut key_types = vec![];
672            let mut value_type = (*list_data.element_type).clone();
673
674            if let Some(known_elements) = &list_data.known_elements {
675                for (key_idx, (_, element_type)) in known_elements {
676                    key_types.push(TAtomic::Scalar(TScalar::literal_int(*key_idx as i64)));
677
678                    value_type = combine_union_types(element_type, &value_type, codebase, interner, false);
679                }
680            }
681
682            if key_types.is_empty() || !value_type.is_never() {
683                if value_type.is_never() {
684                    key_types.push(TAtomic::Never);
685                } else {
686                    key_types.push(TAtomic::Scalar(TScalar::Integer(TInteger::non_negative())));
687                }
688            }
689
690            let key_type = TUnion::new(combiner::combine(key_types, codebase, interner, false));
691
692            (key_type, value_type)
693        }
694    }
695}
696
697pub fn get_iterable_value_parameter(
698    atomic: &TAtomic,
699    codebase: &CodebaseMetadata,
700    interner: &ThreadedInterner,
701) -> Option<TUnion> {
702    if let Some(generator_parameters) = atomic.get_generator_parameters(interner) {
703        return Some(generator_parameters.1);
704    }
705
706    let parameter = match atomic {
707        TAtomic::Iterable(iterable) => Some(iterable.get_value_type().clone()),
708        TAtomic::Array(array_type) => Some(get_array_value_parameter(array_type, codebase, interner)),
709        TAtomic::Object(object) => {
710            let name = object.get_name()?;
711            let traversable = interner.intern("traversable");
712
713            let class_metadata = get_class_like(codebase, interner, name)?;
714            if !is_instance_of(codebase, interner, &class_metadata.name, &traversable) {
715                return None;
716            }
717
718            let traversable_metadata = get_class_like(codebase, interner, &traversable)?;
719            let value_template = traversable_metadata.template_types.get(1).map(|(name, _)| name)?;
720
721            get_specialized_template_type(
722                codebase,
723                interner,
724                value_template,
725                &traversable,
726                class_metadata,
727                object.get_type_parameters(),
728            )
729        }
730        _ => None,
731    };
732
733    if let Some(value_param) = parameter {
734        return Some(value_param);
735    }
736
737    if let Some(intersection_types) = atomic.get_intersection_types() {
738        for intersection_type in intersection_types {
739            if let Some(value_param) = get_iterable_value_parameter(intersection_type, codebase, interner) {
740                return Some(value_param);
741            }
742        }
743    }
744
745    None
746}
747
748pub fn get_array_value_parameter(
749    array_type: &TArray,
750    codebase: &CodebaseMetadata,
751    interner: &ThreadedInterner,
752) -> TUnion {
753    match array_type {
754        TArray::Keyed(keyed_data) => {
755            let mut value_param;
756
757            if let Some((_, value_p)) = &keyed_data.parameters {
758                value_param = (**value_p).clone();
759            } else {
760                value_param = get_never();
761            }
762
763            if let Some(known_items) = &keyed_data.known_items {
764                for (_, item_type) in known_items.values() {
765                    value_param = combine_union_types(item_type, &value_param, codebase, interner, false);
766                }
767            }
768
769            value_param
770        }
771        TArray::List(list_data) => {
772            let mut value_param = (*list_data.element_type).clone();
773
774            if let Some(known_elements) = &list_data.known_elements {
775                for (_, element_type) in known_elements.values() {
776                    value_param = combine_union_types(element_type, &value_param, codebase, interner, false);
777                }
778            }
779
780            value_param
781        }
782    }
783}
784
785/// Resolves a generic template from an ancestor class in the context of a descendant class.
786///
787/// This function correctly traverses the pre-calculated inheritance map to determine the
788/// concrete type of a template parameter.
789pub fn get_specialized_template_type(
790    codebase: &CodebaseMetadata,
791    interner: &ThreadedInterner,
792    template_name: &StringIdentifier,
793    template_defining_class_id: &StringIdentifier,
794    instantiated_class_metadata: &ClassLikeMetadata,
795    instantiated_type_parameters: Option<&[TUnion]>,
796) -> Option<TUnion> {
797    let defining_class_metadata = get_class_like(codebase, interner, template_defining_class_id)?;
798
799    if defining_class_metadata.name == instantiated_class_metadata.name {
800        let index = instantiated_class_metadata.get_template_index_for_name(template_name)?;
801
802        let Some(instantiated_type_parameters) = instantiated_type_parameters else {
803            let type_map = instantiated_class_metadata.get_template_type(template_name)?;
804
805            return type_map.first().map(|(_, constraint)| constraint).cloned();
806        };
807
808        return instantiated_type_parameters.get(index).cloned();
809    }
810
811    let defining_template_type = defining_class_metadata.get_template_type(template_name)?;
812    let template_union = TUnion::new(
813        defining_template_type
814            .iter()
815            .map(|(defining_entity, constraint)| {
816                TAtomic::GenericParameter(TGenericParameter {
817                    parameter_name: *template_name,
818                    defining_entity: *defining_entity,
819                    constraint: Box::new(constraint.clone()),
820                    intersection_types: None,
821                })
822            })
823            .collect::<Vec<_>>(),
824    );
825
826    let mut template_result = TemplateResult::default();
827    for (defining_class, type_parameters_map) in &instantiated_class_metadata.template_extended_parameters {
828        for (parameter_name, parameter_type) in type_parameters_map {
829            template_result.add_lower_bound(
830                *parameter_name,
831                GenericParent::ClassLike(*defining_class),
832                parameter_type.clone(),
833            );
834        }
835    }
836
837    let mut template_type = inferred_type_replacer::replace(&template_union, &template_result, codebase, interner);
838    if let Some(type_parameters) = instantiated_type_parameters {
839        let mut template_result = TemplateResult::default();
840        for (i, parameter_type) in type_parameters.iter().enumerate() {
841            if let Some(parameter_name) = instantiated_class_metadata.get_template_name_for_index(i) {
842                template_result.add_lower_bound(
843                    parameter_name,
844                    GenericParent::ClassLike(instantiated_class_metadata.name),
845                    parameter_type.clone(),
846                );
847            }
848        }
849
850        if !template_result.lower_bounds.is_empty() {
851            template_type = inferred_type_replacer::replace(&template_type, &template_result, codebase, interner);
852        }
853    }
854
855    Some(template_type)
856}