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}