1use std::borrow::Cow;
2use std::hash::Hash;
3use std::hash::Hasher;
4use std::sync::Arc;
5
6use mago_word::Word;
7use mago_word::concat_word;
8use mago_word::empty_word;
9use mago_word::join_words;
10use mago_word::word;
11
12use crate::metadata::CodebaseMetadata;
13use crate::reference::ReferenceSource;
14use crate::reference::SymbolReferences;
15use crate::symbol::Symbols;
16use crate::ttype::TType;
17use crate::ttype::TypeRef;
18use crate::ttype::atomic::TAtomic;
19use crate::ttype::atomic::array::TArray;
20use crate::ttype::atomic::array::key::ArrayKey;
21use crate::ttype::atomic::generic::TGenericParameter;
22use crate::ttype::atomic::mixed::truthiness::TMixedTruthiness;
23use crate::ttype::atomic::object::TObject;
24use crate::ttype::atomic::object::named::TNamedObject;
25use crate::ttype::atomic::populate_atomic_type;
26use crate::ttype::atomic::scalar::TScalar;
27use crate::ttype::atomic::scalar::bool::TBool;
28use crate::ttype::atomic::scalar::class_like_string::TClassLikeString;
29use crate::ttype::atomic::scalar::float::TFloat;
30use crate::ttype::atomic::scalar::int::TInteger;
31use crate::ttype::atomic::scalar::string::TString;
32use crate::ttype::atomic::scalar::string::TStringCasing;
33use crate::ttype::flags::UnionFlags;
34use crate::ttype::get_mixed;
35
36#[derive(Debug, Clone, Eq, PartialOrd, Ord)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct TUnion {
39 pub types: Cow<'static, [TAtomic]>,
40 pub flags: UnionFlags,
41}
42
43impl Hash for TUnion {
44 fn hash<H>(&self, state: &mut H)
45 where
46 H: Hasher,
47 {
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 from_unspecified_template(&self) -> bool {
191 self.flags.contains(UnionFlags::FROM_UNSPECIFIED_TEMPLATE)
192 }
193
194 #[inline]
195 #[must_use]
196 pub const fn from_template_fallback(&self) -> bool {
197 self.from_template_default() || self.from_unspecified_template()
198 }
199
200 #[must_use]
201 pub fn contains_unspecified_template_arguments(&self) -> bool {
202 self.from_unspecified_template()
203 || self
204 .get_all_child_nodes()
205 .into_iter()
206 .any(|node| matches!(node, TypeRef::Union(union) if union.from_unspecified_template()))
207 }
208
209 #[inline]
210 #[must_use]
211 pub const fn populated(&self) -> bool {
212 self.flags.contains(UnionFlags::POPULATED)
213 }
214
215 #[inline]
216 #[must_use]
217 pub const fn has_nullsafe_null(&self) -> bool {
218 self.flags.contains(UnionFlags::NULLSAFE_NULL)
219 }
220
221 #[inline]
222 pub fn set_had_template(&mut self, value: bool) {
223 self.flags.set(UnionFlags::HAD_TEMPLATE, value);
224 }
225
226 #[inline]
227 pub fn set_by_reference(&mut self, value: bool) {
228 self.flags.set(UnionFlags::BY_REFERENCE, value);
229 }
230
231 #[inline]
232 pub fn set_reference_free(&mut self, value: bool) {
233 self.flags.set(UnionFlags::REFERENCE_FREE, value);
234 }
235
236 #[inline]
237 pub fn set_possibly_undefined_from_try(&mut self, value: bool) {
238 self.flags.set(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY, value);
239 }
240
241 #[inline]
242 pub fn set_ignore_nullable_issues(&mut self, value: bool) {
243 self.flags.set(UnionFlags::IGNORE_NULLABLE_ISSUES, value);
244 }
245
246 #[inline]
247 pub fn set_ignore_falsable_issues(&mut self, value: bool) {
248 self.flags.set(UnionFlags::IGNORE_FALSABLE_ISSUES, value);
249 }
250
251 #[inline]
252 pub fn set_from_template_default(&mut self, value: bool) {
253 self.flags.set(UnionFlags::FROM_TEMPLATE_DEFAULT, value);
254 }
255
256 #[inline]
257 pub fn set_from_unspecified_template(&mut self, value: bool) {
258 self.flags.set(UnionFlags::FROM_UNSPECIFIED_TEMPLATE, value);
259 }
260
261 #[inline]
262 pub fn set_populated(&mut self, value: bool) {
263 self.flags.set(UnionFlags::POPULATED, value);
264 }
265
266 #[inline]
267 pub fn set_nullsafe_null(&mut self, value: bool) {
268 self.flags.set(UnionFlags::NULLSAFE_NULL, value);
269 }
270
271 #[must_use]
273 pub fn clone_with_types(&self, types: Vec<TAtomic>) -> TUnion {
274 TUnion { types: Cow::Owned(ensure_non_empty_types(types)), flags: self.flags }
275 }
276
277 #[must_use]
278 pub fn to_non_nullable(&self) -> TUnion {
279 TUnion {
280 types: Cow::Owned(ensure_non_empty_types(self.get_non_nullable_types())),
281 flags: self.flags & !UnionFlags::NULLSAFE_NULL,
282 }
283 }
284
285 #[must_use]
286 pub fn to_truthy(&self) -> TUnion {
287 TUnion { types: Cow::Owned(ensure_non_empty_types(self.get_truthy_types())), flags: self.flags }
288 }
289
290 #[must_use]
291 pub fn get_non_nullable_types(&self) -> Vec<TAtomic> {
292 self.types
293 .iter()
294 .filter_map(|t| match t {
295 TAtomic::Null | TAtomic::Void => None,
296 TAtomic::GenericParameter(parameter) => Some(TAtomic::GenericParameter(TGenericParameter {
297 parameter_name: parameter.parameter_name,
298 defining_entity: parameter.defining_entity,
299 intersection_types: parameter.intersection_types.clone(),
300 constraint: Arc::new(parameter.constraint.to_non_nullable()),
301 })),
302 TAtomic::Mixed(mixed) => Some(TAtomic::Mixed(mixed.with_is_non_null(true))),
303 atomic => Some(atomic.clone()),
304 })
305 .collect()
306 }
307
308 #[must_use]
309 pub fn get_truthy_types(&self) -> Vec<TAtomic> {
310 self.types
311 .iter()
312 .filter_map(|t| match t {
313 TAtomic::GenericParameter(parameter) => Some(TAtomic::GenericParameter(TGenericParameter {
314 parameter_name: parameter.parameter_name,
315 defining_entity: parameter.defining_entity,
316 intersection_types: parameter.intersection_types.clone(),
317 constraint: Arc::new(parameter.constraint.to_truthy()),
318 })),
319 TAtomic::Mixed(mixed) => Some(TAtomic::Mixed(mixed.with_truthiness(TMixedTruthiness::Truthy))),
320 atomic => {
321 if atomic.is_falsy() {
322 None
323 } else {
324 Some(atomic.clone())
325 }
326 }
327 })
328 .collect()
329 }
330
331 pub fn widen_scalars(&mut self) {
341 self.widen(WidenKind::Scalars);
342 }
343
344 pub fn widen_literals(&mut self) {
350 self.widen(WidenKind::Literals);
351 }
352
353 fn widen(&mut self, kind: WidenKind) {
354 for atomic in self.types.to_mut() {
355 widen_atomic(atomic, kind);
356 }
357 }
358
359 #[must_use]
361 pub fn as_nullable(mut self) -> TUnion {
362 let types = self.types.to_mut();
363
364 for atomic in types.iter_mut() {
365 if let TAtomic::Mixed(mixed) = atomic {
366 *mixed = mixed.with_is_non_null(false);
367 }
368 }
369
370 if !types.iter().any(|atomic| atomic.is_null() || atomic.is_mixed()) {
371 types.push(TAtomic::Null);
372 }
373
374 self
375 }
376
377 pub fn remove_type(&mut self, bad_type: &TAtomic) {
379 self.types.to_mut().retain(|t| t != bad_type);
380 }
381
382 pub fn replace_type(&mut self, remove_type: &TAtomic, add_type: TAtomic) {
384 let types = self.types.to_mut();
385
386 if let Some(index) = types.iter().position(|t| t == remove_type) {
387 types[index] = add_type;
388 } else {
389 types.push(add_type);
390 }
391 }
392
393 #[must_use]
394 pub fn is_int(&self) -> bool {
395 for atomic in self.types.as_ref() {
396 if !atomic.is_int() {
397 return false;
398 }
399 }
400
401 true
402 }
403
404 #[must_use]
405 pub fn has_int_and_string(&self) -> bool {
406 let mut has_int = false;
407 let mut has_string = false;
408
409 for atomic in self.types.as_ref() {
410 if atomic.is_int() {
411 has_int = true;
412 } else if atomic.is_string() {
413 has_string = true;
414 } else if atomic.is_array_key() {
415 has_int = true;
416 has_string = true;
417 }
418
419 if has_int && has_string {
420 return true;
421 }
422 }
423
424 false
425 }
426
427 #[must_use]
428 pub fn has_int(&self) -> bool {
429 for atomic in self.types.as_ref() {
430 if atomic.is_int() || atomic.is_array_key() || atomic.is_numeric() {
431 return true;
432 }
433 }
434
435 false
436 }
437
438 #[must_use]
439 pub fn has_float(&self) -> bool {
440 for atomic in self.types.as_ref() {
441 if atomic.is_float() {
442 return true;
443 }
444 }
445
446 false
447 }
448
449 #[must_use]
450 pub fn is_array_key(&self) -> bool {
451 for atomic in self.types.as_ref() {
452 if atomic.is_array_key() {
453 continue;
454 }
455
456 return false;
457 }
458
459 true
460 }
461
462 #[must_use]
463 pub fn is_any_string(&self) -> bool {
464 for atomic in self.types.as_ref() {
465 if !atomic.is_any_string() {
466 return false;
467 }
468 }
469
470 true
471 }
472
473 pub fn is_string(&self) -> bool {
474 self.types.iter().all(TAtomic::is_string) && !self.types.is_empty()
475 }
476
477 #[must_use]
478 pub fn is_always_array_key(&self, ignore_never: bool) -> bool {
479 self.types.iter().all(|atomic| match atomic {
480 TAtomic::Never => ignore_never,
481 TAtomic::Scalar(scalar) => matches!(
482 scalar,
483 TScalar::ArrayKey | TScalar::Integer(_) | TScalar::String(_) | TScalar::ClassLikeString(_)
484 ),
485 TAtomic::GenericParameter(generic_parameter) => {
486 generic_parameter.constraint.is_always_array_key(ignore_never)
487 }
488 _ => false,
489 })
490 }
491
492 pub fn is_non_empty_string(&self) -> bool {
493 self.types.iter().all(TAtomic::is_non_empty_string) && !self.types.is_empty()
494 }
495
496 pub fn is_empty_array(&self) -> bool {
497 self.types.iter().all(TAtomic::is_empty_array) && !self.types.is_empty()
498 }
499
500 pub fn has_string(&self) -> bool {
501 self.types.iter().any(TAtomic::is_string) && !self.types.is_empty()
502 }
503
504 pub fn is_float(&self) -> bool {
505 self.types.iter().all(TAtomic::is_float) && !self.types.is_empty()
506 }
507
508 pub fn is_bool(&self) -> bool {
509 self.types.iter().all(TAtomic::is_bool) && !self.types.is_empty()
510 }
511
512 pub fn is_never(&self) -> bool {
513 self.types.iter().all(TAtomic::is_never) || self.types.is_empty()
514 }
515
516 pub fn is_never_template(&self) -> bool {
517 self.types.iter().all(TAtomic::is_templated_as_never) && !self.types.is_empty()
518 }
519
520 #[must_use]
521 pub fn is_placeholder(&self) -> bool {
522 self.types.iter().all(|t| matches!(t, TAtomic::Placeholder)) && !self.types.is_empty()
523 }
524
525 #[must_use]
527 pub fn contains_placeholder(&self) -> bool {
528 self.types.iter().any(|t| t.contains_placeholder())
529 }
530
531 pub fn is_true(&self) -> bool {
532 self.types.iter().all(TAtomic::is_true) && !self.types.is_empty()
533 }
534
535 pub fn is_false(&self) -> bool {
536 self.types.iter().all(TAtomic::is_false) && !self.types.is_empty()
537 }
538
539 pub fn is_numeric(&self) -> bool {
540 self.types.iter().all(TAtomic::is_numeric) && !self.types.is_empty()
541 }
542
543 pub fn is_int_or_float(&self) -> bool {
544 self.types.iter().all(TAtomic::is_int_or_float) && !self.types.is_empty()
545 }
546
547 #[must_use]
550 pub fn effective_int_or_float(&self) -> Option<bool> {
551 let mut result: Option<bool> = None;
552 for atomic in self.types.as_ref() {
553 {
554 let is_int = atomic.effective_int_or_float()?;
555 if let Some(prev) = result {
556 if prev != is_int {
557 return None;
558 }
559 } else {
560 result = Some(is_int);
561 }
562 }
563 }
564
565 result
566 }
567
568 #[must_use]
569 pub fn is_mixed(&self) -> bool {
570 self.types.iter().all(|t| matches!(t, TAtomic::Mixed(_))) && !self.types.is_empty()
571 }
572
573 #[must_use]
574 pub fn has_mixed(&self) -> bool {
575 self.types.iter().any(|t| matches!(t, TAtomic::Mixed(_))) && !self.types.is_empty()
576 }
577
578 pub fn has_mixed_template(&self) -> bool {
579 self.types.iter().any(TAtomic::is_templated_as_mixed) && !self.types.is_empty()
580 }
581
582 #[must_use]
583 pub fn has_nullable_mixed(&self) -> bool {
584 self.types.iter().any(|t| matches!(t, TAtomic::Mixed(mixed) if !mixed.is_non_null())) && !self.types.is_empty()
585 }
586
587 #[must_use]
588 pub fn has_null(&self) -> bool {
589 self.types.iter().any(|t| matches!(t, TAtomic::Null)) && !self.types.is_empty()
590 }
591
592 #[must_use]
593 pub fn has_nullish(&self) -> bool {
594 self.types.iter().any(|t| match t {
595 TAtomic::Null | TAtomic::Void => true,
596 TAtomic::Mixed(mixed) => !mixed.is_non_null(),
597 TAtomic::GenericParameter(parameter) => parameter.constraint.has_nullish(),
598 _ => false,
599 }) && !self.types.is_empty()
600 }
601
602 #[must_use]
603 pub fn is_falsy_mixed(&self) -> bool {
604 if self.types.len() != 1 {
605 return false;
606 }
607
608 matches!(&self.types[0], &TAtomic::Mixed(mixed) if mixed.is_falsy())
609 }
610
611 #[must_use]
612 pub fn is_vanilla_mixed(&self) -> bool {
613 if self.types.len() != 1 {
614 return false;
615 }
616
617 self.types[0].is_vanilla_mixed()
618 }
619
620 #[must_use]
621 pub fn is_templated_as_vanilla_mixed(&self) -> bool {
622 if self.types.len() != 1 {
623 return false;
624 }
625
626 self.types[0].is_templated_as_vanilla_mixed()
627 }
628
629 #[must_use]
630 pub fn has_template_or_static(&self) -> bool {
631 for atomic in self.types.as_ref() {
632 if let TAtomic::GenericParameter(_) = atomic {
633 return true;
634 }
635
636 if let TAtomic::Object(TObject::Named(named_object)) = atomic {
637 if named_object.is_static {
638 return true;
639 }
640
641 if let Some(intersections) = named_object.get_intersection_types() {
642 for intersection in intersections {
643 if let TAtomic::GenericParameter(_) = intersection {
644 return true;
645 }
646 }
647 }
648 }
649 }
650
651 false
652 }
653
654 #[must_use]
655 pub fn has_template(&self) -> bool {
656 for atomic in self.types.as_ref() {
657 if let TAtomic::GenericParameter(_) = atomic {
658 return true;
659 }
660
661 if let Some(intersections) = atomic.get_intersection_types() {
662 for intersection in intersections {
663 if let TAtomic::GenericParameter(_) = intersection {
664 return true;
665 }
666 }
667 }
668 }
669
670 false
671 }
672
673 #[must_use]
674 pub fn has_template_types(&self) -> bool {
675 let all_child_nodes = self.get_all_child_nodes();
676
677 for child_node in all_child_nodes {
678 if let TypeRef::Atomic(
679 TAtomic::GenericParameter(_)
680 | TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic { .. })),
681 ) = child_node
682 {
683 return true;
684 }
685 }
686
687 false
688 }
689
690 #[must_use]
691 pub fn get_template_types(&self) -> Vec<&TAtomic> {
692 let all_child_nodes = self.get_all_child_nodes();
693
694 let mut template_types = Vec::new();
695
696 for child_node in all_child_nodes {
697 if let TypeRef::Atomic(inner) = child_node {
698 match inner {
699 TAtomic::GenericParameter(_) => {
700 template_types.push(inner);
701 }
702 TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic { .. })) => {
703 template_types.push(inner);
704 }
705 _ => {}
706 }
707 }
708 }
709
710 template_types
711 }
712
713 #[must_use]
714 pub fn is_objecty(&self) -> bool {
715 for atomic in self.types.as_ref() {
716 if let &TAtomic::Object(_) = atomic {
717 continue;
718 }
719
720 if let TAtomic::Callable(callable) = atomic
721 && callable.is_closure()
722 {
723 continue;
724 }
725
726 return false;
727 }
728
729 true
730 }
731
732 #[must_use]
733 pub fn is_generator(&self) -> bool {
734 for atomic in self.types.as_ref() {
735 if atomic.is_generator() {
736 continue;
737 }
738
739 return false;
740 }
741
742 true
743 }
744
745 #[must_use]
746 pub fn extends_or_implements(&self, codebase: &CodebaseMetadata, interface: &[u8]) -> bool {
747 for atomic in self.types.as_ref() {
748 if !atomic.extends_or_implements(codebase, interface) {
749 return false;
750 }
751 }
752
753 true
754 }
755
756 #[must_use]
757 pub fn is_generic_parameter(&self) -> bool {
758 self.types.len() == 1 && matches!(self.types[0], TAtomic::GenericParameter(_))
759 }
760
761 #[must_use]
762 pub fn is_null(&self) -> bool {
763 self.types.iter().all(|t| matches!(t, TAtomic::Null)) && !self.types.is_empty()
764 }
765
766 #[must_use]
767 pub fn is_nullable(&self) -> bool {
768 self.types.iter().any(|t| match t {
769 TAtomic::Null => self.types.len() >= 2,
770 TAtomic::GenericParameter(param) => param.constraint.is_nullable(),
771 _ => false,
772 })
773 }
774
775 #[must_use]
776 pub fn can_be_null(&self) -> bool {
777 self.types.iter().any(|t| match t {
778 TAtomic::Null => true,
779 TAtomic::Void => true,
780 TAtomic::Mixed(mixed) if !mixed.is_non_null() => true,
781 TAtomic::GenericParameter(param) => param.constraint.can_be_null(),
782 _ => false,
783 })
784 }
785
786 #[must_use]
787 pub fn is_void(&self) -> bool {
788 self.types.iter().all(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
789 }
790
791 pub fn is_resource(&self) -> bool {
792 self.types.iter().all(TAtomic::is_resource) && !self.types.is_empty()
793 }
794
795 pub fn is_array(&self) -> bool {
796 self.types.iter().all(TAtomic::is_array) && !self.types.is_empty()
797 }
798
799 pub fn is_list(&self) -> bool {
800 self.types.iter().all(TAtomic::is_list) && !self.types.is_empty()
801 }
802
803 pub fn is_vanilla_array(&self) -> bool {
804 self.types.iter().all(TAtomic::is_vanilla_array) && !self.types.is_empty()
805 }
806
807 pub fn is_keyed_array(&self) -> bool {
808 self.types.iter().all(TAtomic::is_keyed_array) && !self.types.is_empty()
809 }
810
811 pub fn is_falsable(&self) -> bool {
812 self.types.len() >= 2 && self.types.iter().any(TAtomic::is_false)
813 }
814
815 #[must_use]
816 pub fn has_bool(&self) -> bool {
817 if self.types.is_empty() {
818 return false;
819 }
820
821 self.types.iter().any(|atomic| {
822 atomic.is_bool()
823 || atomic.is_generic_scalar()
824 || atomic.map_generic_parameter_constraint(TUnion::has_bool).unwrap_or(false)
825 })
826 }
827
828 pub fn has_scalar(&self) -> bool {
834 self.types.iter().any(TAtomic::is_generic_scalar)
835 }
836
837 #[must_use]
840 pub fn has_scalar_combination(&self) -> bool {
841 const HAS_INT: u8 = 1 << 0;
842 const HAS_FLOAT: u8 = 1 << 1;
843 const HAS_BOOL: u8 = 1 << 2;
844 const HAS_STRING: u8 = 1 << 3;
845 const ALL_SCALARS: u8 = HAS_INT | HAS_FLOAT | HAS_BOOL | HAS_STRING;
846
847 let mut flags = 0u8;
848
849 for atomic in self.types.as_ref() {
850 if atomic.is_int() {
851 flags |= HAS_INT;
852 } else if atomic.is_float() {
853 flags |= HAS_FLOAT;
854 } else if atomic.is_bool() {
855 flags |= HAS_BOOL;
856 } else if atomic.is_string() {
857 flags |= HAS_STRING;
858 } else if atomic.is_array_key() {
859 flags |= HAS_INT | HAS_STRING;
860 } else if atomic.is_numeric() {
861 flags |= HAS_INT | HAS_FLOAT;
863 } else if atomic.is_generic_scalar() {
864 return true;
865 }
866
867 if flags == ALL_SCALARS {
869 return true;
870 }
871 }
872
873 flags == ALL_SCALARS
874 }
875 pub fn has_array_key(&self) -> bool {
876 self.types.iter().any(TAtomic::is_array_key)
877 }
878
879 pub fn has_iterable(&self) -> bool {
880 self.types.iter().any(TAtomic::is_iterable) && !self.types.is_empty()
881 }
882
883 pub fn has_array(&self) -> bool {
884 self.types.iter().any(TAtomic::is_array) && !self.types.is_empty()
885 }
886
887 #[must_use]
888 pub fn has_traversable(&self, codebase: &CodebaseMetadata) -> bool {
889 self.types.iter().any(|atomic| atomic.is_traversable(codebase)) && !self.types.is_empty()
890 }
891
892 #[must_use]
893 pub fn has_array_key_like(&self) -> bool {
894 self.types.iter().any(|atomic| atomic.is_array_key() || atomic.is_int() || atomic.is_string())
895 }
896
897 pub fn has_numeric(&self) -> bool {
898 self.types.iter().any(TAtomic::is_numeric) && !self.types.is_empty()
899 }
900
901 pub fn is_always_truthy(&self) -> bool {
902 self.types.iter().all(TAtomic::is_truthy) && !self.types.is_empty()
903 }
904
905 pub fn is_always_falsy(&self) -> bool {
906 self.types.iter().all(TAtomic::is_falsy) && !self.types.is_empty()
907 }
908
909 #[must_use]
910 pub fn is_literal_of(&self, other: &TUnion) -> bool {
911 let is_matching_literal: fn(&TAtomic) -> bool = match other.types.first() {
912 Some(TAtomic::Scalar(TScalar::String(_))) => TAtomic::is_string_of_literal_origin,
913 Some(TAtomic::Scalar(TScalar::Integer(_))) => TAtomic::is_literal_int,
914 Some(TAtomic::Scalar(TScalar::Float(_))) => TAtomic::is_literal_float,
915 _ => return false,
916 };
917
918 self.types.iter().all(is_matching_literal)
919 }
920
921 #[must_use]
922 pub fn all_literals(&self) -> bool {
923 self.types
924 .iter()
925 .all(|atomic| atomic.is_string_of_literal_origin() || atomic.is_literal_int() || atomic.is_literal_float())
926 }
927
928 #[inline]
929 #[must_use]
930 pub fn is_single(&self) -> bool {
931 self.types.len() == 1
932 }
933
934 #[inline]
935 #[must_use]
936 pub fn get_single_string(&self) -> Option<&TString> {
937 if self.is_single()
938 && let TAtomic::Scalar(TScalar::String(string)) = &self.types[0]
939 {
940 Some(string)
941 } else {
942 None
943 }
944 }
945
946 #[inline]
947 #[must_use]
948 pub fn get_single_array(&self) -> Option<&TArray> {
949 if self.is_single()
950 && let TAtomic::Array(array) = &self.types[0]
951 {
952 Some(array)
953 } else {
954 None
955 }
956 }
957
958 #[inline]
959 #[must_use]
960 pub fn get_single_bool(&self) -> Option<&TBool> {
961 if self.is_single()
962 && let TAtomic::Scalar(TScalar::Bool(bool)) = &self.types[0]
963 {
964 Some(bool)
965 } else {
966 None
967 }
968 }
969
970 #[inline]
971 #[must_use]
972 pub fn get_single_named_object(&self) -> Option<&TNamedObject> {
973 if self.is_single()
974 && let TAtomic::Object(TObject::Named(named_object)) = &self.types[0]
975 {
976 Some(named_object)
977 } else {
978 None
979 }
980 }
981
982 #[inline]
983 #[must_use]
984 pub fn get_single(&self) -> &TAtomic {
985 &self.types[0]
986 }
987
988 #[inline]
989 #[must_use]
990 pub fn get_single_owned(self) -> TAtomic {
991 self.types[0].clone()
992 }
993
994 #[inline]
995 #[must_use]
996 pub fn is_named_object(&self) -> bool {
997 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Named(_))))
998 }
999
1000 #[must_use]
1001 pub fn is_enum(&self) -> bool {
1002 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(_))))
1003 }
1004
1005 #[must_use]
1006 pub fn is_enum_case(&self) -> bool {
1007 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(r#enum)) if r#enum.case.is_some()))
1008 }
1009
1010 #[must_use]
1011 pub fn is_single_enum_case(&self) -> bool {
1012 self.is_single()
1013 && self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(r#enum)) if r#enum.case.is_some()))
1014 }
1015
1016 #[inline]
1017 #[must_use]
1018 pub fn has_callable(&self) -> bool {
1019 self.types.iter().any(|t| matches!(t, TAtomic::Callable(_)))
1020 }
1021
1022 #[inline]
1023 #[must_use]
1024 pub fn is_callable(&self) -> bool {
1025 self.types.iter().all(|t| matches!(t, TAtomic::Callable(_)))
1026 }
1027
1028 #[inline]
1029 #[must_use]
1030 pub fn has_object_type(&self) -> bool {
1031 self.types.iter().any(|t| matches!(t, TAtomic::Object(_)))
1032 }
1033
1034 #[must_use]
1037 pub fn get_enum_cases(&self) -> Vec<(Word, Option<Word>)> {
1038 self.types
1039 .iter()
1040 .filter_map(|t| match t {
1041 TAtomic::Object(TObject::Enum(enum_object)) => Some((enum_object.name, enum_object.case)),
1042 _ => None,
1043 })
1044 .collect()
1045 }
1046
1047 #[must_use]
1048 pub fn get_single_int(&self) -> Option<TInteger> {
1049 if self.is_single() { self.get_single().get_integer() } else { None }
1050 }
1051
1052 #[must_use]
1053 pub fn get_single_literal_int_value(&self) -> Option<i64> {
1054 if self.is_single() { self.get_single().get_literal_int_value() } else { None }
1055 }
1056
1057 #[must_use]
1062 pub fn get_maximum_int_value(&self) -> Option<i64> {
1063 self.types.iter().map(TAtomic::get_maximum_int_value).reduce(|first, second| Some(first?.max(second?)))?
1064 }
1065
1066 #[must_use]
1071 pub fn get_minimum_int_value(&self) -> Option<i64> {
1072 self.types.iter().map(TAtomic::get_minimum_int_value).reduce(|first, second| Some(first?.min(second?)))?
1073 }
1074
1075 #[must_use]
1076 pub fn get_single_literal_float_value(&self) -> Option<f64> {
1077 if self.is_single() { self.get_single().get_literal_float_value() } else { None }
1078 }
1079
1080 #[must_use]
1081 pub fn get_single_literal_string_value(&self) -> Option<&[u8]> {
1082 if self.is_single() { self.get_single().get_literal_string_value() } else { None }
1083 }
1084
1085 #[must_use]
1086 pub fn get_single_class_string_value(&self) -> Option<Word> {
1087 if self.is_single() { self.get_single().get_class_string_value() } else { None }
1088 }
1089
1090 #[must_use]
1091 pub fn get_single_array_key(&self) -> Option<ArrayKey> {
1092 if self.is_single() { self.get_single().to_array_key() } else { None }
1093 }
1094
1095 #[must_use]
1096 pub fn get_single_value_of_array_like(&self) -> Option<Cow<'_, TUnion>> {
1097 if !self.is_single() {
1098 return None;
1099 }
1100
1101 match self.get_single() {
1102 TAtomic::Array(array) => match array {
1103 TArray::List(list) => Some(Cow::Borrowed(&list.element_type)),
1104 TArray::Keyed(keyed_array) => match &keyed_array.parameters {
1105 Some((_, v)) => Some(Cow::Borrowed(v)),
1106 None => Some(Cow::Owned(get_mixed())),
1107 },
1108 },
1109 _ => None,
1110 }
1111 }
1112
1113 fn any_scalar(&self, is_matching: fn(&TScalar) -> bool) -> bool {
1114 self.types.iter().any(|atomic| match atomic {
1115 TAtomic::Scalar(scalar) => is_matching(scalar),
1116 _ => false,
1117 })
1118 }
1119
1120 #[must_use]
1121 pub fn has_literal_float(&self) -> bool {
1122 self.any_scalar(TScalar::is_literal_float)
1123 }
1124
1125 #[must_use]
1126 pub fn has_literal_int(&self) -> bool {
1127 self.any_scalar(TScalar::is_literal_int)
1128 }
1129
1130 #[must_use]
1131 pub fn has_literal_string(&self) -> bool {
1132 self.any_scalar(TScalar::is_known_literal_string)
1133 }
1134
1135 #[must_use]
1136 pub fn has_literal_value(&self) -> bool {
1137 self.any_scalar(TScalar::is_literal_value)
1138 }
1139
1140 #[must_use]
1141 pub fn accepts_false(&self) -> bool {
1142 self.types.iter().any(|t| match t {
1143 TAtomic::GenericParameter(parameter) => parameter.constraint.accepts_false(),
1144 TAtomic::Mixed(mixed) if !mixed.is_truthy() => true,
1145 TAtomic::Scalar(TScalar::Generic | TScalar::Bool(TBool { value: None | Some(false) })) => true,
1146 _ => false,
1147 })
1148 }
1149
1150 #[must_use]
1151 pub fn accepts_null(&self) -> bool {
1152 self.types.iter().any(|t| match t {
1153 TAtomic::GenericParameter(generic_parameter) => generic_parameter.constraint.accepts_null(),
1154 TAtomic::Mixed(mixed) if !mixed.is_non_null() => true,
1155 TAtomic::Null | TAtomic::Placeholder => true,
1156 _ => false,
1157 })
1158 }
1159}
1160
1161impl TType for TUnion {
1162 fn get_child_nodes(&self) -> Vec<TypeRef<'_>> {
1163 self.types.iter().map(TypeRef::Atomic).collect()
1164 }
1165
1166 fn needs_population(&self) -> bool {
1167 !self.flags.contains(UnionFlags::POPULATED) && self.types.iter().any(super::TType::needs_population)
1168 }
1169
1170 #[inline]
1171 fn is_expandable(&self) -> bool {
1172 if self.types.is_empty() {
1173 return true;
1174 }
1175
1176 self.types.iter().any(super::TType::is_expandable)
1177 }
1178
1179 fn is_complex(&self) -> bool {
1180 self.types.len() > 3 || self.types.iter().any(super::TType::is_complex)
1181 }
1182
1183 fn get_id(&self) -> Word {
1184 let len = self.types.len();
1185
1186 let mut atomic_ids: Vec<Word> = self
1187 .types
1188 .as_ref()
1189 .iter()
1190 .map(|atomic| {
1191 let id = atomic.get_id();
1192 if atomic.is_generic_parameter() || atomic.has_intersection_types() && len > 1 {
1193 concat_word!(b"(", id.as_bytes(), b")")
1194 } else {
1195 id
1196 }
1197 })
1198 .collect();
1199
1200 if len <= 1 {
1201 return atomic_ids.pop().unwrap_or_else(empty_word);
1202 }
1203
1204 atomic_ids.sort_unstable();
1205 join_words(&atomic_ids, b"|")
1206 }
1207
1208 fn get_pretty_id_with_indent(&self, indent: usize) -> Word {
1209 let len = self.types.len();
1210
1211 if len <= 1 {
1212 return self.types.first().map_or_else(empty_word, |atomic| atomic.get_pretty_id_with_indent(indent));
1213 }
1214
1215 if len > 3 {
1217 let mut atomic_ids: Vec<Word> = self
1218 .types
1219 .as_ref()
1220 .iter()
1221 .map(|atomic| {
1222 let id = atomic.get_pretty_id_with_indent(indent + 2);
1223 if atomic.has_intersection_types() { concat_word!(b"(", id.as_bytes(), b")") } else { id }
1224 })
1225 .collect();
1226
1227 atomic_ids.sort_unstable();
1228
1229 let mut result: Vec<u8> = Vec::new();
1230 result.extend_from_slice(atomic_ids[0].as_bytes());
1231 for id in &atomic_ids[1..] {
1232 result.extend_from_slice(b"\n");
1233 result.resize(result.len() + indent, b' ');
1234 result.extend_from_slice(b"| ");
1235 result.extend_from_slice(id.as_bytes());
1236 }
1237
1238 word(&result)
1239 } else {
1240 let mut atomic_ids: Vec<Word> = self
1242 .types
1243 .as_ref()
1244 .iter()
1245 .map(|atomic| {
1246 let id = atomic.get_pretty_id_with_indent(indent);
1247 if atomic.has_intersection_types() && len > 1 {
1248 concat_word!(b"(", id.as_bytes(), b")")
1249 } else {
1250 id
1251 }
1252 })
1253 .collect();
1254
1255 atomic_ids.sort_unstable();
1256 let mut result = atomic_ids[0];
1257 for id in &atomic_ids[1..] {
1258 result = concat_word!(result.as_bytes(), b" | ", id.as_bytes());
1259 }
1260
1261 result
1262 }
1263 }
1264}
1265
1266impl PartialEq for TUnion {
1267 fn eq(&self, other: &TUnion) -> bool {
1268 if std::ptr::eq(self, other) {
1269 return true;
1270 }
1271
1272 const SEMANTIC_FLAGS: UnionFlags = UnionFlags::HAD_TEMPLATE
1273 .union(UnionFlags::BY_REFERENCE)
1274 .union(UnionFlags::REFERENCE_FREE)
1275 .union(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY)
1276 .union(UnionFlags::POSSIBLY_UNDEFINED)
1277 .union(UnionFlags::IGNORE_NULLABLE_ISSUES)
1278 .union(UnionFlags::IGNORE_FALSABLE_ISSUES)
1279 .union(UnionFlags::FROM_TEMPLATE_DEFAULT)
1280 .union(UnionFlags::FROM_UNSPECIFIED_TEMPLATE);
1281
1282 if self.flags.intersection(SEMANTIC_FLAGS) != other.flags.intersection(SEMANTIC_FLAGS) {
1283 return false;
1284 }
1285
1286 let len = self.types.len();
1287 if len != other.types.len() {
1288 return false;
1289 }
1290
1291 if self.types == other.types {
1294 return true;
1295 }
1296
1297 fn is_subset(first: &[TAtomic], second: &[TAtomic]) -> bool {
1300 first.iter().all(|atomic| second.contains(atomic))
1301 }
1302
1303 is_subset(&self.types, &other.types) && is_subset(&other.types, &self.types)
1306 }
1307}
1308
1309#[inline]
1311fn ensure_non_empty_types(mut types: Vec<TAtomic>) -> Vec<TAtomic> {
1312 if types.is_empty() {
1313 types.push(TAtomic::Never);
1314 }
1315
1316 types
1317}
1318
1319pub fn populate_union_type(
1320 unpopulated_union: &mut TUnion,
1321 codebase_symbols: &Symbols,
1322 reference_source: Option<&ReferenceSource>,
1323 symbol_references: &mut SymbolReferences,
1324 force: bool,
1325) {
1326 if unpopulated_union.flags.contains(UnionFlags::POPULATED) && !force {
1327 return;
1328 }
1329
1330 if !force && !unpopulated_union.needs_population() {
1331 return;
1332 }
1333
1334 unpopulated_union.flags.insert(UnionFlags::POPULATED);
1335 let unpopulated_atomics = unpopulated_union.types.to_mut();
1336 for unpopulated_atomic in unpopulated_atomics {
1337 match unpopulated_atomic {
1338 TAtomic::Scalar(TScalar::ClassLikeString(
1339 TClassLikeString::Generic { constraint, .. } | TClassLikeString::OfType { constraint, .. },
1340 )) => {
1341 populate_atomic_type(
1342 Arc::make_mut(constraint),
1343 codebase_symbols,
1344 reference_source,
1345 symbol_references,
1346 force,
1347 );
1348 }
1349 _ => {
1350 populate_atomic_type(unpopulated_atomic, codebase_symbols, reference_source, symbol_references, force);
1351 }
1352 }
1353 }
1354}
1355
1356#[derive(Clone, Copy, PartialEq, Eq)]
1357enum WidenKind {
1358 Scalars,
1359 Literals,
1360}
1361
1362fn widen_atomic(atomic: &mut TAtomic, kind: WidenKind) {
1373 match atomic {
1374 TAtomic::Scalar(scalar) => {
1375 if let Some(widened) = widened_scalar(scalar, kind) {
1376 *scalar = widened;
1377 }
1378 }
1379 TAtomic::Array(array) => match array {
1380 TArray::List(list) => {
1381 widen_arc_union(&mut list.element_type, kind);
1382 if let Some(known) = list.known_elements.as_mut() {
1383 for (_, ty) in known.values_mut() {
1384 ty.widen(kind);
1385 }
1386 }
1387 }
1388 TArray::Keyed(keyed) => {
1389 if let Some((key, value)) = keyed.parameters.as_mut() {
1390 widen_arc_union(key, kind);
1391 widen_arc_union(value, kind);
1392 }
1393 if let Some(known) = keyed.known_items.as_mut() {
1394 for (_, ty) in known.values_mut() {
1395 ty.widen(kind);
1396 }
1397 }
1398 }
1399 },
1400 TAtomic::Iterable(iterable) => {
1401 widen_arc_union(&mut iterable.key_type, kind);
1402 widen_arc_union(&mut iterable.value_type, kind);
1403 if let Some(intersections) = iterable.intersection_types.as_mut() {
1404 for inner in intersections.iter_mut() {
1405 widen_atomic(inner, kind);
1406 }
1407 }
1408 }
1409 TAtomic::Object(TObject::Named(named)) => {
1410 if let Some(params) = named.type_parameters.as_mut() {
1411 for ty in params.iter_mut() {
1412 ty.widen(kind);
1413 }
1414 }
1415 }
1416 TAtomic::Object(TObject::WithProperties(with_props)) => {
1417 for (_, ty) in with_props.known_properties.values_mut() {
1418 ty.widen(kind);
1419 }
1420 }
1421 TAtomic::GenericParameter(generic) => {
1422 widen_arc_union(&mut generic.constraint, kind);
1423 if let Some(intersections) = generic.intersection_types.as_mut() {
1424 for inner in intersections.iter_mut() {
1425 widen_atomic(inner, kind);
1426 }
1427 }
1428 }
1429 TAtomic::Conditional(conditional) => {
1430 widen_arc_union(&mut conditional.subject, kind);
1431 widen_arc_union(&mut conditional.target, kind);
1432 widen_arc_union(&mut conditional.then, kind);
1433 widen_arc_union(&mut conditional.otherwise, kind);
1434 }
1435 _ => {}
1436 }
1437}
1438
1439#[inline]
1440fn widen_arc_union(union: &mut Arc<TUnion>, kind: WidenKind) {
1441 if union_has_widenable(union, kind) {
1442 Arc::make_mut(union).widen(kind);
1443 }
1444}
1445
1446fn widened_scalar(scalar: &TScalar, kind: WidenKind) -> Option<TScalar> {
1449 match (scalar, kind) {
1450 (TScalar::String(string), WidenKind::Scalars) if !is_string_fully_general(string) => {
1451 Some(TScalar::String(TString::general()))
1452 }
1453 (TScalar::String(string), WidenKind::Literals) if string.literal.is_some() => {
1454 Some(TScalar::String(string.without_literal()))
1455 }
1456 (TScalar::Integer(integer), WidenKind::Scalars) if !matches!(integer, TInteger::Unspecified) => {
1457 Some(TScalar::Integer(TInteger::Unspecified))
1458 }
1459 (TScalar::Integer(TInteger::Literal(_) | TInteger::UnspecifiedLiteral), WidenKind::Literals) => {
1460 Some(TScalar::Integer(TInteger::Unspecified))
1461 }
1462 (TScalar::Float(float), WidenKind::Scalars) if !matches!(float, TFloat::Float) => {
1463 Some(TScalar::Float(TFloat::Float))
1464 }
1465 (TScalar::Float(TFloat::Literal(_) | TFloat::UnspecifiedLiteral), WidenKind::Literals) => {
1466 Some(TScalar::Float(TFloat::Float))
1467 }
1468 (TScalar::Bool(boolean), _) if !boolean.is_general() => Some(TScalar::Bool(TBool::general())),
1469 _ => None,
1470 }
1471}
1472
1473#[inline]
1474fn is_string_fully_general(string: &TString) -> bool {
1475 string.literal.is_none()
1476 && !string.is_numeric
1477 && !string.is_truthy
1478 && !string.is_non_empty
1479 && !string.is_callable
1480 && matches!(string.casing, TStringCasing::Unspecified)
1481}
1482
1483fn union_has_widenable(union: &TUnion, kind: WidenKind) -> bool {
1487 union.types.iter().any(|atomic| atomic_has_widenable(atomic, kind))
1488}
1489
1490fn atomic_has_widenable(atomic: &TAtomic, kind: WidenKind) -> bool {
1491 match atomic {
1492 TAtomic::Scalar(scalar) => widened_scalar(scalar, kind).is_some(),
1493 TAtomic::Array(TArray::List(list)) => {
1494 union_has_widenable(&list.element_type, kind)
1495 || list.known_elements.as_ref().is_some_and(|m| m.values().any(|(_, t)| union_has_widenable(t, kind)))
1496 }
1497 TAtomic::Array(TArray::Keyed(keyed)) => {
1498 keyed.parameters.as_ref().is_some_and(|(k, v)| union_has_widenable(k, kind) || union_has_widenable(v, kind))
1499 || keyed.known_items.as_ref().is_some_and(|m| m.values().any(|(_, t)| union_has_widenable(t, kind)))
1500 }
1501 TAtomic::Iterable(iterable) => {
1502 union_has_widenable(&iterable.key_type, kind)
1503 || union_has_widenable(&iterable.value_type, kind)
1504 || iterable.intersection_types.as_ref().is_some_and(|v| v.iter().any(|t| atomic_has_widenable(t, kind)))
1505 }
1506 TAtomic::Object(TObject::Named(named)) => {
1507 named.type_parameters.as_ref().is_some_and(|p| p.iter().any(|t| union_has_widenable(t, kind)))
1508 }
1509 TAtomic::Object(TObject::WithProperties(with_props)) => {
1510 with_props.known_properties.values().any(|(_, t)| union_has_widenable(t, kind))
1511 }
1512 TAtomic::GenericParameter(generic) => {
1513 union_has_widenable(&generic.constraint, kind)
1514 || generic.intersection_types.as_ref().is_some_and(|v| v.iter().any(|t| atomic_has_widenable(t, kind)))
1515 }
1516 TAtomic::Conditional(conditional) => {
1517 union_has_widenable(&conditional.subject, kind)
1518 || union_has_widenable(&conditional.target, kind)
1519 || union_has_widenable(&conditional.then, kind)
1520 || union_has_widenable(&conditional.otherwise, kind)
1521 }
1522 _ => false,
1523 }
1524}