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