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