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    DoesNotHasAtLeastCount(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::DoesNotHasAtLeastCount(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::DoesNotHasAtLeastCount(_)
132                | Assertion::EmptyCountable
133                | Assertion::Empty
134                | Assertion::NotCountable(_)
135        )
136    }
137
138    pub fn has_isset(&self) -> bool {
139        matches!(
140            self,
141            Assertion::IsIsset | Assertion::ArrayKeyExists | Assertion::HasStringArrayAccess | Assertion::IsEqualIsset
142        )
143    }
144
145    pub fn has_non_isset_equality(&self) -> bool {
146        matches!(
147            self,
148            Assertion::InArray(_)
149                | Assertion::HasIntOrStringArrayAccess
150                | Assertion::HasStringArrayAccess
151                | Assertion::IsIdentical(_)
152                | Assertion::IsEqual(_)
153        )
154    }
155
156    pub fn has_equality(&self) -> bool {
157        matches!(
158            self,
159            Assertion::InArray(_)
160                | Assertion::HasIntOrStringArrayAccess
161                | Assertion::HasStringArrayAccess
162                | Assertion::IsEqualIsset
163                | Assertion::IsIdentical(_)
164                | Assertion::IsNotIdentical(_)
165                | Assertion::IsEqual(_)
166                | Assertion::IsNotEqual(_)
167                | Assertion::HasExactCount(_)
168        )
169    }
170
171    pub fn has_literal_value(&self) -> bool {
172        match self {
173            Assertion::IsIdentical(atomic)
174            | Assertion::IsNotIdentical(atomic)
175            | Assertion::IsType(atomic)
176            | Assertion::IsNotType(atomic)
177            | Assertion::IsEqual(atomic)
178            | Assertion::IsNotEqual(atomic) => {
179                atomic.is_literal_int()
180                    || atomic.is_literal_float()
181                    || atomic.is_known_literal_string()
182                    || atomic.is_literal_class_string()
183            }
184
185            _ => false,
186        }
187    }
188
189    pub fn has_integer(&self) -> bool {
190        match self {
191            Assertion::IsIdentical(atomic)
192            | Assertion::IsNotIdentical(atomic)
193            | Assertion::IsType(atomic)
194            | Assertion::IsNotType(atomic)
195            | Assertion::IsEqual(atomic)
196            | Assertion::IsNotEqual(atomic) => atomic.is_int(),
197            _ => false,
198        }
199    }
200
201    pub fn has_literal_string(&self) -> bool {
202        match self {
203            Assertion::IsIdentical(atomic)
204            | Assertion::IsNotIdentical(atomic)
205            | Assertion::IsType(atomic)
206            | Assertion::IsNotType(atomic)
207            | Assertion::IsEqual(atomic)
208            | Assertion::IsNotEqual(atomic) => atomic.is_known_literal_string(),
209
210            _ => false,
211        }
212    }
213
214    pub fn has_literal_int(&self) -> bool {
215        match self {
216            Assertion::IsIdentical(atomic)
217            | Assertion::IsNotIdentical(atomic)
218            | Assertion::IsType(atomic)
219            | Assertion::IsNotType(atomic)
220            | Assertion::IsEqual(atomic)
221            | Assertion::IsNotEqual(atomic) => atomic.is_literal_int(),
222
223            _ => false,
224        }
225    }
226
227    pub fn has_literal_float(&self) -> bool {
228        match self {
229            Assertion::IsIdentical(atomic)
230            | Assertion::IsNotIdentical(atomic)
231            | Assertion::IsType(atomic)
232            | Assertion::IsNotType(atomic)
233            | Assertion::IsEqual(atomic)
234            | Assertion::IsNotEqual(atomic) => atomic.is_literal_float(),
235
236            _ => false,
237        }
238    }
239
240    pub fn with_type(&self, atomic: TAtomic) -> Self {
241        match self {
242            Assertion::IsType(_) => Assertion::IsType(atomic),
243            Assertion::IsNotType(_) => Assertion::IsNotType(atomic),
244            Assertion::IsIdentical(_) => Assertion::IsIdentical(atomic),
245            Assertion::IsNotIdentical(_) => Assertion::IsNotIdentical(atomic),
246            Assertion::IsEqual(_) => Assertion::IsEqual(atomic),
247            Assertion::IsNotEqual(_) => Assertion::IsNotEqual(atomic),
248            _ => self.clone(),
249        }
250    }
251
252    pub fn get_type(&self) -> Option<&TAtomic> {
253        match self {
254            Assertion::IsIdentical(atomic)
255            | Assertion::IsNotIdentical(atomic)
256            | Assertion::IsType(atomic)
257            | Assertion::IsNotType(atomic)
258            | Assertion::IsEqual(atomic)
259            | Assertion::IsNotEqual(atomic) => Some(atomic),
260            _ => None,
261        }
262    }
263
264    pub fn get_type_mut(&mut self) -> Option<&mut TAtomic> {
265        match self {
266            Assertion::IsIdentical(atomic)
267            | Assertion::IsNotIdentical(atomic)
268            | Assertion::IsType(atomic)
269            | Assertion::IsNotType(atomic)
270            | Assertion::IsEqual(atomic)
271            | Assertion::IsNotEqual(atomic) => Some(atomic),
272            _ => None,
273        }
274    }
275
276    pub fn resolve_templates(&self, codebase: &CodebaseMetadata, template_result: &TemplateResult) -> Vec<Self> {
277        match self {
278            Assertion::IsType(atomic) => {
279                let union = TUnion::from_single(Cow::Owned(atomic.clone()));
280                let resolved_union = inferred_type_replacer::replace(&union, template_result, codebase);
281
282                let mut result = vec![];
283                for resolved_atomic in resolved_union.types.into_owned() {
284                    result.push(Assertion::IsType(resolved_atomic));
285                }
286
287                if result.is_empty() {
288                    result.push(Assertion::IsType(TAtomic::Never));
289                }
290
291                result
292            }
293            Assertion::IsNotType(atomic) => {
294                let union = TUnion::from_single(Cow::Owned(atomic.clone()));
295                let resolved_union = inferred_type_replacer::replace(&union, template_result, codebase);
296
297                let mut result = vec![];
298                for resolved_atomic in resolved_union.types.into_owned() {
299                    result.push(Assertion::IsNotType(resolved_atomic));
300                }
301
302                if result.is_empty() {
303                    result.push(Assertion::IsNotType(TAtomic::Never));
304                }
305
306                result
307            }
308            Assertion::InArray(union) => {
309                let resolved_union = inferred_type_replacer::replace(union, template_result, codebase);
310
311                vec![Assertion::InArray(resolved_union)]
312            }
313            Assertion::NotInArray(union) => {
314                let resolved_union = inferred_type_replacer::replace(union, template_result, codebase);
315
316                vec![Assertion::NotInArray(resolved_union)]
317            }
318            _ => {
319                vec![self.clone()]
320            }
321        }
322    }
323
324    pub fn is_negation_of(&self, other: &Assertion) -> bool {
325        match self {
326            Assertion::Any => false,
327            Assertion::Falsy => matches!(other, Assertion::Truthy),
328            Assertion::Truthy => matches!(other, Assertion::Falsy),
329            Assertion::IsType(atomic) => match other {
330                Assertion::IsNotType(other_atomic) => other_atomic == atomic,
331                _ => false,
332            },
333            Assertion::IsNotType(atomic) => match other {
334                Assertion::IsType(other_atomic) => other_atomic == atomic,
335                _ => false,
336            },
337            Assertion::IsIdentical(atomic) => match other {
338                Assertion::IsNotIdentical(other_atomic) => other_atomic == atomic,
339                _ => false,
340            },
341            Assertion::IsNotIdentical(atomic) => match other {
342                Assertion::IsIdentical(other_atomic) => other_atomic == atomic,
343                _ => false,
344            },
345            Assertion::IsEqual(atomic) => match other {
346                Assertion::IsNotEqual(other_atomic) => other_atomic == atomic,
347                _ => false,
348            },
349            Assertion::IsNotEqual(atomic) => match other {
350                Assertion::IsEqual(other_atomic) => other_atomic == atomic,
351                _ => false,
352            },
353            Assertion::IsEqualIsset => false,
354            Assertion::IsIsset => matches!(other, Assertion::IsNotIsset),
355            Assertion::IsNotIsset => matches!(other, Assertion::IsIsset),
356            Assertion::HasStringArrayAccess => false,
357            Assertion::HasIntOrStringArrayAccess => false,
358            Assertion::ArrayKeyExists => matches!(other, Assertion::ArrayKeyDoesNotExist),
359            Assertion::ArrayKeyDoesNotExist => matches!(other, Assertion::ArrayKeyExists),
360            Assertion::HasArrayKey(str) => match other {
361                Assertion::DoesNotHaveArrayKey(other_str) => other_str == str,
362                _ => false,
363            },
364            Assertion::DoesNotHaveArrayKey(str) => match other {
365                Assertion::HasArrayKey(other_str) => other_str == str,
366                _ => false,
367            },
368            Assertion::HasNonnullEntryForKey(str) => match other {
369                Assertion::DoesNotHaveNonnullEntryForKey(other_str) => other_str == str,
370                _ => false,
371            },
372            Assertion::DoesNotHaveNonnullEntryForKey(str) => match other {
373                Assertion::HasNonnullEntryForKey(other_str) => other_str == str,
374                _ => false,
375            },
376            Assertion::InArray(union) => match other {
377                Assertion::NotInArray(other_union) => other_union == union,
378                _ => false,
379            },
380            Assertion::NotInArray(union) => match other {
381                Assertion::InArray(other_union) => other_union == union,
382                _ => false,
383            },
384            Assertion::Empty => matches!(other, Assertion::NonEmpty),
385            Assertion::NonEmpty => matches!(other, Assertion::Empty),
386            Assertion::NonEmptyCountable(negatable) => {
387                if *negatable {
388                    matches!(other, Assertion::EmptyCountable)
389                } else {
390                    false
391                }
392            }
393            Assertion::EmptyCountable => matches!(other, Assertion::NonEmptyCountable(true)),
394            Assertion::HasExactCount(number) => match other {
395                Assertion::DoesNotHaveExactCount(other_number) => other_number == number,
396                _ => false,
397            },
398            Assertion::DoesNotHaveExactCount(number) => match other {
399                Assertion::HasExactCount(other_number) => other_number == number,
400                _ => false,
401            },
402            Assertion::HasAtLeastCount(number) => match other {
403                Assertion::DoesNotHasAtLeastCount(other_number) => other_number == number,
404                _ => false,
405            },
406            Assertion::DoesNotHasAtLeastCount(number) => match other {
407                Assertion::HasAtLeastCount(other_number) => other_number == number,
408                _ => false,
409            },
410            Assertion::IsLessThan(number) => match other {
411                Assertion::IsGreaterThanOrEqual(other_number) => other_number == number,
412                _ => false,
413            },
414            Assertion::IsLessThanOrEqual(number) => match other {
415                Assertion::IsGreaterThan(other_number) => other_number == number,
416                _ => false,
417            },
418            Assertion::IsGreaterThan(number) => match other {
419                Assertion::IsLessThanOrEqual(other_number) => other_number == number,
420                _ => false,
421            },
422            Assertion::IsGreaterThanOrEqual(number) => match other {
423                Assertion::IsLessThan(other_number) => other_number == number,
424                _ => false,
425            },
426            Assertion::Countable => matches!(other, Assertion::NotCountable(negatable) if *negatable),
427            Assertion::NotCountable(_) => matches!(other, Assertion::Countable),
428        }
429    }
430
431    pub fn get_negation(&self) -> Self {
432        match self {
433            Assertion::Any => Assertion::Any,
434            Assertion::Falsy => Assertion::Truthy,
435            Assertion::IsType(atomic) => Assertion::IsNotType(atomic.clone()),
436            Assertion::IsNotType(atomic) => Assertion::IsType(atomic.clone()),
437            Assertion::Truthy => Assertion::Falsy,
438            Assertion::IsIdentical(atomic) => Assertion::IsNotIdentical(atomic.clone()),
439            Assertion::IsNotIdentical(atomic) => Assertion::IsIdentical(atomic.clone()),
440            Assertion::IsEqual(atomic) => Assertion::IsNotEqual(atomic.clone()),
441            Assertion::IsNotEqual(atomic) => Assertion::IsEqual(atomic.clone()),
442            Assertion::IsIsset => Assertion::IsNotIsset,
443            Assertion::IsNotIsset => Assertion::IsIsset,
444            Assertion::Empty => Assertion::NonEmpty,
445            Assertion::NonEmpty => Assertion::Empty,
446            Assertion::NonEmptyCountable(negatable) => {
447                if *negatable {
448                    Assertion::EmptyCountable
449                } else {
450                    Assertion::Any
451                }
452            }
453            Assertion::EmptyCountable => Assertion::NonEmptyCountable(true),
454            Assertion::ArrayKeyExists => Assertion::ArrayKeyDoesNotExist,
455            Assertion::ArrayKeyDoesNotExist => Assertion::ArrayKeyExists,
456            Assertion::InArray(union) => Assertion::NotInArray(union.clone()),
457            Assertion::NotInArray(union) => Assertion::InArray(union.clone()),
458            Assertion::HasExactCount(size) => Assertion::DoesNotHaveExactCount(*size),
459            Assertion::DoesNotHaveExactCount(size) => Assertion::HasExactCount(*size),
460            Assertion::HasAtLeastCount(size) => Assertion::DoesNotHasAtLeastCount(*size),
461            Assertion::DoesNotHasAtLeastCount(size) => Assertion::HasAtLeastCount(*size),
462            Assertion::HasArrayKey(str) => Assertion::DoesNotHaveArrayKey(*str),
463            Assertion::DoesNotHaveArrayKey(str) => Assertion::HasArrayKey(*str),
464            Assertion::HasNonnullEntryForKey(str) => Assertion::DoesNotHaveNonnullEntryForKey(*str),
465            Assertion::DoesNotHaveNonnullEntryForKey(str) => Assertion::HasNonnullEntryForKey(*str),
466            Assertion::HasStringArrayAccess => Assertion::Any,
467            Assertion::HasIntOrStringArrayAccess => Assertion::Any,
468            Assertion::IsEqualIsset => Assertion::Any,
469            Assertion::IsLessThan(number) => Assertion::IsGreaterThanOrEqual(*number),
470            Assertion::IsLessThanOrEqual(number) => Assertion::IsGreaterThan(*number),
471            Assertion::IsGreaterThan(number) => Assertion::IsLessThanOrEqual(*number),
472            Assertion::IsGreaterThanOrEqual(number) => Assertion::IsLessThan(*number),
473            Assertion::Countable => Assertion::NotCountable(true),
474            Assertion::NotCountable(_) => Assertion::Countable,
475        }
476    }
477}