Skip to main content

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