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