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