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