mago_codex/
assertion.rs

1use std::borrow::Cow;
2use std::hash::Hash;
3use std::hash::Hasher;
4
5use ahash::AHasher;
6use mago_atom::Atom;
7use mago_atom::atom;
8use mago_atom::concat_atom;
9use mago_atom::i64_atom;
10use mago_atom::usize_atom;
11use serde::Deserialize;
12use serde::Serialize;
13
14use crate::metadata::CodebaseMetadata;
15use crate::ttype::TType;
16use crate::ttype::atomic::TAtomic;
17use crate::ttype::atomic::array::key::ArrayKey;
18use crate::ttype::template::TemplateResult;
19use crate::ttype::template::inferred_type_replacer;
20use crate::ttype::union::TUnion;
21
22#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
23pub enum Assertion {
24    Any,
25    IsType(TAtomic),
26    IsNotType(TAtomic),
27    Falsy,
28    Truthy,
29    IsIdentical(TAtomic),
30    IsNotIdentical(TAtomic),
31    IsEqual(TAtomic),
32    IsNotEqual(TAtomic),
33    IsEqualIsset,
34    IsIsset,
35    IsNotIsset,
36    HasStringArrayAccess,
37    HasIntOrStringArrayAccess,
38    ArrayKeyExists,
39    ArrayKeyDoesNotExist,
40    InArray(TUnion),
41    NotInArray(TUnion),
42    HasArrayKey(ArrayKey),
43    DoesNotHaveArrayKey(ArrayKey),
44    HasNonnullEntryForKey(ArrayKey),
45    DoesNotHaveNonnullEntryForKey(ArrayKey),
46    Empty,
47    NonEmpty,
48    NonEmptyCountable(bool),
49    EmptyCountable,
50    HasExactCount(usize),
51    HasAtLeastCount(usize),
52    DoesNotHaveExactCount(usize),
53    HasAtMostCount(usize),
54    IsLessThan(i64),
55    IsLessThanOrEqual(i64),
56    IsGreaterThan(i64),
57    IsGreaterThanOrEqual(i64),
58    Countable,
59    NotCountable(bool),
60}
61
62impl Assertion {
63    pub fn to_atom(&self) -> Atom {
64        match self {
65            Assertion::Any => atom("any"),
66            Assertion::Falsy => atom("falsy"),
67            Assertion::Truthy => atom("truthy"),
68            Assertion::IsEqualIsset => atom("=isset"),
69            Assertion::IsIsset => atom("isset"),
70            Assertion::IsNotIsset => atom("!isset"),
71            Assertion::HasStringArrayAccess => atom("=string-array-access"),
72            Assertion::HasIntOrStringArrayAccess => atom("=int-or-string-array-access"),
73            Assertion::ArrayKeyExists => atom("array-key-exists"),
74            Assertion::ArrayKeyDoesNotExist => atom("!array-key-exists"),
75            Assertion::EmptyCountable => atom("empty-countable"),
76            Assertion::Empty => atom("empty"),
77            Assertion::NonEmpty => atom("non-empty"),
78            Assertion::Countable => atom("countable"),
79            Assertion::NotCountable(_) => atom("!countable"),
80            Assertion::IsType(atomic) => atomic.get_id(),
81            Assertion::IsNotType(atomic) => concat_atom!("!", atomic.get_id()),
82            Assertion::IsIdentical(atomic) => concat_atom!("=", atomic.get_id()),
83            Assertion::IsNotIdentical(atomic) => concat_atom!("!=", atomic.get_id()),
84            Assertion::IsEqual(atomic) => concat_atom!("~", atomic.get_id()),
85            Assertion::IsNotEqual(atomic) => concat_atom!("!~", atomic.get_id()),
86            Assertion::InArray(union) => concat_atom!("=in-array-", union.get_id()),
87            Assertion::NotInArray(union) => concat_atom!("!=in-array-", union.get_id()),
88            Assertion::HasArrayKey(key) => concat_atom!("=has-array-key-", key.to_atom()),
89            Assertion::DoesNotHaveArrayKey(key) => concat_atom!("!=has-array-key-", key.to_atom()),
90            Assertion::HasNonnullEntryForKey(key) => concat_atom!("=has-nonnull-entry-for-", key.to_atom()),
91            Assertion::DoesNotHaveNonnullEntryForKey(key) => {
92                concat_atom!("!=has-nonnull-entry-for-", key.to_atom())
93            }
94            Assertion::HasExactCount(number) => concat_atom!("has-exactly-", usize_atom(*number)),
95            Assertion::HasAtLeastCount(number) => concat_atom!("has-at-least-", usize_atom(*number)),
96            Assertion::DoesNotHaveExactCount(number) => concat_atom!("!has-exactly-", usize_atom(*number)),
97            Assertion::HasAtMostCount(number) => concat_atom!("has-at-most-", usize_atom(*number)),
98            Assertion::IsLessThan(number) => concat_atom!("is-less-than-", i64_atom(*number)),
99            Assertion::IsLessThanOrEqual(number) => concat_atom!("is-less-than-or-equal-", i64_atom(*number)),
100            Assertion::IsGreaterThan(number) => concat_atom!("is-greater-than-", i64_atom(*number)),
101            Assertion::IsGreaterThanOrEqual(number) => concat_atom!("is-greater-than-or-equal-", i64_atom(*number)),
102            Assertion::NonEmptyCountable(negatable) => {
103                if *negatable {
104                    atom("non-empty-countable")
105                } else {
106                    atom("=non-empty-countable")
107                }
108            }
109        }
110    }
111
112    pub fn to_hash(&self) -> u64 {
113        let mut state = AHasher::default();
114        self.to_atom().hash(&mut state);
115        state.finish()
116    }
117
118    pub fn is_negation(&self) -> bool {
119        matches!(
120            self,
121            Assertion::Falsy
122                | Assertion::IsNotType(_)
123                | Assertion::IsNotEqual(_)
124                | Assertion::IsNotIdentical(_)
125                | Assertion::IsNotIsset
126                | Assertion::NotInArray(..)
127                | Assertion::ArrayKeyDoesNotExist
128                | Assertion::DoesNotHaveArrayKey(_)
129                | Assertion::DoesNotHaveExactCount(_)
130                | Assertion::DoesNotHaveNonnullEntryForKey(_)
131                | Assertion::HasAtMostCount(_)
132                | Assertion::HasExactCount(_)
133                | Assertion::HasAtLeastCount(_)
134                | Assertion::EmptyCountable
135                | Assertion::Empty
136                | Assertion::NotCountable(_)
137        )
138    }
139
140    pub fn has_isset(&self) -> bool {
141        matches!(
142            self,
143            Assertion::IsIsset | Assertion::ArrayKeyExists | Assertion::HasStringArrayAccess | Assertion::IsEqualIsset
144        )
145    }
146
147    pub fn has_non_isset_equality(&self) -> bool {
148        matches!(
149            self,
150            Assertion::InArray(_)
151                | Assertion::HasIntOrStringArrayAccess
152                | Assertion::HasStringArrayAccess
153                | Assertion::IsIdentical(_)
154                | Assertion::IsEqual(_)
155        )
156    }
157
158    pub fn has_equality(&self) -> bool {
159        matches!(
160            self,
161            Assertion::InArray(_)
162                | Assertion::HasIntOrStringArrayAccess
163                | Assertion::HasStringArrayAccess
164                | Assertion::IsEqualIsset
165                | Assertion::IsIdentical(_)
166                | Assertion::IsNotIdentical(_)
167                | Assertion::IsEqual(_)
168                | Assertion::IsNotEqual(_)
169                | Assertion::HasExactCount(_)
170        )
171    }
172
173    pub fn has_literal_value(&self) -> bool {
174        match self {
175            Assertion::IsIdentical(atomic)
176            | Assertion::IsNotIdentical(atomic)
177            | Assertion::IsType(atomic)
178            | Assertion::IsNotType(atomic)
179            | Assertion::IsEqual(atomic)
180            | Assertion::IsNotEqual(atomic) => {
181                atomic.is_literal_int() || atomic.is_literal_float() || atomic.is_known_literal_string()
182            }
183
184            _ => false,
185        }
186    }
187
188    pub fn has_integer(&self) -> bool {
189        match self {
190            Assertion::IsIdentical(atomic)
191            | Assertion::IsNotIdentical(atomic)
192            | Assertion::IsType(atomic)
193            | Assertion::IsNotType(atomic)
194            | Assertion::IsEqual(atomic)
195            | Assertion::IsNotEqual(atomic) => atomic.is_int(),
196            _ => false,
197        }
198    }
199
200    pub fn has_literal_string(&self) -> bool {
201        match self {
202            Assertion::IsIdentical(atomic)
203            | Assertion::IsNotIdentical(atomic)
204            | Assertion::IsType(atomic)
205            | Assertion::IsNotType(atomic)
206            | Assertion::IsEqual(atomic)
207            | Assertion::IsNotEqual(atomic) => atomic.is_known_literal_string(),
208
209            _ => false,
210        }
211    }
212
213    pub fn has_literal_int(&self) -> bool {
214        match self {
215            Assertion::IsIdentical(atomic)
216            | Assertion::IsNotIdentical(atomic)
217            | Assertion::IsType(atomic)
218            | Assertion::IsNotType(atomic)
219            | Assertion::IsEqual(atomic)
220            | Assertion::IsNotEqual(atomic) => atomic.is_literal_int(),
221
222            _ => false,
223        }
224    }
225
226    pub fn has_literal_float(&self) -> bool {
227        match self {
228            Assertion::IsIdentical(atomic)
229            | Assertion::IsNotIdentical(atomic)
230            | Assertion::IsType(atomic)
231            | Assertion::IsNotType(atomic)
232            | Assertion::IsEqual(atomic)
233            | Assertion::IsNotEqual(atomic) => atomic.is_literal_float(),
234
235            _ => false,
236        }
237    }
238
239    pub fn with_type(&self, atomic: TAtomic) -> Self {
240        match self {
241            Assertion::IsType(_) => Assertion::IsType(atomic),
242            Assertion::IsNotType(_) => Assertion::IsNotType(atomic),
243            Assertion::IsIdentical(_) => Assertion::IsIdentical(atomic),
244            Assertion::IsNotIdentical(_) => Assertion::IsNotIdentical(atomic),
245            Assertion::IsEqual(_) => Assertion::IsEqual(atomic),
246            Assertion::IsNotEqual(_) => Assertion::IsNotEqual(atomic),
247            _ => self.clone(),
248        }
249    }
250
251    pub fn get_type(&self) -> Option<&TAtomic> {
252        match self {
253            Assertion::IsIdentical(atomic)
254            | Assertion::IsNotIdentical(atomic)
255            | Assertion::IsType(atomic)
256            | Assertion::IsNotType(atomic)
257            | Assertion::IsEqual(atomic)
258            | Assertion::IsNotEqual(atomic) => Some(atomic),
259            _ => None,
260        }
261    }
262
263    pub fn get_type_mut(&mut self) -> Option<&mut TAtomic> {
264        match self {
265            Assertion::IsIdentical(atomic)
266            | Assertion::IsNotIdentical(atomic)
267            | Assertion::IsType(atomic)
268            | Assertion::IsNotType(atomic)
269            | Assertion::IsEqual(atomic)
270            | Assertion::IsNotEqual(atomic) => Some(atomic),
271            _ => None,
272        }
273    }
274
275    pub fn resolve_templates(&self, codebase: &CodebaseMetadata, template_result: &TemplateResult) -> Vec<Self> {
276        match self {
277            Assertion::IsType(atomic) => {
278                let union = TUnion::from_single(Cow::Owned(atomic.clone()));
279                let resolved_union = inferred_type_replacer::replace(&union, template_result, codebase);
280
281                let mut result = vec![];
282                for resolved_atomic in resolved_union.types.into_owned() {
283                    result.push(Assertion::IsType(resolved_atomic));
284                }
285
286                if result.is_empty() {
287                    result.push(Assertion::IsType(TAtomic::Never));
288                }
289
290                result
291            }
292            Assertion::IsNotType(atomic) => {
293                let union = TUnion::from_single(Cow::Owned(atomic.clone()));
294                let resolved_union = inferred_type_replacer::replace(&union, template_result, codebase);
295
296                let mut result = vec![];
297                for resolved_atomic in resolved_union.types.into_owned() {
298                    result.push(Assertion::IsNotType(resolved_atomic));
299                }
300
301                if result.is_empty() {
302                    result.push(Assertion::IsNotType(TAtomic::Never));
303                }
304
305                result
306            }
307            Assertion::InArray(union) => {
308                let resolved_union = inferred_type_replacer::replace(union, template_result, codebase);
309
310                vec![Assertion::InArray(resolved_union)]
311            }
312            Assertion::NotInArray(union) => {
313                let resolved_union = inferred_type_replacer::replace(union, template_result, codebase);
314
315                vec![Assertion::NotInArray(resolved_union)]
316            }
317            _ => {
318                vec![self.clone()]
319            }
320        }
321    }
322
323    pub fn is_negation_of(&self, other: &Assertion) -> bool {
324        match self {
325            Assertion::Any => false,
326            Assertion::Falsy => matches!(other, Assertion::Truthy),
327            Assertion::Truthy => matches!(other, Assertion::Falsy),
328            Assertion::IsType(atomic) => match other {
329                Assertion::IsNotType(other_atomic) => other_atomic == atomic,
330                _ => false,
331            },
332            Assertion::IsNotType(atomic) => match other {
333                Assertion::IsType(other_atomic) => other_atomic == atomic,
334                _ => false,
335            },
336            Assertion::IsIdentical(atomic) => match other {
337                Assertion::IsNotIdentical(other_atomic) => other_atomic == atomic,
338                _ => false,
339            },
340            Assertion::IsNotIdentical(atomic) => match other {
341                Assertion::IsIdentical(other_atomic) => other_atomic == atomic,
342                _ => false,
343            },
344            Assertion::IsEqual(atomic) => match other {
345                Assertion::IsNotEqual(other_atomic) => other_atomic == atomic,
346                _ => false,
347            },
348            Assertion::IsNotEqual(atomic) => match other {
349                Assertion::IsEqual(other_atomic) => other_atomic == atomic,
350                _ => false,
351            },
352            Assertion::IsEqualIsset => false,
353            Assertion::IsIsset => matches!(other, Assertion::IsNotIsset),
354            Assertion::IsNotIsset => matches!(other, Assertion::IsIsset),
355            Assertion::HasStringArrayAccess => false,
356            Assertion::HasIntOrStringArrayAccess => false,
357            Assertion::ArrayKeyExists => matches!(other, Assertion::ArrayKeyDoesNotExist),
358            Assertion::ArrayKeyDoesNotExist => matches!(other, Assertion::ArrayKeyExists),
359            Assertion::HasArrayKey(str) => match other {
360                Assertion::DoesNotHaveArrayKey(other_str) => other_str == str,
361                _ => false,
362            },
363            Assertion::DoesNotHaveArrayKey(str) => match other {
364                Assertion::HasArrayKey(other_str) => other_str == str,
365                _ => false,
366            },
367            Assertion::HasNonnullEntryForKey(str) => match other {
368                Assertion::DoesNotHaveNonnullEntryForKey(other_str) => other_str == str,
369                _ => false,
370            },
371            Assertion::DoesNotHaveNonnullEntryForKey(str) => match other {
372                Assertion::HasNonnullEntryForKey(other_str) => other_str == str,
373                _ => false,
374            },
375            Assertion::InArray(union) => match other {
376                Assertion::NotInArray(other_union) => other_union == union,
377                _ => false,
378            },
379            Assertion::NotInArray(union) => match other {
380                Assertion::InArray(other_union) => other_union == union,
381                _ => false,
382            },
383            Assertion::Empty => matches!(other, Assertion::NonEmpty),
384            Assertion::NonEmpty => matches!(other, Assertion::Empty),
385            Assertion::NonEmptyCountable(negatable) => {
386                if *negatable {
387                    matches!(other, Assertion::EmptyCountable)
388                } else {
389                    false
390                }
391            }
392            Assertion::EmptyCountable => matches!(other, Assertion::NonEmptyCountable(true)),
393            Assertion::HasExactCount(number) => match other {
394                Assertion::DoesNotHaveExactCount(other_number) => other_number == number,
395                _ => false,
396            },
397            Assertion::DoesNotHaveExactCount(number) => match other {
398                Assertion::HasExactCount(other_number) => other_number == number,
399                _ => false,
400            },
401            Assertion::HasAtLeastCount(number) => match other {
402                Assertion::HasAtMostCount(other_number) => other_number == number,
403                _ => false,
404            },
405            Assertion::HasAtMostCount(number) => match other {
406                Assertion::HasAtLeastCount(other_number) => other_number == number,
407                _ => false,
408            },
409            Assertion::IsLessThan(number) => match other {
410                Assertion::IsGreaterThanOrEqual(other_number) => other_number == number,
411                _ => false,
412            },
413            Assertion::IsLessThanOrEqual(number) => match other {
414                Assertion::IsGreaterThan(other_number) => other_number == number,
415                _ => false,
416            },
417            Assertion::IsGreaterThan(number) => match other {
418                Assertion::IsLessThanOrEqual(other_number) => other_number == number,
419                _ => false,
420            },
421            Assertion::IsGreaterThanOrEqual(number) => match other {
422                Assertion::IsLessThan(other_number) => other_number == number,
423                _ => false,
424            },
425            Assertion::Countable => matches!(other, Assertion::NotCountable(negatable) if *negatable),
426            Assertion::NotCountable(_) => matches!(other, Assertion::Countable),
427        }
428    }
429
430    pub fn get_negation(&self) -> Self {
431        match self {
432            Assertion::Any => Assertion::Any,
433            Assertion::Falsy => Assertion::Truthy,
434            Assertion::IsType(atomic) => Assertion::IsNotType(atomic.clone()),
435            Assertion::IsNotType(atomic) => Assertion::IsType(atomic.clone()),
436            Assertion::Truthy => Assertion::Falsy,
437            Assertion::IsIdentical(atomic) => Assertion::IsNotIdentical(atomic.clone()),
438            Assertion::IsNotIdentical(atomic) => Assertion::IsIdentical(atomic.clone()),
439            Assertion::IsEqual(atomic) => Assertion::IsNotEqual(atomic.clone()),
440            Assertion::IsNotEqual(atomic) => Assertion::IsEqual(atomic.clone()),
441            Assertion::IsIsset => Assertion::IsNotIsset,
442            Assertion::IsNotIsset => Assertion::IsIsset,
443            Assertion::Empty => Assertion::NonEmpty,
444            Assertion::NonEmpty => Assertion::Empty,
445            Assertion::NonEmptyCountable(negatable) => {
446                if *negatable {
447                    Assertion::EmptyCountable
448                } else {
449                    Assertion::Any
450                }
451            }
452            Assertion::EmptyCountable => Assertion::NonEmptyCountable(true),
453            Assertion::ArrayKeyExists => Assertion::ArrayKeyDoesNotExist,
454            Assertion::ArrayKeyDoesNotExist => Assertion::ArrayKeyExists,
455            Assertion::InArray(union) => Assertion::NotInArray(union.clone()),
456            Assertion::NotInArray(union) => Assertion::InArray(union.clone()),
457            Assertion::HasExactCount(size) => Assertion::DoesNotHaveExactCount(*size),
458            Assertion::DoesNotHaveExactCount(size) => Assertion::HasExactCount(*size),
459            Assertion::HasAtLeastCount(size) => Assertion::HasAtMostCount(*size),
460            Assertion::HasAtMostCount(size) => Assertion::HasAtLeastCount(*size),
461            Assertion::HasArrayKey(str) => Assertion::DoesNotHaveArrayKey(*str),
462            Assertion::DoesNotHaveArrayKey(str) => Assertion::HasArrayKey(*str),
463            Assertion::HasNonnullEntryForKey(str) => Assertion::DoesNotHaveNonnullEntryForKey(*str),
464            Assertion::DoesNotHaveNonnullEntryForKey(str) => Assertion::HasNonnullEntryForKey(*str),
465            Assertion::HasStringArrayAccess => Assertion::Any,
466            Assertion::HasIntOrStringArrayAccess => Assertion::Any,
467            Assertion::IsEqualIsset => Assertion::Any,
468            Assertion::IsLessThan(number) => Assertion::IsGreaterThanOrEqual(*number),
469            Assertion::IsLessThanOrEqual(number) => Assertion::IsGreaterThan(*number),
470            Assertion::IsGreaterThan(number) => Assertion::IsLessThanOrEqual(*number),
471            Assertion::IsGreaterThanOrEqual(number) => Assertion::IsLessThan(*number),
472            Assertion::Countable => Assertion::NotCountable(true),
473            Assertion::NotCountable(_) => Assertion::Countable,
474        }
475    }
476}