1use std::borrow::Cow;
2use std::hash::Hash;
3use std::hash::Hasher;
4
5use derivative::Derivative;
6use mago_atom::concat_atom;
7use mago_atom::empty_atom;
8use serde::Deserialize;
9use serde::Serialize;
10
11use mago_atom::Atom;
12
13use crate::metadata::CodebaseMetadata;
14use crate::reference::ReferenceSource;
15use crate::reference::SymbolReferences;
16use crate::symbol::Symbols;
17use crate::ttype::TType;
18use crate::ttype::TypeRef;
19use crate::ttype::atomic::TAtomic;
20use crate::ttype::atomic::array::TArray;
21use crate::ttype::atomic::array::key::ArrayKey;
22use crate::ttype::atomic::generic::TGenericParameter;
23use crate::ttype::atomic::mixed::truthiness::TMixedTruthiness;
24use crate::ttype::atomic::object::TObject;
25use crate::ttype::atomic::object::named::TNamedObject;
26use crate::ttype::atomic::populate_atomic_type;
27use crate::ttype::atomic::scalar::TScalar;
28use crate::ttype::atomic::scalar::bool::TBool;
29use crate::ttype::atomic::scalar::class_like_string::TClassLikeString;
30use crate::ttype::atomic::scalar::int::TInteger;
31use crate::ttype::atomic::scalar::string::TString;
32use crate::ttype::atomic::scalar::string::TStringLiteral;
33use crate::ttype::get_arraykey;
34use crate::ttype::get_int;
35use crate::ttype::get_mixed;
36
37#[derive(Debug, Clone, Serialize, Deserialize, Eq, Derivative, PartialOrd, Ord)]
38pub struct TUnion {
39 pub types: Cow<'static, [TAtomic]>,
40 pub had_template: bool,
41 pub by_reference: bool,
42 pub reference_free: bool,
43 pub possibly_undefined_from_try: bool,
44 pub possibly_undefined: bool,
45 pub ignore_nullable_issues: bool,
46 pub ignore_falsable_issues: bool,
47 pub from_template_default: bool,
48 pub populated: bool,
49}
50
51impl Hash for TUnion {
52 fn hash<H: Hasher>(&self, state: &mut H) {
53 for t in self.types.as_ref() {
54 t.hash(state);
55 }
56 }
57}
58
59impl TUnion {
60 pub fn new(types: Cow<'static, [TAtomic]>) -> TUnion {
65 TUnion {
66 types,
67 had_template: false,
68 by_reference: false,
69 reference_free: false,
70 possibly_undefined_from_try: false,
71 possibly_undefined: false,
72 ignore_nullable_issues: false,
73 ignore_falsable_issues: false,
74 from_template_default: false,
75 populated: false,
76 }
77 }
78
79 pub fn from_vec(mut types: Vec<TAtomic>) -> TUnion {
84 if cfg!(debug_assertions) {
85 if types.is_empty() {
86 panic!("TUnion::new() should not be called with an empty Vec.");
87 }
88
89 if types.len() > 1
90 && types.iter().any(|atomic| {
91 atomic.is_never()
92 || atomic.map_generic_parameter_constraint(|constraint| constraint.is_never()).unwrap_or(false)
93 })
94 {
95 panic!("TUnion::new() was called with a mix of 'never' and other types: {types:#?}")
96 }
97 } else {
98 if types.len() > 1 {
101 types.retain(|atomic| {
102 !atomic.is_never()
103 && !atomic.map_generic_parameter_constraint(|constraint| constraint.is_never()).unwrap_or(false)
104 });
105 }
106
107 if types.is_empty() {
110 types.push(TAtomic::Never);
111 }
112 }
113
114 Self::new(Cow::Owned(types))
115 }
116
117 pub fn from_single(atomic: Cow<'static, TAtomic>) -> TUnion {
123 let types_cow = match atomic {
124 Cow::Borrowed(borrowed_atomic) => Cow::Borrowed(std::slice::from_ref(borrowed_atomic)),
125 Cow::Owned(owned_atomic) => Cow::Owned(vec![owned_atomic]),
126 };
127
128 TUnion::new(types_cow)
129 }
130
131 pub fn from_atomic(atomic: TAtomic) -> TUnion {
133 TUnion::new(Cow::Owned(vec![atomic]))
134 }
135
136 pub fn set_possibly_undefined(&mut self, possibly_undefined: bool, from_try: Option<bool>) {
137 let from_try = from_try.unwrap_or(self.possibly_undefined_from_try);
138
139 self.possibly_undefined = possibly_undefined;
140 self.possibly_undefined_from_try = from_try;
141 }
142
143 pub fn clone_with_types(&self, types: Vec<TAtomic>) -> TUnion {
145 TUnion {
146 types: Cow::Owned(types),
147 had_template: self.had_template,
148 by_reference: self.by_reference,
149 reference_free: self.reference_free,
150 possibly_undefined_from_try: self.possibly_undefined_from_try,
151 possibly_undefined: self.possibly_undefined,
152 ignore_falsable_issues: self.ignore_falsable_issues,
153 ignore_nullable_issues: self.ignore_nullable_issues,
154 from_template_default: self.from_template_default,
155 populated: self.populated,
156 }
157 }
158
159 pub fn to_non_nullable(&self) -> TUnion {
160 TUnion {
161 types: Cow::Owned(self.get_non_nullable_types()),
162 had_template: self.had_template,
163 by_reference: self.by_reference,
164 reference_free: self.reference_free,
165 possibly_undefined_from_try: self.possibly_undefined_from_try,
166 possibly_undefined: self.possibly_undefined,
167 ignore_falsable_issues: self.ignore_falsable_issues,
168 ignore_nullable_issues: self.ignore_nullable_issues,
169 from_template_default: self.from_template_default,
170 populated: self.populated,
171 }
172 }
173
174 pub fn to_truthy(&self) -> TUnion {
175 TUnion {
176 types: Cow::Owned(self.get_truthy_types()),
177 had_template: self.had_template,
178 by_reference: self.by_reference,
179 reference_free: self.reference_free,
180 possibly_undefined_from_try: self.possibly_undefined_from_try,
181 possibly_undefined: self.possibly_undefined,
182 ignore_falsable_issues: self.ignore_falsable_issues,
183 ignore_nullable_issues: self.ignore_nullable_issues,
184 from_template_default: self.from_template_default,
185 populated: self.populated,
186 }
187 }
188
189 pub fn get_non_nullable_types(&self) -> Vec<TAtomic> {
190 self.types
191 .iter()
192 .filter_map(|t| match t {
193 TAtomic::Null | TAtomic::Void => None,
194 TAtomic::GenericParameter(parameter) => Some(TAtomic::GenericParameter(TGenericParameter {
195 parameter_name: parameter.parameter_name,
196 defining_entity: parameter.defining_entity,
197 intersection_types: parameter.intersection_types.clone(),
198 constraint: Box::new(parameter.constraint.to_non_nullable()),
199 })),
200 TAtomic::Mixed(mixed) => Some(TAtomic::Mixed(mixed.with_is_non_null(true))),
201 atomic => Some(atomic.clone()),
202 })
203 .collect()
204 }
205
206 pub fn get_truthy_types(&self) -> Vec<TAtomic> {
207 self.types
208 .iter()
209 .filter_map(|t| match t {
210 TAtomic::GenericParameter(parameter) => Some(TAtomic::GenericParameter(TGenericParameter {
211 parameter_name: parameter.parameter_name,
212 defining_entity: parameter.defining_entity,
213 intersection_types: parameter.intersection_types.clone(),
214 constraint: Box::new(parameter.constraint.to_truthy()),
215 })),
216 TAtomic::Mixed(mixed) => Some(TAtomic::Mixed(mixed.with_truthiness(TMixedTruthiness::Truthy))),
217 atomic => {
218 if atomic.is_falsy() {
219 None
220 } else {
221 Some(atomic.clone())
222 }
223 }
224 })
225 .collect()
226 }
227
228 pub fn as_nullable(mut self) -> TUnion {
230 let types = self.types.to_mut();
231
232 types.iter_mut().for_each(|atomic| {
233 if let TAtomic::Mixed(mixed) = atomic {
234 *mixed = mixed.with_is_non_null(false);
235 }
236 });
237
238 if !types.iter().any(|atomic| atomic.is_null() || atomic.is_mixed()) {
239 types.push(TAtomic::Null);
240 }
241
242 self
243 }
244
245 pub fn remove_type(&mut self, bad_type: &TAtomic) {
247 self.types.to_mut().retain(|t| t != bad_type);
248 }
249
250 pub fn replace_type(&mut self, remove_type: &TAtomic, add_type: TAtomic) {
252 let types = self.types.to_mut();
253
254 if let Some(index) = types.iter().position(|t| t == remove_type) {
255 types[index] = add_type;
256 } else {
257 types.push(add_type);
258 }
259 }
260
261 pub fn is_int(&self) -> bool {
262 for atomic in self.types.as_ref() {
263 if !atomic.is_int() {
264 return false;
265 }
266 }
267
268 true
269 }
270
271 pub fn has_int_or_float(&self) -> bool {
272 for atomic in self.types.as_ref() {
273 if atomic.is_int_or_float() {
274 return true;
275 }
276 }
277
278 false
279 }
280
281 pub fn has_int_and_float(&self) -> bool {
282 let mut has_int = false;
283 let mut has_float = false;
284
285 for atomic in self.types.as_ref() {
286 if atomic.is_int() {
287 has_int = true;
288 } else if atomic.is_float() {
289 has_float = true;
290 } else if atomic.is_int_or_float() {
291 has_int = true;
292 has_float = true;
293 }
294
295 if has_int && has_float {
296 return true;
297 }
298 }
299
300 false
301 }
302
303 pub fn has_int_and_string(&self) -> bool {
304 let mut has_int = false;
305 let mut has_string = false;
306
307 for atomic in self.types.as_ref() {
308 if atomic.is_int() {
309 has_int = true;
310 } else if atomic.is_string() {
311 has_string = true;
312 } else if atomic.is_array_key() {
313 has_int = true;
314 has_string = true;
315 }
316
317 if has_int && has_string {
318 return true;
319 }
320 }
321
322 false
323 }
324
325 pub fn has_int(&self) -> bool {
326 for atomic in self.types.as_ref() {
327 if atomic.is_int() || atomic.is_array_key() || atomic.is_numeric() {
328 return true;
329 }
330 }
331
332 false
333 }
334
335 pub fn has_float(&self) -> bool {
336 for atomic in self.types.as_ref() {
337 if atomic.is_float() {
338 return true;
339 }
340 }
341
342 false
343 }
344
345 pub fn is_array_key(&self) -> bool {
346 for atomic in self.types.as_ref() {
347 if atomic.is_array_key() {
348 continue;
349 }
350
351 return false;
352 }
353
354 true
355 }
356
357 pub fn is_any_string(&self) -> bool {
358 for atomic in self.types.as_ref() {
359 if !atomic.is_any_string() {
360 return false;
361 }
362 }
363
364 true
365 }
366
367 pub fn is_string(&self) -> bool {
368 self.types.iter().all(|t| t.is_string()) && !self.types.is_empty()
369 }
370
371 pub fn is_always_array_key(&self, ignore_never: bool) -> bool {
372 self.types.iter().all(|atomic| match atomic {
373 TAtomic::Never => ignore_never,
374 TAtomic::Scalar(scalar) => matches!(
375 scalar,
376 TScalar::ArrayKey | TScalar::Integer(_) | TScalar::String(_) | TScalar::ClassLikeString(_)
377 ),
378 TAtomic::GenericParameter(generic_parameter) => {
379 generic_parameter.constraint.is_always_array_key(ignore_never)
380 }
381 _ => false,
382 })
383 }
384
385 pub fn is_non_empty_string(&self) -> bool {
386 self.types.iter().all(|t| t.is_non_empty_string()) && !self.types.is_empty()
387 }
388
389 pub fn is_empty_array(&self) -> bool {
390 self.types.iter().all(|t| t.is_empty_array()) && !self.types.is_empty()
391 }
392
393 pub fn has_string(&self) -> bool {
394 self.types.iter().any(|t| t.is_string()) && !self.types.is_empty()
395 }
396
397 pub fn is_float(&self) -> bool {
398 self.types.iter().all(|t| t.is_float()) && !self.types.is_empty()
399 }
400
401 pub fn is_bool(&self) -> bool {
402 self.types.iter().all(|t| t.is_bool()) && !self.types.is_empty()
403 }
404
405 pub fn is_never(&self) -> bool {
406 self.types.iter().all(|t| t.is_never()) || self.types.is_empty()
407 }
408
409 pub fn is_never_template(&self) -> bool {
410 self.types.iter().all(|t| t.is_templated_as_never()) && !self.types.is_empty()
411 }
412
413 pub fn is_placeholder(&self) -> bool {
414 self.types.iter().all(|t| matches!(t, TAtomic::Placeholder)) && !self.types.is_empty()
415 }
416
417 pub fn is_true(&self) -> bool {
418 self.types.iter().all(|t| t.is_true()) && !self.types.is_empty()
419 }
420
421 pub fn is_false(&self) -> bool {
422 self.types.iter().all(|t| t.is_false()) && !self.types.is_empty()
423 }
424
425 pub fn is_nonnull(&self) -> bool {
426 self.types.len() == 1 && matches!(self.types[0], TAtomic::Mixed(mixed) if mixed.is_non_null())
427 }
428
429 pub fn is_numeric(&self) -> bool {
430 self.types.iter().all(|t| t.is_numeric()) && !self.types.is_empty()
431 }
432
433 pub fn is_int_or_float(&self) -> bool {
434 self.types.iter().all(|t| t.is_int_or_float()) && !self.types.is_empty()
435 }
436
437 pub fn is_mixed(&self) -> bool {
438 self.types.iter().all(|t| matches!(t, TAtomic::Mixed(_))) && !self.types.is_empty()
439 }
440
441 pub fn is_mixed_template(&self) -> bool {
442 self.types.iter().all(|t| t.is_templated_as_mixed()) && !self.types.is_empty()
443 }
444
445 pub fn has_mixed(&self) -> bool {
446 self.types.iter().any(|t| matches!(t, TAtomic::Mixed(_))) && !self.types.is_empty()
447 }
448
449 pub fn has_mixed_template(&self) -> bool {
450 self.types.iter().any(|t| t.is_templated_as_mixed()) && !self.types.is_empty()
451 }
452
453 pub fn has_nullable_mixed(&self) -> bool {
454 self.types.iter().any(|t| matches!(t, TAtomic::Mixed(mixed) if !mixed.is_non_null())) && !self.types.is_empty()
455 }
456
457 pub fn has_void(&self) -> bool {
458 self.types.iter().any(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
459 }
460
461 pub fn has_null(&self) -> bool {
462 self.types.iter().any(|t| matches!(t, TAtomic::Null)) && !self.types.is_empty()
463 }
464
465 pub fn has_nullish(&self) -> bool {
466 self.types.iter().any(|t| match t {
467 TAtomic::Null | TAtomic::Void => true,
468 TAtomic::Mixed(mixed) => !mixed.is_non_null(),
469 TAtomic::GenericParameter(parameter) => parameter.constraint.has_nullish(),
470 _ => false,
471 }) && !self.types.is_empty()
472 }
473
474 pub fn is_nullable_mixed(&self) -> bool {
475 if self.types.len() != 1 {
476 return false;
477 }
478
479 match &self.types[0] {
480 TAtomic::Mixed(mixed) => !mixed.is_non_null(),
481 _ => false,
482 }
483 }
484
485 pub fn is_falsy_mixed(&self) -> bool {
486 if self.types.len() != 1 {
487 return false;
488 }
489
490 matches!(&self.types[0], &TAtomic::Mixed(mixed) if mixed.is_falsy())
491 }
492
493 pub fn is_vanilla_mixed(&self) -> bool {
494 if self.types.len() != 1 {
495 return false;
496 }
497
498 matches!(&self.types[0], TAtomic::Mixed(mixed) if mixed.is_vanilla())
499 }
500
501 pub fn has_template_or_static(&self) -> bool {
502 for atomic in self.types.as_ref() {
503 if let TAtomic::GenericParameter(_) = atomic {
504 return true;
505 }
506
507 if let TAtomic::Object(TObject::Named(named_object)) = atomic {
508 if named_object.is_this() {
509 return true;
510 }
511
512 if let Some(intersections) = named_object.get_intersection_types() {
513 for intersection in intersections {
514 if let TAtomic::GenericParameter(_) = intersection {
515 return true;
516 }
517 }
518 }
519 }
520 }
521
522 false
523 }
524
525 pub fn has_template(&self) -> bool {
526 for atomic in self.types.as_ref() {
527 if let TAtomic::GenericParameter(_) = atomic {
528 return true;
529 }
530
531 if let Some(intersections) = atomic.get_intersection_types() {
532 for intersection in intersections {
533 if let TAtomic::GenericParameter(_) = intersection {
534 return true;
535 }
536 }
537 }
538 }
539
540 false
541 }
542
543 pub fn has_template_types(&self) -> bool {
544 let all_child_nodes = self.get_all_child_nodes();
545
546 for child_node in all_child_nodes {
547 if let TypeRef::Atomic(
548 TAtomic::GenericParameter(_)
549 | TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic { .. })),
550 ) = child_node
551 {
552 return true;
553 }
554 }
555
556 false
557 }
558
559 pub fn get_template_types(&self) -> Vec<&TAtomic> {
560 let all_child_nodes = self.get_all_child_nodes();
561
562 let mut template_types = Vec::new();
563
564 for child_node in all_child_nodes {
565 if let TypeRef::Atomic(inner) = child_node {
566 match inner {
567 TAtomic::GenericParameter(_) => {
568 template_types.push(inner);
569 }
570 TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic { .. })) => {
571 template_types.push(inner);
572 }
573 _ => {}
574 }
575 }
576 }
577
578 template_types
579 }
580
581 pub fn is_objecty(&self) -> bool {
582 for atomic in self.types.as_ref() {
583 if let &TAtomic::Object(_) = atomic {
584 continue;
585 }
586
587 if let TAtomic::Callable(callable) = atomic
588 && callable.get_signature().is_none_or(|signature| signature.is_closure())
589 {
590 continue;
591 }
592
593 return false;
594 }
595
596 true
597 }
598
599 pub fn is_generator(&self) -> bool {
600 for atomic in self.types.as_ref() {
601 if atomic.is_generator() {
602 continue;
603 }
604
605 return false;
606 }
607
608 true
609 }
610
611 pub fn extends_or_implements(&self, codebase: &CodebaseMetadata, interface: &str) -> bool {
612 for atomic in self.types.as_ref() {
613 if !atomic.extends_or_implements(codebase, interface) {
614 return false;
615 }
616 }
617
618 true
619 }
620
621 pub fn is_generic_parameter(&self) -> bool {
622 self.types.len() == 1 && matches!(self.types[0], TAtomic::GenericParameter(_))
623 }
624
625 pub fn get_generic_parameter_constraint(&self) -> Option<&TUnion> {
626 if self.is_generic_parameter()
627 && let TAtomic::GenericParameter(parameter) = &self.types[0]
628 {
629 return Some(¶meter.constraint);
630 }
631
632 None
633 }
634
635 pub fn is_null(&self) -> bool {
636 self.types.iter().all(|t| matches!(t, TAtomic::Null)) && !self.types.is_empty()
637 }
638
639 pub fn is_nullable(&self) -> bool {
640 self.types.iter().any(|t| match t {
641 TAtomic::Null => self.types.len() >= 2,
642 _ => false,
643 })
644 }
645
646 pub fn is_void(&self) -> bool {
647 self.types.iter().all(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
648 }
649
650 pub fn is_voidable(&self) -> bool {
651 self.types.iter().any(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
652 }
653
654 pub fn has_resource(&self) -> bool {
655 self.types.iter().any(|t| t.is_resource())
656 }
657
658 pub fn is_resource(&self) -> bool {
659 self.types.iter().all(|t| t.is_resource()) && !self.types.is_empty()
660 }
661
662 pub fn is_array(&self) -> bool {
663 self.types.iter().all(|t| t.is_array()) && !self.types.is_empty()
664 }
665
666 pub fn is_list(&self) -> bool {
667 self.types.iter().all(|t| t.is_list()) && !self.types.is_empty()
668 }
669
670 pub fn is_keyed_array(&self) -> bool {
671 self.types.iter().all(|t| t.is_keyed_array()) && !self.types.is_empty()
672 }
673
674 pub fn is_falsable(&self) -> bool {
675 self.types.len() >= 2 && self.types.iter().any(|t| t.is_false())
676 }
677
678 pub fn has_bool(&self) -> bool {
679 self.types.iter().any(|t| t.is_bool() || t.is_generic_scalar()) && !self.types.is_empty()
680 }
681
682 pub fn has_scalar(&self) -> bool {
688 self.types.iter().any(|atomic| atomic.is_generic_scalar())
689 }
690
691 pub fn has_scalar_combination(&self) -> bool {
694 const HAS_INT: u8 = 1 << 0;
695 const HAS_FLOAT: u8 = 1 << 1;
696 const HAS_BOOL: u8 = 1 << 2;
697 const HAS_STRING: u8 = 1 << 3;
698 const ALL_SCALARS: u8 = HAS_INT | HAS_FLOAT | HAS_BOOL | HAS_STRING;
699
700 let mut flags = 0u8;
701
702 for atomic in self.types.as_ref() {
703 if atomic.is_int() {
704 flags |= HAS_INT;
705 } else if atomic.is_float() {
706 flags |= HAS_FLOAT;
707 } else if atomic.is_bool() {
708 flags |= HAS_BOOL;
709 } else if atomic.is_string() {
710 flags |= HAS_STRING;
711 } else if atomic.is_array_key() {
712 flags |= HAS_INT | HAS_STRING;
713 } else if atomic.is_numeric() {
714 flags |= HAS_INT | HAS_FLOAT;
716 } else if atomic.is_generic_scalar() {
717 return true;
718 }
719
720 if flags == ALL_SCALARS {
722 return true;
723 }
724 }
725
726 flags == ALL_SCALARS
727 }
728 pub fn has_array_key(&self) -> bool {
729 self.types.iter().any(|atomic| atomic.is_array_key())
730 }
731
732 pub fn has_iterable(&self) -> bool {
733 self.types.iter().any(|atomic| atomic.is_iterable()) && !self.types.is_empty()
734 }
735
736 pub fn has_array(&self) -> bool {
737 self.types.iter().any(|atomic| atomic.is_array()) && !self.types.is_empty()
738 }
739
740 pub fn has_traversable(&self, codebase: &CodebaseMetadata) -> bool {
741 self.types.iter().any(|atomic| atomic.is_traversable(codebase)) && !self.types.is_empty()
742 }
743
744 pub fn has_array_key_like(&self) -> bool {
745 self.types.iter().any(|atomic| atomic.is_array_key() || atomic.is_int() || atomic.is_string())
746 }
747
748 pub fn has_numeric(&self) -> bool {
749 self.types.iter().any(|atomic| atomic.is_numeric()) && !self.types.is_empty()
750 }
751
752 pub fn is_always_truthy(&self) -> bool {
753 self.types.iter().all(|atomic| atomic.is_truthy()) && !self.types.is_empty()
754 }
755
756 pub fn is_always_falsy(&self) -> bool {
757 self.types.iter().all(|atomic| atomic.is_falsy()) && !self.types.is_empty()
758 }
759
760 pub fn is_literal_of(&self, other: &TUnion) -> bool {
761 let Some(other_atomic_type) = other.types.first() else {
762 return false;
763 };
764
765 match other_atomic_type {
766 TAtomic::Scalar(TScalar::String(_)) => {
767 for self_atomic_type in self.types.as_ref() {
768 if self_atomic_type.is_string_of_literal_origin() {
769 continue;
770 }
771
772 return false;
773 }
774
775 true
776 }
777 TAtomic::Scalar(TScalar::Integer(_)) => {
778 for self_atomic_type in self.types.as_ref() {
779 if self_atomic_type.is_literal_int() {
780 continue;
781 }
782
783 return false;
784 }
785
786 true
787 }
788 TAtomic::Scalar(TScalar::Float(_)) => {
789 for self_atomic_type in self.types.as_ref() {
790 if self_atomic_type.is_literal_float() {
791 continue;
792 }
793
794 return false;
795 }
796
797 true
798 }
799 _ => false,
800 }
801 }
802
803 pub fn all_literals(&self) -> bool {
804 self.types
805 .iter()
806 .all(|atomic| atomic.is_string_of_literal_origin() || atomic.is_literal_int() || atomic.is_literal_float())
807 }
808
809 pub fn has_static_object(&self) -> bool {
810 self.types
811 .iter()
812 .any(|atomic| matches!(atomic, TAtomic::Object(TObject::Named(named_object)) if named_object.is_this()))
813 }
814
815 pub fn is_static_object(&self) -> bool {
816 self.types
817 .iter()
818 .all(|atomic| matches!(atomic, TAtomic::Object(TObject::Named(named_object)) if named_object.is_this()))
819 }
820
821 #[inline]
822 pub fn is_single(&self) -> bool {
823 self.types.len() == 1
824 }
825
826 #[inline]
827 pub fn get_single_string(&self) -> Option<&TString> {
828 if self.is_single()
829 && let TAtomic::Scalar(TScalar::String(string)) = &self.types[0]
830 {
831 Some(string)
832 } else {
833 None
834 }
835 }
836
837 #[inline]
838 pub fn get_single_array(&self) -> Option<&TArray> {
839 if self.is_single()
840 && let TAtomic::Array(array) = &self.types[0]
841 {
842 Some(array)
843 } else {
844 None
845 }
846 }
847
848 #[inline]
849 pub fn get_single_bool(&self) -> Option<&TBool> {
850 if self.is_single()
851 && let TAtomic::Scalar(TScalar::Bool(bool)) = &self.types[0]
852 {
853 Some(bool)
854 } else {
855 None
856 }
857 }
858
859 #[inline]
860 pub fn get_single_named_object(&self) -> Option<&TNamedObject> {
861 if self.is_single()
862 && let TAtomic::Object(TObject::Named(named_object)) = &self.types[0]
863 {
864 Some(named_object)
865 } else {
866 None
867 }
868 }
869
870 #[inline]
871 pub fn get_single(&self) -> &TAtomic {
872 &self.types[0]
873 }
874
875 #[inline]
876 pub fn get_single_owned(self) -> TAtomic {
877 self.types[0].to_owned()
878 }
879
880 #[inline]
881 pub fn is_named_object(&self) -> bool {
882 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Named(_))))
883 }
884
885 pub fn is_enum(&self) -> bool {
886 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(_))))
887 }
888
889 pub fn is_enum_case(&self) -> bool {
890 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(r#enum)) if r#enum.case.is_some()))
891 }
892
893 pub fn is_single_enum_case(&self) -> bool {
894 self.is_single()
895 && self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(r#enum)) if r#enum.case.is_some()))
896 }
897
898 #[inline]
899 pub fn has_named_object(&self) -> bool {
900 self.types.iter().any(|t| matches!(t, TAtomic::Object(TObject::Named(_))))
901 }
902
903 #[inline]
904 pub fn has_object(&self) -> bool {
905 self.types.iter().any(|t| matches!(t, TAtomic::Object(TObject::Any)))
906 }
907
908 #[inline]
909 pub fn has_callable(&self) -> bool {
910 self.types.iter().any(|t| matches!(t, TAtomic::Callable(_)))
911 }
912
913 #[inline]
914 pub fn is_callable(&self) -> bool {
915 self.types.iter().all(|t| matches!(t, TAtomic::Callable(_)))
916 }
917
918 #[inline]
919 pub fn has_object_type(&self) -> bool {
920 self.types.iter().any(|t| matches!(t, TAtomic::Object(_)))
921 }
922
923 pub fn get_enum_cases(&self) -> Vec<(Atom, Option<Atom>)> {
926 self.types
927 .iter()
928 .filter_map(|t| match t {
929 TAtomic::Object(TObject::Enum(enum_object)) => Some((enum_object.name, enum_object.case)),
930 _ => None,
931 })
932 .collect()
933 }
934
935 pub fn get_single_int(&self) -> Option<TInteger> {
936 if self.is_single() { self.get_single().get_integer() } else { None }
937 }
938
939 pub fn get_single_literal_int_value(&self) -> Option<i64> {
940 if self.is_single() { self.get_single().get_literal_int_value() } else { None }
941 }
942
943 pub fn get_single_maximum_int_value(&self) -> Option<i64> {
944 if self.is_single() { self.get_single().get_maximum_int_value() } else { None }
945 }
946
947 pub fn get_single_minimum_int_value(&self) -> Option<i64> {
948 if self.is_single() { self.get_single().get_minimum_int_value() } else { None }
949 }
950
951 pub fn get_single_literal_float_value(&self) -> Option<f64> {
952 if self.is_single() { self.get_single().get_literal_float_value() } else { None }
953 }
954
955 pub fn get_single_literal_string_value(&self) -> Option<&str> {
956 if self.is_single() { self.get_single().get_literal_string_value() } else { None }
957 }
958
959 pub fn get_single_class_string_value(&self) -> Option<Atom> {
960 if self.is_single() { self.get_single().get_class_string_value() } else { None }
961 }
962
963 pub fn get_single_array_key(&self) -> Option<ArrayKey> {
964 if self.is_single() { self.get_single().to_array_key() } else { None }
965 }
966
967 pub fn get_single_key_of_array_like(&self) -> Option<TUnion> {
968 if !self.is_single() {
969 return None;
970 }
971
972 match self.get_single() {
973 TAtomic::Array(array) => match array {
974 TArray::List(_) => Some(get_int()),
975 TArray::Keyed(keyed_array) => match &keyed_array.parameters {
976 Some((k, _)) => Some(*k.clone()),
977 None => Some(get_arraykey()),
978 },
979 },
980 _ => None,
981 }
982 }
983
984 pub fn get_single_value_of_array_like(&self) -> Option<Cow<'_, TUnion>> {
985 if !self.is_single() {
986 return None;
987 }
988
989 match self.get_single() {
990 TAtomic::Array(array) => match array {
991 TArray::List(list) => Some(Cow::Borrowed(&list.element_type)),
992 TArray::Keyed(keyed_array) => match &keyed_array.parameters {
993 Some((_, v)) => Some(Cow::Borrowed(v)),
994 None => Some(Cow::Owned(get_mixed())),
995 },
996 },
997 _ => None,
998 }
999 }
1000
1001 pub fn get_literal_ints(&self) -> Vec<&TAtomic> {
1002 self.types.iter().filter(|a| a.is_literal_int()).collect()
1003 }
1004
1005 pub fn get_literal_strings(&self) -> Vec<&TAtomic> {
1006 self.types.iter().filter(|a| a.is_known_literal_string()).collect()
1007 }
1008
1009 pub fn get_literal_string_values(&self) -> Vec<Option<Atom>> {
1010 self.get_literal_strings()
1011 .into_iter()
1012 .map(|atom| match atom {
1013 TAtomic::Scalar(TScalar::String(TString { literal: Some(TStringLiteral::Value(value)), .. })) => {
1014 Some(*value)
1015 }
1016 _ => None,
1017 })
1018 .collect()
1019 }
1020
1021 pub fn has_literal_float(&self) -> bool {
1022 self.types.iter().any(|atomic| match atomic {
1023 TAtomic::Scalar(scalar) => scalar.is_literal_float(),
1024 _ => false,
1025 })
1026 }
1027
1028 pub fn has_literal_int(&self) -> bool {
1029 self.types.iter().any(|atomic| match atomic {
1030 TAtomic::Scalar(scalar) => scalar.is_literal_int(),
1031 _ => false,
1032 })
1033 }
1034
1035 pub fn has_literal_string(&self) -> bool {
1036 self.types.iter().any(|atomic| match atomic {
1037 TAtomic::Scalar(scalar) => scalar.is_known_literal_string(),
1038 _ => false,
1039 })
1040 }
1041
1042 pub fn has_literal_value(&self) -> bool {
1043 self.types.iter().any(|atomic| match atomic {
1044 TAtomic::Scalar(scalar) => scalar.is_literal_value(),
1045 _ => false,
1046 })
1047 }
1048
1049 pub fn accepts_false(&self) -> bool {
1050 self.types.iter().any(|t| match t {
1051 TAtomic::GenericParameter(parameter) => parameter.constraint.accepts_false(),
1052 TAtomic::Mixed(mixed) if !mixed.is_truthy() => true,
1053 TAtomic::Scalar(TScalar::Generic | TScalar::Bool(TBool { value: None | Some(false) })) => true,
1054 _ => false,
1055 })
1056 }
1057
1058 pub fn accepts_null(&self) -> bool {
1059 self.types.iter().any(|t| match t {
1060 TAtomic::GenericParameter(generic_parameter) => generic_parameter.constraint.accepts_null(),
1061 TAtomic::Mixed(mixed) if !mixed.is_non_null() => true,
1062 TAtomic::Null => true,
1063 _ => false,
1064 })
1065 }
1066}
1067
1068impl TType for TUnion {
1069 fn get_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
1070 self.types.iter().map(TypeRef::Atomic).collect()
1071 }
1072
1073 fn needs_population(&self) -> bool {
1074 !self.populated && self.types.iter().any(|v| v.needs_population())
1075 }
1076
1077 fn is_expandable(&self) -> bool {
1078 if self.types.is_empty() {
1079 return true;
1080 }
1081
1082 self.types.iter().any(|t| t.is_expandable())
1083 }
1084
1085 fn get_id(&self) -> Atom {
1086 let len = self.types.len();
1087
1088 let mut atomic_ids: Vec<Atom> = self
1089 .types
1090 .as_ref()
1091 .iter()
1092 .map(|atomic| {
1093 let id = atomic.get_id();
1094 if atomic.has_intersection_types() && len > 1 { concat_atom!("(", id.as_str(), ")") } else { id }
1095 })
1096 .collect();
1097
1098 if len <= 1 {
1099 return atomic_ids.pop().unwrap_or_else(empty_atom);
1100 }
1101
1102 atomic_ids.sort_unstable();
1103 let mut result = atomic_ids[0];
1104 for id in &atomic_ids[1..] {
1105 result = concat_atom!(result.as_str(), "|", id.as_str());
1106 }
1107
1108 result
1109 }
1110}
1111
1112impl PartialEq for TUnion {
1113 fn eq(&self, other: &TUnion) -> bool {
1114 if self.reference_free != other.reference_free
1115 || self.by_reference != other.by_reference
1116 || self.had_template != other.had_template
1117 || self.possibly_undefined_from_try != other.possibly_undefined_from_try
1118 || self.possibly_undefined != other.possibly_undefined
1119 || self.ignore_falsable_issues != other.ignore_falsable_issues
1120 || self.ignore_nullable_issues != other.ignore_nullable_issues
1121 || self.from_template_default != other.from_template_default
1122 {
1123 return false;
1124 }
1125
1126 let len = self.types.len();
1127 if len != other.types.len() {
1128 return false;
1129 }
1130
1131 for i in 0..len {
1132 let mut has_match = false;
1133 for j in 0..len {
1134 if self.types[i] == other.types[j] {
1135 has_match = true;
1136 break;
1137 }
1138 }
1139
1140 if !has_match {
1141 return false;
1142 }
1143 }
1144
1145 true
1146 }
1147}
1148
1149pub fn populate_union_type(
1150 unpopulated_union: &mut TUnion,
1151 codebase_symbols: &Symbols,
1152 reference_source: Option<&ReferenceSource>,
1153 symbol_references: &mut SymbolReferences,
1154 force: bool,
1155) {
1156 if unpopulated_union.populated && !force {
1157 return;
1158 }
1159
1160 if !unpopulated_union.needs_population() {
1161 return;
1162 }
1163
1164 unpopulated_union.populated = true;
1165 let unpopulated_atomics = unpopulated_union.types.to_mut();
1166 for unpopulated_atomic in unpopulated_atomics {
1167 match unpopulated_atomic {
1168 TAtomic::Scalar(TScalar::ClassLikeString(
1169 TClassLikeString::Generic { constraint, .. } | TClassLikeString::OfType { constraint, .. },
1170 )) => {
1171 let mut new_constraint = (**constraint).clone();
1172
1173 populate_atomic_type(&mut new_constraint, codebase_symbols, reference_source, symbol_references, force);
1174
1175 *constraint = Box::new(new_constraint);
1176 }
1177 _ => {
1178 populate_atomic_type(unpopulated_atomic, codebase_symbols, reference_source, symbol_references, force);
1179 }
1180 }
1181 }
1182}