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}