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}