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