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