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