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