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 DoesNotHasAtLeastCount(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::DoesNotHasAtLeastCount(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::DoesNotHasAtLeastCount(_)
132 | Assertion::EmptyCountable
133 | Assertion::Empty
134 | Assertion::NotCountable(_)
135 )
136 }
137
138 pub fn has_isset(&self) -> bool {
139 matches!(
140 self,
141 Assertion::IsIsset | Assertion::ArrayKeyExists | Assertion::HasStringArrayAccess | Assertion::IsEqualIsset
142 )
143 }
144
145 pub fn has_non_isset_equality(&self) -> bool {
146 matches!(
147 self,
148 Assertion::InArray(_)
149 | Assertion::HasIntOrStringArrayAccess
150 | Assertion::HasStringArrayAccess
151 | Assertion::IsIdentical(_)
152 | Assertion::IsEqual(_)
153 )
154 }
155
156 pub fn has_equality(&self) -> bool {
157 matches!(
158 self,
159 Assertion::InArray(_)
160 | Assertion::HasIntOrStringArrayAccess
161 | Assertion::HasStringArrayAccess
162 | Assertion::IsEqualIsset
163 | Assertion::IsIdentical(_)
164 | Assertion::IsNotIdentical(_)
165 | Assertion::IsEqual(_)
166 | Assertion::IsNotEqual(_)
167 | Assertion::HasExactCount(_)
168 )
169 }
170
171 pub fn has_literal_value(&self) -> bool {
172 match self {
173 Assertion::IsIdentical(atomic)
174 | Assertion::IsNotIdentical(atomic)
175 | Assertion::IsType(atomic)
176 | Assertion::IsNotType(atomic)
177 | Assertion::IsEqual(atomic)
178 | Assertion::IsNotEqual(atomic) => {
179 atomic.is_literal_int()
180 || atomic.is_literal_float()
181 || atomic.is_known_literal_string()
182 || atomic.is_literal_class_string()
183 }
184
185 _ => false,
186 }
187 }
188
189 pub fn has_integer(&self) -> bool {
190 match self {
191 Assertion::IsIdentical(atomic)
192 | Assertion::IsNotIdentical(atomic)
193 | Assertion::IsType(atomic)
194 | Assertion::IsNotType(atomic)
195 | Assertion::IsEqual(atomic)
196 | Assertion::IsNotEqual(atomic) => atomic.is_int(),
197 _ => false,
198 }
199 }
200
201 pub fn has_literal_string(&self) -> bool {
202 match self {
203 Assertion::IsIdentical(atomic)
204 | Assertion::IsNotIdentical(atomic)
205 | Assertion::IsType(atomic)
206 | Assertion::IsNotType(atomic)
207 | Assertion::IsEqual(atomic)
208 | Assertion::IsNotEqual(atomic) => atomic.is_known_literal_string(),
209
210 _ => false,
211 }
212 }
213
214 pub fn has_literal_int(&self) -> bool {
215 match self {
216 Assertion::IsIdentical(atomic)
217 | Assertion::IsNotIdentical(atomic)
218 | Assertion::IsType(atomic)
219 | Assertion::IsNotType(atomic)
220 | Assertion::IsEqual(atomic)
221 | Assertion::IsNotEqual(atomic) => atomic.is_literal_int(),
222
223 _ => false,
224 }
225 }
226
227 pub fn has_literal_float(&self) -> bool {
228 match self {
229 Assertion::IsIdentical(atomic)
230 | Assertion::IsNotIdentical(atomic)
231 | Assertion::IsType(atomic)
232 | Assertion::IsNotType(atomic)
233 | Assertion::IsEqual(atomic)
234 | Assertion::IsNotEqual(atomic) => atomic.is_literal_float(),
235
236 _ => false,
237 }
238 }
239
240 pub fn with_type(&self, atomic: TAtomic) -> Self {
241 match self {
242 Assertion::IsType(_) => Assertion::IsType(atomic),
243 Assertion::IsNotType(_) => Assertion::IsNotType(atomic),
244 Assertion::IsIdentical(_) => Assertion::IsIdentical(atomic),
245 Assertion::IsNotIdentical(_) => Assertion::IsNotIdentical(atomic),
246 Assertion::IsEqual(_) => Assertion::IsEqual(atomic),
247 Assertion::IsNotEqual(_) => Assertion::IsNotEqual(atomic),
248 _ => self.clone(),
249 }
250 }
251
252 pub fn get_type(&self) -> Option<&TAtomic> {
253 match self {
254 Assertion::IsIdentical(atomic)
255 | Assertion::IsNotIdentical(atomic)
256 | Assertion::IsType(atomic)
257 | Assertion::IsNotType(atomic)
258 | Assertion::IsEqual(atomic)
259 | Assertion::IsNotEqual(atomic) => Some(atomic),
260 _ => None,
261 }
262 }
263
264 pub fn get_type_mut(&mut self) -> Option<&mut 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 resolve_templates(&self, codebase: &CodebaseMetadata, template_result: &TemplateResult) -> Vec<Self> {
277 match self {
278 Assertion::IsType(atomic) => {
279 let union = TUnion::from_single(Cow::Owned(atomic.clone()));
280 let resolved_union = inferred_type_replacer::replace(&union, template_result, codebase);
281
282 let mut result = vec![];
283 for resolved_atomic in resolved_union.types.into_owned() {
284 result.push(Assertion::IsType(resolved_atomic));
285 }
286
287 if result.is_empty() {
288 result.push(Assertion::IsType(TAtomic::Never));
289 }
290
291 result
292 }
293 Assertion::IsNotType(atomic) => {
294 let union = TUnion::from_single(Cow::Owned(atomic.clone()));
295 let resolved_union = inferred_type_replacer::replace(&union, template_result, codebase);
296
297 let mut result = vec![];
298 for resolved_atomic in resolved_union.types.into_owned() {
299 result.push(Assertion::IsNotType(resolved_atomic));
300 }
301
302 if result.is_empty() {
303 result.push(Assertion::IsNotType(TAtomic::Never));
304 }
305
306 result
307 }
308 Assertion::InArray(union) => {
309 let resolved_union = inferred_type_replacer::replace(union, template_result, codebase);
310
311 vec![Assertion::InArray(resolved_union)]
312 }
313 Assertion::NotInArray(union) => {
314 let resolved_union = inferred_type_replacer::replace(union, template_result, codebase);
315
316 vec![Assertion::NotInArray(resolved_union)]
317 }
318 _ => {
319 vec![self.clone()]
320 }
321 }
322 }
323
324 pub fn is_negation_of(&self, other: &Assertion) -> bool {
325 match self {
326 Assertion::Any => false,
327 Assertion::Falsy => matches!(other, Assertion::Truthy),
328 Assertion::Truthy => matches!(other, Assertion::Falsy),
329 Assertion::IsType(atomic) => match other {
330 Assertion::IsNotType(other_atomic) => other_atomic == atomic,
331 _ => false,
332 },
333 Assertion::IsNotType(atomic) => match other {
334 Assertion::IsType(other_atomic) => other_atomic == atomic,
335 _ => false,
336 },
337 Assertion::IsIdentical(atomic) => match other {
338 Assertion::IsNotIdentical(other_atomic) => other_atomic == atomic,
339 _ => false,
340 },
341 Assertion::IsNotIdentical(atomic) => match other {
342 Assertion::IsIdentical(other_atomic) => other_atomic == atomic,
343 _ => false,
344 },
345 Assertion::IsEqual(atomic) => match other {
346 Assertion::IsNotEqual(other_atomic) => other_atomic == atomic,
347 _ => false,
348 },
349 Assertion::IsNotEqual(atomic) => match other {
350 Assertion::IsEqual(other_atomic) => other_atomic == atomic,
351 _ => false,
352 },
353 Assertion::IsEqualIsset => false,
354 Assertion::IsIsset => matches!(other, Assertion::IsNotIsset),
355 Assertion::IsNotIsset => matches!(other, Assertion::IsIsset),
356 Assertion::HasStringArrayAccess => false,
357 Assertion::HasIntOrStringArrayAccess => false,
358 Assertion::ArrayKeyExists => matches!(other, Assertion::ArrayKeyDoesNotExist),
359 Assertion::ArrayKeyDoesNotExist => matches!(other, Assertion::ArrayKeyExists),
360 Assertion::HasArrayKey(str) => match other {
361 Assertion::DoesNotHaveArrayKey(other_str) => other_str == str,
362 _ => false,
363 },
364 Assertion::DoesNotHaveArrayKey(str) => match other {
365 Assertion::HasArrayKey(other_str) => other_str == str,
366 _ => false,
367 },
368 Assertion::HasNonnullEntryForKey(str) => match other {
369 Assertion::DoesNotHaveNonnullEntryForKey(other_str) => other_str == str,
370 _ => false,
371 },
372 Assertion::DoesNotHaveNonnullEntryForKey(str) => match other {
373 Assertion::HasNonnullEntryForKey(other_str) => other_str == str,
374 _ => false,
375 },
376 Assertion::InArray(union) => match other {
377 Assertion::NotInArray(other_union) => other_union == union,
378 _ => false,
379 },
380 Assertion::NotInArray(union) => match other {
381 Assertion::InArray(other_union) => other_union == union,
382 _ => false,
383 },
384 Assertion::Empty => matches!(other, Assertion::NonEmpty),
385 Assertion::NonEmpty => matches!(other, Assertion::Empty),
386 Assertion::NonEmptyCountable(negatable) => {
387 if *negatable {
388 matches!(other, Assertion::EmptyCountable)
389 } else {
390 false
391 }
392 }
393 Assertion::EmptyCountable => matches!(other, Assertion::NonEmptyCountable(true)),
394 Assertion::HasExactCount(number) => match other {
395 Assertion::DoesNotHaveExactCount(other_number) => other_number == number,
396 _ => false,
397 },
398 Assertion::DoesNotHaveExactCount(number) => match other {
399 Assertion::HasExactCount(other_number) => other_number == number,
400 _ => false,
401 },
402 Assertion::HasAtLeastCount(number) => match other {
403 Assertion::DoesNotHasAtLeastCount(other_number) => other_number == number,
404 _ => false,
405 },
406 Assertion::DoesNotHasAtLeastCount(number) => match other {
407 Assertion::HasAtLeastCount(other_number) => other_number == number,
408 _ => false,
409 },
410 Assertion::IsLessThan(number) => match other {
411 Assertion::IsGreaterThanOrEqual(other_number) => other_number == number,
412 _ => false,
413 },
414 Assertion::IsLessThanOrEqual(number) => match other {
415 Assertion::IsGreaterThan(other_number) => other_number == number,
416 _ => false,
417 },
418 Assertion::IsGreaterThan(number) => match other {
419 Assertion::IsLessThanOrEqual(other_number) => other_number == number,
420 _ => false,
421 },
422 Assertion::IsGreaterThanOrEqual(number) => match other {
423 Assertion::IsLessThan(other_number) => other_number == number,
424 _ => false,
425 },
426 Assertion::Countable => matches!(other, Assertion::NotCountable(negatable) if *negatable),
427 Assertion::NotCountable(_) => matches!(other, Assertion::Countable),
428 }
429 }
430
431 pub fn get_negation(&self) -> Self {
432 match self {
433 Assertion::Any => Assertion::Any,
434 Assertion::Falsy => Assertion::Truthy,
435 Assertion::IsType(atomic) => Assertion::IsNotType(atomic.clone()),
436 Assertion::IsNotType(atomic) => Assertion::IsType(atomic.clone()),
437 Assertion::Truthy => Assertion::Falsy,
438 Assertion::IsIdentical(atomic) => Assertion::IsNotIdentical(atomic.clone()),
439 Assertion::IsNotIdentical(atomic) => Assertion::IsIdentical(atomic.clone()),
440 Assertion::IsEqual(atomic) => Assertion::IsNotEqual(atomic.clone()),
441 Assertion::IsNotEqual(atomic) => Assertion::IsEqual(atomic.clone()),
442 Assertion::IsIsset => Assertion::IsNotIsset,
443 Assertion::IsNotIsset => Assertion::IsIsset,
444 Assertion::Empty => Assertion::NonEmpty,
445 Assertion::NonEmpty => Assertion::Empty,
446 Assertion::NonEmptyCountable(negatable) => {
447 if *negatable {
448 Assertion::EmptyCountable
449 } else {
450 Assertion::Any
451 }
452 }
453 Assertion::EmptyCountable => Assertion::NonEmptyCountable(true),
454 Assertion::ArrayKeyExists => Assertion::ArrayKeyDoesNotExist,
455 Assertion::ArrayKeyDoesNotExist => Assertion::ArrayKeyExists,
456 Assertion::InArray(union) => Assertion::NotInArray(union.clone()),
457 Assertion::NotInArray(union) => Assertion::InArray(union.clone()),
458 Assertion::HasExactCount(size) => Assertion::DoesNotHaveExactCount(*size),
459 Assertion::DoesNotHaveExactCount(size) => Assertion::HasExactCount(*size),
460 Assertion::HasAtLeastCount(size) => Assertion::DoesNotHasAtLeastCount(*size),
461 Assertion::DoesNotHasAtLeastCount(size) => Assertion::HasAtLeastCount(*size),
462 Assertion::HasArrayKey(str) => Assertion::DoesNotHaveArrayKey(*str),
463 Assertion::DoesNotHaveArrayKey(str) => Assertion::HasArrayKey(*str),
464 Assertion::HasNonnullEntryForKey(str) => Assertion::DoesNotHaveNonnullEntryForKey(*str),
465 Assertion::DoesNotHaveNonnullEntryForKey(str) => Assertion::HasNonnullEntryForKey(*str),
466 Assertion::HasStringArrayAccess => Assertion::Any,
467 Assertion::HasIntOrStringArrayAccess => Assertion::Any,
468 Assertion::IsEqualIsset => Assertion::Any,
469 Assertion::IsLessThan(number) => Assertion::IsGreaterThanOrEqual(*number),
470 Assertion::IsLessThanOrEqual(number) => Assertion::IsGreaterThan(*number),
471 Assertion::IsGreaterThan(number) => Assertion::IsLessThanOrEqual(*number),
472 Assertion::IsGreaterThanOrEqual(number) => Assertion::IsLessThan(*number),
473 Assertion::Countable => Assertion::NotCountable(true),
474 Assertion::NotCountable(_) => Assertion::Countable,
475 }
476 }
477}