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