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