1use std::borrow::Cow;
2use std::hash::Hash;
3use std::hash::Hasher;
4
5use bitflags::bitflags;
6use derivative::Derivative;
7use mago_atom::atom;
8use mago_atom::concat_atom;
9use mago_atom::empty_atom;
10use serde::Deserialize;
11use serde::Serialize;
12
13use mago_atom::Atom;
14
15use crate::metadata::CodebaseMetadata;
16use crate::reference::ReferenceSource;
17use crate::reference::SymbolReferences;
18use crate::symbol::Symbols;
19use crate::ttype::TType;
20use crate::ttype::TypeRef;
21use crate::ttype::atomic::TAtomic;
22use crate::ttype::atomic::array::TArray;
23use crate::ttype::atomic::array::key::ArrayKey;
24use crate::ttype::atomic::generic::TGenericParameter;
25use crate::ttype::atomic::mixed::truthiness::TMixedTruthiness;
26use crate::ttype::atomic::object::TObject;
27use crate::ttype::atomic::object::named::TNamedObject;
28use crate::ttype::atomic::object::with_properties::TObjectWithProperties;
29use crate::ttype::atomic::populate_atomic_type;
30use crate::ttype::atomic::scalar::TScalar;
31use crate::ttype::atomic::scalar::bool::TBool;
32use crate::ttype::atomic::scalar::class_like_string::TClassLikeString;
33use crate::ttype::atomic::scalar::int::TInteger;
34use crate::ttype::atomic::scalar::string::TString;
35use crate::ttype::atomic::scalar::string::TStringLiteral;
36use crate::ttype::get_arraykey;
37use crate::ttype::get_int;
38use crate::ttype::get_mixed;
39
40bitflags! {
41 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
46 pub struct UnionFlags: u16 {
47 const HAD_TEMPLATE = 1 << 0;
49 const BY_REFERENCE = 1 << 1;
51 const REFERENCE_FREE = 1 << 2;
53 const POSSIBLY_UNDEFINED_FROM_TRY = 1 << 3;
55 const POSSIBLY_UNDEFINED = 1 << 4;
57 const IGNORE_NULLABLE_ISSUES = 1 << 5;
59 const IGNORE_FALSABLE_ISSUES = 1 << 6;
61 const FROM_TEMPLATE_DEFAULT = 1 << 7;
63 const POPULATED = 1 << 8;
65 }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, Eq, Derivative, PartialOrd, Ord)]
69pub struct TUnion {
70 pub types: Cow<'static, [TAtomic]>,
71 pub flags: UnionFlags,
72}
73
74impl Hash for TUnion {
75 fn hash<H: Hasher>(&self, state: &mut H) {
76 for t in self.types.as_ref() {
77 t.hash(state);
78 }
79 }
80}
81
82impl TUnion {
83 pub fn new(types: Cow<'static, [TAtomic]>) -> TUnion {
88 TUnion { types, flags: UnionFlags::empty() }
89 }
90
91 pub fn from_vec(mut types: Vec<TAtomic>) -> TUnion {
96 if cfg!(debug_assertions) {
97 if types.is_empty() {
98 panic!(
99 "TUnion::from_vec() received an empty Vec. This indicates a logic error \
100 in type construction - unions must contain at least one type. \
101 Consider using TAtomic::Never for empty/impossible types."
102 );
103 }
104
105 if types.len() > 1
106 && types.iter().any(|atomic| {
107 atomic.is_never()
108 || atomic.map_generic_parameter_constraint(|constraint| constraint.is_never()).unwrap_or(false)
109 })
110 {
111 panic!(
112 "TUnion::from_vec() received a mix of 'never' and other types. \
113 This indicates a logic error - 'never' should be filtered out before \
114 creating unions since (A | never) = A. Types received: {types:#?}"
115 )
116 }
117 } else {
118 if types.len() > 1 {
121 types.retain(|atomic| {
122 !atomic.is_never()
123 && !atomic.map_generic_parameter_constraint(|constraint| constraint.is_never()).unwrap_or(false)
124 });
125 }
126
127 if types.is_empty() {
130 types.push(TAtomic::Never);
131 }
132 }
133
134 Self::new(Cow::Owned(types))
135 }
136
137 pub fn from_single(atomic: Cow<'static, TAtomic>) -> TUnion {
143 let types_cow = match atomic {
144 Cow::Borrowed(borrowed_atomic) => Cow::Borrowed(std::slice::from_ref(borrowed_atomic)),
145 Cow::Owned(owned_atomic) => Cow::Owned(vec![owned_atomic]),
146 };
147
148 TUnion::new(types_cow)
149 }
150
151 pub fn from_atomic(atomic: TAtomic) -> TUnion {
153 TUnion::new(Cow::Owned(vec![atomic]))
154 }
155
156 #[inline]
157 pub fn set_possibly_undefined(&mut self, possibly_undefined: bool, from_try: Option<bool>) {
158 let from_try = from_try.unwrap_or(self.flags.contains(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY));
159
160 self.flags.set(UnionFlags::POSSIBLY_UNDEFINED, possibly_undefined);
161 self.flags.set(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY, from_try);
162 }
163
164 #[inline]
165 pub const fn had_template(&self) -> bool {
166 self.flags.contains(UnionFlags::HAD_TEMPLATE)
167 }
168
169 #[inline]
170 pub const fn by_reference(&self) -> bool {
171 self.flags.contains(UnionFlags::BY_REFERENCE)
172 }
173
174 #[inline]
175 pub const fn reference_free(&self) -> bool {
176 self.flags.contains(UnionFlags::REFERENCE_FREE)
177 }
178
179 #[inline]
180 pub const fn possibly_undefined_from_try(&self) -> bool {
181 self.flags.contains(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY)
182 }
183
184 #[inline]
185 pub const fn possibly_undefined(&self) -> bool {
186 self.flags.contains(UnionFlags::POSSIBLY_UNDEFINED)
187 }
188
189 #[inline]
190 pub const fn ignore_nullable_issues(&self) -> bool {
191 self.flags.contains(UnionFlags::IGNORE_NULLABLE_ISSUES)
192 }
193
194 #[inline]
195 pub const fn ignore_falsable_issues(&self) -> bool {
196 self.flags.contains(UnionFlags::IGNORE_FALSABLE_ISSUES)
197 }
198
199 #[inline]
200 pub const fn from_template_default(&self) -> bool {
201 self.flags.contains(UnionFlags::FROM_TEMPLATE_DEFAULT)
202 }
203
204 #[inline]
205 pub const fn populated(&self) -> bool {
206 self.flags.contains(UnionFlags::POPULATED)
207 }
208
209 #[inline]
210 pub fn set_had_template(&mut self, value: bool) {
211 self.flags.set(UnionFlags::HAD_TEMPLATE, value);
212 }
213
214 #[inline]
215 pub fn set_by_reference(&mut self, value: bool) {
216 self.flags.set(UnionFlags::BY_REFERENCE, value);
217 }
218
219 #[inline]
220 pub fn set_reference_free(&mut self, value: bool) {
221 self.flags.set(UnionFlags::REFERENCE_FREE, value);
222 }
223
224 #[inline]
225 pub fn set_possibly_undefined_from_try(&mut self, value: bool) {
226 self.flags.set(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY, value);
227 }
228
229 #[inline]
230 pub fn set_ignore_nullable_issues(&mut self, value: bool) {
231 self.flags.set(UnionFlags::IGNORE_NULLABLE_ISSUES, value);
232 }
233
234 #[inline]
235 pub fn set_ignore_falsable_issues(&mut self, value: bool) {
236 self.flags.set(UnionFlags::IGNORE_FALSABLE_ISSUES, value);
237 }
238
239 #[inline]
240 pub fn set_from_template_default(&mut self, value: bool) {
241 self.flags.set(UnionFlags::FROM_TEMPLATE_DEFAULT, value);
242 }
243
244 #[inline]
245 pub fn set_populated(&mut self, value: bool) {
246 self.flags.set(UnionFlags::POPULATED, value);
247 }
248
249 pub fn clone_with_types(&self, types: Vec<TAtomic>) -> TUnion {
251 TUnion { types: Cow::Owned(types), flags: self.flags }
252 }
253
254 pub fn to_non_nullable(&self) -> TUnion {
255 TUnion { types: Cow::Owned(self.get_non_nullable_types()), flags: self.flags }
256 }
257
258 pub fn to_truthy(&self) -> TUnion {
259 TUnion { types: Cow::Owned(self.get_truthy_types()), flags: self.flags }
260 }
261
262 pub fn get_non_nullable_types(&self) -> Vec<TAtomic> {
263 self.types
264 .iter()
265 .filter_map(|t| match t {
266 TAtomic::Null | TAtomic::Void => None,
267 TAtomic::GenericParameter(parameter) => Some(TAtomic::GenericParameter(TGenericParameter {
268 parameter_name: parameter.parameter_name,
269 defining_entity: parameter.defining_entity,
270 intersection_types: parameter.intersection_types.clone(),
271 constraint: Box::new(parameter.constraint.to_non_nullable()),
272 })),
273 TAtomic::Mixed(mixed) => Some(TAtomic::Mixed(mixed.with_is_non_null(true))),
274 atomic => Some(atomic.clone()),
275 })
276 .collect()
277 }
278
279 pub fn get_truthy_types(&self) -> Vec<TAtomic> {
280 self.types
281 .iter()
282 .filter_map(|t| match t {
283 TAtomic::GenericParameter(parameter) => Some(TAtomic::GenericParameter(TGenericParameter {
284 parameter_name: parameter.parameter_name,
285 defining_entity: parameter.defining_entity,
286 intersection_types: parameter.intersection_types.clone(),
287 constraint: Box::new(parameter.constraint.to_truthy()),
288 })),
289 TAtomic::Mixed(mixed) => Some(TAtomic::Mixed(mixed.with_truthiness(TMixedTruthiness::Truthy))),
290 atomic => {
291 if atomic.is_falsy() {
292 None
293 } else {
294 Some(atomic.clone())
295 }
296 }
297 })
298 .collect()
299 }
300
301 pub fn as_nullable(mut self) -> TUnion {
303 let types = self.types.to_mut();
304
305 types.iter_mut().for_each(|atomic| {
306 if let TAtomic::Mixed(mixed) = atomic {
307 *mixed = mixed.with_is_non_null(false);
308 }
309 });
310
311 if !types.iter().any(|atomic| atomic.is_null() || atomic.is_mixed()) {
312 types.push(TAtomic::Null);
313 }
314
315 self
316 }
317
318 pub fn remove_type(&mut self, bad_type: &TAtomic) {
320 self.types.to_mut().retain(|t| t != bad_type);
321 }
322
323 pub fn replace_type(&mut self, remove_type: &TAtomic, add_type: TAtomic) {
325 let types = self.types.to_mut();
326
327 if let Some(index) = types.iter().position(|t| t == remove_type) {
328 types[index] = add_type;
329 } else {
330 types.push(add_type);
331 }
332 }
333
334 pub fn is_int(&self) -> bool {
335 for atomic in self.types.as_ref() {
336 if !atomic.is_int() {
337 return false;
338 }
339 }
340
341 true
342 }
343
344 pub fn has_int_or_float(&self) -> bool {
345 for atomic in self.types.as_ref() {
346 if atomic.is_int_or_float() {
347 return true;
348 }
349 }
350
351 false
352 }
353
354 pub fn has_int_and_float(&self) -> bool {
355 let mut has_int = false;
356 let mut has_float = false;
357
358 for atomic in self.types.as_ref() {
359 if atomic.is_int() {
360 has_int = true;
361 } else if atomic.is_float() {
362 has_float = true;
363 } else if atomic.is_int_or_float() {
364 has_int = true;
365 has_float = true;
366 }
367
368 if has_int && has_float {
369 return true;
370 }
371 }
372
373 false
374 }
375
376 pub fn has_int_and_string(&self) -> bool {
377 let mut has_int = false;
378 let mut has_string = false;
379
380 for atomic in self.types.as_ref() {
381 if atomic.is_int() {
382 has_int = true;
383 } else if atomic.is_string() {
384 has_string = true;
385 } else if atomic.is_array_key() {
386 has_int = true;
387 has_string = true;
388 }
389
390 if has_int && has_string {
391 return true;
392 }
393 }
394
395 false
396 }
397
398 pub fn has_int(&self) -> bool {
399 for atomic in self.types.as_ref() {
400 if atomic.is_int() || atomic.is_array_key() || atomic.is_numeric() {
401 return true;
402 }
403 }
404
405 false
406 }
407
408 pub fn has_float(&self) -> bool {
409 for atomic in self.types.as_ref() {
410 if atomic.is_float() {
411 return true;
412 }
413 }
414
415 false
416 }
417
418 pub fn is_array_key(&self) -> bool {
419 for atomic in self.types.as_ref() {
420 if atomic.is_array_key() {
421 continue;
422 }
423
424 return false;
425 }
426
427 true
428 }
429
430 pub fn is_any_string(&self) -> bool {
431 for atomic in self.types.as_ref() {
432 if !atomic.is_any_string() {
433 return false;
434 }
435 }
436
437 true
438 }
439
440 pub fn is_string(&self) -> bool {
441 self.types.iter().all(|t| t.is_string()) && !self.types.is_empty()
442 }
443
444 pub fn is_always_array_key(&self, ignore_never: bool) -> bool {
445 self.types.iter().all(|atomic| match atomic {
446 TAtomic::Never => ignore_never,
447 TAtomic::Scalar(scalar) => matches!(
448 scalar,
449 TScalar::ArrayKey | TScalar::Integer(_) | TScalar::String(_) | TScalar::ClassLikeString(_)
450 ),
451 TAtomic::GenericParameter(generic_parameter) => {
452 generic_parameter.constraint.is_always_array_key(ignore_never)
453 }
454 _ => false,
455 })
456 }
457
458 pub fn is_non_empty_string(&self) -> bool {
459 self.types.iter().all(|t| t.is_non_empty_string()) && !self.types.is_empty()
460 }
461
462 pub fn is_empty_array(&self) -> bool {
463 self.types.iter().all(|t| t.is_empty_array()) && !self.types.is_empty()
464 }
465
466 pub fn has_string(&self) -> bool {
467 self.types.iter().any(|t| t.is_string()) && !self.types.is_empty()
468 }
469
470 pub fn is_float(&self) -> bool {
471 self.types.iter().all(|t| t.is_float()) && !self.types.is_empty()
472 }
473
474 pub fn is_bool(&self) -> bool {
475 self.types.iter().all(|t| t.is_bool()) && !self.types.is_empty()
476 }
477
478 pub fn is_never(&self) -> bool {
479 self.types.iter().all(|t| t.is_never()) || self.types.is_empty()
480 }
481
482 pub fn is_never_template(&self) -> bool {
483 self.types.iter().all(|t| t.is_templated_as_never()) && !self.types.is_empty()
484 }
485
486 pub fn is_placeholder(&self) -> bool {
487 self.types.iter().all(|t| matches!(t, TAtomic::Placeholder)) && !self.types.is_empty()
488 }
489
490 pub fn is_true(&self) -> bool {
491 self.types.iter().all(|t| t.is_true()) && !self.types.is_empty()
492 }
493
494 pub fn is_false(&self) -> bool {
495 self.types.iter().all(|t| t.is_false()) && !self.types.is_empty()
496 }
497
498 pub fn is_nonnull(&self) -> bool {
499 self.types.len() == 1 && matches!(self.types[0], TAtomic::Mixed(mixed) if mixed.is_non_null())
500 }
501
502 pub fn is_numeric(&self) -> bool {
503 self.types.iter().all(|t| t.is_numeric()) && !self.types.is_empty()
504 }
505
506 pub fn is_int_or_float(&self) -> bool {
507 self.types.iter().all(|t| t.is_int_or_float()) && !self.types.is_empty()
508 }
509
510 pub fn effective_int_or_float(&self) -> Option<bool> {
513 let mut result: Option<bool> = None;
514 for atomic in self.types.as_ref() {
515 match atomic.effective_int_or_float() {
516 Some(is_int) => {
517 if let Some(prev) = result {
518 if prev != is_int {
519 return None;
520 }
521 } else {
522 result = Some(is_int);
523 }
524 }
525 None => return None,
526 }
527 }
528
529 result
530 }
531
532 pub fn is_mixed(&self) -> bool {
533 self.types.iter().all(|t| matches!(t, TAtomic::Mixed(_))) && !self.types.is_empty()
534 }
535
536 pub fn is_mixed_template(&self) -> bool {
537 self.types.iter().all(|t| t.is_templated_as_mixed()) && !self.types.is_empty()
538 }
539
540 pub fn has_mixed(&self) -> bool {
541 self.types.iter().any(|t| matches!(t, TAtomic::Mixed(_))) && !self.types.is_empty()
542 }
543
544 pub fn has_mixed_template(&self) -> bool {
545 self.types.iter().any(|t| t.is_templated_as_mixed()) && !self.types.is_empty()
546 }
547
548 pub fn has_nullable_mixed(&self) -> bool {
549 self.types.iter().any(|t| matches!(t, TAtomic::Mixed(mixed) if !mixed.is_non_null())) && !self.types.is_empty()
550 }
551
552 pub fn has_void(&self) -> bool {
553 self.types.iter().any(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
554 }
555
556 pub fn has_null(&self) -> bool {
557 self.types.iter().any(|t| matches!(t, TAtomic::Null)) && !self.types.is_empty()
558 }
559
560 pub fn has_nullish(&self) -> bool {
561 self.types.iter().any(|t| match t {
562 TAtomic::Null | TAtomic::Void => true,
563 TAtomic::Mixed(mixed) => !mixed.is_non_null(),
564 TAtomic::GenericParameter(parameter) => parameter.constraint.has_nullish(),
565 _ => false,
566 }) && !self.types.is_empty()
567 }
568
569 pub fn is_nullable_mixed(&self) -> bool {
570 if self.types.len() != 1 {
571 return false;
572 }
573
574 match &self.types[0] {
575 TAtomic::Mixed(mixed) => !mixed.is_non_null(),
576 _ => false,
577 }
578 }
579
580 pub fn is_falsy_mixed(&self) -> bool {
581 if self.types.len() != 1 {
582 return false;
583 }
584
585 matches!(&self.types[0], &TAtomic::Mixed(mixed) if mixed.is_falsy())
586 }
587
588 pub fn is_vanilla_mixed(&self) -> bool {
589 if self.types.len() != 1 {
590 return false;
591 }
592
593 self.types[0].is_vanilla_mixed()
594 }
595
596 pub fn is_templated_as_vanilla_mixed(&self) -> bool {
597 if self.types.len() != 1 {
598 return false;
599 }
600
601 self.types[0].is_templated_as_vanilla_mixed()
602 }
603
604 pub fn has_template_or_static(&self) -> bool {
605 for atomic in self.types.as_ref() {
606 if let TAtomic::GenericParameter(_) = atomic {
607 return true;
608 }
609
610 if let TAtomic::Object(TObject::Named(named_object)) = atomic {
611 if named_object.is_this() {
612 return true;
613 }
614
615 if let Some(intersections) = named_object.get_intersection_types() {
616 for intersection in intersections {
617 if let TAtomic::GenericParameter(_) = intersection {
618 return true;
619 }
620 }
621 }
622 }
623 }
624
625 false
626 }
627
628 pub fn has_template(&self) -> bool {
629 for atomic in self.types.as_ref() {
630 if let TAtomic::GenericParameter(_) = atomic {
631 return true;
632 }
633
634 if let Some(intersections) = atomic.get_intersection_types() {
635 for intersection in intersections {
636 if let TAtomic::GenericParameter(_) = intersection {
637 return true;
638 }
639 }
640 }
641 }
642
643 false
644 }
645
646 pub fn has_template_types(&self) -> bool {
647 let all_child_nodes = self.get_all_child_nodes();
648
649 for child_node in all_child_nodes {
650 if let TypeRef::Atomic(
651 TAtomic::GenericParameter(_)
652 | TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic { .. })),
653 ) = child_node
654 {
655 return true;
656 }
657 }
658
659 false
660 }
661
662 pub fn get_template_types(&self) -> Vec<&TAtomic> {
663 let all_child_nodes = self.get_all_child_nodes();
664
665 let mut template_types = Vec::new();
666
667 for child_node in all_child_nodes {
668 if let TypeRef::Atomic(inner) = child_node {
669 match inner {
670 TAtomic::GenericParameter(_) => {
671 template_types.push(inner);
672 }
673 TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic { .. })) => {
674 template_types.push(inner);
675 }
676 _ => {}
677 }
678 }
679 }
680
681 template_types
682 }
683
684 pub fn is_objecty(&self) -> bool {
685 for atomic in self.types.as_ref() {
686 if let &TAtomic::Object(_) = atomic {
687 continue;
688 }
689
690 if let TAtomic::Callable(callable) = atomic
691 && callable.get_signature().is_none_or(|signature| signature.is_closure())
692 {
693 continue;
694 }
695
696 return false;
697 }
698
699 true
700 }
701
702 pub fn is_generator(&self) -> bool {
703 for atomic in self.types.as_ref() {
704 if atomic.is_generator() {
705 continue;
706 }
707
708 return false;
709 }
710
711 true
712 }
713
714 pub fn extends_or_implements(&self, codebase: &CodebaseMetadata, interface: &str) -> bool {
715 for atomic in self.types.as_ref() {
716 if !atomic.extends_or_implements(codebase, interface) {
717 return false;
718 }
719 }
720
721 true
722 }
723
724 pub fn is_generic_parameter(&self) -> bool {
725 self.types.len() == 1 && matches!(self.types[0], TAtomic::GenericParameter(_))
726 }
727
728 pub fn get_generic_parameter_constraint(&self) -> Option<&TUnion> {
729 if self.is_generic_parameter()
730 && let TAtomic::GenericParameter(parameter) = &self.types[0]
731 {
732 return Some(¶meter.constraint);
733 }
734
735 None
736 }
737
738 pub fn is_null(&self) -> bool {
739 self.types.iter().all(|t| matches!(t, TAtomic::Null)) && !self.types.is_empty()
740 }
741
742 pub fn is_nullable(&self) -> bool {
743 self.types.iter().any(|t| match t {
744 TAtomic::Null => self.types.len() >= 2,
745 _ => false,
746 })
747 }
748
749 pub fn is_void(&self) -> bool {
750 self.types.iter().all(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
751 }
752
753 pub fn is_voidable(&self) -> bool {
754 self.types.iter().any(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
755 }
756
757 pub fn has_resource(&self) -> bool {
758 self.types.iter().any(|t| t.is_resource())
759 }
760
761 pub fn is_resource(&self) -> bool {
762 self.types.iter().all(|t| t.is_resource()) && !self.types.is_empty()
763 }
764
765 pub fn is_array(&self) -> bool {
766 self.types.iter().all(|t| t.is_array()) && !self.types.is_empty()
767 }
768
769 pub fn is_list(&self) -> bool {
770 self.types.iter().all(|t| t.is_list()) && !self.types.is_empty()
771 }
772
773 pub fn is_keyed_array(&self) -> bool {
774 self.types.iter().all(|t| t.is_keyed_array()) && !self.types.is_empty()
775 }
776
777 pub fn is_falsable(&self) -> bool {
778 self.types.len() >= 2 && self.types.iter().any(|t| t.is_false())
779 }
780
781 pub fn has_bool(&self) -> bool {
782 self.types.iter().any(|t| t.is_bool() || t.is_generic_scalar()) && !self.types.is_empty()
783 }
784
785 pub fn has_scalar(&self) -> bool {
791 self.types.iter().any(|atomic| atomic.is_generic_scalar())
792 }
793
794 pub fn has_scalar_combination(&self) -> bool {
797 const HAS_INT: u8 = 1 << 0;
798 const HAS_FLOAT: u8 = 1 << 1;
799 const HAS_BOOL: u8 = 1 << 2;
800 const HAS_STRING: u8 = 1 << 3;
801 const ALL_SCALARS: u8 = HAS_INT | HAS_FLOAT | HAS_BOOL | HAS_STRING;
802
803 let mut flags = 0u8;
804
805 for atomic in self.types.as_ref() {
806 if atomic.is_int() {
807 flags |= HAS_INT;
808 } else if atomic.is_float() {
809 flags |= HAS_FLOAT;
810 } else if atomic.is_bool() {
811 flags |= HAS_BOOL;
812 } else if atomic.is_string() {
813 flags |= HAS_STRING;
814 } else if atomic.is_array_key() {
815 flags |= HAS_INT | HAS_STRING;
816 } else if atomic.is_numeric() {
817 flags |= HAS_INT | HAS_FLOAT;
819 } else if atomic.is_generic_scalar() {
820 return true;
821 }
822
823 if flags == ALL_SCALARS {
825 return true;
826 }
827 }
828
829 flags == ALL_SCALARS
830 }
831 pub fn has_array_key(&self) -> bool {
832 self.types.iter().any(|atomic| atomic.is_array_key())
833 }
834
835 pub fn has_iterable(&self) -> bool {
836 self.types.iter().any(|atomic| atomic.is_iterable()) && !self.types.is_empty()
837 }
838
839 pub fn has_array(&self) -> bool {
840 self.types.iter().any(|atomic| atomic.is_array()) && !self.types.is_empty()
841 }
842
843 pub fn has_traversable(&self, codebase: &CodebaseMetadata) -> bool {
844 self.types.iter().any(|atomic| atomic.is_traversable(codebase)) && !self.types.is_empty()
845 }
846
847 pub fn has_array_key_like(&self) -> bool {
848 self.types.iter().any(|atomic| atomic.is_array_key() || atomic.is_int() || atomic.is_string())
849 }
850
851 pub fn has_numeric(&self) -> bool {
852 self.types.iter().any(|atomic| atomic.is_numeric()) && !self.types.is_empty()
853 }
854
855 pub fn is_always_truthy(&self) -> bool {
856 self.types.iter().all(|atomic| atomic.is_truthy()) && !self.types.is_empty()
857 }
858
859 pub fn is_always_falsy(&self) -> bool {
860 self.types.iter().all(|atomic| atomic.is_falsy()) && !self.types.is_empty()
861 }
862
863 pub fn is_literal_of(&self, other: &TUnion) -> bool {
864 let Some(other_atomic_type) = other.types.first() else {
865 return false;
866 };
867
868 match other_atomic_type {
869 TAtomic::Scalar(TScalar::String(_)) => {
870 for self_atomic_type in self.types.as_ref() {
871 if self_atomic_type.is_string_of_literal_origin() {
872 continue;
873 }
874
875 return false;
876 }
877
878 true
879 }
880 TAtomic::Scalar(TScalar::Integer(_)) => {
881 for self_atomic_type in self.types.as_ref() {
882 if self_atomic_type.is_literal_int() {
883 continue;
884 }
885
886 return false;
887 }
888
889 true
890 }
891 TAtomic::Scalar(TScalar::Float(_)) => {
892 for self_atomic_type in self.types.as_ref() {
893 if self_atomic_type.is_literal_float() {
894 continue;
895 }
896
897 return false;
898 }
899
900 true
901 }
902 _ => false,
903 }
904 }
905
906 pub fn all_literals(&self) -> bool {
907 self.types
908 .iter()
909 .all(|atomic| atomic.is_string_of_literal_origin() || atomic.is_literal_int() || atomic.is_literal_float())
910 }
911
912 pub fn has_static_object(&self) -> bool {
913 self.types
914 .iter()
915 .any(|atomic| matches!(atomic, TAtomic::Object(TObject::Named(named_object)) if named_object.is_this()))
916 }
917
918 pub fn is_static_object(&self) -> bool {
919 self.types
920 .iter()
921 .all(|atomic| matches!(atomic, TAtomic::Object(TObject::Named(named_object)) if named_object.is_this()))
922 }
923
924 #[inline]
925 pub fn is_single(&self) -> bool {
926 self.types.len() == 1
927 }
928
929 #[inline]
930 pub fn get_single_string(&self) -> Option<&TString> {
931 if self.is_single()
932 && let TAtomic::Scalar(TScalar::String(string)) = &self.types[0]
933 {
934 Some(string)
935 } else {
936 None
937 }
938 }
939
940 #[inline]
941 pub fn get_single_array(&self) -> Option<&TArray> {
942 if self.is_single()
943 && let TAtomic::Array(array) = &self.types[0]
944 {
945 Some(array)
946 } else {
947 None
948 }
949 }
950
951 #[inline]
952 pub fn get_single_bool(&self) -> Option<&TBool> {
953 if self.is_single()
954 && let TAtomic::Scalar(TScalar::Bool(bool)) = &self.types[0]
955 {
956 Some(bool)
957 } else {
958 None
959 }
960 }
961
962 #[inline]
963 pub fn get_single_named_object(&self) -> Option<&TNamedObject> {
964 if self.is_single()
965 && let TAtomic::Object(TObject::Named(named_object)) = &self.types[0]
966 {
967 Some(named_object)
968 } else {
969 None
970 }
971 }
972
973 #[inline]
974 pub fn get_single_shaped_object(&self) -> Option<&TObjectWithProperties> {
975 if self.is_single()
976 && let TAtomic::Object(TObject::WithProperties(shaped_object)) = &self.types[0]
977 {
978 Some(shaped_object)
979 } else {
980 None
981 }
982 }
983
984 #[inline]
985 pub fn get_single(&self) -> &TAtomic {
986 &self.types[0]
987 }
988
989 #[inline]
990 pub fn get_single_owned(self) -> TAtomic {
991 self.types[0].to_owned()
992 }
993
994 #[inline]
995 pub fn is_named_object(&self) -> bool {
996 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Named(_))))
997 }
998
999 pub fn is_enum(&self) -> bool {
1000 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(_))))
1001 }
1002
1003 pub fn is_enum_case(&self) -> bool {
1004 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(r#enum)) if r#enum.case.is_some()))
1005 }
1006
1007 pub fn is_single_enum_case(&self) -> bool {
1008 self.is_single()
1009 && self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(r#enum)) if r#enum.case.is_some()))
1010 }
1011
1012 #[inline]
1013 pub fn has_named_object(&self) -> bool {
1014 self.types.iter().any(|t| matches!(t, TAtomic::Object(TObject::Named(_))))
1015 }
1016
1017 #[inline]
1018 pub fn has_object(&self) -> bool {
1019 self.types
1020 .iter()
1021 .any(|t| matches!(t, TAtomic::Object(TObject::Any) | TAtomic::Object(TObject::WithProperties(_))))
1022 }
1023
1024 #[inline]
1025 pub fn has_callable(&self) -> bool {
1026 self.types.iter().any(|t| matches!(t, TAtomic::Callable(_)))
1027 }
1028
1029 #[inline]
1030 pub fn is_callable(&self) -> bool {
1031 self.types.iter().all(|t| matches!(t, TAtomic::Callable(_)))
1032 }
1033
1034 #[inline]
1035 pub fn has_object_type(&self) -> bool {
1036 self.types.iter().any(|t| matches!(t, TAtomic::Object(_)))
1037 }
1038
1039 pub fn get_enum_cases(&self) -> Vec<(Atom, Option<Atom>)> {
1042 self.types
1043 .iter()
1044 .filter_map(|t| match t {
1045 TAtomic::Object(TObject::Enum(enum_object)) => Some((enum_object.name, enum_object.case)),
1046 _ => None,
1047 })
1048 .collect()
1049 }
1050
1051 pub fn get_single_int(&self) -> Option<TInteger> {
1052 if self.is_single() { self.get_single().get_integer() } else { None }
1053 }
1054
1055 pub fn get_single_literal_int_value(&self) -> Option<i64> {
1056 if self.is_single() { self.get_single().get_literal_int_value() } else { None }
1057 }
1058
1059 pub fn get_single_maximum_int_value(&self) -> Option<i64> {
1060 if self.is_single() { self.get_single().get_maximum_int_value() } else { None }
1061 }
1062
1063 pub fn get_single_minimum_int_value(&self) -> Option<i64> {
1064 if self.is_single() { self.get_single().get_minimum_int_value() } else { None }
1065 }
1066
1067 pub fn get_single_literal_float_value(&self) -> Option<f64> {
1068 if self.is_single() { self.get_single().get_literal_float_value() } else { None }
1069 }
1070
1071 pub fn get_single_literal_string_value(&self) -> Option<&str> {
1072 if self.is_single() { self.get_single().get_literal_string_value() } else { None }
1073 }
1074
1075 pub fn get_single_class_string_value(&self) -> Option<Atom> {
1076 if self.is_single() { self.get_single().get_class_string_value() } else { None }
1077 }
1078
1079 pub fn get_single_array_key(&self) -> Option<ArrayKey> {
1080 if self.is_single() { self.get_single().to_array_key() } else { None }
1081 }
1082
1083 pub fn get_single_key_of_array_like(&self) -> Option<TUnion> {
1084 if !self.is_single() {
1085 return None;
1086 }
1087
1088 match self.get_single() {
1089 TAtomic::Array(array) => match array {
1090 TArray::List(_) => Some(get_int()),
1091 TArray::Keyed(keyed_array) => match &keyed_array.parameters {
1092 Some((k, _)) => Some(*k.clone()),
1093 None => Some(get_arraykey()),
1094 },
1095 },
1096 _ => None,
1097 }
1098 }
1099
1100 pub fn get_single_value_of_array_like(&self) -> Option<Cow<'_, TUnion>> {
1101 if !self.is_single() {
1102 return None;
1103 }
1104
1105 match self.get_single() {
1106 TAtomic::Array(array) => match array {
1107 TArray::List(list) => Some(Cow::Borrowed(&list.element_type)),
1108 TArray::Keyed(keyed_array) => match &keyed_array.parameters {
1109 Some((_, v)) => Some(Cow::Borrowed(v)),
1110 None => Some(Cow::Owned(get_mixed())),
1111 },
1112 },
1113 _ => None,
1114 }
1115 }
1116
1117 pub fn get_literal_ints(&self) -> Vec<&TAtomic> {
1118 self.types.iter().filter(|a| a.is_literal_int()).collect()
1119 }
1120
1121 pub fn get_literal_strings(&self) -> Vec<&TAtomic> {
1122 self.types.iter().filter(|a| a.is_known_literal_string()).collect()
1123 }
1124
1125 pub fn get_literal_string_values(&self) -> Vec<Option<Atom>> {
1126 self.get_literal_strings()
1127 .into_iter()
1128 .map(|atom| match atom {
1129 TAtomic::Scalar(TScalar::String(TString { literal: Some(TStringLiteral::Value(value)), .. })) => {
1130 Some(*value)
1131 }
1132 _ => None,
1133 })
1134 .collect()
1135 }
1136
1137 pub fn has_literal_float(&self) -> bool {
1138 self.types.iter().any(|atomic| match atomic {
1139 TAtomic::Scalar(scalar) => scalar.is_literal_float(),
1140 _ => false,
1141 })
1142 }
1143
1144 pub fn has_literal_int(&self) -> bool {
1145 self.types.iter().any(|atomic| match atomic {
1146 TAtomic::Scalar(scalar) => scalar.is_literal_int(),
1147 _ => false,
1148 })
1149 }
1150
1151 pub fn has_literal_string(&self) -> bool {
1152 self.types.iter().any(|atomic| match atomic {
1153 TAtomic::Scalar(scalar) => scalar.is_known_literal_string(),
1154 _ => false,
1155 })
1156 }
1157
1158 pub fn has_literal_value(&self) -> bool {
1159 self.types.iter().any(|atomic| match atomic {
1160 TAtomic::Scalar(scalar) => scalar.is_literal_value(),
1161 _ => false,
1162 })
1163 }
1164
1165 pub fn accepts_false(&self) -> bool {
1166 self.types.iter().any(|t| match t {
1167 TAtomic::GenericParameter(parameter) => parameter.constraint.accepts_false(),
1168 TAtomic::Mixed(mixed) if !mixed.is_truthy() => true,
1169 TAtomic::Scalar(TScalar::Generic | TScalar::Bool(TBool { value: None | Some(false) })) => true,
1170 _ => false,
1171 })
1172 }
1173
1174 pub fn accepts_null(&self) -> bool {
1175 self.types.iter().any(|t| match t {
1176 TAtomic::GenericParameter(generic_parameter) => generic_parameter.constraint.accepts_null(),
1177 TAtomic::Mixed(mixed) if !mixed.is_non_null() => true,
1178 TAtomic::Null => true,
1179 _ => false,
1180 })
1181 }
1182}
1183
1184impl TType for TUnion {
1185 fn get_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
1186 self.types.iter().map(TypeRef::Atomic).collect()
1187 }
1188
1189 fn needs_population(&self) -> bool {
1190 !self.flags.contains(UnionFlags::POPULATED) && self.types.iter().any(|v| v.needs_population())
1191 }
1192
1193 fn is_expandable(&self) -> bool {
1194 if self.types.is_empty() {
1195 return true;
1196 }
1197
1198 self.types.iter().any(|t| t.is_expandable())
1199 }
1200
1201 fn is_complex(&self) -> bool {
1202 self.types.len() > 3 || self.types.iter().any(|t| t.is_complex())
1203 }
1204
1205 fn get_id(&self) -> Atom {
1206 let len = self.types.len();
1207
1208 let mut atomic_ids: Vec<Atom> = self
1209 .types
1210 .as_ref()
1211 .iter()
1212 .map(|atomic| {
1213 let id = atomic.get_id();
1214 if atomic.is_generic_parameter() || atomic.has_intersection_types() && len > 1 {
1215 concat_atom!("(", id.as_str(), ")")
1216 } else {
1217 id
1218 }
1219 })
1220 .collect();
1221
1222 if len <= 1 {
1223 return atomic_ids.pop().unwrap_or_else(empty_atom);
1224 }
1225
1226 atomic_ids.sort_unstable();
1227 let mut result = atomic_ids[0];
1228 for id in &atomic_ids[1..] {
1229 result = concat_atom!(result.as_str(), "|", id.as_str());
1230 }
1231
1232 result
1233 }
1234
1235 fn get_pretty_id_with_indent(&self, indent: usize) -> Atom {
1236 let len = self.types.len();
1237
1238 if len <= 1 {
1239 return self
1240 .types
1241 .first()
1242 .map(|atomic| atomic.get_pretty_id_with_indent(indent))
1243 .unwrap_or_else(empty_atom);
1244 }
1245
1246 if len > 3 {
1248 let mut atomic_ids: Vec<Atom> = self
1249 .types
1250 .as_ref()
1251 .iter()
1252 .map(|atomic| {
1253 let id = atomic.get_pretty_id_with_indent(indent + 2);
1254 if atomic.has_intersection_types() { concat_atom!("(", id.as_str(), ")") } else { id }
1255 })
1256 .collect();
1257
1258 atomic_ids.sort_unstable();
1259
1260 let mut result = String::new();
1261 result += &atomic_ids[0];
1262 for id in &atomic_ids[1..] {
1263 result += "\n";
1264 result += &" ".repeat(indent);
1265 result += "| ";
1266 result += id.as_str();
1267 }
1268
1269 atom(&result)
1270 } else {
1271 let mut atomic_ids: Vec<Atom> = self
1273 .types
1274 .as_ref()
1275 .iter()
1276 .map(|atomic| {
1277 let id = atomic.get_pretty_id_with_indent(indent);
1278 if atomic.has_intersection_types() && len > 1 { concat_atom!("(", id.as_str(), ")") } else { id }
1279 })
1280 .collect();
1281
1282 atomic_ids.sort_unstable();
1283 let mut result = atomic_ids[0];
1284 for id in &atomic_ids[1..] {
1285 result = concat_atom!(result.as_str(), " | ", id.as_str());
1286 }
1287
1288 result
1289 }
1290 }
1291}
1292
1293impl PartialEq for TUnion {
1294 fn eq(&self, other: &TUnion) -> bool {
1295 const SEMANTIC_FLAGS: UnionFlags = UnionFlags::HAD_TEMPLATE
1296 .union(UnionFlags::BY_REFERENCE)
1297 .union(UnionFlags::REFERENCE_FREE)
1298 .union(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY)
1299 .union(UnionFlags::POSSIBLY_UNDEFINED)
1300 .union(UnionFlags::IGNORE_NULLABLE_ISSUES)
1301 .union(UnionFlags::IGNORE_FALSABLE_ISSUES)
1302 .union(UnionFlags::FROM_TEMPLATE_DEFAULT);
1303
1304 if self.flags.intersection(SEMANTIC_FLAGS) != other.flags.intersection(SEMANTIC_FLAGS) {
1305 return false;
1306 }
1307
1308 let len = self.types.len();
1309 if len != other.types.len() {
1310 return false;
1311 }
1312
1313 for i in 0..len {
1314 let mut has_match = false;
1315 for j in 0..len {
1316 if self.types[i] == other.types[j] {
1317 has_match = true;
1318 break;
1319 }
1320 }
1321
1322 if !has_match {
1323 return false;
1324 }
1325 }
1326
1327 true
1328 }
1329}
1330
1331pub fn populate_union_type(
1332 unpopulated_union: &mut TUnion,
1333 codebase_symbols: &Symbols,
1334 reference_source: Option<&ReferenceSource>,
1335 symbol_references: &mut SymbolReferences,
1336 force: bool,
1337) {
1338 if unpopulated_union.flags.contains(UnionFlags::POPULATED) && !force {
1339 return;
1340 }
1341
1342 if !unpopulated_union.needs_population() {
1343 return;
1344 }
1345
1346 unpopulated_union.flags.insert(UnionFlags::POPULATED);
1347 let unpopulated_atomics = unpopulated_union.types.to_mut();
1348 for unpopulated_atomic in unpopulated_atomics {
1349 match unpopulated_atomic {
1350 TAtomic::Scalar(TScalar::ClassLikeString(
1351 TClassLikeString::Generic { constraint, .. } | TClassLikeString::OfType { constraint, .. },
1352 )) => {
1353 let mut new_constraint = (**constraint).clone();
1354
1355 populate_atomic_type(&mut new_constraint, codebase_symbols, reference_source, symbol_references, force);
1356
1357 **constraint = new_constraint;
1358 }
1359 _ => {
1360 populate_atomic_type(unpopulated_atomic, codebase_symbols, reference_source, symbol_references, force);
1361 }
1362 }
1363 }
1364}