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::word;
10
11use crate::metadata::CodebaseMetadata;
12use crate::reference::ReferenceSource;
13use crate::reference::SymbolReferences;
14use crate::symbol::Symbols;
15use crate::ttype::TType;
16use crate::ttype::TypeRef;
17use crate::ttype::atomic::TAtomic;
18use crate::ttype::atomic::array::TArray;
19use crate::ttype::atomic::array::key::ArrayKey;
20use crate::ttype::atomic::generic::TGenericParameter;
21use crate::ttype::atomic::mixed::truthiness::TMixedTruthiness;
22use crate::ttype::atomic::object::TObject;
23use crate::ttype::atomic::object::named::TNamedObject;
24use crate::ttype::atomic::object::with_properties::TObjectWithProperties;
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::atomic::scalar::string::TStringLiteral;
34use crate::ttype::flags::UnionFlags;
35use crate::ttype::get_arraykey;
36use crate::ttype::get_int;
37use crate::ttype::get_mixed;
38
39#[derive(Debug, Clone, Eq, PartialOrd, Ord)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub struct TUnion {
42 pub types: Cow<'static, [TAtomic]>,
43 pub flags: UnionFlags,
44}
45
46impl Hash for TUnion {
47 fn hash<H>(&self, state: &mut H)
48 where
49 H: Hasher,
50 {
51 for t in self.types.as_ref() {
52 t.hash(state);
53 }
54 }
55}
56
57impl TUnion {
58 #[must_use]
63 pub fn new(types: Cow<'static, [TAtomic]>) -> TUnion {
64 TUnion { types, flags: UnionFlags::empty() }
65 }
66
67 #[must_use]
83 pub fn from_vec(mut types: Vec<TAtomic>) -> TUnion {
84 if cfg!(debug_assertions) {
85 assert!(
86 !types.is_empty(),
87 "TUnion::from_vec() received an empty Vec. This indicates a logic error \
88 in type construction - unions must contain at least one type. \
89 Consider using TAtomic::Never for empty/impossible types."
90 );
91 }
92
93 if types.len() > 1 {
96 types.retain(|atomic| {
97 !atomic.is_never() && !atomic.map_generic_parameter_constraint(TUnion::is_never).unwrap_or(false)
98 });
99 }
100
101 if types.is_empty() {
104 types.push(TAtomic::Never);
105 }
106
107 if types.len() > 1 {
108 types.sort_unstable();
109 }
110
111 Self::new(Cow::Owned(types))
112 }
113
114 #[must_use]
120 pub fn from_single(atomic: Cow<'static, TAtomic>) -> TUnion {
121 let types_cow = match atomic {
122 Cow::Borrowed(borrowed_atomic) => Cow::Borrowed(std::slice::from_ref(borrowed_atomic)),
123 Cow::Owned(owned_atomic) => Cow::Owned(vec![owned_atomic]),
124 };
125
126 TUnion::new(types_cow)
127 }
128
129 #[must_use]
131 pub fn from_atomic(atomic: TAtomic) -> TUnion {
132 TUnion::new(Cow::Owned(vec![atomic]))
133 }
134
135 #[inline]
136 pub fn set_possibly_undefined(&mut self, possibly_undefined: bool, from_try: Option<bool>) {
137 let from_try = from_try.unwrap_or(self.flags.contains(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY));
138
139 self.flags.set(UnionFlags::POSSIBLY_UNDEFINED, possibly_undefined);
140 self.flags.set(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY, from_try);
141 }
142
143 #[inline]
144 #[must_use]
145 pub const fn had_template(&self) -> bool {
146 self.flags.contains(UnionFlags::HAD_TEMPLATE)
147 }
148
149 #[inline]
150 #[must_use]
151 pub const fn by_reference(&self) -> bool {
152 self.flags.contains(UnionFlags::BY_REFERENCE)
153 }
154
155 #[inline]
156 #[must_use]
157 pub const fn reference_free(&self) -> bool {
158 self.flags.contains(UnionFlags::REFERENCE_FREE)
159 }
160
161 #[inline]
162 #[must_use]
163 pub const fn possibly_undefined_from_try(&self) -> bool {
164 self.flags.contains(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY)
165 }
166
167 #[inline]
168 #[must_use]
169 pub const fn possibly_undefined(&self) -> bool {
170 self.flags.contains(UnionFlags::POSSIBLY_UNDEFINED)
171 }
172
173 #[inline]
174 #[must_use]
175 pub const fn ignore_nullable_issues(&self) -> bool {
176 self.flags.contains(UnionFlags::IGNORE_NULLABLE_ISSUES)
177 }
178
179 #[inline]
180 #[must_use]
181 pub const fn ignore_falsable_issues(&self) -> bool {
182 self.flags.contains(UnionFlags::IGNORE_FALSABLE_ISSUES)
183 }
184
185 #[inline]
186 #[must_use]
187 pub const fn from_template_default(&self) -> bool {
188 self.flags.contains(UnionFlags::FROM_TEMPLATE_DEFAULT)
189 }
190
191 #[inline]
192 #[must_use]
193 pub const fn populated(&self) -> bool {
194 self.flags.contains(UnionFlags::POPULATED)
195 }
196
197 #[inline]
198 #[must_use]
199 pub const fn has_nullsafe_null(&self) -> bool {
200 self.flags.contains(UnionFlags::NULLSAFE_NULL)
201 }
202
203 #[inline]
204 pub fn set_had_template(&mut self, value: bool) {
205 self.flags.set(UnionFlags::HAD_TEMPLATE, value);
206 }
207
208 #[inline]
209 pub fn set_by_reference(&mut self, value: bool) {
210 self.flags.set(UnionFlags::BY_REFERENCE, value);
211 }
212
213 #[inline]
214 pub fn set_reference_free(&mut self, value: bool) {
215 self.flags.set(UnionFlags::REFERENCE_FREE, value);
216 }
217
218 #[inline]
219 pub fn set_possibly_undefined_from_try(&mut self, value: bool) {
220 self.flags.set(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY, value);
221 }
222
223 #[inline]
224 pub fn set_ignore_nullable_issues(&mut self, value: bool) {
225 self.flags.set(UnionFlags::IGNORE_NULLABLE_ISSUES, value);
226 }
227
228 #[inline]
229 pub fn set_ignore_falsable_issues(&mut self, value: bool) {
230 self.flags.set(UnionFlags::IGNORE_FALSABLE_ISSUES, value);
231 }
232
233 #[inline]
234 pub fn set_from_template_default(&mut self, value: bool) {
235 self.flags.set(UnionFlags::FROM_TEMPLATE_DEFAULT, value);
236 }
237
238 #[inline]
239 pub fn set_populated(&mut self, value: bool) {
240 self.flags.set(UnionFlags::POPULATED, value);
241 }
242
243 #[inline]
244 pub fn set_nullsafe_null(&mut self, value: bool) {
245 self.flags.set(UnionFlags::NULLSAFE_NULL, value);
246 }
247
248 #[must_use]
250 pub fn clone_with_types(&self, types: Vec<TAtomic>) -> TUnion {
251 TUnion { types: Cow::Owned(ensure_non_empty_types(types)), flags: self.flags }
252 }
253
254 #[must_use]
255 pub fn to_non_nullable(&self) -> TUnion {
256 TUnion {
257 types: Cow::Owned(ensure_non_empty_types(self.get_non_nullable_types())),
258 flags: self.flags & !UnionFlags::NULLSAFE_NULL,
259 }
260 }
261
262 #[must_use]
263 pub fn to_truthy(&self) -> TUnion {
264 TUnion { types: Cow::Owned(ensure_non_empty_types(self.get_truthy_types())), flags: self.flags }
265 }
266
267 #[must_use]
268 pub fn get_non_nullable_types(&self) -> Vec<TAtomic> {
269 self.types
270 .iter()
271 .filter_map(|t| match t {
272 TAtomic::Null | TAtomic::Void => None,
273 TAtomic::GenericParameter(parameter) => Some(TAtomic::GenericParameter(TGenericParameter {
274 parameter_name: parameter.parameter_name,
275 defining_entity: parameter.defining_entity,
276 intersection_types: parameter.intersection_types.clone(),
277 constraint: Arc::new(parameter.constraint.to_non_nullable()),
278 })),
279 TAtomic::Mixed(mixed) => Some(TAtomic::Mixed(mixed.with_is_non_null(true))),
280 atomic => Some(atomic.clone()),
281 })
282 .collect()
283 }
284
285 #[must_use]
286 pub fn get_truthy_types(&self) -> Vec<TAtomic> {
287 self.types
288 .iter()
289 .filter_map(|t| match t {
290 TAtomic::GenericParameter(parameter) => Some(TAtomic::GenericParameter(TGenericParameter {
291 parameter_name: parameter.parameter_name,
292 defining_entity: parameter.defining_entity,
293 intersection_types: parameter.intersection_types.clone(),
294 constraint: Arc::new(parameter.constraint.to_truthy()),
295 })),
296 TAtomic::Mixed(mixed) => Some(TAtomic::Mixed(mixed.with_truthiness(TMixedTruthiness::Truthy))),
297 atomic => {
298 if atomic.is_falsy() {
299 None
300 } else {
301 Some(atomic.clone())
302 }
303 }
304 })
305 .collect()
306 }
307
308 pub fn widen_scalars(&mut self) {
318 for atomic in self.types.to_mut() {
319 widen_atomic_scalars(atomic);
320 }
321 }
322
323 pub fn widen_literals(&mut self) {
329 for atomic in self.types.to_mut() {
330 widen_atomic_literals(atomic);
331 }
332 }
333
334 #[must_use]
336 pub fn as_nullable(mut self) -> TUnion {
337 let types = self.types.to_mut();
338
339 for atomic in types.iter_mut() {
340 if let TAtomic::Mixed(mixed) = atomic {
341 *mixed = mixed.with_is_non_null(false);
342 }
343 }
344
345 if !types.iter().any(|atomic| atomic.is_null() || atomic.is_mixed()) {
346 types.push(TAtomic::Null);
347 }
348
349 self
350 }
351
352 pub fn remove_type(&mut self, bad_type: &TAtomic) {
354 self.types.to_mut().retain(|t| t != bad_type);
355 }
356
357 pub fn replace_type(&mut self, remove_type: &TAtomic, add_type: TAtomic) {
359 let types = self.types.to_mut();
360
361 if let Some(index) = types.iter().position(|t| t == remove_type) {
362 types[index] = add_type;
363 } else {
364 types.push(add_type);
365 }
366 }
367
368 #[must_use]
369 pub fn is_int(&self) -> bool {
370 for atomic in self.types.as_ref() {
371 if !atomic.is_int() {
372 return false;
373 }
374 }
375
376 true
377 }
378
379 #[must_use]
380 pub fn has_int_or_float(&self) -> bool {
381 for atomic in self.types.as_ref() {
382 if atomic.is_int_or_float() {
383 return true;
384 }
385 }
386
387 false
388 }
389
390 #[must_use]
391 pub fn has_int_and_float(&self) -> bool {
392 let mut has_int = false;
393 let mut has_float = false;
394
395 for atomic in self.types.as_ref() {
396 if atomic.is_int() {
397 has_int = true;
398 } else if atomic.is_float() {
399 has_float = true;
400 } else if atomic.is_int_or_float() {
401 has_int = true;
402 has_float = true;
403 }
404
405 if has_int && has_float {
406 return true;
407 }
408 }
409
410 false
411 }
412
413 #[must_use]
414 pub fn has_int_and_string(&self) -> bool {
415 let mut has_int = false;
416 let mut has_string = false;
417
418 for atomic in self.types.as_ref() {
419 if atomic.is_int() {
420 has_int = true;
421 } else if atomic.is_string() {
422 has_string = true;
423 } else if atomic.is_array_key() {
424 has_int = true;
425 has_string = true;
426 }
427
428 if has_int && has_string {
429 return true;
430 }
431 }
432
433 false
434 }
435
436 #[must_use]
437 pub fn has_int(&self) -> bool {
438 for atomic in self.types.as_ref() {
439 if atomic.is_int() || atomic.is_array_key() || atomic.is_numeric() {
440 return true;
441 }
442 }
443
444 false
445 }
446
447 #[must_use]
448 pub fn has_float(&self) -> bool {
449 for atomic in self.types.as_ref() {
450 if atomic.is_float() {
451 return true;
452 }
453 }
454
455 false
456 }
457
458 #[must_use]
459 pub fn is_array_key(&self) -> bool {
460 for atomic in self.types.as_ref() {
461 if atomic.is_array_key() {
462 continue;
463 }
464
465 return false;
466 }
467
468 true
469 }
470
471 #[must_use]
472 pub fn is_any_string(&self) -> bool {
473 for atomic in self.types.as_ref() {
474 if !atomic.is_any_string() {
475 return false;
476 }
477 }
478
479 true
480 }
481
482 pub fn is_string(&self) -> bool {
483 self.types.iter().all(TAtomic::is_string) && !self.types.is_empty()
484 }
485
486 #[must_use]
487 pub fn is_always_array_key(&self, ignore_never: bool) -> bool {
488 self.types.iter().all(|atomic| match atomic {
489 TAtomic::Never => ignore_never,
490 TAtomic::Scalar(scalar) => matches!(
491 scalar,
492 TScalar::ArrayKey | TScalar::Integer(_) | TScalar::String(_) | TScalar::ClassLikeString(_)
493 ),
494 TAtomic::GenericParameter(generic_parameter) => {
495 generic_parameter.constraint.is_always_array_key(ignore_never)
496 }
497 _ => false,
498 })
499 }
500
501 pub fn is_non_empty_string(&self) -> bool {
502 self.types.iter().all(TAtomic::is_non_empty_string) && !self.types.is_empty()
503 }
504
505 pub fn is_empty_array(&self) -> bool {
506 self.types.iter().all(TAtomic::is_empty_array) && !self.types.is_empty()
507 }
508
509 pub fn has_string(&self) -> bool {
510 self.types.iter().any(TAtomic::is_string) && !self.types.is_empty()
511 }
512
513 pub fn is_float(&self) -> bool {
514 self.types.iter().all(TAtomic::is_float) && !self.types.is_empty()
515 }
516
517 pub fn is_bool(&self) -> bool {
518 self.types.iter().all(TAtomic::is_bool) && !self.types.is_empty()
519 }
520
521 pub fn is_never(&self) -> bool {
522 self.types.iter().all(TAtomic::is_never) || self.types.is_empty()
523 }
524
525 pub fn is_never_template(&self) -> bool {
526 self.types.iter().all(TAtomic::is_templated_as_never) && !self.types.is_empty()
527 }
528
529 #[must_use]
530 pub fn is_placeholder(&self) -> bool {
531 self.types.iter().all(|t| matches!(t, TAtomic::Placeholder)) && !self.types.is_empty()
532 }
533
534 #[must_use]
536 pub fn contains_placeholder(&self) -> bool {
537 self.types.iter().any(|t| t.contains_placeholder())
538 }
539
540 pub fn is_true(&self) -> bool {
541 self.types.iter().all(TAtomic::is_true) && !self.types.is_empty()
542 }
543
544 pub fn is_false(&self) -> bool {
545 self.types.iter().all(TAtomic::is_false) && !self.types.is_empty()
546 }
547
548 #[must_use]
549 pub fn is_nonnull(&self) -> bool {
550 self.types.len() == 1 && matches!(self.types[0], TAtomic::Mixed(mixed) if mixed.is_non_null())
551 }
552
553 pub fn is_numeric(&self) -> bool {
554 self.types.iter().all(TAtomic::is_numeric) && !self.types.is_empty()
555 }
556
557 pub fn is_int_or_float(&self) -> bool {
558 self.types.iter().all(TAtomic::is_int_or_float) && !self.types.is_empty()
559 }
560
561 #[must_use]
564 pub fn effective_int_or_float(&self) -> Option<bool> {
565 let mut result: Option<bool> = None;
566 for atomic in self.types.as_ref() {
567 {
568 let is_int = atomic.effective_int_or_float()?;
569 if let Some(prev) = result {
570 if prev != is_int {
571 return None;
572 }
573 } else {
574 result = Some(is_int);
575 }
576 }
577 }
578
579 result
580 }
581
582 #[must_use]
583 pub fn is_mixed(&self) -> bool {
584 self.types.iter().all(|t| matches!(t, TAtomic::Mixed(_))) && !self.types.is_empty()
585 }
586
587 pub fn is_mixed_template(&self) -> bool {
588 self.types.iter().all(TAtomic::is_templated_as_mixed) && !self.types.is_empty()
589 }
590
591 #[must_use]
592 pub fn has_mixed(&self) -> bool {
593 self.types.iter().any(|t| matches!(t, TAtomic::Mixed(_))) && !self.types.is_empty()
594 }
595
596 pub fn has_mixed_template(&self) -> bool {
597 self.types.iter().any(TAtomic::is_templated_as_mixed) && !self.types.is_empty()
598 }
599
600 #[must_use]
601 pub fn has_nullable_mixed(&self) -> bool {
602 self.types.iter().any(|t| matches!(t, TAtomic::Mixed(mixed) if !mixed.is_non_null())) && !self.types.is_empty()
603 }
604
605 #[must_use]
606 pub fn has_void(&self) -> bool {
607 self.types.iter().any(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
608 }
609
610 #[must_use]
611 pub fn has_null(&self) -> bool {
612 self.types.iter().any(|t| matches!(t, TAtomic::Null)) && !self.types.is_empty()
613 }
614
615 #[must_use]
616 pub fn has_nullish(&self) -> bool {
617 self.types.iter().any(|t| match t {
618 TAtomic::Null | TAtomic::Void => true,
619 TAtomic::Mixed(mixed) => !mixed.is_non_null(),
620 TAtomic::GenericParameter(parameter) => parameter.constraint.has_nullish(),
621 _ => false,
622 }) && !self.types.is_empty()
623 }
624
625 #[must_use]
626 pub fn is_nullable_mixed(&self) -> bool {
627 if self.types.len() != 1 {
628 return false;
629 }
630
631 match &self.types[0] {
632 TAtomic::Mixed(mixed) => !mixed.is_non_null(),
633 _ => false,
634 }
635 }
636
637 #[must_use]
638 pub fn is_falsy_mixed(&self) -> bool {
639 if self.types.len() != 1 {
640 return false;
641 }
642
643 matches!(&self.types[0], &TAtomic::Mixed(mixed) if mixed.is_falsy())
644 }
645
646 #[must_use]
647 pub fn is_vanilla_mixed(&self) -> bool {
648 if self.types.len() != 1 {
649 return false;
650 }
651
652 self.types[0].is_vanilla_mixed()
653 }
654
655 #[must_use]
656 pub fn is_templated_as_vanilla_mixed(&self) -> bool {
657 if self.types.len() != 1 {
658 return false;
659 }
660
661 self.types[0].is_templated_as_vanilla_mixed()
662 }
663
664 #[must_use]
665 pub fn has_template_or_static(&self) -> bool {
666 for atomic in self.types.as_ref() {
667 if let TAtomic::GenericParameter(_) = atomic {
668 return true;
669 }
670
671 if let TAtomic::Object(TObject::Named(named_object)) = atomic {
672 if named_object.is_static {
673 return true;
674 }
675
676 if let Some(intersections) = named_object.get_intersection_types() {
677 for intersection in intersections {
678 if let TAtomic::GenericParameter(_) = intersection {
679 return true;
680 }
681 }
682 }
683 }
684 }
685
686 false
687 }
688
689 #[must_use]
690 pub fn has_template(&self) -> bool {
691 for atomic in self.types.as_ref() {
692 if let TAtomic::GenericParameter(_) = atomic {
693 return true;
694 }
695
696 if let Some(intersections) = atomic.get_intersection_types() {
697 for intersection in intersections {
698 if let TAtomic::GenericParameter(_) = intersection {
699 return true;
700 }
701 }
702 }
703 }
704
705 false
706 }
707
708 #[must_use]
709 pub fn has_template_types(&self) -> bool {
710 let all_child_nodes = self.get_all_child_nodes();
711
712 for child_node in all_child_nodes {
713 if let TypeRef::Atomic(
714 TAtomic::GenericParameter(_)
715 | TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic { .. })),
716 ) = child_node
717 {
718 return true;
719 }
720 }
721
722 false
723 }
724
725 #[must_use]
726 pub fn get_template_types(&self) -> Vec<&TAtomic> {
727 let all_child_nodes = self.get_all_child_nodes();
728
729 let mut template_types = Vec::new();
730
731 for child_node in all_child_nodes {
732 if let TypeRef::Atomic(inner) = child_node {
733 match inner {
734 TAtomic::GenericParameter(_) => {
735 template_types.push(inner);
736 }
737 TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic { .. })) => {
738 template_types.push(inner);
739 }
740 _ => {}
741 }
742 }
743 }
744
745 template_types
746 }
747
748 pub fn is_objecty(&self) -> bool {
749 for atomic in self.types.as_ref() {
750 if let &TAtomic::Object(_) = atomic {
751 continue;
752 }
753
754 if let TAtomic::Callable(callable) = atomic
755 && callable.get_signature().is_none_or(super::atomic::callable::TCallableSignature::is_closure)
756 {
757 continue;
758 }
759
760 return false;
761 }
762
763 true
764 }
765
766 #[must_use]
767 pub fn is_generator(&self) -> bool {
768 for atomic in self.types.as_ref() {
769 if atomic.is_generator() {
770 continue;
771 }
772
773 return false;
774 }
775
776 true
777 }
778
779 #[must_use]
780 pub fn extends_or_implements(&self, codebase: &CodebaseMetadata, interface: &[u8]) -> bool {
781 for atomic in self.types.as_ref() {
782 if !atomic.extends_or_implements(codebase, interface) {
783 return false;
784 }
785 }
786
787 true
788 }
789
790 #[must_use]
791 pub fn is_generic_parameter(&self) -> bool {
792 self.types.len() == 1 && matches!(self.types[0], TAtomic::GenericParameter(_))
793 }
794
795 #[must_use]
796 pub fn get_generic_parameter_constraint(&self) -> Option<&TUnion> {
797 if self.is_generic_parameter()
798 && let TAtomic::GenericParameter(parameter) = &self.types[0]
799 {
800 return Some(¶meter.constraint);
801 }
802
803 None
804 }
805
806 #[must_use]
807 pub fn is_null(&self) -> bool {
808 self.types.iter().all(|t| matches!(t, TAtomic::Null)) && !self.types.is_empty()
809 }
810
811 #[must_use]
812 pub fn is_nullable(&self) -> bool {
813 self.types.iter().any(|t| match t {
814 TAtomic::Null => self.types.len() >= 2,
815 TAtomic::GenericParameter(param) => param.constraint.is_nullable(),
816 _ => false,
817 })
818 }
819
820 #[must_use]
821 pub fn can_be_null(&self) -> bool {
822 self.types.iter().any(|t| match t {
823 TAtomic::Null => true,
824 TAtomic::Void => true,
825 TAtomic::Mixed(mixed) if !mixed.is_non_null() => true,
826 TAtomic::GenericParameter(param) => param.constraint.can_be_null(),
827 _ => false,
828 })
829 }
830
831 #[must_use]
832 pub fn is_void(&self) -> bool {
833 self.types.iter().all(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
834 }
835
836 #[must_use]
837 pub fn is_voidable(&self) -> bool {
838 self.types.iter().any(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
839 }
840
841 pub fn has_resource(&self) -> bool {
842 self.types.iter().any(TAtomic::is_resource)
843 }
844
845 pub fn is_resource(&self) -> bool {
846 self.types.iter().all(TAtomic::is_resource) && !self.types.is_empty()
847 }
848
849 pub fn is_array(&self) -> bool {
850 self.types.iter().all(TAtomic::is_array) && !self.types.is_empty()
851 }
852
853 pub fn is_list(&self) -> bool {
854 self.types.iter().all(TAtomic::is_list) && !self.types.is_empty()
855 }
856
857 pub fn is_vanilla_array(&self) -> bool {
858 self.types.iter().all(TAtomic::is_vanilla_array) && !self.types.is_empty()
859 }
860
861 pub fn is_keyed_array(&self) -> bool {
862 self.types.iter().all(TAtomic::is_keyed_array) && !self.types.is_empty()
863 }
864
865 pub fn is_falsable(&self) -> bool {
866 self.types.len() >= 2 && self.types.iter().any(TAtomic::is_false)
867 }
868
869 #[must_use]
870 pub fn has_bool(&self) -> bool {
871 self.types.iter().any(|t| t.is_bool() || t.is_generic_scalar()) && !self.types.is_empty()
872 }
873
874 pub fn has_scalar(&self) -> bool {
880 self.types.iter().any(TAtomic::is_generic_scalar)
881 }
882
883 #[must_use]
886 pub fn has_scalar_combination(&self) -> bool {
887 const HAS_INT: u8 = 1 << 0;
888 const HAS_FLOAT: u8 = 1 << 1;
889 const HAS_BOOL: u8 = 1 << 2;
890 const HAS_STRING: u8 = 1 << 3;
891 const ALL_SCALARS: u8 = HAS_INT | HAS_FLOAT | HAS_BOOL | HAS_STRING;
892
893 let mut flags = 0u8;
894
895 for atomic in self.types.as_ref() {
896 if atomic.is_int() {
897 flags |= HAS_INT;
898 } else if atomic.is_float() {
899 flags |= HAS_FLOAT;
900 } else if atomic.is_bool() {
901 flags |= HAS_BOOL;
902 } else if atomic.is_string() {
903 flags |= HAS_STRING;
904 } else if atomic.is_array_key() {
905 flags |= HAS_INT | HAS_STRING;
906 } else if atomic.is_numeric() {
907 flags |= HAS_INT | HAS_FLOAT;
909 } else if atomic.is_generic_scalar() {
910 return true;
911 }
912
913 if flags == ALL_SCALARS {
915 return true;
916 }
917 }
918
919 flags == ALL_SCALARS
920 }
921 pub fn has_array_key(&self) -> bool {
922 self.types.iter().any(TAtomic::is_array_key)
923 }
924
925 pub fn has_iterable(&self) -> bool {
926 self.types.iter().any(TAtomic::is_iterable) && !self.types.is_empty()
927 }
928
929 pub fn has_array(&self) -> bool {
930 self.types.iter().any(TAtomic::is_array) && !self.types.is_empty()
931 }
932
933 #[must_use]
934 pub fn has_traversable(&self, codebase: &CodebaseMetadata) -> bool {
935 self.types.iter().any(|atomic| atomic.is_traversable(codebase)) && !self.types.is_empty()
936 }
937
938 #[must_use]
939 pub fn has_array_key_like(&self) -> bool {
940 self.types.iter().any(|atomic| atomic.is_array_key() || atomic.is_int() || atomic.is_string())
941 }
942
943 pub fn has_numeric(&self) -> bool {
944 self.types.iter().any(TAtomic::is_numeric) && !self.types.is_empty()
945 }
946
947 pub fn is_always_truthy(&self) -> bool {
948 self.types.iter().all(TAtomic::is_truthy) && !self.types.is_empty()
949 }
950
951 pub fn is_always_falsy(&self) -> bool {
952 self.types.iter().all(TAtomic::is_falsy) && !self.types.is_empty()
953 }
954
955 #[must_use]
956 pub fn is_literal_of(&self, other: &TUnion) -> bool {
957 let Some(other_atomic_type) = other.types.first() else {
958 return false;
959 };
960
961 match other_atomic_type {
962 TAtomic::Scalar(TScalar::String(_)) => {
963 for self_atomic_type in self.types.as_ref() {
964 if self_atomic_type.is_string_of_literal_origin() {
965 continue;
966 }
967
968 return false;
969 }
970
971 true
972 }
973 TAtomic::Scalar(TScalar::Integer(_)) => {
974 for self_atomic_type in self.types.as_ref() {
975 if self_atomic_type.is_literal_int() {
976 continue;
977 }
978
979 return false;
980 }
981
982 true
983 }
984 TAtomic::Scalar(TScalar::Float(_)) => {
985 for self_atomic_type in self.types.as_ref() {
986 if self_atomic_type.is_literal_float() {
987 continue;
988 }
989
990 return false;
991 }
992
993 true
994 }
995 _ => false,
996 }
997 }
998
999 #[must_use]
1000 pub fn all_literals(&self) -> bool {
1001 self.types
1002 .iter()
1003 .all(|atomic| atomic.is_string_of_literal_origin() || atomic.is_literal_int() || atomic.is_literal_float())
1004 }
1005
1006 #[must_use]
1007 pub fn has_static_object(&self) -> bool {
1008 self.types
1009 .iter()
1010 .any(|atomic| matches!(atomic, TAtomic::Object(TObject::Named(named_object)) if named_object.is_static))
1011 }
1012
1013 #[must_use]
1014 pub fn is_static_object(&self) -> bool {
1015 self.types
1016 .iter()
1017 .all(|atomic| matches!(atomic, TAtomic::Object(TObject::Named(named_object)) if named_object.is_static))
1018 }
1019
1020 #[inline]
1021 #[must_use]
1022 pub fn is_single(&self) -> bool {
1023 self.types.len() == 1
1024 }
1025
1026 #[inline]
1027 #[must_use]
1028 pub fn get_single_string(&self) -> Option<&TString> {
1029 if self.is_single()
1030 && let TAtomic::Scalar(TScalar::String(string)) = &self.types[0]
1031 {
1032 Some(string)
1033 } else {
1034 None
1035 }
1036 }
1037
1038 #[inline]
1039 #[must_use]
1040 pub fn get_single_array(&self) -> Option<&TArray> {
1041 if self.is_single()
1042 && let TAtomic::Array(array) = &self.types[0]
1043 {
1044 Some(array)
1045 } else {
1046 None
1047 }
1048 }
1049
1050 #[inline]
1051 #[must_use]
1052 pub fn get_single_bool(&self) -> Option<&TBool> {
1053 if self.is_single()
1054 && let TAtomic::Scalar(TScalar::Bool(bool)) = &self.types[0]
1055 {
1056 Some(bool)
1057 } else {
1058 None
1059 }
1060 }
1061
1062 #[inline]
1063 #[must_use]
1064 pub fn get_single_named_object(&self) -> Option<&TNamedObject> {
1065 if self.is_single()
1066 && let TAtomic::Object(TObject::Named(named_object)) = &self.types[0]
1067 {
1068 Some(named_object)
1069 } else {
1070 None
1071 }
1072 }
1073
1074 #[inline]
1075 #[must_use]
1076 pub fn get_single_shaped_object(&self) -> Option<&TObjectWithProperties> {
1077 if self.is_single()
1078 && let TAtomic::Object(TObject::WithProperties(shaped_object)) = &self.types[0]
1079 {
1080 Some(shaped_object)
1081 } else {
1082 None
1083 }
1084 }
1085
1086 #[inline]
1087 #[must_use]
1088 pub fn get_single(&self) -> &TAtomic {
1089 &self.types[0]
1090 }
1091
1092 #[inline]
1093 #[must_use]
1094 pub fn get_single_owned(self) -> TAtomic {
1095 self.types[0].clone()
1096 }
1097
1098 #[inline]
1099 #[must_use]
1100 pub fn is_named_object(&self) -> bool {
1101 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Named(_))))
1102 }
1103
1104 #[must_use]
1105 pub fn is_enum(&self) -> bool {
1106 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(_))))
1107 }
1108
1109 #[must_use]
1110 pub fn is_enum_case(&self) -> bool {
1111 self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(r#enum)) if r#enum.case.is_some()))
1112 }
1113
1114 #[must_use]
1115 pub fn is_single_enum_case(&self) -> bool {
1116 self.is_single()
1117 && self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(r#enum)) if r#enum.case.is_some()))
1118 }
1119
1120 #[inline]
1121 #[must_use]
1122 pub fn has_named_object(&self) -> bool {
1123 self.types.iter().any(|t| matches!(t, TAtomic::Object(TObject::Named(_))))
1124 }
1125
1126 #[inline]
1127 #[must_use]
1128 pub fn has_object(&self) -> bool {
1129 self.types.iter().any(|t| matches!(t, TAtomic::Object(TObject::Any | TObject::WithProperties(_))))
1130 }
1131
1132 #[inline]
1133 #[must_use]
1134 pub fn has_callable(&self) -> bool {
1135 self.types.iter().any(|t| matches!(t, TAtomic::Callable(_)))
1136 }
1137
1138 #[inline]
1139 #[must_use]
1140 pub fn is_callable(&self) -> bool {
1141 self.types.iter().all(|t| matches!(t, TAtomic::Callable(_)))
1142 }
1143
1144 #[inline]
1145 #[must_use]
1146 pub fn has_object_type(&self) -> bool {
1147 self.types.iter().any(|t| matches!(t, TAtomic::Object(_)))
1148 }
1149
1150 #[must_use]
1153 pub fn get_enum_cases(&self) -> Vec<(Word, Option<Word>)> {
1154 self.types
1155 .iter()
1156 .filter_map(|t| match t {
1157 TAtomic::Object(TObject::Enum(enum_object)) => Some((enum_object.name, enum_object.case)),
1158 _ => None,
1159 })
1160 .collect()
1161 }
1162
1163 #[must_use]
1164 pub fn get_single_int(&self) -> Option<TInteger> {
1165 if self.is_single() { self.get_single().get_integer() } else { None }
1166 }
1167
1168 #[must_use]
1169 pub fn get_single_literal_int_value(&self) -> Option<i64> {
1170 if self.is_single() { self.get_single().get_literal_int_value() } else { None }
1171 }
1172
1173 #[must_use]
1174 pub fn get_single_maximum_int_value(&self) -> Option<i64> {
1175 if self.is_single() { self.get_single().get_maximum_int_value() } else { None }
1176 }
1177
1178 #[must_use]
1179 pub fn get_single_minimum_int_value(&self) -> Option<i64> {
1180 if self.is_single() { self.get_single().get_minimum_int_value() } else { None }
1181 }
1182
1183 #[must_use]
1184 pub fn get_single_literal_float_value(&self) -> Option<f64> {
1185 if self.is_single() { self.get_single().get_literal_float_value() } else { None }
1186 }
1187
1188 #[must_use]
1189 pub fn get_single_literal_string_value(&self) -> Option<&[u8]> {
1190 if self.is_single() { self.get_single().get_literal_string_value() } else { None }
1191 }
1192
1193 #[must_use]
1194 pub fn get_single_class_string_value(&self) -> Option<Word> {
1195 if self.is_single() { self.get_single().get_class_string_value() } else { None }
1196 }
1197
1198 #[must_use]
1199 pub fn get_single_array_key(&self) -> Option<ArrayKey> {
1200 if self.is_single() { self.get_single().to_array_key() } else { None }
1201 }
1202
1203 #[must_use]
1204 pub fn get_single_key_of_array_like(&self) -> Option<TUnion> {
1205 if !self.is_single() {
1206 return None;
1207 }
1208
1209 match self.get_single() {
1210 TAtomic::Array(array) => match array {
1211 TArray::List(_) => Some(get_int()),
1212 TArray::Keyed(keyed_array) => match &keyed_array.parameters {
1213 Some((k, _)) => Some((**k).clone()),
1214 None => Some(get_arraykey()),
1215 },
1216 },
1217 _ => None,
1218 }
1219 }
1220
1221 #[must_use]
1222 pub fn get_single_value_of_array_like(&self) -> Option<Cow<'_, TUnion>> {
1223 if !self.is_single() {
1224 return None;
1225 }
1226
1227 match self.get_single() {
1228 TAtomic::Array(array) => match array {
1229 TArray::List(list) => Some(Cow::Borrowed(&list.element_type)),
1230 TArray::Keyed(keyed_array) => match &keyed_array.parameters {
1231 Some((_, v)) => Some(Cow::Borrowed(v)),
1232 None => Some(Cow::Owned(get_mixed())),
1233 },
1234 },
1235 _ => None,
1236 }
1237 }
1238
1239 #[must_use]
1240 pub fn get_literal_ints(&self) -> Vec<&TAtomic> {
1241 self.types.iter().filter(|a| a.is_literal_int()).collect()
1242 }
1243
1244 #[must_use]
1245 pub fn get_literal_strings(&self) -> Vec<&TAtomic> {
1246 self.types.iter().filter(|a| a.is_known_literal_string()).collect()
1247 }
1248
1249 #[must_use]
1250 pub fn get_literal_string_values(&self) -> Vec<Option<Word>> {
1251 self.get_literal_strings()
1252 .into_iter()
1253 .map(|atom| match atom {
1254 TAtomic::Scalar(TScalar::String(TString { literal: Some(TStringLiteral::Value(value)), .. })) => {
1255 Some(*value)
1256 }
1257 _ => None,
1258 })
1259 .collect()
1260 }
1261
1262 #[must_use]
1263 pub fn has_literal_float(&self) -> bool {
1264 self.types.iter().any(|atomic| match atomic {
1265 TAtomic::Scalar(scalar) => scalar.is_literal_float(),
1266 _ => false,
1267 })
1268 }
1269
1270 #[must_use]
1271 pub fn has_literal_int(&self) -> bool {
1272 self.types.iter().any(|atomic| match atomic {
1273 TAtomic::Scalar(scalar) => scalar.is_literal_int(),
1274 _ => false,
1275 })
1276 }
1277
1278 #[must_use]
1279 pub fn has_literal_string(&self) -> bool {
1280 self.types.iter().any(|atomic| match atomic {
1281 TAtomic::Scalar(scalar) => scalar.is_known_literal_string(),
1282 _ => false,
1283 })
1284 }
1285
1286 #[must_use]
1287 pub fn has_literal_value(&self) -> bool {
1288 self.types.iter().any(|atomic| match atomic {
1289 TAtomic::Scalar(scalar) => scalar.is_literal_value(),
1290 _ => false,
1291 })
1292 }
1293
1294 #[must_use]
1295 pub fn accepts_false(&self) -> bool {
1296 self.types.iter().any(|t| match t {
1297 TAtomic::GenericParameter(parameter) => parameter.constraint.accepts_false(),
1298 TAtomic::Mixed(mixed) if !mixed.is_truthy() => true,
1299 TAtomic::Scalar(TScalar::Generic | TScalar::Bool(TBool { value: None | Some(false) })) => true,
1300 _ => false,
1301 })
1302 }
1303
1304 #[must_use]
1305 pub fn accepts_null(&self) -> bool {
1306 self.types.iter().any(|t| match t {
1307 TAtomic::GenericParameter(generic_parameter) => generic_parameter.constraint.accepts_null(),
1308 TAtomic::Mixed(mixed) if !mixed.is_non_null() => true,
1309 TAtomic::Null | TAtomic::Placeholder => true,
1310 _ => false,
1311 })
1312 }
1313}
1314
1315impl TType for TUnion {
1316 fn get_child_nodes(&self) -> Vec<TypeRef<'_>> {
1317 self.types.iter().map(TypeRef::Atomic).collect()
1318 }
1319
1320 fn needs_population(&self) -> bool {
1321 !self.flags.contains(UnionFlags::POPULATED) && self.types.iter().any(super::TType::needs_population)
1322 }
1323
1324 #[inline]
1325 fn is_expandable(&self) -> bool {
1326 if self.types.is_empty() {
1327 return true;
1328 }
1329
1330 self.types.iter().any(super::TType::is_expandable)
1331 }
1332
1333 fn is_complex(&self) -> bool {
1334 self.types.len() > 3 || self.types.iter().any(super::TType::is_complex)
1335 }
1336
1337 fn get_id(&self) -> Word {
1338 let len = self.types.len();
1339
1340 let mut atomic_ids: Vec<Word> = self
1341 .types
1342 .as_ref()
1343 .iter()
1344 .map(|atomic| {
1345 let id = atomic.get_id();
1346 if atomic.is_generic_parameter() || atomic.has_intersection_types() && len > 1 {
1347 concat_word!(b"(", id.as_bytes(), b")")
1348 } else {
1349 id
1350 }
1351 })
1352 .collect();
1353
1354 if len <= 1 {
1355 return atomic_ids.pop().unwrap_or_else(empty_word);
1356 }
1357
1358 atomic_ids.sort_unstable();
1359 let mut result = atomic_ids[0];
1360 for id in &atomic_ids[1..] {
1361 result = concat_word!(result.as_bytes(), b"|", id.as_bytes());
1362 }
1363
1364 result
1365 }
1366
1367 fn get_pretty_id_with_indent(&self, indent: usize) -> Word {
1368 let len = self.types.len();
1369
1370 if len <= 1 {
1371 return self.types.first().map_or_else(empty_word, |atomic| atomic.get_pretty_id_with_indent(indent));
1372 }
1373
1374 if len > 3 {
1376 let mut atomic_ids: Vec<Word> = self
1377 .types
1378 .as_ref()
1379 .iter()
1380 .map(|atomic| {
1381 let id = atomic.get_pretty_id_with_indent(indent + 2);
1382 if atomic.has_intersection_types() { concat_word!(b"(", id.as_bytes(), b")") } else { id }
1383 })
1384 .collect();
1385
1386 atomic_ids.sort_unstable();
1387
1388 let mut result: Vec<u8> = Vec::new();
1389 result.extend_from_slice(atomic_ids[0].as_bytes());
1390 for id in &atomic_ids[1..] {
1391 result.extend_from_slice(b"\n");
1392 result.resize(result.len() + indent, b' ');
1393 result.extend_from_slice(b"| ");
1394 result.extend_from_slice(id.as_bytes());
1395 }
1396
1397 word(&result)
1398 } else {
1399 let mut atomic_ids: Vec<Word> = self
1401 .types
1402 .as_ref()
1403 .iter()
1404 .map(|atomic| {
1405 let id = atomic.get_pretty_id_with_indent(indent);
1406 if atomic.has_intersection_types() && len > 1 {
1407 concat_word!(b"(", id.as_bytes(), b")")
1408 } else {
1409 id
1410 }
1411 })
1412 .collect();
1413
1414 atomic_ids.sort_unstable();
1415 let mut result = atomic_ids[0];
1416 for id in &atomic_ids[1..] {
1417 result = concat_word!(result.as_bytes(), b" | ", id.as_bytes());
1418 }
1419
1420 result
1421 }
1422 }
1423}
1424
1425impl PartialEq for TUnion {
1426 fn eq(&self, other: &TUnion) -> bool {
1427 if std::ptr::eq(self, other) {
1428 return true;
1429 }
1430
1431 const SEMANTIC_FLAGS: UnionFlags = UnionFlags::HAD_TEMPLATE
1432 .union(UnionFlags::BY_REFERENCE)
1433 .union(UnionFlags::REFERENCE_FREE)
1434 .union(UnionFlags::POSSIBLY_UNDEFINED_FROM_TRY)
1435 .union(UnionFlags::POSSIBLY_UNDEFINED)
1436 .union(UnionFlags::IGNORE_NULLABLE_ISSUES)
1437 .union(UnionFlags::IGNORE_FALSABLE_ISSUES)
1438 .union(UnionFlags::FROM_TEMPLATE_DEFAULT);
1439
1440 if self.flags.intersection(SEMANTIC_FLAGS) != other.flags.intersection(SEMANTIC_FLAGS) {
1441 return false;
1442 }
1443
1444 let len = self.types.len();
1445 if len != other.types.len() {
1446 return false;
1447 }
1448
1449 if self.types == other.types {
1452 return true;
1453 }
1454
1455 for i in 0..len {
1457 let mut has_match = false;
1458 for j in 0..len {
1459 if self.types[i] == other.types[j] {
1460 has_match = true;
1461 break;
1462 }
1463 }
1464
1465 if !has_match {
1466 return false;
1467 }
1468 }
1469
1470 for i in 0..len {
1472 let mut has_match = false;
1473 for j in 0..len {
1474 if other.types[i] == self.types[j] {
1475 has_match = true;
1476 break;
1477 }
1478 }
1479
1480 if !has_match {
1481 return false;
1482 }
1483 }
1484
1485 true
1486 }
1487}
1488
1489#[inline]
1491fn ensure_non_empty_types(mut types: Vec<TAtomic>) -> Vec<TAtomic> {
1492 if types.is_empty() {
1493 types.push(TAtomic::Never);
1494 }
1495
1496 types
1497}
1498
1499pub fn populate_union_type(
1500 unpopulated_union: &mut TUnion,
1501 codebase_symbols: &Symbols,
1502 reference_source: Option<&ReferenceSource>,
1503 symbol_references: &mut SymbolReferences,
1504 force: bool,
1505) {
1506 if unpopulated_union.flags.contains(UnionFlags::POPULATED) && !force {
1507 return;
1508 }
1509
1510 if !unpopulated_union.needs_population() {
1511 return;
1512 }
1513
1514 unpopulated_union.flags.insert(UnionFlags::POPULATED);
1515 let unpopulated_atomics = unpopulated_union.types.to_mut();
1516 for unpopulated_atomic in unpopulated_atomics {
1517 match unpopulated_atomic {
1518 TAtomic::Scalar(TScalar::ClassLikeString(
1519 TClassLikeString::Generic { constraint, .. } | TClassLikeString::OfType { constraint, .. },
1520 )) => {
1521 populate_atomic_type(
1522 Arc::make_mut(constraint),
1523 codebase_symbols,
1524 reference_source,
1525 symbol_references,
1526 force,
1527 );
1528 }
1529 _ => {
1530 populate_atomic_type(unpopulated_atomic, codebase_symbols, reference_source, symbol_references, force);
1531 }
1532 }
1533 }
1534}
1535
1536fn widen_atomic_scalars(atomic: &mut TAtomic) {
1541 match atomic {
1542 TAtomic::Scalar(scalar) => widen_scalar(scalar),
1543 TAtomic::Array(array) => match array {
1544 TArray::List(list) => {
1545 widen_arc_union_scalars(&mut list.element_type);
1546 if let Some(known) = list.known_elements.as_mut() {
1547 for (_, ty) in known.values_mut() {
1548 ty.widen_scalars();
1549 }
1550 }
1551 }
1552 TArray::Keyed(keyed) => {
1553 if let Some((key, value)) = keyed.parameters.as_mut() {
1554 widen_arc_union_scalars(key);
1555 widen_arc_union_scalars(value);
1556 }
1557 if let Some(known) = keyed.known_items.as_mut() {
1558 for (_, ty) in known.values_mut() {
1559 ty.widen_scalars();
1560 }
1561 }
1562 }
1563 },
1564 TAtomic::Iterable(iterable) => {
1565 widen_arc_union_scalars(&mut iterable.key_type);
1566 widen_arc_union_scalars(&mut iterable.value_type);
1567 if let Some(intersections) = iterable.intersection_types.as_mut() {
1568 for inner in intersections.iter_mut() {
1569 widen_atomic_scalars(inner);
1570 }
1571 }
1572 }
1573 TAtomic::Object(TObject::Named(named)) => {
1574 if let Some(params) = named.type_parameters.as_mut() {
1575 for ty in params.iter_mut() {
1576 ty.widen_scalars();
1577 }
1578 }
1579 }
1580 TAtomic::Object(TObject::WithProperties(with_props)) => {
1581 for (_, ty) in with_props.known_properties.values_mut() {
1582 ty.widen_scalars();
1583 }
1584 }
1585 TAtomic::GenericParameter(generic) => {
1586 widen_arc_union_scalars(&mut generic.constraint);
1587 if let Some(intersections) = generic.intersection_types.as_mut() {
1588 for inner in intersections.iter_mut() {
1589 widen_atomic_scalars(inner);
1590 }
1591 }
1592 }
1593 TAtomic::Conditional(conditional) => {
1594 widen_arc_union_scalars(&mut conditional.subject);
1595 widen_arc_union_scalars(&mut conditional.target);
1596 widen_arc_union_scalars(&mut conditional.then);
1597 widen_arc_union_scalars(&mut conditional.otherwise);
1598 }
1599 _ => {}
1600 }
1601}
1602
1603#[inline]
1604fn widen_arc_union_scalars(union: &mut Arc<TUnion>) {
1605 if union_has_widenable_nested_scalar(union) {
1606 Arc::make_mut(union).widen_scalars();
1607 }
1608}
1609
1610fn widen_scalar(scalar: &mut TScalar) {
1611 match scalar {
1612 TScalar::String(string) if !is_string_fully_general(string) => {
1613 *string = TString::general();
1614 }
1615 TScalar::Integer(integer) if !matches!(integer, TInteger::Unspecified) => {
1616 *integer = TInteger::Unspecified;
1617 }
1618 TScalar::Float(float) if !matches!(float, TFloat::Float) => {
1619 *float = TFloat::Float;
1620 }
1621 TScalar::Bool(b) if !b.is_general() => {
1622 *b = TBool::general();
1623 }
1624 _ => {}
1625 }
1626}
1627
1628#[inline]
1629fn is_string_fully_general(string: &TString) -> bool {
1630 string.literal.is_none()
1631 && !string.is_numeric
1632 && !string.is_truthy
1633 && !string.is_non_empty
1634 && !string.is_callable
1635 && matches!(string.casing, TStringCasing::Unspecified)
1636}
1637
1638fn widen_atomic_literals(atomic: &mut TAtomic) {
1644 match atomic {
1645 TAtomic::Scalar(scalar) => widen_scalar_literal(scalar),
1646 TAtomic::Array(array) => match array {
1647 TArray::List(list) => {
1648 widen_arc_union_literals(&mut list.element_type);
1649 if let Some(known) = list.known_elements.as_mut() {
1650 for (_, ty) in known.values_mut() {
1651 ty.widen_literals();
1652 }
1653 }
1654 }
1655 TArray::Keyed(keyed) => {
1656 if let Some((key, value)) = keyed.parameters.as_mut() {
1657 widen_arc_union_literals(key);
1658 widen_arc_union_literals(value);
1659 }
1660 if let Some(known) = keyed.known_items.as_mut() {
1661 for (_, ty) in known.values_mut() {
1662 ty.widen_literals();
1663 }
1664 }
1665 }
1666 },
1667 TAtomic::Iterable(iterable) => {
1668 widen_arc_union_literals(&mut iterable.key_type);
1669 widen_arc_union_literals(&mut iterable.value_type);
1670 if let Some(intersections) = iterable.intersection_types.as_mut() {
1671 for inner in intersections.iter_mut() {
1672 widen_atomic_literals(inner);
1673 }
1674 }
1675 }
1676 TAtomic::Object(TObject::Named(named)) => {
1677 if let Some(params) = named.type_parameters.as_mut() {
1678 for ty in params.iter_mut() {
1679 ty.widen_literals();
1680 }
1681 }
1682 }
1683 TAtomic::Object(TObject::WithProperties(with_props)) => {
1684 for (_, ty) in with_props.known_properties.values_mut() {
1685 ty.widen_literals();
1686 }
1687 }
1688 TAtomic::GenericParameter(generic) => {
1689 widen_arc_union_literals(&mut generic.constraint);
1690 if let Some(intersections) = generic.intersection_types.as_mut() {
1691 for inner in intersections.iter_mut() {
1692 widen_atomic_literals(inner);
1693 }
1694 }
1695 }
1696 TAtomic::Conditional(conditional) => {
1697 widen_arc_union_literals(&mut conditional.subject);
1698 widen_arc_union_literals(&mut conditional.target);
1699 widen_arc_union_literals(&mut conditional.then);
1700 widen_arc_union_literals(&mut conditional.otherwise);
1701 }
1702 _ => {}
1703 }
1704}
1705
1706#[inline]
1707fn widen_arc_union_literals(union: &mut Arc<TUnion>) {
1708 if union_has_widenable_nested_literal(union) {
1709 Arc::make_mut(union).widen_literals();
1710 }
1711}
1712
1713fn widen_scalar_literal(scalar: &mut TScalar) {
1714 match scalar {
1715 TScalar::String(string) if string.literal.is_some() => {
1716 *string = string.without_literal();
1717 }
1718 TScalar::Integer(integer) if matches!(integer, TInteger::Literal(_) | TInteger::UnspecifiedLiteral) => {
1719 *integer = TInteger::Unspecified;
1720 }
1721 TScalar::Float(float) if matches!(float, TFloat::Literal(_) | TFloat::UnspecifiedLiteral) => {
1722 *float = TFloat::Float;
1723 }
1724 TScalar::Bool(b) if !b.is_general() => {
1725 *b = TBool::general();
1726 }
1727 _ => {}
1728 }
1729}
1730
1731fn union_has_widenable_nested_literal(union: &TUnion) -> bool {
1732 union.types.iter().any(atomic_has_widenable_literal)
1733}
1734
1735fn atomic_has_widenable_literal(atomic: &TAtomic) -> bool {
1736 match atomic {
1737 TAtomic::Scalar(TScalar::String(s)) => s.literal.is_some(),
1738 TAtomic::Scalar(TScalar::Integer(i)) => matches!(i, TInteger::Literal(_) | TInteger::UnspecifiedLiteral),
1739 TAtomic::Scalar(TScalar::Float(f)) => matches!(f, TFloat::Literal(_) | TFloat::UnspecifiedLiteral),
1740 TAtomic::Scalar(TScalar::Bool(b)) => !b.is_general(),
1741 TAtomic::Array(TArray::List(list)) => {
1742 union_has_widenable_nested_literal(&list.element_type)
1743 || list
1744 .known_elements
1745 .as_ref()
1746 .is_some_and(|m| m.values().any(|(_, t)| union_has_widenable_nested_literal(t)))
1747 }
1748 TAtomic::Array(TArray::Keyed(keyed)) => {
1749 keyed
1750 .parameters
1751 .as_ref()
1752 .is_some_and(|(k, v)| union_has_widenable_nested_literal(k) || union_has_widenable_nested_literal(v))
1753 || keyed
1754 .known_items
1755 .as_ref()
1756 .is_some_and(|m| m.values().any(|(_, t)| union_has_widenable_nested_literal(t)))
1757 }
1758 TAtomic::Iterable(iterable) => {
1759 union_has_widenable_nested_literal(&iterable.key_type)
1760 || union_has_widenable_nested_literal(&iterable.value_type)
1761 || iterable.intersection_types.as_ref().is_some_and(|v| v.iter().any(atomic_has_widenable_literal))
1762 }
1763 TAtomic::Object(TObject::Named(named)) => {
1764 named.type_parameters.as_ref().is_some_and(|p| p.iter().any(union_has_widenable_nested_literal))
1765 }
1766 TAtomic::Object(TObject::WithProperties(with_props)) => {
1767 with_props.known_properties.values().any(|(_, t)| union_has_widenable_nested_literal(t))
1768 }
1769 TAtomic::GenericParameter(generic) => {
1770 union_has_widenable_nested_literal(&generic.constraint)
1771 || generic.intersection_types.as_ref().is_some_and(|v| v.iter().any(atomic_has_widenable_literal))
1772 }
1773 TAtomic::Conditional(conditional) => {
1774 union_has_widenable_nested_literal(&conditional.subject)
1775 || union_has_widenable_nested_literal(&conditional.target)
1776 || union_has_widenable_nested_literal(&conditional.then)
1777 || union_has_widenable_nested_literal(&conditional.otherwise)
1778 }
1779 _ => false,
1780 }
1781}
1782
1783fn union_has_widenable_nested_scalar(union: &TUnion) -> bool {
1787 union.types.iter().any(atomic_has_widenable_scalar)
1788}
1789
1790fn atomic_has_widenable_scalar(atomic: &TAtomic) -> bool {
1791 match atomic {
1792 TAtomic::Scalar(TScalar::String(s)) => !is_string_fully_general(s),
1793 TAtomic::Scalar(TScalar::Integer(i)) => !matches!(i, TInteger::Unspecified),
1794 TAtomic::Scalar(TScalar::Float(f)) => !matches!(f, TFloat::Float),
1795 TAtomic::Scalar(TScalar::Bool(b)) => !b.is_general(),
1796 TAtomic::Array(TArray::List(list)) => {
1797 union_has_widenable_nested_scalar(&list.element_type)
1798 || list
1799 .known_elements
1800 .as_ref()
1801 .is_some_and(|m| m.values().any(|(_, t)| union_has_widenable_nested_scalar(t)))
1802 }
1803 TAtomic::Array(TArray::Keyed(keyed)) => {
1804 keyed
1805 .parameters
1806 .as_ref()
1807 .is_some_and(|(k, v)| union_has_widenable_nested_scalar(k) || union_has_widenable_nested_scalar(v))
1808 || keyed
1809 .known_items
1810 .as_ref()
1811 .is_some_and(|m| m.values().any(|(_, t)| union_has_widenable_nested_scalar(t)))
1812 }
1813 TAtomic::Iterable(iterable) => {
1814 union_has_widenable_nested_scalar(&iterable.key_type)
1815 || union_has_widenable_nested_scalar(&iterable.value_type)
1816 || iterable.intersection_types.as_ref().is_some_and(|v| v.iter().any(atomic_has_widenable_scalar))
1817 }
1818 TAtomic::Object(TObject::Named(named)) => {
1819 named.type_parameters.as_ref().is_some_and(|p| p.iter().any(union_has_widenable_nested_scalar))
1820 }
1821 TAtomic::Object(TObject::WithProperties(with_props)) => {
1822 with_props.known_properties.values().any(|(_, t)| union_has_widenable_nested_scalar(t))
1823 }
1824 TAtomic::GenericParameter(generic) => {
1825 union_has_widenable_nested_scalar(&generic.constraint)
1826 || generic.intersection_types.as_ref().is_some_and(|v| v.iter().any(atomic_has_widenable_scalar))
1827 }
1828 TAtomic::Conditional(conditional) => {
1829 union_has_widenable_nested_scalar(&conditional.subject)
1830 || union_has_widenable_nested_scalar(&conditional.target)
1831 || union_has_widenable_nested_scalar(&conditional.then)
1832 || union_has_widenable_nested_scalar(&conditional.otherwise)
1833 }
1834 _ => false,
1835 }
1836}