mago_codex/ttype/
combination.rs

1use std::collections::BTreeMap;
2
3use ahash::HashSet;
4use ordered_float::OrderedFloat;
5
6use mago_atom::Atom;
7use mago_atom::AtomMap;
8use mago_atom::AtomSet;
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
17bitflags::bitflags! {
18    /// Compact bitflags for boolean state in TypeCombination.
19    /// This reduces struct size and enables single-cycle multi-flag checks.
20    #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21    pub struct CombinationFlags: u32 {
22        const HAS_OBJECT_TOP_TYPE           = 1 << 0;
23        const LIST_ARRAY_SOMETIMES_FILLED   = 1 << 1;
24        const LIST_ARRAY_ALWAYS_FILLED      = 1 << 2;
25        const KEYED_ARRAY_SOMETIMES_FILLED  = 1 << 3;
26        const KEYED_ARRAY_ALWAYS_FILLED     = 1 << 4;
27        const HAS_EMPTY_ARRAY               = 1 << 5;
28        const HAS_KEYED_ARRAY               = 1 << 6;
29        const GENERIC_MIXED                 = 1 << 7;
30        const HAS_MIXED                     = 1 << 8;
31        const RESOURCE                      = 1 << 9;
32        const OPEN_RESOURCE                 = 1 << 10;
33        const CLOSED_RESOURCE               = 1 << 11;
34
35        // Tristate encoding: 2 bits each (None=neither set, Some(false)=SET only, Some(true)=SET+VALUE)
36        const FALSY_MIXED_SET               = 1 << 12;
37        const FALSY_MIXED_VALUE             = 1 << 13;
38        const TRUTHY_MIXED_SET              = 1 << 14;
39        const TRUTHY_MIXED_VALUE            = 1 << 15;
40        const NONNULL_MIXED_SET             = 1 << 16;
41        const NONNULL_MIXED_VALUE           = 1 << 17;
42        const MIXED_FROM_LOOP_ISSET_SET     = 1 << 18;
43        const MIXED_FROM_LOOP_ISSET_VALUE   = 1 << 19;
44    }
45}
46
47impl CombinationFlags {
48    /// Get a tristate value (Option<bool>) from two bits.
49    #[inline]
50    #[must_use]
51    pub fn get_tristate(self, set_bit: Self, value_bit: Self) -> Option<bool> {
52        if self.contains(set_bit) { Some(self.contains(value_bit)) } else { None }
53    }
54
55    /// Set a tristate value (Option<bool>) using two bits.
56    #[inline]
57    pub fn set_tristate(&mut self, set_bit: Self, value_bit: Self, value: Option<bool>) {
58        match value {
59            None => {
60                self.remove(set_bit);
61                self.remove(value_bit);
62            }
63            Some(false) => {
64                self.insert(set_bit);
65                self.remove(value_bit);
66            }
67            Some(true) => {
68                self.insert(set_bit);
69                self.insert(value_bit);
70            }
71        }
72    }
73
74    #[inline]
75    #[must_use]
76    pub fn falsy_mixed(self) -> Option<bool> {
77        self.get_tristate(Self::FALSY_MIXED_SET, Self::FALSY_MIXED_VALUE)
78    }
79
80    #[inline]
81    pub fn set_falsy_mixed(&mut self, value: Option<bool>) {
82        self.set_tristate(Self::FALSY_MIXED_SET, Self::FALSY_MIXED_VALUE, value);
83    }
84
85    #[inline]
86    #[must_use]
87    pub fn truthy_mixed(self) -> Option<bool> {
88        self.get_tristate(Self::TRUTHY_MIXED_SET, Self::TRUTHY_MIXED_VALUE)
89    }
90
91    #[inline]
92    pub fn set_truthy_mixed(&mut self, value: Option<bool>) {
93        self.set_tristate(Self::TRUTHY_MIXED_SET, Self::TRUTHY_MIXED_VALUE, value);
94    }
95
96    #[inline]
97    #[must_use]
98    pub fn nonnull_mixed(self) -> Option<bool> {
99        self.get_tristate(Self::NONNULL_MIXED_SET, Self::NONNULL_MIXED_VALUE)
100    }
101
102    #[inline]
103    pub fn set_nonnull_mixed(&mut self, value: Option<bool>) {
104        self.set_tristate(Self::NONNULL_MIXED_SET, Self::NONNULL_MIXED_VALUE, value);
105    }
106
107    #[inline]
108    #[must_use]
109    pub fn mixed_from_loop_isset(self) -> Option<bool> {
110        self.get_tristate(Self::MIXED_FROM_LOOP_ISSET_SET, Self::MIXED_FROM_LOOP_ISSET_VALUE)
111    }
112
113    #[inline]
114    pub fn set_mixed_from_loop_isset(&mut self, value: Option<bool>) {
115        self.set_tristate(Self::MIXED_FROM_LOOP_ISSET_SET, Self::MIXED_FROM_LOOP_ISSET_VALUE, value);
116    }
117}
118
119#[derive(Debug)]
120pub struct TypeCombination {
121    pub flags: CombinationFlags,
122    pub value_types: AtomMap<TAtomic>,
123    pub enum_names: HashSet<(Atom, Option<Atom>)>,
124    pub object_type_params: AtomMap<(Atom, Vec<TUnion>)>,
125    pub object_static: AtomMap<bool>,
126    pub list_array_counts: Option<HashSet<usize>>,
127    pub keyed_array_entries: BTreeMap<ArrayKey, (bool, TUnion)>,
128    pub list_array_entries: BTreeMap<usize, (bool, TUnion)>,
129    pub keyed_array_parameters: Option<(TUnion, TUnion)>,
130    pub list_array_parameter: Option<TUnion>,
131    pub sealed_arrays: Vec<TArray>,
132    pub integers: Vec<TInteger>,
133    pub literal_strings: AtomSet,
134    pub literal_floats: Vec<OrderedFloat<f64>>,
135    pub class_string_types: AtomMap<TAtomic>,
136    pub derived_types: HashSet<TDerived>,
137}
138
139impl Default for TypeCombination {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145impl TypeCombination {
146    #[must_use]
147    pub fn new() -> Self {
148        let flags = CombinationFlags::LIST_ARRAY_ALWAYS_FILLED | CombinationFlags::KEYED_ARRAY_ALWAYS_FILLED;
149
150        Self {
151            flags,
152            value_types: AtomMap::default(),
153            object_type_params: AtomMap::default(),
154            object_static: AtomMap::default(),
155            list_array_counts: Some(HashSet::default()),
156            keyed_array_entries: BTreeMap::new(),
157            list_array_entries: BTreeMap::new(),
158            keyed_array_parameters: None,
159            list_array_parameter: None,
160            sealed_arrays: Vec::new(),
161            literal_strings: AtomSet::default(),
162            integers: Vec::new(),
163            literal_floats: Vec::new(),
164            class_string_types: AtomMap::default(),
165            enum_names: HashSet::default(),
166            derived_types: HashSet::default(),
167        }
168    }
169
170    #[inline]
171    #[must_use]
172    pub fn is_simple(&self) -> bool {
173        if self.value_types.len() == 1
174            && self.sealed_arrays.is_empty()
175            && !self.flags.contains(CombinationFlags::HAS_KEYED_ARRAY)
176            && !self.flags.contains(CombinationFlags::HAS_EMPTY_ARRAY)
177            && !self.flags.intersects(
178                CombinationFlags::RESOURCE | CombinationFlags::OPEN_RESOURCE | CombinationFlags::CLOSED_RESOURCE,
179            )
180            && self.keyed_array_parameters.is_none()
181            && self.list_array_parameter.is_none()
182        {
183            return self.object_type_params.is_empty()
184                && self.enum_names.is_empty()
185                && self.literal_strings.is_empty()
186                && self.class_string_types.is_empty()
187                && self.integers.is_empty()
188                && self.derived_types.is_empty();
189        }
190
191        false
192    }
193}