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