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