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