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