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