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