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