Skip to main content

mago_codex/
assertion.rs

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