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