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