mago_codex/ttype/
union.rs

1use std::borrow::Cow;
2use std::hash::Hash;
3use std::hash::Hasher;
4
5use derivative::Derivative;
6use mago_atom::concat_atom;
7use mago_atom::empty_atom;
8use serde::Deserialize;
9use serde::Serialize;
10
11use mago_atom::Atom;
12
13use crate::metadata::CodebaseMetadata;
14use crate::reference::ReferenceSource;
15use crate::reference::SymbolReferences;
16use crate::symbol::Symbols;
17use crate::ttype::TType;
18use crate::ttype::TypeRef;
19use crate::ttype::atomic::TAtomic;
20use crate::ttype::atomic::array::TArray;
21use crate::ttype::atomic::array::key::ArrayKey;
22use crate::ttype::atomic::generic::TGenericParameter;
23use crate::ttype::atomic::mixed::truthiness::TMixedTruthiness;
24use crate::ttype::atomic::object::TObject;
25use crate::ttype::atomic::object::named::TNamedObject;
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::int::TInteger;
31use crate::ttype::atomic::scalar::string::TString;
32use crate::ttype::atomic::scalar::string::TStringLiteral;
33use crate::ttype::get_arraykey;
34use crate::ttype::get_int;
35use crate::ttype::get_mixed;
36
37#[derive(Debug, Clone, Serialize, Deserialize, Eq, Derivative, PartialOrd, Ord)]
38pub struct TUnion {
39    pub types: Cow<'static, [TAtomic]>,
40    pub had_template: bool,
41    pub by_reference: bool,
42    pub reference_free: bool,
43    pub possibly_undefined_from_try: bool,
44    pub possibly_undefined: bool,
45    pub ignore_nullable_issues: bool,
46    pub ignore_falsable_issues: bool,
47    pub from_template_default: bool,
48    pub populated: bool,
49}
50
51impl Hash for TUnion {
52    fn hash<H: Hasher>(&self, state: &mut H) {
53        for t in self.types.as_ref() {
54            t.hash(state);
55        }
56    }
57}
58
59impl TUnion {
60    /// The primary constructor for creating a TUnion from a Cow.
61    ///
62    /// This is the most basic way to create a TUnion and is used by both the
63    /// zero-allocation static helpers and the `from_vec` constructor.
64    pub fn new(types: Cow<'static, [TAtomic]>) -> TUnion {
65        TUnion {
66            types,
67            had_template: false,
68            by_reference: false,
69            reference_free: false,
70            possibly_undefined_from_try: false,
71            possibly_undefined: false,
72            ignore_nullable_issues: false,
73            ignore_falsable_issues: false,
74            from_template_default: false,
75            populated: false,
76        }
77    }
78
79    /// Creates a TUnion from an owned Vec, performing necessary cleanup.
80    ///
81    /// This preserves the original logic for cleaning up dynamically created unions,
82    /// such as removing redundant `never` types.
83    pub fn from_vec(mut types: Vec<TAtomic>) -> TUnion {
84        if cfg!(debug_assertions) {
85            if types.is_empty() {
86                panic!("TUnion::new() should not be called with an empty Vec.");
87            }
88
89            if types.len() > 1
90                && types.iter().any(|atomic| {
91                    atomic.is_never()
92                        || atomic.map_generic_parameter_constraint(|constraint| constraint.is_never()).unwrap_or(false)
93                })
94            {
95                panic!("TUnion::new() was called with a mix of 'never' and other types: {types:#?}")
96            }
97        } else {
98            // If we have more than one type, 'never' is redundant and can be removed,
99            // as the union `A|never` is simply `A`.
100            if types.len() > 1 {
101                types.retain(|atomic| {
102                    !atomic.is_never()
103                        && !atomic.map_generic_parameter_constraint(|constraint| constraint.is_never()).unwrap_or(false)
104                });
105            }
106
107            // If the vector was originally empty, or contained only 'never' types
108            // which were removed, ensure the final union is `never`.
109            if types.is_empty() {
110                types.push(TAtomic::Never);
111            }
112        }
113
114        Self::new(Cow::Owned(types))
115    }
116
117    /// Creates a TUnion from a single atomic type, which can be either
118    /// borrowed from a static source or owned.
119    ///
120    /// This function is a key optimization point. When passed a `Cow::Borrowed`,
121    /// it creates the TUnion without any heap allocation.
122    pub fn from_single(atomic: Cow<'static, TAtomic>) -> TUnion {
123        let types_cow = match atomic {
124            Cow::Borrowed(borrowed_atomic) => Cow::Borrowed(std::slice::from_ref(borrowed_atomic)),
125            Cow::Owned(owned_atomic) => Cow::Owned(vec![owned_atomic]),
126        };
127
128        TUnion::new(types_cow)
129    }
130
131    /// Creates a TUnion from a single owned atomic type.
132    pub fn from_atomic(atomic: TAtomic) -> TUnion {
133        TUnion::new(Cow::Owned(vec![atomic]))
134    }
135
136    pub fn set_possibly_undefined(&mut self, possibly_undefined: bool, from_try: Option<bool>) {
137        let from_try = from_try.unwrap_or(self.possibly_undefined_from_try);
138
139        self.possibly_undefined = possibly_undefined;
140        self.possibly_undefined_from_try = from_try;
141    }
142
143    /// Creates a new TUnion with the same properties as the original, but with a new set of types.
144    pub fn clone_with_types(&self, types: Vec<TAtomic>) -> TUnion {
145        TUnion {
146            types: Cow::Owned(types),
147            had_template: self.had_template,
148            by_reference: self.by_reference,
149            reference_free: self.reference_free,
150            possibly_undefined_from_try: self.possibly_undefined_from_try,
151            possibly_undefined: self.possibly_undefined,
152            ignore_falsable_issues: self.ignore_falsable_issues,
153            ignore_nullable_issues: self.ignore_nullable_issues,
154            from_template_default: self.from_template_default,
155            populated: self.populated,
156        }
157    }
158
159    pub fn to_non_nullable(&self) -> TUnion {
160        TUnion {
161            types: Cow::Owned(self.get_non_nullable_types()),
162            had_template: self.had_template,
163            by_reference: self.by_reference,
164            reference_free: self.reference_free,
165            possibly_undefined_from_try: self.possibly_undefined_from_try,
166            possibly_undefined: self.possibly_undefined,
167            ignore_falsable_issues: self.ignore_falsable_issues,
168            ignore_nullable_issues: self.ignore_nullable_issues,
169            from_template_default: self.from_template_default,
170            populated: self.populated,
171        }
172    }
173
174    pub fn to_truthy(&self) -> TUnion {
175        TUnion {
176            types: Cow::Owned(self.get_truthy_types()),
177            had_template: self.had_template,
178            by_reference: self.by_reference,
179            reference_free: self.reference_free,
180            possibly_undefined_from_try: self.possibly_undefined_from_try,
181            possibly_undefined: self.possibly_undefined,
182            ignore_falsable_issues: self.ignore_falsable_issues,
183            ignore_nullable_issues: self.ignore_nullable_issues,
184            from_template_default: self.from_template_default,
185            populated: self.populated,
186        }
187    }
188
189    pub fn get_non_nullable_types(&self) -> Vec<TAtomic> {
190        self.types
191            .iter()
192            .filter_map(|t| match t {
193                TAtomic::Null | TAtomic::Void => None,
194                TAtomic::GenericParameter(parameter) => Some(TAtomic::GenericParameter(TGenericParameter {
195                    parameter_name: parameter.parameter_name,
196                    defining_entity: parameter.defining_entity,
197                    intersection_types: parameter.intersection_types.clone(),
198                    constraint: Box::new(parameter.constraint.to_non_nullable()),
199                })),
200                TAtomic::Mixed(mixed) => Some(TAtomic::Mixed(mixed.with_is_non_null(true))),
201                atomic => Some(atomic.clone()),
202            })
203            .collect()
204    }
205
206    pub fn get_truthy_types(&self) -> Vec<TAtomic> {
207        self.types
208            .iter()
209            .filter_map(|t| match t {
210                TAtomic::GenericParameter(parameter) => Some(TAtomic::GenericParameter(TGenericParameter {
211                    parameter_name: parameter.parameter_name,
212                    defining_entity: parameter.defining_entity,
213                    intersection_types: parameter.intersection_types.clone(),
214                    constraint: Box::new(parameter.constraint.to_truthy()),
215                })),
216                TAtomic::Mixed(mixed) => Some(TAtomic::Mixed(mixed.with_truthiness(TMixedTruthiness::Truthy))),
217                atomic => {
218                    if atomic.is_falsy() {
219                        None
220                    } else {
221                        Some(atomic.clone())
222                    }
223                }
224            })
225            .collect()
226    }
227
228    /// Adds `null` to the union type, making it nullable.
229    pub fn as_nullable(mut self) -> TUnion {
230        let types = self.types.to_mut();
231
232        types.iter_mut().for_each(|atomic| {
233            if let TAtomic::Mixed(mixed) = atomic {
234                *mixed = mixed.with_is_non_null(false);
235            }
236        });
237
238        if !types.iter().any(|atomic| atomic.is_null() || atomic.is_mixed()) {
239            types.push(TAtomic::Null);
240        }
241
242        self
243    }
244
245    /// Removes a specific atomic type from the union.
246    pub fn remove_type(&mut self, bad_type: &TAtomic) {
247        self.types.to_mut().retain(|t| t != bad_type);
248    }
249
250    /// Replaces a specific atomic type in the union with a new type.
251    pub fn replace_type(&mut self, remove_type: &TAtomic, add_type: TAtomic) {
252        let types = self.types.to_mut();
253
254        if let Some(index) = types.iter().position(|t| t == remove_type) {
255            types[index] = add_type;
256        } else {
257            types.push(add_type);
258        }
259    }
260
261    pub fn is_int(&self) -> bool {
262        for atomic in self.types.as_ref() {
263            if !atomic.is_int() {
264                return false;
265            }
266        }
267
268        true
269    }
270
271    pub fn has_int_or_float(&self) -> bool {
272        for atomic in self.types.as_ref() {
273            if atomic.is_int_or_float() {
274                return true;
275            }
276        }
277
278        false
279    }
280
281    pub fn has_int_and_float(&self) -> bool {
282        let mut has_int = false;
283        let mut has_float = false;
284
285        for atomic in self.types.as_ref() {
286            if atomic.is_int() {
287                has_int = true;
288            } else if atomic.is_float() {
289                has_float = true;
290            } else if atomic.is_int_or_float() {
291                has_int = true;
292                has_float = true;
293            }
294
295            if has_int && has_float {
296                return true;
297            }
298        }
299
300        false
301    }
302
303    pub fn has_int_and_string(&self) -> bool {
304        let mut has_int = false;
305        let mut has_string = false;
306
307        for atomic in self.types.as_ref() {
308            if atomic.is_int() {
309                has_int = true;
310            } else if atomic.is_string() {
311                has_string = true;
312            } else if atomic.is_array_key() {
313                has_int = true;
314                has_string = true;
315            }
316
317            if has_int && has_string {
318                return true;
319            }
320        }
321
322        false
323    }
324
325    pub fn has_int(&self) -> bool {
326        for atomic in self.types.as_ref() {
327            if atomic.is_int() || atomic.is_array_key() || atomic.is_numeric() {
328                return true;
329            }
330        }
331
332        false
333    }
334
335    pub fn has_float(&self) -> bool {
336        for atomic in self.types.as_ref() {
337            if atomic.is_float() {
338                return true;
339            }
340        }
341
342        false
343    }
344
345    pub fn is_array_key(&self) -> bool {
346        for atomic in self.types.as_ref() {
347            if atomic.is_array_key() {
348                continue;
349            }
350
351            return false;
352        }
353
354        true
355    }
356
357    pub fn is_any_string(&self) -> bool {
358        for atomic in self.types.as_ref() {
359            if !atomic.is_any_string() {
360                return false;
361            }
362        }
363
364        true
365    }
366
367    pub fn is_string(&self) -> bool {
368        self.types.iter().all(|t| t.is_string()) && !self.types.is_empty()
369    }
370
371    pub fn is_always_array_key(&self, ignore_never: bool) -> bool {
372        self.types.iter().all(|atomic| match atomic {
373            TAtomic::Never => ignore_never,
374            TAtomic::Scalar(scalar) => matches!(
375                scalar,
376                TScalar::ArrayKey | TScalar::Integer(_) | TScalar::String(_) | TScalar::ClassLikeString(_)
377            ),
378            TAtomic::GenericParameter(generic_parameter) => {
379                generic_parameter.constraint.is_always_array_key(ignore_never)
380            }
381            _ => false,
382        })
383    }
384
385    pub fn is_non_empty_string(&self) -> bool {
386        self.types.iter().all(|t| t.is_non_empty_string()) && !self.types.is_empty()
387    }
388
389    pub fn is_empty_array(&self) -> bool {
390        self.types.iter().all(|t| t.is_empty_array()) && !self.types.is_empty()
391    }
392
393    pub fn has_string(&self) -> bool {
394        self.types.iter().any(|t| t.is_string()) && !self.types.is_empty()
395    }
396
397    pub fn is_float(&self) -> bool {
398        self.types.iter().all(|t| t.is_float()) && !self.types.is_empty()
399    }
400
401    pub fn is_bool(&self) -> bool {
402        self.types.iter().all(|t| t.is_bool()) && !self.types.is_empty()
403    }
404
405    pub fn is_never(&self) -> bool {
406        self.types.iter().all(|t| t.is_never()) || self.types.is_empty()
407    }
408
409    pub fn is_never_template(&self) -> bool {
410        self.types.iter().all(|t| t.is_templated_as_never()) && !self.types.is_empty()
411    }
412
413    pub fn is_placeholder(&self) -> bool {
414        self.types.iter().all(|t| matches!(t, TAtomic::Placeholder)) && !self.types.is_empty()
415    }
416
417    pub fn is_true(&self) -> bool {
418        self.types.iter().all(|t| t.is_true()) && !self.types.is_empty()
419    }
420
421    pub fn is_false(&self) -> bool {
422        self.types.iter().all(|t| t.is_false()) && !self.types.is_empty()
423    }
424
425    pub fn is_nonnull(&self) -> bool {
426        self.types.len() == 1 && matches!(self.types[0], TAtomic::Mixed(mixed) if mixed.is_non_null())
427    }
428
429    pub fn is_numeric(&self) -> bool {
430        self.types.iter().all(|t| t.is_numeric()) && !self.types.is_empty()
431    }
432
433    pub fn is_int_or_float(&self) -> bool {
434        self.types.iter().all(|t| t.is_int_or_float()) && !self.types.is_empty()
435    }
436
437    pub fn is_mixed(&self) -> bool {
438        self.types.iter().all(|t| matches!(t, TAtomic::Mixed(_))) && !self.types.is_empty()
439    }
440
441    pub fn is_mixed_template(&self) -> bool {
442        self.types.iter().all(|t| t.is_templated_as_mixed()) && !self.types.is_empty()
443    }
444
445    pub fn has_mixed(&self) -> bool {
446        self.types.iter().any(|t| matches!(t, TAtomic::Mixed(_))) && !self.types.is_empty()
447    }
448
449    pub fn has_mixed_template(&self) -> bool {
450        self.types.iter().any(|t| t.is_templated_as_mixed()) && !self.types.is_empty()
451    }
452
453    pub fn has_nullable_mixed(&self) -> bool {
454        self.types.iter().any(|t| matches!(t, TAtomic::Mixed(mixed) if !mixed.is_non_null())) && !self.types.is_empty()
455    }
456
457    pub fn has_void(&self) -> bool {
458        self.types.iter().any(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
459    }
460
461    pub fn has_null(&self) -> bool {
462        self.types.iter().any(|t| matches!(t, TAtomic::Null)) && !self.types.is_empty()
463    }
464
465    pub fn has_nullish(&self) -> bool {
466        self.types.iter().any(|t| match t {
467            TAtomic::Null | TAtomic::Void => true,
468            TAtomic::Mixed(mixed) => !mixed.is_non_null(),
469            TAtomic::GenericParameter(parameter) => parameter.constraint.has_nullish(),
470            _ => false,
471        }) && !self.types.is_empty()
472    }
473
474    pub fn is_nullable_mixed(&self) -> bool {
475        if self.types.len() != 1 {
476            return false;
477        }
478
479        match &self.types[0] {
480            TAtomic::Mixed(mixed) => !mixed.is_non_null(),
481            _ => false,
482        }
483    }
484
485    pub fn is_falsy_mixed(&self) -> bool {
486        if self.types.len() != 1 {
487            return false;
488        }
489
490        matches!(&self.types[0], &TAtomic::Mixed(mixed) if mixed.is_falsy())
491    }
492
493    pub fn is_vanilla_mixed(&self) -> bool {
494        if self.types.len() != 1 {
495            return false;
496        }
497
498        matches!(&self.types[0], TAtomic::Mixed(mixed) if mixed.is_vanilla())
499    }
500
501    pub fn has_template_or_static(&self) -> bool {
502        for atomic in self.types.as_ref() {
503            if let TAtomic::GenericParameter(_) = atomic {
504                return true;
505            }
506
507            if let TAtomic::Object(TObject::Named(named_object)) = atomic {
508                if named_object.is_this() {
509                    return true;
510                }
511
512                if let Some(intersections) = named_object.get_intersection_types() {
513                    for intersection in intersections {
514                        if let TAtomic::GenericParameter(_) = intersection {
515                            return true;
516                        }
517                    }
518                }
519            }
520        }
521
522        false
523    }
524
525    pub fn has_template(&self) -> bool {
526        for atomic in self.types.as_ref() {
527            if let TAtomic::GenericParameter(_) = atomic {
528                return true;
529            }
530
531            if let Some(intersections) = atomic.get_intersection_types() {
532                for intersection in intersections {
533                    if let TAtomic::GenericParameter(_) = intersection {
534                        return true;
535                    }
536                }
537            }
538        }
539
540        false
541    }
542
543    pub fn has_template_types(&self) -> bool {
544        let all_child_nodes = self.get_all_child_nodes();
545
546        for child_node in all_child_nodes {
547            if let TypeRef::Atomic(
548                TAtomic::GenericParameter(_)
549                | TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic { .. })),
550            ) = child_node
551            {
552                return true;
553            }
554        }
555
556        false
557    }
558
559    pub fn get_template_types(&self) -> Vec<&TAtomic> {
560        let all_child_nodes = self.get_all_child_nodes();
561
562        let mut template_types = Vec::new();
563
564        for child_node in all_child_nodes {
565            if let TypeRef::Atomic(inner) = child_node {
566                match inner {
567                    TAtomic::GenericParameter(_) => {
568                        template_types.push(inner);
569                    }
570                    TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic { .. })) => {
571                        template_types.push(inner);
572                    }
573                    _ => {}
574                }
575            }
576        }
577
578        template_types
579    }
580
581    pub fn is_objecty(&self) -> bool {
582        for atomic in self.types.as_ref() {
583            if let &TAtomic::Object(_) = atomic {
584                continue;
585            }
586
587            if let TAtomic::Callable(callable) = atomic
588                && callable.get_signature().is_none_or(|signature| signature.is_closure())
589            {
590                continue;
591            }
592
593            return false;
594        }
595
596        true
597    }
598
599    pub fn is_generator(&self) -> bool {
600        for atomic in self.types.as_ref() {
601            if atomic.is_generator() {
602                continue;
603            }
604
605            return false;
606        }
607
608        true
609    }
610
611    pub fn extends_or_implements(&self, codebase: &CodebaseMetadata, interface: &str) -> bool {
612        for atomic in self.types.as_ref() {
613            if !atomic.extends_or_implements(codebase, interface) {
614                return false;
615            }
616        }
617
618        true
619    }
620
621    pub fn is_generic_parameter(&self) -> bool {
622        self.types.len() == 1 && matches!(self.types[0], TAtomic::GenericParameter(_))
623    }
624
625    pub fn get_generic_parameter_constraint(&self) -> Option<&TUnion> {
626        if self.is_generic_parameter()
627            && let TAtomic::GenericParameter(parameter) = &self.types[0]
628        {
629            return Some(&parameter.constraint);
630        }
631
632        None
633    }
634
635    pub fn is_null(&self) -> bool {
636        self.types.iter().all(|t| matches!(t, TAtomic::Null)) && !self.types.is_empty()
637    }
638
639    pub fn is_nullable(&self) -> bool {
640        self.types.iter().any(|t| match t {
641            TAtomic::Null => self.types.len() >= 2,
642            _ => false,
643        })
644    }
645
646    pub fn is_void(&self) -> bool {
647        self.types.iter().all(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
648    }
649
650    pub fn is_voidable(&self) -> bool {
651        self.types.iter().any(|t| matches!(t, TAtomic::Void)) && !self.types.is_empty()
652    }
653
654    pub fn has_resource(&self) -> bool {
655        self.types.iter().any(|t| t.is_resource())
656    }
657
658    pub fn is_resource(&self) -> bool {
659        self.types.iter().all(|t| t.is_resource()) && !self.types.is_empty()
660    }
661
662    pub fn is_array(&self) -> bool {
663        self.types.iter().all(|t| t.is_array()) && !self.types.is_empty()
664    }
665
666    pub fn is_list(&self) -> bool {
667        self.types.iter().all(|t| t.is_list()) && !self.types.is_empty()
668    }
669
670    pub fn is_keyed_array(&self) -> bool {
671        self.types.iter().all(|t| t.is_keyed_array()) && !self.types.is_empty()
672    }
673
674    pub fn is_falsable(&self) -> bool {
675        self.types.len() >= 2 && self.types.iter().any(|t| t.is_false())
676    }
677
678    pub fn has_bool(&self) -> bool {
679        self.types.iter().any(|t| t.is_bool() || t.is_generic_scalar()) && !self.types.is_empty()
680    }
681
682    /// Checks if the union explicitly contains the generic `scalar` type.
683    ///
684    /// This is a specific check for the `scalar` type itself, not for a
685    /// combination of types that would form a scalar (e.g., `int|string|bool|float`).
686    /// For that, see `has_scalar_combination`.
687    pub fn has_scalar(&self) -> bool {
688        self.types.iter().any(|atomic| atomic.is_generic_scalar())
689    }
690
691    /// Checks if the union contains a combination of types that is equivalent
692    /// to the generic `scalar` type (i.e., contains `int`, `float`, `bool`, and `string`).
693    pub fn has_scalar_combination(&self) -> bool {
694        const HAS_INT: u8 = 1 << 0;
695        const HAS_FLOAT: u8 = 1 << 1;
696        const HAS_BOOL: u8 = 1 << 2;
697        const HAS_STRING: u8 = 1 << 3;
698        const ALL_SCALARS: u8 = HAS_INT | HAS_FLOAT | HAS_BOOL | HAS_STRING;
699
700        let mut flags = 0u8;
701
702        for atomic in self.types.as_ref() {
703            if atomic.is_int() {
704                flags |= HAS_INT;
705            } else if atomic.is_float() {
706                flags |= HAS_FLOAT;
707            } else if atomic.is_bool() {
708                flags |= HAS_BOOL;
709            } else if atomic.is_string() {
710                flags |= HAS_STRING;
711            } else if atomic.is_array_key() {
712                flags |= HAS_INT | HAS_STRING;
713            } else if atomic.is_numeric() {
714                // We don't add `string` as `numeric-string` does not contain `string` type
715                flags |= HAS_INT | HAS_FLOAT;
716            } else if atomic.is_generic_scalar() {
717                return true;
718            }
719
720            // Early exit if we've already found all scalar types
721            if flags == ALL_SCALARS {
722                return true;
723            }
724        }
725
726        flags == ALL_SCALARS
727    }
728    pub fn has_array_key(&self) -> bool {
729        self.types.iter().any(|atomic| atomic.is_array_key())
730    }
731
732    pub fn has_iterable(&self) -> bool {
733        self.types.iter().any(|atomic| atomic.is_iterable()) && !self.types.is_empty()
734    }
735
736    pub fn has_array(&self) -> bool {
737        self.types.iter().any(|atomic| atomic.is_array()) && !self.types.is_empty()
738    }
739
740    pub fn has_traversable(&self, codebase: &CodebaseMetadata) -> bool {
741        self.types.iter().any(|atomic| atomic.is_traversable(codebase)) && !self.types.is_empty()
742    }
743
744    pub fn has_array_key_like(&self) -> bool {
745        self.types.iter().any(|atomic| atomic.is_array_key() || atomic.is_int() || atomic.is_string())
746    }
747
748    pub fn has_numeric(&self) -> bool {
749        self.types.iter().any(|atomic| atomic.is_numeric()) && !self.types.is_empty()
750    }
751
752    pub fn is_always_truthy(&self) -> bool {
753        self.types.iter().all(|atomic| atomic.is_truthy()) && !self.types.is_empty()
754    }
755
756    pub fn is_always_falsy(&self) -> bool {
757        self.types.iter().all(|atomic| atomic.is_falsy()) && !self.types.is_empty()
758    }
759
760    pub fn is_literal_of(&self, other: &TUnion) -> bool {
761        let Some(other_atomic_type) = other.types.first() else {
762            return false;
763        };
764
765        match other_atomic_type {
766            TAtomic::Scalar(TScalar::String(_)) => {
767                for self_atomic_type in self.types.as_ref() {
768                    if self_atomic_type.is_string_of_literal_origin() {
769                        continue;
770                    }
771
772                    return false;
773                }
774
775                true
776            }
777            TAtomic::Scalar(TScalar::Integer(_)) => {
778                for self_atomic_type in self.types.as_ref() {
779                    if self_atomic_type.is_literal_int() {
780                        continue;
781                    }
782
783                    return false;
784                }
785
786                true
787            }
788            TAtomic::Scalar(TScalar::Float(_)) => {
789                for self_atomic_type in self.types.as_ref() {
790                    if self_atomic_type.is_literal_float() {
791                        continue;
792                    }
793
794                    return false;
795                }
796
797                true
798            }
799            _ => false,
800        }
801    }
802
803    pub fn all_literals(&self) -> bool {
804        self.types
805            .iter()
806            .all(|atomic| atomic.is_string_of_literal_origin() || atomic.is_literal_int() || atomic.is_literal_float())
807    }
808
809    pub fn has_static_object(&self) -> bool {
810        self.types
811            .iter()
812            .any(|atomic| matches!(atomic, TAtomic::Object(TObject::Named(named_object)) if named_object.is_this()))
813    }
814
815    pub fn is_static_object(&self) -> bool {
816        self.types
817            .iter()
818            .all(|atomic| matches!(atomic, TAtomic::Object(TObject::Named(named_object)) if named_object.is_this()))
819    }
820
821    #[inline]
822    pub fn is_single(&self) -> bool {
823        self.types.len() == 1
824    }
825
826    #[inline]
827    pub fn get_single_string(&self) -> Option<&TString> {
828        if self.is_single()
829            && let TAtomic::Scalar(TScalar::String(string)) = &self.types[0]
830        {
831            Some(string)
832        } else {
833            None
834        }
835    }
836
837    #[inline]
838    pub fn get_single_array(&self) -> Option<&TArray> {
839        if self.is_single()
840            && let TAtomic::Array(array) = &self.types[0]
841        {
842            Some(array)
843        } else {
844            None
845        }
846    }
847
848    #[inline]
849    pub fn get_single_bool(&self) -> Option<&TBool> {
850        if self.is_single()
851            && let TAtomic::Scalar(TScalar::Bool(bool)) = &self.types[0]
852        {
853            Some(bool)
854        } else {
855            None
856        }
857    }
858
859    #[inline]
860    pub fn get_single_named_object(&self) -> Option<&TNamedObject> {
861        if self.is_single()
862            && let TAtomic::Object(TObject::Named(named_object)) = &self.types[0]
863        {
864            Some(named_object)
865        } else {
866            None
867        }
868    }
869
870    #[inline]
871    pub fn get_single(&self) -> &TAtomic {
872        &self.types[0]
873    }
874
875    #[inline]
876    pub fn get_single_owned(self) -> TAtomic {
877        self.types[0].to_owned()
878    }
879
880    #[inline]
881    pub fn is_named_object(&self) -> bool {
882        self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Named(_))))
883    }
884
885    pub fn is_enum(&self) -> bool {
886        self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(_))))
887    }
888
889    pub fn is_enum_case(&self) -> bool {
890        self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(r#enum)) if r#enum.case.is_some()))
891    }
892
893    pub fn is_single_enum_case(&self) -> bool {
894        self.is_single()
895            && self.types.iter().all(|t| matches!(t, TAtomic::Object(TObject::Enum(r#enum)) if r#enum.case.is_some()))
896    }
897
898    #[inline]
899    pub fn has_named_object(&self) -> bool {
900        self.types.iter().any(|t| matches!(t, TAtomic::Object(TObject::Named(_))))
901    }
902
903    #[inline]
904    pub fn has_object(&self) -> bool {
905        self.types.iter().any(|t| matches!(t, TAtomic::Object(TObject::Any)))
906    }
907
908    #[inline]
909    pub fn has_callable(&self) -> bool {
910        self.types.iter().any(|t| matches!(t, TAtomic::Callable(_)))
911    }
912
913    #[inline]
914    pub fn is_callable(&self) -> bool {
915        self.types.iter().all(|t| matches!(t, TAtomic::Callable(_)))
916    }
917
918    #[inline]
919    pub fn has_object_type(&self) -> bool {
920        self.types.iter().any(|t| matches!(t, TAtomic::Object(_)))
921    }
922
923    /// Return a vector of pairs containing the enum name, and their case name
924    /// if specified.
925    pub fn get_enum_cases(&self) -> Vec<(Atom, Option<Atom>)> {
926        self.types
927            .iter()
928            .filter_map(|t| match t {
929                TAtomic::Object(TObject::Enum(enum_object)) => Some((enum_object.name, enum_object.case)),
930                _ => None,
931            })
932            .collect()
933    }
934
935    pub fn get_single_int(&self) -> Option<TInteger> {
936        if self.is_single() { self.get_single().get_integer() } else { None }
937    }
938
939    pub fn get_single_literal_int_value(&self) -> Option<i64> {
940        if self.is_single() { self.get_single().get_literal_int_value() } else { None }
941    }
942
943    pub fn get_single_maximum_int_value(&self) -> Option<i64> {
944        if self.is_single() { self.get_single().get_maximum_int_value() } else { None }
945    }
946
947    pub fn get_single_minimum_int_value(&self) -> Option<i64> {
948        if self.is_single() { self.get_single().get_minimum_int_value() } else { None }
949    }
950
951    pub fn get_single_literal_float_value(&self) -> Option<f64> {
952        if self.is_single() { self.get_single().get_literal_float_value() } else { None }
953    }
954
955    pub fn get_single_literal_string_value(&self) -> Option<&str> {
956        if self.is_single() { self.get_single().get_literal_string_value() } else { None }
957    }
958
959    pub fn get_single_class_string_value(&self) -> Option<Atom> {
960        if self.is_single() { self.get_single().get_class_string_value() } else { None }
961    }
962
963    pub fn get_single_array_key(&self) -> Option<ArrayKey> {
964        if self.is_single() { self.get_single().to_array_key() } else { None }
965    }
966
967    pub fn get_single_key_of_array_like(&self) -> Option<TUnion> {
968        if !self.is_single() {
969            return None;
970        }
971
972        match self.get_single() {
973            TAtomic::Array(array) => match array {
974                TArray::List(_) => Some(get_int()),
975                TArray::Keyed(keyed_array) => match &keyed_array.parameters {
976                    Some((k, _)) => Some(*k.clone()),
977                    None => Some(get_arraykey()),
978                },
979            },
980            _ => None,
981        }
982    }
983
984    pub fn get_single_value_of_array_like(&self) -> Option<Cow<'_, TUnion>> {
985        if !self.is_single() {
986            return None;
987        }
988
989        match self.get_single() {
990            TAtomic::Array(array) => match array {
991                TArray::List(list) => Some(Cow::Borrowed(&list.element_type)),
992                TArray::Keyed(keyed_array) => match &keyed_array.parameters {
993                    Some((_, v)) => Some(Cow::Borrowed(v)),
994                    None => Some(Cow::Owned(get_mixed())),
995                },
996            },
997            _ => None,
998        }
999    }
1000
1001    pub fn get_literal_ints(&self) -> Vec<&TAtomic> {
1002        self.types.iter().filter(|a| a.is_literal_int()).collect()
1003    }
1004
1005    pub fn get_literal_strings(&self) -> Vec<&TAtomic> {
1006        self.types.iter().filter(|a| a.is_known_literal_string()).collect()
1007    }
1008
1009    pub fn get_literal_string_values(&self) -> Vec<Option<Atom>> {
1010        self.get_literal_strings()
1011            .into_iter()
1012            .map(|atom| match atom {
1013                TAtomic::Scalar(TScalar::String(TString { literal: Some(TStringLiteral::Value(value)), .. })) => {
1014                    Some(*value)
1015                }
1016                _ => None,
1017            })
1018            .collect()
1019    }
1020
1021    pub fn has_literal_float(&self) -> bool {
1022        self.types.iter().any(|atomic| match atomic {
1023            TAtomic::Scalar(scalar) => scalar.is_literal_float(),
1024            _ => false,
1025        })
1026    }
1027
1028    pub fn has_literal_int(&self) -> bool {
1029        self.types.iter().any(|atomic| match atomic {
1030            TAtomic::Scalar(scalar) => scalar.is_literal_int(),
1031            _ => false,
1032        })
1033    }
1034
1035    pub fn has_literal_string(&self) -> bool {
1036        self.types.iter().any(|atomic| match atomic {
1037            TAtomic::Scalar(scalar) => scalar.is_known_literal_string(),
1038            _ => false,
1039        })
1040    }
1041
1042    pub fn has_literal_value(&self) -> bool {
1043        self.types.iter().any(|atomic| match atomic {
1044            TAtomic::Scalar(scalar) => scalar.is_literal_value(),
1045            _ => false,
1046        })
1047    }
1048
1049    pub fn accepts_false(&self) -> bool {
1050        self.types.iter().any(|t| match t {
1051            TAtomic::GenericParameter(parameter) => parameter.constraint.accepts_false(),
1052            TAtomic::Mixed(mixed) if !mixed.is_truthy() => true,
1053            TAtomic::Scalar(TScalar::Generic | TScalar::Bool(TBool { value: None | Some(false) })) => true,
1054            _ => false,
1055        })
1056    }
1057
1058    pub fn accepts_null(&self) -> bool {
1059        self.types.iter().any(|t| match t {
1060            TAtomic::GenericParameter(generic_parameter) => generic_parameter.constraint.accepts_null(),
1061            TAtomic::Mixed(mixed) if !mixed.is_non_null() => true,
1062            TAtomic::Null => true,
1063            _ => false,
1064        })
1065    }
1066}
1067
1068impl TType for TUnion {
1069    fn get_child_nodes<'a>(&'a self) -> Vec<TypeRef<'a>> {
1070        self.types.iter().map(TypeRef::Atomic).collect()
1071    }
1072
1073    fn needs_population(&self) -> bool {
1074        !self.populated && self.types.iter().any(|v| v.needs_population())
1075    }
1076
1077    fn is_expandable(&self) -> bool {
1078        if self.types.is_empty() {
1079            return true;
1080        }
1081
1082        self.types.iter().any(|t| t.is_expandable())
1083    }
1084
1085    fn get_id(&self) -> Atom {
1086        let len = self.types.len();
1087
1088        let mut atomic_ids: Vec<Atom> = self
1089            .types
1090            .as_ref()
1091            .iter()
1092            .map(|atomic| {
1093                let id = atomic.get_id();
1094                if atomic.has_intersection_types() && len > 1 { concat_atom!("(", id.as_str(), ")") } else { id }
1095            })
1096            .collect();
1097
1098        if len <= 1 {
1099            return atomic_ids.pop().unwrap_or_else(empty_atom);
1100        }
1101
1102        atomic_ids.sort_unstable();
1103        let mut result = atomic_ids[0];
1104        for id in &atomic_ids[1..] {
1105            result = concat_atom!(result.as_str(), "|", id.as_str());
1106        }
1107
1108        result
1109    }
1110}
1111
1112impl PartialEq for TUnion {
1113    fn eq(&self, other: &TUnion) -> bool {
1114        if self.reference_free != other.reference_free
1115            || self.by_reference != other.by_reference
1116            || self.had_template != other.had_template
1117            || self.possibly_undefined_from_try != other.possibly_undefined_from_try
1118            || self.possibly_undefined != other.possibly_undefined
1119            || self.ignore_falsable_issues != other.ignore_falsable_issues
1120            || self.ignore_nullable_issues != other.ignore_nullable_issues
1121            || self.from_template_default != other.from_template_default
1122        {
1123            return false;
1124        }
1125
1126        let len = self.types.len();
1127        if len != other.types.len() {
1128            return false;
1129        }
1130
1131        for i in 0..len {
1132            let mut has_match = false;
1133            for j in 0..len {
1134                if self.types[i] == other.types[j] {
1135                    has_match = true;
1136                    break;
1137                }
1138            }
1139
1140            if !has_match {
1141                return false;
1142            }
1143        }
1144
1145        true
1146    }
1147}
1148
1149pub fn populate_union_type(
1150    unpopulated_union: &mut TUnion,
1151    codebase_symbols: &Symbols,
1152    reference_source: Option<&ReferenceSource>,
1153    symbol_references: &mut SymbolReferences,
1154    force: bool,
1155) {
1156    if unpopulated_union.populated && !force {
1157        return;
1158    }
1159
1160    if !unpopulated_union.needs_population() {
1161        return;
1162    }
1163
1164    unpopulated_union.populated = true;
1165    let unpopulated_atomics = unpopulated_union.types.to_mut();
1166    for unpopulated_atomic in unpopulated_atomics {
1167        match unpopulated_atomic {
1168            TAtomic::Scalar(TScalar::ClassLikeString(
1169                TClassLikeString::Generic { constraint, .. } | TClassLikeString::OfType { constraint, .. },
1170            )) => {
1171                let mut new_constraint = (**constraint).clone();
1172
1173                populate_atomic_type(&mut new_constraint, codebase_symbols, reference_source, symbol_references, force);
1174
1175                *constraint = Box::new(new_constraint);
1176            }
1177            _ => {
1178                populate_atomic_type(unpopulated_atomic, codebase_symbols, reference_source, symbol_references, force);
1179            }
1180        }
1181    }
1182}