Skip to main content

mago_codex/ttype/
combination.rs

1use std::collections::BTreeMap;
2
3use foldhash::HashSet;
4use ordered_float::OrderedFloat;
5
6use mago_word::Word;
7use mago_word::WordMap;
8use mago_word::WordSet;
9
10use crate::ttype::atomic::TAtomic;
11use crate::ttype::atomic::array::TArray;
12use crate::ttype::atomic::array::key::ArrayKey;
13use crate::ttype::atomic::derived::TDerived;
14use crate::ttype::atomic::scalar::int::TInteger;
15use crate::ttype::union::TUnion;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18#[repr(transparent)]
19pub struct CombinationFlags(u32);
20
21impl CombinationFlags {
22    pub const HAS_OBJECT_TOP_TYPE: CombinationFlags = CombinationFlags(1 << 0);
23    pub const LIST_ARRAY_SOMETIMES_FILLED: CombinationFlags = CombinationFlags(1 << 1);
24    pub const LIST_ARRAY_ALWAYS_FILLED: CombinationFlags = CombinationFlags(1 << 2);
25    pub const KEYED_ARRAY_SOMETIMES_FILLED: CombinationFlags = CombinationFlags(1 << 3);
26    pub const KEYED_ARRAY_ALWAYS_FILLED: CombinationFlags = CombinationFlags(1 << 4);
27    pub const HAS_EMPTY_ARRAY: CombinationFlags = CombinationFlags(1 << 5);
28    pub const HAS_KEYED_ARRAY: CombinationFlags = CombinationFlags(1 << 6);
29    pub const GENERIC_MIXED: CombinationFlags = CombinationFlags(1 << 7);
30    pub const HAS_MIXED: CombinationFlags = CombinationFlags(1 << 8);
31    pub const RESOURCE: CombinationFlags = CombinationFlags(1 << 9);
32    pub const OPEN_RESOURCE: CombinationFlags = CombinationFlags(1 << 10);
33    pub const CLOSED_RESOURCE: CombinationFlags = CombinationFlags(1 << 11);
34    // Tristate encoding: 2 bits each (None=neither set, Some(false)=SET only, Some(true)=SET+VALUE)
35    const FALSY_MIXED_SET: CombinationFlags = CombinationFlags(1 << 12);
36    const FALSY_MIXED_VALUE: CombinationFlags = CombinationFlags(1 << 13);
37    const TRUTHY_MIXED_SET: CombinationFlags = CombinationFlags(1 << 14);
38    const TRUTHY_MIXED_VALUE: CombinationFlags = CombinationFlags(1 << 15);
39    const NONNULL_MIXED_SET: CombinationFlags = CombinationFlags(1 << 16);
40    const NONNULL_MIXED_VALUE: CombinationFlags = CombinationFlags(1 << 17);
41    const MIXED_FROM_LOOP_ISSET_SET: CombinationFlags = CombinationFlags(1 << 18);
42    const MIXED_FROM_LOOP_ISSET_VALUE: CombinationFlags = CombinationFlags(1 << 19);
43}
44
45impl CombinationFlags {
46    #[inline]
47    pub const fn insert(&mut self, other: CombinationFlags) {
48        self.0 |= other.0;
49    }
50
51    #[inline]
52    pub const fn remove(&mut self, other: CombinationFlags) {
53        self.0 &= !other.0;
54    }
55
56    #[inline]
57    #[must_use]
58    pub const fn contains(self, other: CombinationFlags) -> bool {
59        (self.0 & other.0) == other.0
60    }
61
62    #[inline]
63    #[must_use]
64    pub const fn intersects(self, other: CombinationFlags) -> bool {
65        (self.0 & other.0) != 0
66    }
67
68    /// Get a tristate value (Option<bool>) from two bits.
69    #[inline]
70    #[must_use]
71    pub fn get_tristate(self, set_bit: CombinationFlags, value_bit: CombinationFlags) -> Option<bool> {
72        if self.contains(set_bit) { Some(self.contains(value_bit)) } else { None }
73    }
74
75    /// Set a tristate value (Option<bool>) using two bits.
76    #[inline]
77    pub fn set_tristate(&mut self, set_bit: CombinationFlags, value_bit: CombinationFlags, value: Option<bool>) {
78        match value {
79            None => {
80                self.remove(set_bit);
81                self.remove(value_bit);
82            }
83            Some(false) => {
84                self.insert(set_bit);
85                self.remove(value_bit);
86            }
87            Some(true) => {
88                self.insert(set_bit);
89                self.insert(value_bit);
90            }
91        }
92    }
93
94    #[inline]
95    #[must_use]
96    pub fn falsy_mixed(self) -> Option<bool> {
97        self.get_tristate(Self::FALSY_MIXED_SET, Self::FALSY_MIXED_VALUE)
98    }
99
100    #[inline]
101    pub fn set_falsy_mixed(&mut self, value: Option<bool>) {
102        self.set_tristate(Self::FALSY_MIXED_SET, Self::FALSY_MIXED_VALUE, value);
103    }
104
105    #[inline]
106    #[must_use]
107    pub fn truthy_mixed(self) -> Option<bool> {
108        self.get_tristate(Self::TRUTHY_MIXED_SET, Self::TRUTHY_MIXED_VALUE)
109    }
110
111    #[inline]
112    pub fn set_truthy_mixed(&mut self, value: Option<bool>) {
113        self.set_tristate(Self::TRUTHY_MIXED_SET, Self::TRUTHY_MIXED_VALUE, value);
114    }
115
116    #[inline]
117    #[must_use]
118    pub fn nonnull_mixed(self) -> Option<bool> {
119        self.get_tristate(Self::NONNULL_MIXED_SET, Self::NONNULL_MIXED_VALUE)
120    }
121
122    #[inline]
123    pub fn set_nonnull_mixed(&mut self, value: Option<bool>) {
124        self.set_tristate(Self::NONNULL_MIXED_SET, Self::NONNULL_MIXED_VALUE, value);
125    }
126
127    #[inline]
128    #[must_use]
129    pub fn mixed_from_loop_isset(self) -> Option<bool> {
130        self.get_tristate(Self::MIXED_FROM_LOOP_ISSET_SET, Self::MIXED_FROM_LOOP_ISSET_VALUE)
131    }
132
133    #[inline]
134    pub fn set_mixed_from_loop_isset(&mut self, value: Option<bool>) {
135        self.set_tristate(Self::MIXED_FROM_LOOP_ISSET_SET, Self::MIXED_FROM_LOOP_ISSET_VALUE, value);
136    }
137}
138
139#[derive(Debug)]
140pub struct TypeCombination {
141    pub flags: CombinationFlags,
142    pub value_types: WordMap<TAtomic>,
143    pub enum_names: HashSet<(Word, Option<Word>)>,
144    pub object_type_params: WordMap<(Word, Vec<TUnion>)>,
145    pub object_static: WordMap<bool>,
146    pub list_array_counts: Option<HashSet<usize>>,
147    pub keyed_array_entries: BTreeMap<ArrayKey, (bool, TUnion)>,
148    pub list_array_entries: BTreeMap<usize, (bool, TUnion)>,
149    pub keyed_array_parameters: Option<(TUnion, TUnion)>,
150    pub list_array_parameter: Option<TUnion>,
151    pub sealed_arrays: Vec<TArray>,
152    pub sealed_keyed_budget_exhausted: bool,
153    pub integers: Vec<TInteger>,
154    pub literal_strings: WordSet,
155    pub literal_floats: Vec<OrderedFloat<f64>>,
156    pub class_string_types: WordMap<TAtomic>,
157    pub derived_types: HashSet<TDerived>,
158}
159
160impl Default for TypeCombination {
161    fn default() -> Self {
162        Self::new()
163    }
164}
165
166impl TypeCombination {
167    #[must_use]
168    pub fn new() -> Self {
169        let flags = CombinationFlags::LIST_ARRAY_ALWAYS_FILLED | CombinationFlags::KEYED_ARRAY_ALWAYS_FILLED;
170
171        Self {
172            flags,
173            value_types: WordMap::default(),
174            object_type_params: WordMap::default(),
175            object_static: WordMap::default(),
176            list_array_counts: Some(HashSet::default()),
177            keyed_array_entries: BTreeMap::new(),
178            list_array_entries: BTreeMap::new(),
179            keyed_array_parameters: None,
180            list_array_parameter: None,
181            sealed_arrays: Vec::new(),
182            sealed_keyed_budget_exhausted: false,
183            literal_strings: WordSet::default(),
184            integers: Vec::new(),
185            literal_floats: Vec::new(),
186            class_string_types: WordMap::default(),
187            enum_names: HashSet::default(),
188            derived_types: HashSet::default(),
189        }
190    }
191
192    #[inline]
193    #[must_use]
194    pub fn is_simple(&self) -> bool {
195        if self.value_types.len() == 1
196            && self.sealed_arrays.is_empty()
197            && !self.flags.contains(CombinationFlags::HAS_KEYED_ARRAY)
198            && !self.flags.contains(CombinationFlags::HAS_EMPTY_ARRAY)
199            && !self.flags.intersects(
200                CombinationFlags::RESOURCE | CombinationFlags::OPEN_RESOURCE | CombinationFlags::CLOSED_RESOURCE,
201            )
202            && self.keyed_array_parameters.is_none()
203            && self.list_array_parameter.is_none()
204        {
205            return self.object_type_params.is_empty()
206                && self.enum_names.is_empty()
207                && self.literal_strings.is_empty()
208                && self.literal_floats.is_empty()
209                && self.class_string_types.is_empty()
210                && self.integers.is_empty()
211                && self.derived_types.is_empty();
212        }
213
214        false
215    }
216}
217
218impl std::ops::BitOr for CombinationFlags {
219    type Output = Self;
220
221    #[inline]
222    fn bitor(self, rhs: Self) -> Self::Output {
223        CombinationFlags(self.0 | rhs.0)
224    }
225}
226
227impl std::ops::BitAnd for CombinationFlags {
228    type Output = Self;
229
230    #[inline]
231    fn bitand(self, rhs: Self) -> Self::Output {
232        CombinationFlags(self.0 & rhs.0)
233    }
234}
235
236impl std::ops::BitXor for CombinationFlags {
237    type Output = Self;
238
239    #[inline]
240    fn bitxor(self, rhs: Self) -> Self::Output {
241        CombinationFlags(self.0 ^ rhs.0)
242    }
243}
244
245impl std::ops::Not for CombinationFlags {
246    type Output = Self;
247
248    #[inline]
249    fn not(self) -> Self::Output {
250        CombinationFlags(!self.0)
251    }
252}