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