Skip to main content

mago_codex/ttype/
builder.rs

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