Skip to main content

mago_codex/ttype/atomic/
mod.rs

1use std::sync::Arc;
2
3use mago_word::Word;
4use mago_word::ascii_lowercase_word;
5use mago_word::concat_word;
6use mago_word::word;
7
8use crate::metadata::CodebaseMetadata;
9use crate::reference::ReferenceSource;
10use crate::reference::SymbolReferences;
11use crate::symbol::SymbolKind;
12use crate::symbol::Symbols;
13use crate::ttype::TType;
14use crate::ttype::TypeRef;
15use crate::ttype::atomic::alias::TAlias;
16use crate::ttype::atomic::array::TArray;
17use crate::ttype::atomic::array::key::ArrayKey;
18use crate::ttype::atomic::callable::TCallable;
19use crate::ttype::atomic::conditional::TConditional;
20use crate::ttype::atomic::derived::TDerived;
21use crate::ttype::atomic::generic::TGenericParameter;
22use crate::ttype::atomic::iterable::TIterable;
23use crate::ttype::atomic::mixed::TMixed;
24use crate::ttype::atomic::object::TObject;
25use crate::ttype::atomic::object::r#enum::TEnum;
26use crate::ttype::atomic::object::named::TNamedObject;
27use crate::ttype::atomic::reference::TReference;
28use crate::ttype::atomic::reference::TReferenceMemberSelector;
29use crate::ttype::atomic::resource::TResource;
30use crate::ttype::atomic::scalar::TScalar;
31use crate::ttype::atomic::scalar::class_like_string::TClassLikeString;
32use crate::ttype::atomic::scalar::int::TInteger;
33use crate::ttype::atomic::scalar::string::TString;
34use crate::ttype::atomic::scalar::string::TStringLiteral;
35use crate::ttype::get_arraykey;
36use crate::ttype::get_mixed;
37use crate::ttype::union::TUnion;
38use crate::ttype::union::populate_union_type;
39
40pub mod alias;
41pub mod array;
42pub mod callable;
43pub mod conditional;
44pub mod derived;
45pub mod generic;
46pub mod iterable;
47pub mod mixed;
48pub mod object;
49pub mod reference;
50pub mod resource;
51pub mod scalar;
52
53#[allow(clippy::derived_hash_with_manual_eq)]
54#[derive(Debug, Clone, Eq, Hash, PartialOrd, Ord)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56pub enum TAtomic {
57    Scalar(TScalar),
58    Callable(TCallable),
59    Mixed(TMixed),
60    Object(TObject),
61    Array(TArray),
62    Iterable(TIterable),
63    Resource(TResource),
64    Reference(TReference),
65    GenericParameter(TGenericParameter),
66    Variable(Word),
67    Conditional(TConditional),
68    Derived(TDerived),
69    Alias(TAlias),
70    Never,
71    Null,
72    Void,
73    Placeholder,
74}
75
76impl PartialEq for TAtomic {
77    #[inline]
78    fn eq(&self, other: &Self) -> bool {
79        if std::ptr::eq(self, other) {
80            return true;
81        }
82
83        match (self, other) {
84            (TAtomic::Scalar(a), TAtomic::Scalar(b)) => a == b,
85            (TAtomic::Callable(a), TAtomic::Callable(b)) => a == b,
86            (TAtomic::Mixed(a), TAtomic::Mixed(b)) => a == b,
87            (TAtomic::Object(a), TAtomic::Object(b)) => a == b,
88            (TAtomic::Array(a), TAtomic::Array(b)) => a == b,
89            (TAtomic::Iterable(a), TAtomic::Iterable(b)) => a == b,
90            (TAtomic::Resource(a), TAtomic::Resource(b)) => a == b,
91            (TAtomic::Reference(a), TAtomic::Reference(b)) => a == b,
92            (TAtomic::GenericParameter(a), TAtomic::GenericParameter(b)) => a == b,
93            (TAtomic::Variable(a), TAtomic::Variable(b)) => a == b,
94            (TAtomic::Conditional(a), TAtomic::Conditional(b)) => a == b,
95            (TAtomic::Derived(a), TAtomic::Derived(b)) => a == b,
96            (TAtomic::Alias(a), TAtomic::Alias(b)) => a == b,
97            (TAtomic::Never, TAtomic::Never)
98            | (TAtomic::Null, TAtomic::Null)
99            | (TAtomic::Void, TAtomic::Void)
100            | (TAtomic::Placeholder, TAtomic::Placeholder) => true,
101            _ => false,
102        }
103    }
104}
105
106impl TAtomic {
107    /// Returns true if this atomic is a Placeholder or contains Placeholder in type parameters.
108    #[must_use]
109    pub fn contains_placeholder(&self) -> bool {
110        match self {
111            TAtomic::Placeholder => true,
112            TAtomic::Object(TObject::Named(named)) => {
113                named.get_type_parameters().is_some_and(|params| params.iter().any(|p| p.contains_placeholder()))
114            }
115            TAtomic::Array(array) => array.contains_placeholder(),
116            _ => false,
117        }
118    }
119
120    #[must_use]
121    pub fn is_numeric(&self) -> bool {
122        match self {
123            TAtomic::Scalar(scalar) => scalar.is_numeric(),
124            TAtomic::GenericParameter(parameter) => parameter.constraint.is_numeric(),
125            _ => false,
126        }
127    }
128
129    #[must_use]
130    pub fn is_int_or_float(&self) -> bool {
131        match self {
132            TAtomic::Scalar(scalar) => scalar.is_int_or_float(),
133            TAtomic::GenericParameter(parameter) => parameter.constraint.is_int_or_float(),
134            _ => false,
135        }
136    }
137
138    /// Returns `Some(true)` if this type is effectively an int, `Some(false)` if effectively a float,
139    /// or `None` if neither. Considers generic parameter constraints (e.g., `T of int` is treated as int).
140    #[must_use]
141    pub fn effective_int_or_float(&self) -> Option<bool> {
142        match self {
143            TAtomic::Scalar(TScalar::Integer(_)) => Some(true),
144            TAtomic::Scalar(TScalar::Float(_)) => Some(false),
145            TAtomic::GenericParameter(parameter) => parameter.constraint.effective_int_or_float(),
146            _ => None,
147        }
148    }
149
150    #[must_use]
151    pub const fn is_mixed(&self) -> bool {
152        matches!(self, TAtomic::Mixed(_))
153    }
154
155    #[must_use]
156    pub const fn is_vanilla_mixed(&self) -> bool {
157        matches!(self, TAtomic::Mixed(_))
158    }
159
160    #[must_use]
161    pub const fn is_mixed_isset_from_loop(&self) -> bool {
162        matches!(self, TAtomic::Mixed(mixed) if mixed.is_isset_from_loop())
163    }
164
165    #[must_use]
166    pub const fn is_never(&self) -> bool {
167        matches!(self, TAtomic::Never)
168    }
169
170    #[must_use]
171    pub fn is_templated_as_never(&self) -> bool {
172        matches!(self, TAtomic::GenericParameter(parameter) if parameter.constraint.is_never())
173    }
174
175    #[must_use]
176    pub fn is_templated_as_mixed(&self) -> bool {
177        matches!(self, TAtomic::GenericParameter(parameter) if parameter.is_constrained_as_mixed())
178    }
179
180    #[must_use]
181    pub fn is_templated_as_vanilla_mixed(&self) -> bool {
182        matches!(self, TAtomic::GenericParameter(parameter) if parameter.is_constrained_as_vanilla_mixed())
183    }
184
185    pub fn map_generic_parameter_constraint<F, T>(&self, f: F) -> Option<T>
186    where
187        F: FnOnce(&TUnion) -> T,
188    {
189        if let TAtomic::GenericParameter(parameter) = self { Some(f(parameter.constraint.as_ref())) } else { None }
190    }
191
192    #[must_use]
193    pub fn is_enum(&self) -> bool {
194        matches!(self, TAtomic::Object(TObject::Enum(TEnum { .. })))
195    }
196
197    #[must_use]
198    pub fn is_object_type(&self) -> bool {
199        match self {
200            TAtomic::Object(_) => true,
201            TAtomic::Callable(callable) => callable.is_closure(),
202            TAtomic::GenericParameter(parameter) => parameter.is_constrained_as_objecty(),
203            _ => false,
204        }
205    }
206
207    #[must_use]
208    pub fn is_static(&self) -> bool {
209        matches!(self, TAtomic::Object(TObject::Named(named_object)) if named_object.is_static)
210    }
211
212    #[must_use]
213    pub fn is_this(&self) -> bool {
214        matches!(self, TAtomic::Object(TObject::Named(named_object)) if named_object.is_this())
215    }
216
217    #[must_use]
218    pub fn get_object_or_enum_name(&self) -> Option<Word> {
219        match self {
220            TAtomic::Object(object) => match object {
221                TObject::Named(named_object) => Some(named_object.get_name()),
222                TObject::Enum(r#enum) => Some(r#enum.get_name()),
223                _ => None,
224            },
225            _ => None,
226        }
227    }
228
229    #[must_use]
230    pub fn get_all_object_names(&self) -> Vec<Word> {
231        let mut object_names = vec![];
232
233        if let TAtomic::Object(object) = self {
234            match object {
235                TObject::Named(named_object) => object_names.push(named_object.get_name()),
236                TObject::Enum(r#enum) => object_names.push(r#enum.get_name()),
237                _ => {}
238            }
239        }
240
241        for intersection_type in self.get_intersection_types().unwrap_or_default() {
242            object_names.extend(intersection_type.get_all_object_names());
243        }
244
245        object_names
246    }
247
248    #[must_use]
249    pub fn is_generator(&self) -> bool {
250        matches!(&self, TAtomic::Object(object) if {
251            object.get_name().is_some_and(|name| name.as_bytes().eq_ignore_ascii_case(b"Generator"))
252        })
253    }
254
255    #[must_use]
256    pub fn get_generator_parameters(&self) -> Option<(TUnion, TUnion, TUnion, TUnion)> {
257        let generator_parameters = 'parameters: {
258            let TAtomic::Object(TObject::Named(named_object)) = self else {
259                break 'parameters None;
260            };
261
262            let object_name = named_object.get_name();
263            if !object_name.as_bytes().eq_ignore_ascii_case(b"Generator") {
264                break 'parameters None;
265            }
266
267            let parameters = named_object.get_type_parameters().unwrap_or_default();
268            match parameters {
269                [] => Some((get_mixed(), get_mixed(), get_mixed(), get_mixed())),
270                [a] => Some((get_mixed(), a.clone(), get_mixed(), get_mixed())),
271                [a, b] => Some((a.clone(), b.clone(), get_mixed(), get_mixed())),
272                [a, b, c] => Some((a.clone(), b.clone(), c.clone(), get_mixed())),
273                [a, b, c, d] => Some((a.clone(), b.clone(), c.clone(), d.clone())),
274                _ => None,
275            }
276        };
277
278        if let Some(parameters) = generator_parameters {
279            return Some(parameters);
280        }
281
282        if let Some(intersection_types) = self.get_intersection_types() {
283            for intersection_type in intersection_types {
284                if let Some(parameters) = intersection_type.get_generator_parameters() {
285                    return Some(parameters);
286                }
287            }
288        }
289
290        None
291    }
292
293    #[must_use]
294    pub fn is_templated_as_object(&self) -> bool {
295        matches!(self, TAtomic::GenericParameter(parameter) if {
296            parameter.constraint.is_objecty() && parameter.intersection_types.is_none()
297        })
298    }
299
300    #[inline]
301    #[must_use]
302    pub const fn is_list(&self) -> bool {
303        matches!(self, TAtomic::Array(array) if array.is_list())
304    }
305
306    #[inline]
307    #[must_use]
308    pub fn is_vanilla_array(&self) -> bool {
309        matches!(self, TAtomic::Array(array) if array.is_vanilla())
310    }
311
312    pub fn get_list_element_type(&self) -> Option<&TUnion> {
313        match self {
314            TAtomic::Array(array) => array.get_list().map(array::list::TList::get_element_type),
315            _ => None,
316        }
317    }
318
319    #[inline]
320    pub fn is_non_empty_list(&self) -> bool {
321        matches!(self, TAtomic::Array(array) if array.get_list().is_some_and(array::list::TList::is_non_empty))
322    }
323
324    #[inline]
325    #[must_use]
326    pub fn is_empty_array(&self) -> bool {
327        matches!(self, TAtomic::Array(array) if array.is_empty())
328    }
329
330    #[inline]
331    #[must_use]
332    pub const fn is_keyed_array(&self) -> bool {
333        matches!(self, TAtomic::Array(array) if array.is_keyed())
334    }
335
336    #[inline]
337    #[must_use]
338    pub const fn is_array(&self) -> bool {
339        matches!(self, TAtomic::Array(_))
340    }
341
342    #[inline]
343    #[must_use]
344    pub const fn is_iterable(&self) -> bool {
345        matches!(self, TAtomic::Iterable(_))
346    }
347
348    #[inline]
349    #[must_use]
350    pub fn extends_or_implements(&self, codebase: &CodebaseMetadata, interface: &[u8]) -> bool {
351        let object = match self {
352            TAtomic::Object(object) => object,
353            TAtomic::GenericParameter(parameter) => {
354                if let Some(intersection_types) = parameter.get_intersection_types() {
355                    for intersection_type in intersection_types {
356                        if intersection_type.extends_or_implements(codebase, interface) {
357                            return true;
358                        }
359                    }
360                }
361
362                for constraint_atomic in parameter.constraint.types.as_ref() {
363                    if constraint_atomic.extends_or_implements(codebase, interface) {
364                        return true;
365                    }
366                }
367
368                return false;
369            }
370            TAtomic::Iterable(iterable) => {
371                if let Some(intersection_types) = iterable.get_intersection_types() {
372                    for intersection_type in intersection_types {
373                        if intersection_type.extends_or_implements(codebase, interface) {
374                            return true;
375                        }
376                    }
377                }
378
379                return false;
380            }
381            // bottom type: subtype of all types
382            TAtomic::Never => return true,
383            _ => return false,
384        };
385
386        if let Some(object_name) = object.get_name() {
387            if object_name.as_bytes() == interface {
388                return true;
389            }
390
391            if codebase.is_instance_of(object_name.as_bytes(), interface) {
392                return true;
393            }
394        }
395
396        if let Some(intersection_types) = object.get_intersection_types() {
397            for intersection_type in intersection_types {
398                if intersection_type.extends_or_implements(codebase, interface) {
399                    return true;
400                }
401            }
402        }
403
404        false
405    }
406
407    #[inline]
408    #[must_use]
409    pub fn is_countable(&self, codebase: &CodebaseMetadata) -> bool {
410        match self {
411            TAtomic::Array(_) => true,
412            _ => self.extends_or_implements(codebase, b"Countable"),
413        }
414    }
415
416    #[inline]
417    #[must_use]
418    pub fn is_traversable(&self, codebase: &CodebaseMetadata) -> bool {
419        self.extends_or_implements(codebase, b"Traversable")
420            || self.extends_or_implements(codebase, b"Iterator")
421            || self.extends_or_implements(codebase, b"IteratorAggregate")
422            || self.extends_or_implements(codebase, b"Generator")
423    }
424
425    #[inline]
426    #[must_use]
427    pub fn is_array_or_traversable(&self, codebase: &CodebaseMetadata) -> bool {
428        match self {
429            TAtomic::Iterable(_) => true,
430            TAtomic::Array(_) => true,
431            _ => self.is_traversable(codebase),
432        }
433    }
434
435    #[inline]
436    #[must_use]
437    pub fn could_be_array_or_traversable(&self, codebase: &CodebaseMetadata) -> bool {
438        self.is_mixed() || self.is_array_or_traversable(codebase)
439    }
440
441    #[must_use]
442    pub fn is_non_empty_array(&self) -> bool {
443        matches!(self, TAtomic::Array(array) if array.is_non_empty())
444    }
445
446    pub fn to_array_key(&self) -> Option<ArrayKey> {
447        match self {
448            TAtomic::Scalar(TScalar::Integer(int)) => int.get_literal_value().map(ArrayKey::Integer),
449            TAtomic::Scalar(TScalar::String(TString { literal: Some(TStringLiteral::Value(value)), .. })) => {
450                Some(ArrayKey::from_string(*value))
451            }
452            TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Literal { value })) => {
453                Some(ArrayKey::String(*value))
454            }
455            _ => None,
456        }
457    }
458
459    #[inline]
460    #[must_use]
461    pub const fn is_generic_scalar(&self) -> bool {
462        matches!(self, TAtomic::Scalar(TScalar::Generic))
463    }
464
465    #[inline]
466    #[must_use]
467    pub const fn is_some_scalar(&self) -> bool {
468        matches!(self, TAtomic::Scalar(_))
469    }
470
471    #[inline]
472    #[must_use]
473    pub const fn is_null(&self) -> bool {
474        matches!(self, TAtomic::Null)
475    }
476
477    #[inline]
478    #[must_use]
479    pub const fn is_void(&self) -> bool {
480        matches!(self, TAtomic::Void)
481    }
482
483    #[inline]
484    #[must_use]
485    pub const fn is_falsable(&self) -> bool {
486        matches!(
487            self,
488            TAtomic::Scalar(scalar) if scalar.is_false() || scalar.is_general_bool() || scalar.is_generic()
489        )
490    }
491
492    #[inline]
493    #[must_use]
494    pub const fn is_resource(&self) -> bool {
495        matches!(self, TAtomic::Resource(_))
496    }
497
498    #[inline]
499    #[must_use]
500    pub const fn is_literal(&self) -> bool {
501        match self {
502            TAtomic::Scalar(scalar) => scalar.is_literal_value(),
503            TAtomic::Null => true,
504            _ => false,
505        }
506    }
507
508    #[inline]
509    #[must_use]
510    pub const fn is_callable(&self) -> bool {
511        matches!(self, TAtomic::Callable(_))
512    }
513
514    #[inline]
515    #[must_use]
516    pub const fn is_conditional(&self) -> bool {
517        matches!(self, TAtomic::Conditional(_))
518    }
519
520    #[inline]
521    #[must_use]
522    pub const fn is_generic_parameter(&self) -> bool {
523        matches!(self, TAtomic::GenericParameter(_))
524    }
525
526    #[inline]
527    #[must_use]
528    pub const fn get_generic_parameter_name(&self) -> Option<Word> {
529        match self {
530            TAtomic::GenericParameter(parameter) => Some(parameter.parameter_name),
531            _ => None,
532        }
533    }
534
535    /// Is this a type that could potentially be callable at runtime?
536    #[inline]
537    #[must_use]
538    pub const fn can_be_callable(&self) -> bool {
539        matches!(
540            self,
541            TAtomic::Callable(_)
542                | TAtomic::Scalar(TScalar::String(_))
543                | TAtomic::Array(TArray::List(_) | TArray::Keyed(_))
544                | TAtomic::Object(TObject::Named(_))
545        )
546    }
547
548    #[must_use]
549    pub fn is_truthy(&self) -> bool {
550        match &self {
551            TAtomic::Scalar(scalar) => scalar.is_truthy(),
552            TAtomic::Array(array) => array.is_truthy(),
553            TAtomic::Mixed(mixed) => mixed.is_truthy(),
554            TAtomic::Resource(resource) => resource.closed.is_none_or(|closed| !closed),
555            TAtomic::Object(_) | TAtomic::Callable(_) => true,
556            _ => false,
557        }
558    }
559
560    #[must_use]
561    pub fn is_falsy(&self) -> bool {
562        match &self {
563            TAtomic::Scalar(scalar) if scalar.is_falsy() => true,
564            TAtomic::Array(array) if array.is_falsy() => true,
565            TAtomic::Mixed(mixed) if mixed.is_falsy() => true,
566            TAtomic::Resource(resource) => resource.closed.is_some_and(|closed| closed),
567            TAtomic::Null | TAtomic::Void => true,
568            _ => false,
569        }
570    }
571
572    #[must_use]
573    pub fn is_array_accessible_with_string_key(&self) -> bool {
574        matches!(self, TAtomic::Array(array) if array.is_keyed())
575    }
576
577    #[must_use]
578    pub fn is_array_accessible_with_int_or_string_key(&self) -> bool {
579        matches!(self, TAtomic::Array(_))
580    }
581
582    #[must_use]
583    pub fn is_derived(&self) -> bool {
584        matches!(self, TAtomic::Derived(_))
585    }
586
587    pub fn remove_placeholders(&mut self) {
588        match self {
589            TAtomic::Array(array) => {
590                array.remove_placeholders();
591            }
592            TAtomic::Object(TObject::Named(named_object)) => {
593                let name = named_object.get_name();
594                if let Some(type_parameters) = named_object.get_type_parameters_mut() {
595                    if name.as_bytes().eq_ignore_ascii_case(b"Traversable") {
596                        let has_kv_pair = type_parameters.len() == 2;
597
598                        if let Some(key_or_value_param) = type_parameters.get_mut(0)
599                            && matches!(key_or_value_param.get_single(), TAtomic::Placeholder)
600                        {
601                            *key_or_value_param = if has_kv_pair { get_arraykey() } else { get_mixed() };
602                        }
603
604                        if has_kv_pair
605                            && let Some(value_param) = type_parameters.get_mut(1)
606                            && matches!(value_param.get_single(), TAtomic::Placeholder)
607                        {
608                            *value_param = get_mixed();
609                        }
610                    } else {
611                        for type_param in type_parameters {
612                            if matches!(type_param.get_single(), TAtomic::Placeholder) {
613                                *type_param = get_mixed();
614                            }
615                        }
616                    }
617                }
618            }
619            _ => {}
620        }
621    }
622
623    #[must_use]
624    pub fn get_integer(&self) -> Option<TInteger> {
625        match self {
626            TAtomic::Scalar(TScalar::Integer(integer)) => Some(*integer),
627            _ => None,
628        }
629    }
630}
631
632macro_rules! scalar_forwarding_predicates {
633    ($($method:ident => $scalar_method:ident),* $(,)?) => {
634        $(
635            #[inline]
636            #[must_use]
637            pub const fn $method(&self) -> bool {
638                matches!(self, TAtomic::Scalar(scalar) if scalar.$scalar_method())
639            }
640        )*
641    };
642}
643
644macro_rules! scalar_forwarding_getters {
645    ($($method:ident => $scalar_method:ident -> $return_type:ty),* $(,)?) => {
646        $(
647            #[inline]
648            #[must_use]
649            pub fn $method(&self) -> Option<$return_type> {
650                match self {
651                    TAtomic::Scalar(scalar) => scalar.$scalar_method(),
652                    _ => None,
653                }
654            }
655        )*
656    };
657}
658
659impl TAtomic {
660    scalar_forwarding_predicates! {
661        is_any_string => is_any_string,
662        is_string => is_string,
663        is_string_of_literal_origin => is_literal_origin_string,
664        is_non_empty_string => is_non_empty_string,
665        is_known_literal_string => is_known_literal_string,
666        is_literal_class_string => is_literal_class_string,
667        is_string_subtype => is_non_boring_string,
668        is_array_key => is_array_key,
669        is_int => is_int,
670        is_literal_int => is_literal_int,
671        is_float => is_float,
672        is_literal_float => is_literal_float,
673        is_bool => is_bool,
674        is_general_bool => is_general_bool,
675        is_general_string => is_general_string,
676        is_true => is_true,
677        is_false => is_false,
678    }
679
680    scalar_forwarding_getters! {
681        get_literal_string_value => get_known_literal_string_value -> &[u8],
682        get_class_string_value => get_literal_class_string_value -> Word,
683        get_literal_int_value => get_literal_int_value -> i64,
684        get_maximum_int_value => get_maximum_int_value -> i64,
685        get_minimum_int_value => get_minimum_int_value -> i64,
686        get_literal_float_value => get_literal_float_value -> f64,
687    }
688}
689
690macro_rules! with_inner_ttype {
691    ($self:expr, $ttype:ident => $body:expr, $fallback:expr) => {
692        match $self {
693            TAtomic::Scalar($ttype) => $body,
694            TAtomic::Callable($ttype) => $body,
695            TAtomic::Mixed($ttype) => $body,
696            TAtomic::Object($ttype) => $body,
697            TAtomic::Array($ttype) => $body,
698            TAtomic::Iterable($ttype) => $body,
699            TAtomic::Resource($ttype) => $body,
700            TAtomic::Reference($ttype) => $body,
701            TAtomic::GenericParameter($ttype) => $body,
702            TAtomic::Conditional($ttype) => $body,
703            TAtomic::Derived($ttype) => $body,
704            TAtomic::Alias($ttype) => $body,
705            _ => $fallback,
706        }
707    };
708}
709
710impl TType for TAtomic {
711    fn get_child_nodes(&self) -> Vec<TypeRef<'_>> {
712        with_inner_ttype!(self, ttype => ttype.get_child_nodes(), vec![])
713    }
714
715    fn can_be_intersected(&self) -> bool {
716        with_inner_ttype!(self, ttype => ttype.can_be_intersected(), false)
717    }
718
719    fn get_intersection_types(&self) -> Option<&[TAtomic]> {
720        with_inner_ttype!(self, ttype => ttype.get_intersection_types(), None)
721    }
722
723    fn get_intersection_types_mut(&mut self) -> Option<&mut Vec<TAtomic>> {
724        with_inner_ttype!(self, ttype => ttype.get_intersection_types_mut(), None)
725    }
726
727    fn has_intersection_types(&self) -> bool {
728        with_inner_ttype!(self, ttype => ttype.has_intersection_types(), false)
729    }
730
731    fn add_intersection_type(&mut self, intersection_type: TAtomic) -> bool {
732        with_inner_ttype!(self, ttype => ttype.add_intersection_type(intersection_type), false)
733    }
734
735    fn needs_population(&self) -> bool {
736        if let Some(intersection) = self.get_intersection_types()
737            && intersection.iter().any(|intersection_type| intersection_type.needs_population())
738        {
739            return true;
740        }
741
742        with_inner_ttype!(self, ttype => ttype.needs_population(), false)
743    }
744
745    #[inline]
746    fn is_expandable(&self) -> bool {
747        if let Some(intersection) = self.get_intersection_types()
748            && intersection.iter().any(|intersection_type| intersection_type.is_expandable())
749        {
750            return true;
751        }
752
753        with_inner_ttype!(self, ttype => ttype.is_expandable(), false)
754    }
755
756    fn is_complex(&self) -> bool {
757        if let Some(intersection) = self.get_intersection_types()
758            && intersection.iter().any(|intersection_type| intersection_type.is_complex())
759        {
760            return true;
761        }
762
763        with_inner_ttype!(self, ttype => ttype.is_complex(), false)
764    }
765
766    fn get_id(&self) -> Word {
767        with_inner_ttype!(self, ttype => ttype.get_id(), match self {
768            TAtomic::Variable(name) => *name,
769            TAtomic::Never => word("never"),
770            TAtomic::Null => word("null"),
771            TAtomic::Void => word("void"),
772            _ => word("_"),
773        })
774    }
775
776    fn get_pretty_id_with_indent(&self, indent: usize) -> Word {
777        with_inner_ttype!(self, ttype => ttype.get_pretty_id_with_indent(indent), match self {
778            TAtomic::Variable(name) => *name,
779            TAtomic::Never => word("never"),
780            TAtomic::Null => word("null"),
781            TAtomic::Void => word("void"),
782            _ => word("_"),
783        })
784    }
785}
786
787pub(crate) fn append_intersection_ids(mut base: Word, intersection_types: &[TAtomic], indent: Option<usize>) -> Word {
788    for atomic in intersection_types {
789        let atomic_id = match indent {
790            Some(indent) => atomic.get_pretty_id_with_indent(indent),
791            None => atomic.get_id(),
792        };
793
794        base = if atomic.has_intersection_types() {
795            concat_word!(base, b"&(", atomic_id, b")")
796        } else {
797            concat_word!(base, b"&", atomic_id)
798        };
799    }
800
801    base
802}
803
804fn add_symbol_reference(reference_source: &ReferenceSource, symbol_references: &mut SymbolReferences, name: Word) {
805    match reference_source {
806        ReferenceSource::Symbol(in_signature, a) => {
807            symbol_references.add_symbol_reference_to_symbol(*a, name, *in_signature);
808        }
809        ReferenceSource::ClassLikeMember(in_signature, a, b) => {
810            symbol_references.add_class_member_reference_to_symbol((*a, *b), name, *in_signature);
811        }
812        ReferenceSource::File(in_signature, file) => {
813            symbol_references.add_file_reference_to_class_member(*file, (name, mago_word::empty_word()), *in_signature);
814        }
815    }
816}
817
818pub fn populate_atomic_type(
819    unpopulated_atomic: &mut TAtomic,
820    codebase_symbols: &Symbols,
821    reference_source: Option<&ReferenceSource>,
822    symbol_references: &mut SymbolReferences,
823    force: bool,
824) {
825    macro_rules! populate {
826        (union $target:expr) => {
827            populate_union_type($target, codebase_symbols, reference_source, symbol_references, force)
828        };
829        (atomic $target:expr) => {
830            populate_atomic_type($target, codebase_symbols, reference_source, symbol_references, force)
831        };
832    }
833
834    match unpopulated_atomic {
835        TAtomic::Array(array) => match array {
836            TArray::List(list) => {
837                populate!(union Arc::make_mut(&mut list.element_type));
838
839                if let Some(known_elements) = list.known_elements.as_mut() {
840                    for (_, element_type) in known_elements.values_mut() {
841                        populate!(union element_type);
842                    }
843                }
844            }
845            TArray::Keyed(keyed_array) => {
846                if let Some(known_items) = keyed_array.known_items.as_mut() {
847                    for (_, item_type) in known_items.values_mut() {
848                        populate!(union item_type);
849                    }
850                }
851
852                if let Some(parameters) = &mut keyed_array.parameters {
853                    populate!(union Arc::make_mut(&mut parameters.0));
854
855                    populate!(union Arc::make_mut(&mut parameters.1));
856                }
857            }
858        },
859        TAtomic::Callable(TCallable::Signature(signature)) => {
860            if let Some(return_type) = signature.get_return_type_mut() {
861                populate!(union return_type);
862            }
863
864            for param in signature.get_parameters_mut() {
865                if let Some(param_type) = param.get_type_signature_mut() {
866                    populate!(union param_type);
867                }
868            }
869
870            for constraint in &mut signature.constraints {
871                populate!(union Arc::make_mut(&mut constraint.input_type));
872                populate!(union Arc::make_mut(&mut constraint.parameter_type));
873            }
874        }
875        TAtomic::Object(TObject::Named(named_object)) => {
876            let name = named_object.get_name();
877
878            if !named_object.is_intersection()
879                && !named_object.has_type_parameters()
880                && codebase_symbols.contains_enum(name)
881            {
882                *unpopulated_atomic = TAtomic::Object(TObject::new_enum(name));
883            } else {
884                if let Some(type_parameters) = named_object.get_type_parameters_mut() {
885                    for parameter in type_parameters {
886                        populate!(union parameter);
887                    }
888                }
889
890                if let Some(intersection_types) = named_object.get_intersection_types_mut() {
891                    for intersection_type in intersection_types {
892                        populate!(atomic intersection_type);
893                    }
894                }
895            }
896
897            if let Some(reference_source) = reference_source {
898                add_symbol_reference(reference_source, symbol_references, name);
899            }
900        }
901        TAtomic::Object(TObject::WithProperties(keyed_array)) => {
902            for (_, item_type) in keyed_array.known_properties.values_mut() {
903                populate!(union item_type);
904            }
905        }
906        TAtomic::Iterable(iterable) => {
907            populate!(union iterable.get_key_type_mut());
908
909            populate!(union iterable.get_value_type_mut());
910
911            if let Some(intersection_types) = iterable.get_intersection_types_mut() {
912                for intersection_type in intersection_types {
913                    populate!(atomic intersection_type);
914                }
915            }
916        }
917        TAtomic::Reference(reference) => match reference {
918            TReference::Symbol { name, parameters, variances, intersection_types } => {
919                if let Some(parameters) = parameters {
920                    for parameter in parameters {
921                        populate!(union parameter);
922                    }
923                }
924
925                if let Some(reference_source) = reference_source {
926                    add_symbol_reference(reference_source, symbol_references, *name);
927                }
928
929                if let Some(symbol_kind) = codebase_symbols.get_kind(ascii_lowercase_word(name.as_bytes())) {
930                    if symbol_kind == SymbolKind::Enum {
931                        *unpopulated_atomic = TAtomic::Object(TObject::new_enum(*name));
932                    } else {
933                        let intersection_types = intersection_types.take().map(|intersection_types| {
934                            intersection_types
935                                .into_iter()
936                                .map(|mut intersection_type| {
937                                    populate!(atomic &mut intersection_type);
938
939                                    intersection_type
940                                })
941                                .collect::<Vec<_>>()
942                        });
943
944                        let mut named_object = TNamedObject::new(*name)
945                            .with_type_parameters(parameters.clone())
946                            .with_variances(variances.clone());
947                        if let Some(intersection_types) = intersection_types {
948                            for intersection_type in intersection_types {
949                                named_object.add_intersection_type(intersection_type);
950                            }
951                        }
952
953                        *unpopulated_atomic = TAtomic::Object(TObject::Named(named_object));
954                    }
955                }
956            }
957            TReference::Member { class_like_name, member_selector } => {
958                if let TReferenceMemberSelector::Identifier(member_name) = member_selector
959                    && let Some(reference_source) = reference_source
960                {
961                    match reference_source {
962                        ReferenceSource::Symbol(in_signature, a) => symbol_references
963                            .add_symbol_reference_to_class_member(*a, (*class_like_name, *member_name), *in_signature),
964                        ReferenceSource::ClassLikeMember(in_signature, a, b) => symbol_references
965                            .add_class_member_reference_to_class_member(
966                                (*a, *b),
967                                (*class_like_name, *member_name),
968                                *in_signature,
969                            ),
970                        ReferenceSource::File(in_signature, file) => symbol_references
971                            .add_file_reference_to_class_member(*file, (*class_like_name, *member_name), *in_signature),
972                    }
973                }
974            }
975            TReference::Global { .. } => {
976                // Global-constant wildcards are resolved at expansion time; nothing to populate.
977            }
978        },
979        TAtomic::GenericParameter(TGenericParameter { constraint, intersection_types, .. }) => {
980            populate!(union Arc::make_mut(constraint));
981
982            if let Some(intersection_types) = intersection_types.as_mut() {
983                for intersection_type in intersection_types {
984                    populate!(atomic intersection_type);
985                }
986            }
987        }
988        TAtomic::Scalar(TScalar::ClassLikeString(
989            TClassLikeString::OfType { constraint, .. } | TClassLikeString::Generic { constraint, .. },
990        )) => {
991            populate!(atomic Arc::make_mut(constraint));
992        }
993        TAtomic::Conditional(conditional) => {
994            populate!(union conditional.get_subject_mut());
995
996            populate!(union conditional.get_target_mut());
997
998            populate!(union conditional.get_then_mut());
999
1000            populate!(union conditional.get_otherwise_mut());
1001        }
1002        TAtomic::Derived(derived) => match derived {
1003            TDerived::IntMask(int_mask) => {
1004                for value in int_mask.get_values_mut() {
1005                    populate!(union value);
1006                }
1007            }
1008            TDerived::IndexAccess(index_access) => {
1009                populate!(union index_access.get_target_type_mut());
1010
1011                populate!(union index_access.get_index_type_mut());
1012            }
1013            TDerived::TemplateType(template_type) => {
1014                populate!(union template_type.get_object_mut());
1015
1016                populate!(union template_type.get_class_name_mut());
1017
1018                populate!(union template_type.get_template_name_mut());
1019            }
1020            TDerived::Intersection(intersection) => {
1021                populate!(union intersection.get_base_type_mut());
1022                if let Some(intersection_types) = intersection.get_intersection_types_mut() {
1023                    for intersection_type in intersection_types {
1024                        populate!(atomic intersection_type);
1025                    }
1026                }
1027            }
1028            _ => {
1029                if let Some(target) = derived.get_target_type_mut() {
1030                    populate!(union target);
1031                }
1032            }
1033        },
1034        _ => {}
1035    }
1036}