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