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