Skip to main content

mago_codex/ttype/
builder.rs

1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::sync::Arc;
4
5use mago_names::kind::NameKind;
6use mago_names::scope::NamespaceScope;
7use mago_phpdoc_syntax::cst::r#type::*;
8use mago_span::*;
9use mago_word::*;
10
11use crate::ttype::TType;
12use crate::ttype::atomic::TAtomic;
13use crate::ttype::atomic::alias::TAlias;
14use crate::ttype::atomic::array::TArray;
15use crate::ttype::atomic::array::key::ArrayKey;
16use crate::ttype::atomic::array::keyed::TKeyedArray;
17use crate::ttype::atomic::array::list::TList;
18use crate::ttype::atomic::callable::TCallable;
19use crate::ttype::atomic::callable::TCallableSignature;
20use crate::ttype::atomic::callable::parameter::TCallableParameter;
21use crate::ttype::atomic::conditional::TConditional;
22use crate::ttype::atomic::derived::TDerived;
23use crate::ttype::atomic::derived::index_access::TIndexAccess;
24use crate::ttype::atomic::derived::int_mask::TIntMask;
25use crate::ttype::atomic::derived::int_mask_of::TIntMaskOf;
26use crate::ttype::atomic::derived::key_of::TKeyOf;
27use crate::ttype::atomic::derived::new::TNew;
28use crate::ttype::atomic::derived::properties_of::TPropertiesOf;
29use crate::ttype::atomic::derived::template_type::TTemplateType;
30use crate::ttype::atomic::derived::value_of::TValueOf;
31use crate::ttype::atomic::generic::TGenericParameter;
32use crate::ttype::atomic::iterable::TIterable;
33use crate::ttype::atomic::object::TObject;
34use crate::ttype::atomic::object::named::TNamedObject;
35use crate::ttype::atomic::reference::*;
36use crate::ttype::atomic::scalar::TScalar;
37use crate::ttype::atomic::scalar::class_like_string::*;
38use crate::ttype::atomic::scalar::int::TInteger;
39use crate::ttype::atomic::scalar::string::TStringCasing;
40use crate::ttype::error::TypeError;
41use crate::ttype::resolution::TypeResolutionContext;
42use crate::ttype::template::GenericTemplate;
43use crate::ttype::union::TUnion;
44use crate::ttype::wrap_atomic;
45use crate::ttype::*;
46
47/// Converts a parsed `PHPDoc` type node into a semantic `TUnion` type representation,
48/// resolving names, templates, and keywords into their semantic counterparts.
49///
50/// # Arguments
51///
52/// * `ttype` - The type node, as parsed by the `mago_phpdoc_syntax` crate.
53/// * `scope` - The `NamespaceScope` active at the location of the type.
54///   Used during conversion to resolve unqualified names, aliases (`use` statements),
55///   and namespace-relative names.
56/// * `type_context` - The context providing information about currently defined
57///   template parameters (e.g., from `@template` tags). Needed
58///   during conversion to resolve template parameter references.
59/// * `classname` - An optional `Word` representing the fully qualified name
60///   of the current class context. Used during conversion to resolve
61///   `self` type references. Should be `None` if not in a class context.
62///
63/// # Errors
64///
65/// Returns a [`TypeError`] if:
66/// - An unsupported type construct is encountered
67/// - Type references cannot be resolved (e.g., `self` outside a class context)
68/// - Invalid type combinations are used (e.g., incompatible intersection types)
69/// - Int range has minimum greater than maximum
70#[inline]
71pub fn get_union_from_type(
72    ttype: &Type<'_>,
73    scope: &NamespaceScope,
74    type_context: &TypeResolutionContext,
75    classname: Option<Word>,
76) -> Result<TUnion, TypeError> {
77    Ok(match ttype {
78        Type::Parenthesized(parenthesized_type) => {
79            get_union_from_type(parenthesized_type.inner, scope, type_context, classname)?
80        }
81        Type::Nullable(nullable_type) => match nullable_type.inner {
82            Type::Null(_) => get_null(),
83            Type::String(_) => get_nullable_string(),
84            Type::Int(_) => get_nullable_int(),
85            Type::Float(_) => get_nullable_float(),
86            Type::Object(_) => get_nullable_object(),
87            Type::Scalar(_) => get_nullable_scalar(),
88            _ => get_union_from_type(nullable_type.inner, scope, type_context, classname)?.as_nullable(),
89        },
90        Type::Union(UnionType { left, right, .. }) if matches!(**left, Type::Null(_)) => match **right {
91            Type::Null(_) => get_null(),
92            Type::String(_) => get_nullable_string(),
93            Type::Int(_) => get_nullable_int(),
94            Type::Float(_) => get_nullable_float(),
95            Type::Object(_) => get_nullable_object(),
96            Type::Scalar(_) => get_nullable_scalar(),
97            _ => get_union_from_type(right, scope, type_context, classname)?.as_nullable(),
98        },
99        Type::Union(UnionType { left, right, .. }) if matches!(**right, Type::Null(_)) => match **left {
100            Type::Null(_) => get_null(),
101            Type::String(_) => get_nullable_string(),
102            Type::Int(_) => get_nullable_int(),
103            Type::Float(_) => get_nullable_float(),
104            Type::Object(_) => get_nullable_object(),
105            Type::Scalar(_) => get_nullable_scalar(),
106            _ => get_union_from_type(left, scope, type_context, classname)?.as_nullable(),
107        },
108        Type::Union(union_type) => {
109            let left = get_union_from_type(union_type.left, scope, type_context, classname)?;
110            let right = get_union_from_type(union_type.right, scope, type_context, classname)?;
111
112            let combined_types: Vec<TAtomic> = left.types.iter().chain(right.types.iter()).cloned().collect();
113
114            TUnion::from_vec(combined_types)
115        }
116        Type::Intersection(intersection) => {
117            if matches!(intersection.left, Type::NonEmptyString(_)) {
118                match intersection.right {
119                    Type::String(_) => return Ok(get_non_empty_string()),
120                    Type::NonEmptyString(_) => return Ok(get_non_empty_string()),
121                    Type::LowercaseString(_) => return Ok(get_non_empty_lowercase_string()),
122                    Type::NonEmptyLowercaseString(_) => return Ok(get_non_empty_lowercase_string()),
123                    Type::UppercaseString(_) => return Ok(get_non_empty_uppercase_string()),
124                    Type::NonEmptyUppercaseString(_) => return Ok(get_non_empty_uppercase_string()),
125                    _ => {}
126                }
127            }
128
129            if matches!(intersection.right, Type::NonEmptyString(_)) {
130                match intersection.left {
131                    Type::String(_) => return Ok(get_non_empty_string()),
132                    Type::NonEmptyString(_) => return Ok(get_non_empty_string()),
133                    Type::LowercaseString(_) => return Ok(get_non_empty_lowercase_string()),
134                    Type::NonEmptyLowercaseString(_) => return Ok(get_non_empty_lowercase_string()),
135                    Type::UppercaseString(_) => return Ok(get_non_empty_uppercase_string()),
136                    Type::NonEmptyUppercaseString(_) => return Ok(get_non_empty_uppercase_string()),
137                    _ => {}
138                }
139            }
140
141            let object_and_callable = (matches!(intersection.left, Type::Object(_))
142                && matches!(intersection.right, Type::Callable(_)))
143                || (matches!(intersection.left, Type::Callable(_)) && matches!(intersection.right, Type::Object(_)));
144            if object_and_callable {
145                return Ok(wrap_atomic(TAtomic::Object(TObject::new_has_method(word("__invoke")))));
146            }
147
148            let left = get_union_from_type(intersection.left, scope, type_context, classname)?;
149            let right = get_union_from_type(intersection.right, scope, type_context, classname)?;
150
151            let left_str = left.get_id();
152            let right_str = right.get_id();
153
154            let left_types = left.types.into_owned();
155            let right_types = right.types.into_owned();
156            let mut intersection_types = vec![];
157            for left_type in left_types {
158                if !left_type.can_be_intersected() {
159                    return Err(TypeError::InvalidType(
160                        ttype.to_string(),
161                        format!(
162                            "Type `{}` used in intersection cannot be intersected with another type ( `{}` )",
163                            left_type.get_id(),
164                            right_str,
165                        ),
166                        ttype.span(),
167                    ));
168                }
169
170                for right_type in &right_types {
171                    let mut intersection = left_type.clone();
172
173                    if !intersection.add_intersection_type(right_type.clone()) {
174                        return Err(TypeError::InvalidType(
175                            ttype.to_string(),
176                            format!(
177                                "Type `{}` used in intersection cannot be intersected with another type ( `{}` )",
178                                right_type.get_id(),
179                                left_str,
180                            ),
181                            ttype.span(),
182                        ));
183                    }
184
185                    intersection_types.push(intersection);
186                }
187            }
188
189            TUnion::from_vec(intersection_types)
190        }
191        Type::Slice(slice) => {
192            wrap_atomic(get_array_type(None, Some(slice.inner), false, scope, type_context, classname)?)
193        }
194        Type::Array(ArrayType { parameters, .. }) | Type::AssociativeArray(AssociativeArrayType { parameters, .. }) => {
195            let (key, value) = match parameters {
196                Some(parameters) => {
197                    let key = parameters.entries.first().map(|g| &g.inner);
198                    let value = parameters.entries.get(1).map(|g| &g.inner);
199
200                    (key, value)
201                }
202                None => (None, None),
203            };
204
205            wrap_atomic(get_array_type(key, value, false, scope, type_context, classname)?)
206        }
207        Type::NonEmptyArray(non_empty_array) => {
208            let (key, value) = match &non_empty_array.parameters {
209                Some(parameters) => {
210                    let key = parameters.entries.first().map(|g| &g.inner);
211                    let value = parameters.entries.get(1).map(|g| &g.inner);
212
213                    (key, value)
214                }
215                None => (None, None),
216            };
217
218            wrap_atomic(get_array_type(key, value, true, scope, type_context, classname)?)
219        }
220        Type::List(list_type) => {
221            let value = list_type.parameters.as_ref().and_then(|p| p.entries.first().map(|g| &g.inner));
222
223            wrap_atomic(get_list_type(value, false, scope, type_context, classname)?)
224        }
225        Type::NonEmptyList(non_empty_list_type) => {
226            let value = non_empty_list_type.parameters.as_ref().and_then(|p| p.entries.first().map(|g| &g.inner));
227
228            wrap_atomic(get_list_type(value, true, scope, type_context, classname)?)
229        }
230        Type::ClassString(class_string_type) => get_class_string_type(
231            class_string_type.span(),
232            TClassLikeStringKind::Class,
233            class_string_type.parameter.as_ref(),
234            scope,
235            type_context,
236            classname,
237        )?,
238        Type::InterfaceString(interface_string_type) => get_class_string_type(
239            interface_string_type.span(),
240            TClassLikeStringKind::Interface,
241            interface_string_type.parameter.as_ref(),
242            scope,
243            type_context,
244            classname,
245        )?,
246        Type::EnumString(enum_string_type) => get_class_string_type(
247            enum_string_type.span(),
248            TClassLikeStringKind::Enum,
249            enum_string_type.parameter.as_ref(),
250            scope,
251            type_context,
252            classname,
253        )?,
254        Type::TraitString(trait_string_type) => get_class_string_type(
255            trait_string_type.span(),
256            TClassLikeStringKind::Trait,
257            trait_string_type.parameter.as_ref(),
258            scope,
259            type_context,
260            classname,
261        )?,
262        Type::ClassLikeString(_) => {
263            return Err(TypeError::UnsupportedType(ttype.to_string(), ttype.span()));
264        }
265        Type::MemberReference(member_reference) => {
266            let class_like_name = match &member_reference.kind {
267                ReferenceKind::Self_(_) | ReferenceKind::Static(_) => {
268                    let Some(classname) = classname else {
269                        return Err(TypeError::InvalidType(
270                            ttype.to_string(),
271                            "Cannot resolve `self` type reference outside of a class context".to_string(),
272                            member_reference.span(),
273                        ));
274                    };
275
276                    classname
277                }
278                ReferenceKind::Identifier(identifier) if identifier.value.eq(b"this") => {
279                    let Some(classname) = classname else {
280                        return Err(TypeError::InvalidType(
281                            ttype.to_string(),
282                            "Cannot resolve `self` type reference outside of a class context".to_string(),
283                            member_reference.span(),
284                        ));
285                    };
286
287                    classname
288                }
289                ReferenceKind::Parent(_) => word("parent"),
290                ReferenceKind::Identifier(identifier) => {
291                    let (class_like_name, _) = scope.resolve(NameKind::Default, identifier.value);
292
293                    word(&class_like_name)
294                }
295            };
296
297            let member_selector = match member_reference.member {
298                MemberReferenceSelector::Wildcard(_) => TReferenceMemberSelector::Wildcard,
299                MemberReferenceSelector::Identifier(identifier) => {
300                    TReferenceMemberSelector::Identifier(word(identifier.value))
301                }
302                MemberReferenceSelector::StartsWith(identifier, _) => {
303                    TReferenceMemberSelector::StartsWith(word(identifier.value))
304                }
305                MemberReferenceSelector::EndsWith(_, identifier) => {
306                    TReferenceMemberSelector::EndsWith(word(identifier.value))
307                }
308            };
309
310            wrap_atomic(TAtomic::Reference(TReference::Member { class_like_name, member_selector }))
311        }
312        Type::GlobalWildcardReference(global_wildcard) => {
313            let selector = match global_wildcard.selector {
314                GlobalWildcardSelector::StartsWith(identifier, _) => {
315                    TGlobalReferenceSelector::StartsWith(word(identifier.value))
316                }
317                GlobalWildcardSelector::EndsWith(_, identifier) => {
318                    TGlobalReferenceSelector::EndsWith(word(identifier.value))
319                }
320            };
321
322            wrap_atomic(TAtomic::Reference(TReference::Global { selector }))
323        }
324        Type::AliasReference(alias_reference) => {
325            let class_like_name = match &alias_reference.class {
326                ReferenceKind::Self_(_) | ReferenceKind::Static(_) => {
327                    let Some(classname) = classname else {
328                        return Err(TypeError::InvalidType(
329                            ttype.to_string(),
330                            "Cannot resolve `self` type reference outside of a class context".to_string(),
331                            alias_reference.span(),
332                        ));
333                    };
334
335                    classname
336                }
337                ReferenceKind::Identifier(identifier) if identifier.value.eq(b"this") => {
338                    let Some(classname) = classname else {
339                        return Err(TypeError::InvalidType(
340                            ttype.to_string(),
341                            "Cannot resolve `self` type reference outside of a class context".to_string(),
342                            alias_reference.span(),
343                        ));
344                    };
345
346                    classname
347                }
348                ReferenceKind::Parent(_) => word("parent"),
349                ReferenceKind::Identifier(identifier) => {
350                    let (class_like_name, _) = scope.resolve(NameKind::Default, identifier.value);
351
352                    ascii_lowercase_word(&class_like_name)
353                }
354            };
355
356            let alias_name = match alias_reference.alias {
357                AliasName::Identifier(identifier) => word(identifier.value),
358                AliasName::Keyword(keyword) => word(keyword.value),
359            };
360
361            wrap_atomic(TAtomic::Alias(TAlias::new(class_like_name, alias_name)))
362        }
363        Type::Object(object_type) => wrap_atomic(get_object_from_type(object_type, scope, type_context, classname)?),
364        Type::Shape(shape_type) => wrap_atomic(get_shape_from_type(shape_type, scope, type_context, classname)?),
365        Type::Callable(callable_type) => {
366            wrap_atomic(get_callable_from_type(callable_type, scope, type_context, classname)?)
367        }
368        Type::Reference(reference_type) => {
369            if let ReferenceKind::Identifier(identifier) = &reference_type.kind {
370                let reference_name_atom = word(identifier.value);
371
372                if let Some((source_class, original_name)) = type_context.get_imported_type_alias(reference_name_atom) {
373                    return Ok(wrap_atomic(TAtomic::Alias(TAlias::new(*source_class, *original_name))));
374                }
375
376                if type_context.has_type_alias(reference_name_atom)
377                    && let Some(class_name) = classname
378                {
379                    return Ok(wrap_atomic(TAtomic::Alias(TAlias::new(class_name, reference_name_atom))));
380                }
381            }
382
383            wrap_atomic(get_reference_from_kind(
384                &reference_type.kind,
385                reference_type.parameters.as_ref(),
386                scope,
387                type_context,
388                classname,
389            )?)
390        }
391        Type::Mixed(_) | Type::Wildcard(_) => get_mixed(),
392        Type::NonEmptyMixed(_) => get_truthy_mixed(),
393        Type::Null(_) => get_null(),
394        Type::Void(_) => get_void(),
395        Type::Never(_) => get_never(),
396        Type::Resource(_) => get_resource(),
397        Type::ClosedResource(_) => get_closed_resource(),
398        Type::OpenResource(_) => get_open_resource(),
399        Type::True(_) => get_true(),
400        Type::False(_) => get_false(),
401        Type::Bool(_) => get_bool(),
402        Type::Float(_) => get_float(),
403        Type::Int(_) => get_int(),
404        Type::String(_) => get_string(),
405        Type::ArrayKey(_) => get_arraykey(),
406        Type::Numeric(_) => get_numeric(),
407        Type::Scalar(_) => get_scalar(),
408        Type::Empty(_) => get_empty(),
409        Type::EmptyScalar(_) => get_empty_scalar(),
410        Type::CallableString(_) => get_callable_string(),
411        Type::LowercaseCallableString(_) => get_string_with_props(false, false, false, true, TStringCasing::Lowercase),
412        Type::UppercaseCallableString(_) => get_string_with_props(false, false, false, true, TStringCasing::Uppercase),
413        Type::NumericString(_) => get_numeric_string(),
414        Type::NonEmptyString(_) => get_non_empty_string(),
415        Type::TruthyString(_) | Type::NonFalsyString(_) => get_truthy_string(),
416        Type::UnspecifiedLiteralString(_) => get_unspecified_literal_string(),
417        Type::NonEmptyUnspecifiedLiteralString(_) => get_non_empty_unspecified_literal_string(),
418        Type::NonEmptyLowercaseString(_) => get_non_empty_lowercase_string(),
419        Type::LowercaseString(_) => get_lowercase_string(),
420        Type::NonEmptyUppercaseString(_) => get_non_empty_uppercase_string(),
421        Type::UppercaseString(_) => get_uppercase_string(),
422        Type::UnspecifiedLiteralInt(_) => get_unspecified_literal_int(),
423        Type::UnspecifiedLiteralFloat(_) => get_unspecified_literal_float(),
424        Type::LiteralFloat(lit) => get_literal_float(*lit.value),
425        Type::LiteralInt(lit) => get_literal_int(lit.value as i64),
426        Type::LiteralString(lit) => get_literal_string(word(lit.value)),
427        Type::Negated(negated) => match negated.operand {
428            Type::LiteralInt(lit) => get_literal_int(-(lit.value as i64)),
429            Type::LiteralFloat(lit) => get_literal_float(-(*lit.value)),
430            _ => return Err(TypeError::UnsupportedType(ttype.to_string(), ttype.span())),
431        },
432        Type::Posited(posited) => match posited.operand {
433            Type::LiteralInt(lit) => get_literal_int(lit.value as i64),
434            Type::LiteralFloat(lit) => get_literal_float(*lit.value),
435            _ => return Err(TypeError::UnsupportedType(ttype.to_string(), ttype.span())),
436        },
437        Type::Iterable(iterable) => match iterable.parameters.as_ref() {
438            Some(parameters) => match parameters.entries.len() {
439                0 => wrap_atomic(TAtomic::Iterable(TIterable::mixed())),
440                1 => {
441                    let value_type = get_union_from_type(&parameters.entries[0].inner, scope, type_context, classname)?;
442
443                    wrap_atomic(TAtomic::Iterable(TIterable::of_value(Arc::new(value_type))))
444                }
445                _ => {
446                    let key_type = get_union_from_type(&parameters.entries[0].inner, scope, type_context, classname)?;
447
448                    let value_type = get_union_from_type(&parameters.entries[1].inner, scope, type_context, classname)?;
449
450                    wrap_atomic(TAtomic::Iterable(TIterable::new(Arc::new(key_type), Arc::new(value_type))))
451                }
452            },
453            None => wrap_atomic(TAtomic::Iterable(TIterable::mixed())),
454        },
455        Type::PositiveInt(_) => get_positive_int(),
456        Type::NegativeInt(_) => get_negative_int(),
457        Type::NonPositiveInt(_) => get_non_positive_int(),
458        Type::NonNegativeInt(_) => get_non_negative_int(),
459        Type::NonZeroInt(_) => get_non_zero_int(),
460        Type::TrailingPipe(trailing) => get_union_from_type(trailing.inner, scope, type_context, classname)?,
461        Type::IntRange(range) => {
462            let min = match range.min {
463                IntOrKeyword::NegativeInt { int, .. } => Some(-(int.value as i64)),
464                IntOrKeyword::Int(literal_int_type) => Some(literal_int_type.value as i64),
465                IntOrKeyword::Keyword(_) => None,
466            };
467
468            let max = match range.max {
469                IntOrKeyword::NegativeInt { int, .. } => Some(-(int.value as i64)),
470                IntOrKeyword::Int(literal_int_type) => Some(literal_int_type.value as i64),
471                IntOrKeyword::Keyword(_) => None,
472            };
473
474            if let (Some(min_value), Some(max_value)) = (min, max)
475                && min_value > max_value
476            {
477                return Err(TypeError::InvalidType(
478                    ttype.to_string(),
479                    "Minimum value of an int range cannot be greater than maximum value".to_string(),
480                    ttype.span(),
481                ));
482            }
483
484            TUnion::from_single(Cow::Owned(TAtomic::Scalar(TScalar::Integer(TInteger::from_bounds(min, max)))))
485        }
486        Type::Conditional(conditional) => TUnion::from_single(Cow::Owned(TAtomic::Conditional(TConditional::new(
487            Arc::new(get_union_from_type(conditional.subject, scope, type_context, classname)?),
488            Arc::new(get_union_from_type(conditional.target, scope, type_context, classname)?),
489            Arc::new(get_union_from_type(conditional.then, scope, type_context, classname)?),
490            Arc::new(get_union_from_type(conditional.r#else, scope, type_context, classname)?),
491            conditional.is_negated(),
492        )))),
493        Type::ThisVariable(_) => {
494            TUnion::from_single(Cow::Owned(TAtomic::Object(TObject::Named(TNamedObject::new_this(word("$this"))))))
495        }
496        Type::Variable(variable) => {
497            if variable.value == b"$this" {
498                TUnion::from_single(Cow::Owned(TAtomic::Object(TObject::Named(TNamedObject::new_this(word("$this"))))))
499            } else {
500                TUnion::from_single(Cow::Owned(TAtomic::Variable(word(variable.value))))
501            }
502        }
503        Type::KeyOf(key_of_type) => TUnion::from_atomic(TAtomic::Derived(TDerived::KeyOf(TKeyOf::new(Arc::new(
504            get_union_from_type(&key_of_type.parameter.entry.inner, scope, type_context, classname)?,
505        ))))),
506        Type::ValueOf(value_of_type) => TUnion::from_atomic(TAtomic::Derived(TDerived::ValueOf(TValueOf::new(
507            Arc::new(get_union_from_type(&value_of_type.parameter.entry.inner, scope, type_context, classname)?),
508        )))),
509        Type::IntMask(int_mask_type) => {
510            let mut values = Vec::new();
511            for entry in &int_mask_type.parameters.entries {
512                values.push(get_union_from_type(&entry.inner, scope, type_context, classname)?);
513            }
514            TUnion::from_atomic(TAtomic::Derived(TDerived::IntMask(TIntMask::new(values))))
515        }
516        Type::IntMaskOf(int_mask_of_type) => {
517            TUnion::from_atomic(TAtomic::Derived(TDerived::IntMaskOf(TIntMaskOf::new(Arc::new(get_union_from_type(
518                &int_mask_of_type.parameter.entry.inner,
519                scope,
520                type_context,
521                classname,
522            )?)))))
523        }
524        Type::New(new_type) => TUnion::from_atomic(TAtomic::Derived(TDerived::New(TNew::new(Arc::new(
525            get_union_from_type(&new_type.parameter.entry.inner, scope, type_context, classname)?,
526        ))))),
527        Type::TemplateType(template_type_type) => {
528            let entries = &template_type_type.parameters.entries;
529            if entries.len() != 3 {
530                return Err(TypeError::InvalidType(
531                    template_type_type.to_string(),
532                    format!(
533                        "`template-type<O, C, T>` expects exactly 3 parameters (object, class-name, template-name), got {}",
534                        entries.len()
535                    ),
536                    template_type_type.span(),
537                ));
538            }
539
540            let object = Arc::new(get_union_from_type(&entries[0].inner, scope, type_context, classname)?);
541            let class_arg = Arc::new(get_union_from_type(&entries[1].inner, scope, type_context, classname)?);
542            let template_name = Arc::new(get_union_from_type(&entries[2].inner, scope, type_context, classname)?);
543
544            TUnion::from_atomic(TAtomic::Derived(TDerived::TemplateType(TTemplateType::new(
545                object,
546                class_arg,
547                template_name,
548            ))))
549        }
550        Type::PropertiesOf(properties_of_type) => {
551            TUnion::from_atomic(TAtomic::Derived(TDerived::PropertiesOf(match properties_of_type.filter {
552                PropertiesOfFilter::All => TPropertiesOf::new(Arc::new(get_union_from_type(
553                    &properties_of_type.parameter.entry.inner,
554                    scope,
555                    type_context,
556                    classname,
557                )?)),
558                PropertiesOfFilter::Public => TPropertiesOf::public(Arc::new(get_union_from_type(
559                    &properties_of_type.parameter.entry.inner,
560                    scope,
561                    type_context,
562                    classname,
563                )?)),
564                PropertiesOfFilter::Protected => TPropertiesOf::protected(Arc::new(get_union_from_type(
565                    &properties_of_type.parameter.entry.inner,
566                    scope,
567                    type_context,
568                    classname,
569                )?)),
570                PropertiesOfFilter::Private => TPropertiesOf::private(Arc::new(get_union_from_type(
571                    &properties_of_type.parameter.entry.inner,
572                    scope,
573                    type_context,
574                    classname,
575                )?)),
576            })))
577        }
578        Type::IndexAccess(index_access_type) => {
579            TUnion::from_atomic(TAtomic::Derived(TDerived::IndexAccess(TIndexAccess::new(
580                get_union_from_type(index_access_type.target, scope, type_context, classname)?,
581                get_union_from_type(index_access_type.index, scope, type_context, classname)?,
582            ))))
583        }
584        _ => {
585            return Err(TypeError::UnsupportedType(ttype.to_string(), ttype.span()));
586        }
587    })
588}
589
590#[inline]
591fn get_object_from_type(
592    object: &ObjectType<'_>,
593    scope: &NamespaceScope,
594    type_context: &TypeResolutionContext,
595    classname: Option<Word>,
596) -> Result<TAtomic, TypeError> {
597    let Some(properties) = object.properties.as_ref() else {
598        return Ok(TAtomic::Object(TObject::Any));
599    };
600
601    let mut known_properties = BTreeMap::new();
602    for property in &properties.fields {
603        let property_is_optional = property.is_optional();
604
605        let Some(field_key) = property.key.as_ref() else {
606            continue;
607        };
608
609        let key = match field_key.key {
610            ShapeKey::String { value, .. } => word(value),
611            ShapeKey::Integer { value, .. } => i64_word(value),
612            ShapeKey::ClassLikeConstant { class_name, constant_name, .. } => {
613                concat_word!(class_name.value, b"::", constant_name.value)
614            }
615        };
616
617        let property_type = get_union_from_type(property.value, scope, type_context, classname)?;
618
619        known_properties.insert(key, (property_is_optional, property_type));
620    }
621
622    Ok(TAtomic::Object(TObject::new_with_properties(properties.ellipsis.is_none(), known_properties)))
623}
624
625#[inline]
626fn get_shape_from_type(
627    shape: &ShapeType<'_>,
628    scope: &NamespaceScope,
629    type_context: &TypeResolutionContext,
630    classname: Option<Word>,
631) -> Result<TAtomic, TypeError> {
632    if shape.kind.is_list() {
633        let mut list = TList::new(match &shape.additional_fields {
634            Some(additional_fields) => match &additional_fields.parameters {
635                Some(parameters) => Arc::new(if let Some(k) = parameters.entries.first().map(|g| &g.inner) {
636                    get_union_from_type(k, scope, type_context, classname)?
637                } else {
638                    get_mixed()
639                }),
640                None => Arc::new(get_mixed()),
641            },
642            None => Arc::new(get_never()),
643        });
644
645        list.known_elements = Some({
646            let mut tree = BTreeMap::new();
647            let mut next_offset: usize = 0;
648
649            for field in &shape.fields {
650                let field_is_optional = field.is_optional();
651
652                let offset = if let Some(field_key) = field.key.as_ref() {
653                    let array_key = match field_key.key {
654                        ShapeKey::String { value, .. } => ArrayKey::String(word(value)),
655                        ShapeKey::Integer { value, .. } => ArrayKey::Integer(value),
656                        ShapeKey::ClassLikeConstant { class_name, constant_name, .. } => {
657                            let class_like_name = if class_name.value.eq_ignore_ascii_case(b"self")
658                                || class_name.value.eq_ignore_ascii_case(b"static")
659                                || class_name.value.eq(b"this")
660                                || class_name.value.eq(b"$this")
661                            {
662                                classname.unwrap_or_else(|| word(class_name.value))
663                            } else if class_name.value.eq_ignore_ascii_case(b"parent") {
664                                word("parent")
665                            } else {
666                                let (resolved, _) = scope.resolve(NameKind::Default, class_name.value);
667                                word(&resolved)
668                            };
669
670                            ArrayKey::ClassLikeConstant { class_like_name, constant_name: word(constant_name.value) }
671                        }
672                    };
673
674                    if let ArrayKey::Integer(offset) = array_key {
675                        if offset > 0 && (offset as usize) == next_offset {
676                            next_offset += 1;
677
678                            offset as usize
679                        } else {
680                            return Err(TypeError::InvalidType(
681                                shape.to_string(),
682                                "List shape keys must be sequential".to_string(),
683                                field_key.span(),
684                            ));
685                        }
686                    } else {
687                        return Err(TypeError::InvalidType(
688                            shape.to_string(),
689                            "List shape keys are expected to be integers".to_string(),
690                            field_key.span(),
691                        ));
692                    }
693                } else {
694                    let offset = next_offset;
695
696                    next_offset += 1;
697
698                    offset
699                };
700
701                let mut field_value_type = get_union_from_type(field.value, scope, type_context, classname)?;
702                if field_is_optional {
703                    field_value_type.set_possibly_undefined(true, None);
704                }
705
706                tree.insert(offset, (field_is_optional, field_value_type));
707            }
708
709            tree
710        });
711
712        list.non_empty = shape.has_non_optional_fields() || shape.kind.is_non_empty();
713
714        Ok(TAtomic::Array(TArray::List(list)))
715    } else {
716        let mut keyed_array = TKeyedArray::new();
717
718        keyed_array.parameters = match &shape.additional_fields {
719            Some(additional_fields) => Some(match &additional_fields.parameters {
720                Some(parameters) => (
721                    Arc::new(if let Some(k) = parameters.entries.first().map(|g| &g.inner) {
722                        get_union_from_type(k, scope, type_context, classname)?
723                    } else {
724                        get_mixed()
725                    }),
726                    Arc::new(if let Some(v) = parameters.entries.get(1).map(|g| &g.inner) {
727                        get_union_from_type(v, scope, type_context, classname)?
728                    } else {
729                        get_mixed()
730                    }),
731                ),
732                None => (Arc::new(get_arraykey()), Arc::new(get_mixed())),
733            }),
734            None => None,
735        };
736
737        keyed_array.known_items = Some({
738            let mut tree = BTreeMap::new();
739            let mut next_offset = 0;
740
741            for field in &shape.fields {
742                let field_is_optional = field.is_optional();
743
744                let array_key = if let Some(field_key) = field.key.as_ref() {
745                    let array_key = match field_key.key {
746                        ShapeKey::String { value, .. } => ArrayKey::String(word(value)),
747                        ShapeKey::Integer { value, .. } => ArrayKey::Integer(value),
748                        ShapeKey::ClassLikeConstant { class_name, constant_name, .. } => {
749                            let class_like_name = if class_name.value.eq_ignore_ascii_case(b"self")
750                                || class_name.value.eq_ignore_ascii_case(b"static")
751                                || class_name.value.eq(b"this")
752                                || class_name.value.eq(b"$this")
753                            {
754                                classname.unwrap_or_else(|| word(class_name.value))
755                            } else if class_name.value.eq_ignore_ascii_case(b"parent") {
756                                word("parent")
757                            } else {
758                                let (resolved, _) = scope.resolve(NameKind::Default, class_name.value);
759                                word(&resolved)
760                            };
761
762                            ArrayKey::ClassLikeConstant { class_like_name, constant_name: word(constant_name.value) }
763                        }
764                    };
765
766                    if let ArrayKey::Integer(offset) = array_key
767                        && offset >= next_offset
768                    {
769                        next_offset = offset.saturating_add(1);
770                    }
771
772                    array_key
773                } else {
774                    let array_key = ArrayKey::Integer(next_offset);
775
776                    next_offset = next_offset.saturating_add(1);
777
778                    array_key
779                };
780
781                let mut field_value_type = get_union_from_type(field.value, scope, type_context, classname)?;
782                if field_is_optional {
783                    field_value_type.set_possibly_undefined(true, None);
784                }
785
786                tree.insert(array_key, (field_is_optional, field_value_type));
787            }
788
789            tree
790        });
791
792        keyed_array.non_empty = shape.has_non_optional_fields() || shape.kind.is_non_empty();
793
794        Ok(TAtomic::Array(TArray::Keyed(keyed_array)))
795    }
796}
797
798#[inline]
799fn get_callable_from_type(
800    callable: &CallableType<'_>,
801    scope: &NamespaceScope,
802    type_context: &TypeResolutionContext,
803    classname: Option<Word>,
804) -> Result<TAtomic, TypeError> {
805    let mut parameters = vec![];
806    let mut return_type = None;
807
808    if let Some(specification) = &callable.specification {
809        for parameter_ast in &specification.parameters.entries {
810            let parameter_type = if let Some(parameter_type) = &parameter_ast.parameter_type {
811                get_union_from_type(parameter_type, scope, type_context, classname)?
812            } else {
813                get_mixed()
814            };
815
816            parameters.push(TCallableParameter::new(
817                Some(Arc::new(parameter_type)),
818                parameter_ast.is_by_reference(),
819                parameter_ast.is_variadic(),
820                parameter_ast.is_optional(),
821            ));
822        }
823
824        if let Some(ret) = specification.return_type.as_ref() {
825            return_type = Some(get_union_from_type(ret.return_type, scope, type_context, classname)?);
826        }
827    } else {
828        // `callable` without a specification should be treated the same as
829        // `callable(mixed...): mixed`
830        parameters.push(TCallableParameter::new(Some(Arc::new(get_mixed())), false, true, false));
831        return_type = Some(get_mixed());
832    }
833
834    Ok(TAtomic::Callable(TCallable::Signature(
835        TCallableSignature::new(callable.kind.is_pure(), callable.kind.is_closure())
836            .with_parameters(parameters)
837            .with_return_type(return_type.map(Arc::new)),
838    )))
839}
840
841#[inline]
842fn get_reference_from_kind(
843    kind: &ReferenceKind<'_>,
844    generics: Option<&GenericParameters<'_>>,
845    scope: &NamespaceScope,
846    type_context: &TypeResolutionContext,
847    classname: Option<Word>,
848) -> Result<TAtomic, TypeError> {
849    let mut is_this = false;
850    let mut is_static = false;
851    let mut is_named_object = false;
852    let fq_reference_name_id = match kind {
853        ReferenceKind::Self_(_) => {
854            is_named_object = true;
855
856            classname.unwrap_or_else(|| word("static"))
857        }
858        ReferenceKind::Static(_) => {
859            is_named_object = true;
860            is_static = true;
861
862            classname.unwrap_or_else(|| word("static"))
863        }
864        ReferenceKind::Identifier(identifier) if identifier.value == b"this" => {
865            is_named_object = true;
866            is_this = true;
867            is_static = true;
868
869            classname.unwrap_or_else(|| word("static"))
870        }
871        ReferenceKind::Parent(_) => {
872            is_named_object = true;
873
874            word("parent")
875        }
876        ReferenceKind::Identifier(identifier) => {
877            let reference_name = identifier.value;
878            let reference_name_atom = word(reference_name);
879            if let Some(defining_entities) = type_context.get_template_definition(reference_name_atom)
880                && generics.is_none()
881            {
882                return Ok(get_template_atomic(defining_entities, reference_name_atom));
883            }
884
885            let (fq_reference_name, _) = scope.resolve(NameKind::Default, reference_name);
886
887            // `Closure` -> `Closure(mixed...): mixed`
888            if fq_reference_name.eq_ignore_ascii_case(b"Closure") && generics.is_none() {
889                return Ok(TAtomic::Callable(TCallable::Signature(
890                    TCallableSignature::new(false, true)
891                        .with_parameters(vec![TCallableParameter::new(Some(Arc::new(get_mixed())), false, true, false)])
892                        .with_return_type(Some(Arc::new(get_mixed()))),
893                )));
894            }
895
896            word(&fq_reference_name)
897        }
898    };
899
900    let mut type_parameters = None;
901    if let Some(generics) = generics {
902        let mut parameters = vec![];
903        for generic in &generics.entries {
904            let mut generic_type = get_union_from_type(&generic.inner, scope, type_context, classname)?;
905
906            for atomic in generic_type.types.to_mut() {
907                if let TAtomic::Object(TObject::Named(named)) = atomic
908                    && named.is_this
909                {
910                    named.name = classname.unwrap_or_else(|| word("static"));
911                    named.is_this = false;
912                }
913            }
914
915            parameters.push(generic_type);
916        }
917
918        type_parameters = Some(parameters);
919    }
920
921    let is_generator = fq_reference_name_id.as_bytes().eq_ignore_ascii_case(b"Generator");
922
923    let is_iterator = is_generator
924        || fq_reference_name_id.as_bytes().eq_ignore_ascii_case(b"Iterator")
925        || fq_reference_name_id.as_bytes().eq_ignore_ascii_case(b"IteratorAggregate")
926        || fq_reference_name_id.as_bytes().eq_ignore_ascii_case(b"Traversable");
927
928    let mixed_default = || {
929        let mut union = get_mixed();
930        union.set_from_template_default(true);
931        union
932    };
933
934    'iterator: {
935        if !is_iterator {
936            break 'iterator;
937        }
938
939        let Some(type_parameters) = &mut type_parameters else {
940            type_parameters = Some(vec![mixed_default(), mixed_default()]);
941
942            break 'iterator;
943        };
944
945        if type_parameters.len() == 1 {
946            type_parameters.insert(0, mixed_default());
947        } else if type_parameters.is_empty() {
948            type_parameters.push(mixed_default());
949            type_parameters.push(mixed_default());
950        }
951
952        if !is_generator {
953            break 'iterator;
954        }
955
956        while type_parameters.len() < 4 {
957            type_parameters.push(mixed_default());
958        }
959    }
960
961    if is_named_object {
962        Ok(TAtomic::Object(TObject::Named(TNamedObject {
963            name: fq_reference_name_id,
964            type_parameters,
965            intersection_types: None,
966            is_static,
967            is_this,
968            remapped_parameters: false,
969        })))
970    } else {
971        Ok(TAtomic::Reference(TReference::Symbol {
972            name: fq_reference_name_id,
973            parameters: type_parameters,
974            intersection_types: None,
975        }))
976    }
977}
978
979#[inline]
980fn get_array_type<'src>(
981    mut key: Option<&'src Type<'src>>,
982    mut value: Option<&'src Type<'src>>,
983    non_empty: bool,
984    scope: &NamespaceScope,
985    type_context: &TypeResolutionContext,
986    classname: Option<Word>,
987) -> Result<TAtomic, TypeError> {
988    if key.is_some() && value.is_none() {
989        std::mem::swap(&mut key, &mut value);
990    }
991
992    let mut array = TKeyedArray::new_with_parameters(
993        Arc::new(if let Some(k) = key {
994            get_union_from_type(k, scope, type_context, classname)?
995        } else {
996            get_arraykey()
997        }),
998        Arc::new(if let Some(v) = value {
999            get_union_from_type(v, scope, type_context, classname)?
1000        } else {
1001            get_mixed()
1002        }),
1003    );
1004
1005    array.non_empty = non_empty;
1006
1007    Ok(TAtomic::Array(TArray::Keyed(array)))
1008}
1009
1010#[inline]
1011fn get_list_type(
1012    value: Option<&Type<'_>>,
1013    non_empty: bool,
1014    scope: &NamespaceScope,
1015    type_context: &TypeResolutionContext,
1016    classname: Option<Word>,
1017) -> Result<TAtomic, TypeError> {
1018    Ok(TAtomic::Array(TArray::List(TList {
1019        element_type: Arc::new(if let Some(v) = value {
1020            get_union_from_type(v, scope, type_context, classname)?
1021        } else {
1022            get_mixed()
1023        }),
1024        known_count: None,
1025        known_elements: None,
1026        non_empty,
1027    })))
1028}
1029
1030#[inline]
1031fn get_class_string_type(
1032    span: Span,
1033    kind: TClassLikeStringKind,
1034    parameter: Option<&SingleGenericParameter<'_>>,
1035    scope: &NamespaceScope,
1036    type_context: &TypeResolutionContext,
1037    classname: Option<Word>,
1038) -> Result<TUnion, TypeError> {
1039    Ok(match parameter {
1040        Some(parameter) => {
1041            let constraint_union = get_union_from_type(&parameter.entry.inner, scope, type_context, classname)?;
1042
1043            let mut class_strings = vec![];
1044            for constraint in constraint_union.types.into_owned() {
1045                match constraint {
1046                    TAtomic::Object(TObject::Named(_) | TObject::Enum(_) | TObject::HasMethod(_))
1047                    | TAtomic::Reference(TReference::Symbol { .. })
1048                    | TAtomic::Alias(_) => class_strings
1049                        .push(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::of_type(kind, constraint)))),
1050                    TAtomic::GenericParameter(TGenericParameter {
1051                        parameter_name,
1052                        defining_entity,
1053                        constraint: nested_constraint,
1054                        ..
1055                    }) => {
1056                        for constraint_atomic in Arc::unwrap_or_clone(nested_constraint).types.into_owned() {
1057                            class_strings.push(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::generic(
1058                                kind,
1059                                parameter_name,
1060                                defining_entity,
1061                                constraint_atomic,
1062                            ))));
1063                        }
1064                    }
1065                    _ => {
1066                        return Err(TypeError::InvalidType(
1067                            kind.to_string(),
1068                            format!(
1069                                "class string parameter must target an object type, found `{}`.",
1070                                constraint.get_id()
1071                            ),
1072                            span,
1073                        ));
1074                    }
1075                }
1076            }
1077
1078            TUnion::from_vec(class_strings)
1079        }
1080        None => wrap_atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::any(kind)))),
1081    })
1082}
1083
1084#[inline]
1085fn get_template_atomic(defining_entities: &[GenericTemplate], parameter_name: Word) -> TAtomic {
1086    let GenericTemplate { defining_entity: template_source, constraint: template_type, .. } = &defining_entities[0];
1087
1088    TAtomic::GenericParameter(TGenericParameter {
1089        parameter_name,
1090        constraint: Arc::new(template_type.clone()),
1091        defining_entity: *template_source,
1092        intersection_types: None,
1093    })
1094}