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