1use std::hash::Hash;
2use std::hash::Hasher;
3
4use ahash::AHasher;
5use serde::Deserialize;
6use serde::Serialize;
7
8use mago_interner::ThreadedInterner;
9
10use crate::metadata::CodebaseMetadata;
11use crate::ttype::TType;
12use crate::ttype::atomic::TAtomic;
13use crate::ttype::atomic::array::key::ArrayKey;
14use crate::ttype::template::TemplateResult;
15use crate::ttype::template::inferred_type_replacer;
16use crate::ttype::union::TUnion;
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
19pub enum Assertion {
20 Any,
21 IsType(TAtomic),
22 IsNotType(TAtomic),
23 Falsy,
24 Truthy,
25 IsIdentical(TAtomic),
26 IsNotIdentical(TAtomic),
27 IsEqual(TAtomic),
28 IsNotEqual(TAtomic),
29 IsEqualIsset,
30 IsIsset,
31 IsNotIsset,
32 HasStringArrayAccess,
33 HasIntOrStringArrayAccess,
34 ArrayKeyExists,
35 ArrayKeyDoesNotExist,
36 InArray(TUnion),
37 NotInArray(TUnion),
38 HasArrayKey(ArrayKey),
39 DoesNotHaveArrayKey(ArrayKey),
40 HasNonnullEntryForKey(ArrayKey),
41 DoesNotHaveNonnullEntryForKey(ArrayKey),
42 Empty,
43 NonEmpty,
44 NonEmptyCountable(bool),
45 EmptyCountable,
46 HasExactCount(usize),
47 HasAtLeastCount(usize),
48 DoesNotHaveExactCount(usize),
49 HasAtMostCount(usize),
50 IsLessThan(i64),
51 IsLessThanOrEqual(i64),
52 IsGreaterThan(i64),
53 IsGreaterThanOrEqual(i64),
54 Countable,
55 NotCountable(bool),
56}
57
58impl Assertion {
59 pub fn as_string(&self, interner: Option<&ThreadedInterner>) -> String {
60 match self {
61 Assertion::Any => "any".to_string(),
62 Assertion::Falsy => "falsy".to_string(),
63 Assertion::Truthy => "truthy".to_string(),
64 Assertion::IsType(atomic) => atomic.get_id(interner),
65 Assertion::IsNotType(atomic) => "!".to_string() + &atomic.get_id(interner),
66 Assertion::IsIdentical(atomic) => "=".to_string() + &atomic.get_id(interner),
67 Assertion::IsNotIdentical(atomic) => "!=".to_string() + &atomic.get_id(interner),
68 Assertion::IsEqual(atomic) => "~".to_string() + &atomic.get_id(interner),
69 Assertion::IsNotEqual(atomic) => "!~".to_string() + &atomic.get_id(interner),
70 Assertion::IsEqualIsset => "=isset".to_string(),
71 Assertion::IsIsset => "isset".to_string(),
72 Assertion::IsNotIsset => "!isset".to_string(),
73 Assertion::HasStringArrayAccess => "=string-array-access".to_string(),
74 Assertion::HasIntOrStringArrayAccess => "=int-or-string-array-access".to_string(),
75 Assertion::ArrayKeyExists => "array-key-exists".to_string(),
76 Assertion::ArrayKeyDoesNotExist => "!array-key-exists".to_string(),
77 Assertion::HasArrayKey(key) => "=has-array-key-".to_string() + key.to_string().as_str(),
78 Assertion::DoesNotHaveArrayKey(key) => "!=has-array-key-".to_string() + key.to_string().as_str(),
79 Assertion::HasNonnullEntryForKey(key) => "=has-nonnull-entry-for-".to_string() + key.to_string().as_str(),
80 Assertion::DoesNotHaveNonnullEntryForKey(key) => {
81 "!=has-nonnull-entry-for-".to_string() + key.to_string().as_str()
82 }
83 Assertion::InArray(union) => "=in-array-".to_string() + &union.get_id(interner),
84 Assertion::NotInArray(union) => "!=in-array-".to_string() + &union.get_id(interner),
85 Assertion::NonEmptyCountable(negatable) => {
86 if *negatable {
87 "non-empty-countable".to_string()
88 } else {
89 "=non-empty-countable".to_string()
90 }
91 }
92 Assertion::EmptyCountable => "empty-countable".to_string(),
93 Assertion::HasExactCount(number) => "has-exactly-".to_string() + &number.to_string(),
94 Assertion::HasAtLeastCount(number) => "has-at-least-".to_string() + &number.to_string(),
95 Assertion::DoesNotHaveExactCount(number) => "!has-exactly-".to_string() + &number.to_string(),
96 Assertion::HasAtMostCount(number) => "has-at-most-".to_string() + &number.to_string(),
97 Assertion::IsLessThan(number) => "is-less-than-".to_string() + &number.to_string(),
98 Assertion::IsLessThanOrEqual(number) => "is-less-than-or-equal-".to_string() + &number.to_string(),
99 Assertion::IsGreaterThan(number) => "is-greater-than-".to_string() + &number.to_string(),
100 Assertion::IsGreaterThanOrEqual(number) => "is-greater-than-or-equal-".to_string() + &number.to_string(),
101 Assertion::Empty => "empty".to_string(),
102 Assertion::NonEmpty => "non-empty".to_string(),
103 Assertion::Countable => "countable".to_string(),
104 Assertion::NotCountable(_) => "!countable".to_string(),
105 }
106 }
107
108 pub fn to_hash(&self) -> u64 {
109 let mut state = AHasher::default();
110 self.as_string(None).hash(&mut state);
111 state.finish()
112 }
113
114 pub fn is_negation(&self) -> bool {
115 matches!(
116 self,
117 Assertion::Falsy
118 | Assertion::IsNotType(_)
119 | Assertion::IsNotEqual(_)
120 | Assertion::IsNotIdentical(_)
121 | Assertion::IsNotIsset
122 | Assertion::NotInArray(..)
123 | Assertion::ArrayKeyDoesNotExist
124 | Assertion::DoesNotHaveArrayKey(_)
125 | Assertion::DoesNotHaveExactCount(_)
126 | Assertion::DoesNotHaveNonnullEntryForKey(_)
127 | Assertion::HasAtMostCount(_)
128 | Assertion::HasExactCount(_)
129 | Assertion::HasAtLeastCount(_)
130 | Assertion::EmptyCountable
131 | Assertion::Empty
132 | Assertion::NotCountable(_)
133 )
134 }
135
136 pub fn has_isset(&self) -> bool {
137 matches!(
138 self,
139 Assertion::IsIsset | Assertion::ArrayKeyExists | Assertion::HasStringArrayAccess | Assertion::IsEqualIsset
140 )
141 }
142
143 pub fn has_non_isset_equality(&self) -> bool {
144 matches!(
145 self,
146 Assertion::InArray(_)
147 | Assertion::HasIntOrStringArrayAccess
148 | Assertion::HasStringArrayAccess
149 | Assertion::IsIdentical(_)
150 | Assertion::IsEqual(_)
151 )
152 }
153
154 pub fn has_equality(&self) -> bool {
155 matches!(
156 self,
157 Assertion::InArray(_)
158 | Assertion::HasIntOrStringArrayAccess
159 | Assertion::HasStringArrayAccess
160 | Assertion::IsEqualIsset
161 | Assertion::IsIdentical(_)
162 | Assertion::IsNotIdentical(_)
163 | Assertion::IsEqual(_)
164 | Assertion::IsNotEqual(_)
165 | Assertion::HasExactCount(_)
166 )
167 }
168
169 pub fn has_literal_value(&self) -> bool {
170 match self {
171 Assertion::IsIdentical(atomic)
172 | Assertion::IsNotIdentical(atomic)
173 | Assertion::IsType(atomic)
174 | Assertion::IsNotType(atomic)
175 | Assertion::IsEqual(atomic)
176 | Assertion::IsNotEqual(atomic) => {
177 atomic.is_literal_int() || atomic.is_literal_float() || atomic.is_known_literal_string()
178 }
179
180 _ => false,
181 }
182 }
183
184 pub fn has_integer(&self) -> bool {
185 match self {
186 Assertion::IsIdentical(atomic)
187 | Assertion::IsNotIdentical(atomic)
188 | Assertion::IsType(atomic)
189 | Assertion::IsNotType(atomic)
190 | Assertion::IsEqual(atomic)
191 | Assertion::IsNotEqual(atomic) => atomic.is_int(),
192 _ => false,
193 }
194 }
195
196 pub fn has_literal_string(&self) -> bool {
197 match self {
198 Assertion::IsIdentical(atomic)
199 | Assertion::IsNotIdentical(atomic)
200 | Assertion::IsType(atomic)
201 | Assertion::IsNotType(atomic)
202 | Assertion::IsEqual(atomic)
203 | Assertion::IsNotEqual(atomic) => atomic.is_known_literal_string(),
204
205 _ => false,
206 }
207 }
208
209 pub fn has_literal_int(&self) -> bool {
210 match self {
211 Assertion::IsIdentical(atomic)
212 | Assertion::IsNotIdentical(atomic)
213 | Assertion::IsType(atomic)
214 | Assertion::IsNotType(atomic)
215 | Assertion::IsEqual(atomic)
216 | Assertion::IsNotEqual(atomic) => atomic.is_literal_int(),
217
218 _ => false,
219 }
220 }
221
222 pub fn has_literal_float(&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_float(),
230
231 _ => false,
232 }
233 }
234
235 pub fn with_type(&self, atomic: TAtomic) -> Self {
236 match self {
237 Assertion::IsType(_) => Assertion::IsType(atomic),
238 Assertion::IsNotType(_) => Assertion::IsNotType(atomic),
239 Assertion::IsIdentical(_) => Assertion::IsIdentical(atomic),
240 Assertion::IsNotIdentical(_) => Assertion::IsNotIdentical(atomic),
241 Assertion::IsEqual(_) => Assertion::IsEqual(atomic),
242 Assertion::IsNotEqual(_) => Assertion::IsNotEqual(atomic),
243 _ => self.clone(),
244 }
245 }
246
247 pub fn get_type(&self) -> Option<&TAtomic> {
248 match self {
249 Assertion::IsIdentical(atomic)
250 | Assertion::IsNotIdentical(atomic)
251 | Assertion::IsType(atomic)
252 | Assertion::IsNotType(atomic)
253 | Assertion::IsEqual(atomic)
254 | Assertion::IsNotEqual(atomic) => Some(atomic),
255 _ => None,
256 }
257 }
258
259 pub fn get_type_mut(&mut self) -> Option<&mut TAtomic> {
260 match self {
261 Assertion::IsIdentical(atomic)
262 | Assertion::IsNotIdentical(atomic)
263 | Assertion::IsType(atomic)
264 | Assertion::IsNotType(atomic)
265 | Assertion::IsEqual(atomic)
266 | Assertion::IsNotEqual(atomic) => Some(atomic),
267 _ => None,
268 }
269 }
270
271 pub fn resolve_templates(
272 &self,
273 codebase: &CodebaseMetadata,
274 interner: &ThreadedInterner,
275 template_result: &TemplateResult,
276 ) -> Vec<Self> {
277 match self {
278 Assertion::IsType(atomic) => {
279 let union = TUnion::new(vec![atomic.clone()]);
280 let resolved_union = inferred_type_replacer::replace(&union, template_result, codebase, interner);
281
282 let mut result = vec![];
283 for resolved_atomic in resolved_union.types {
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::new(vec![atomic.clone()]);
295 let resolved_union = inferred_type_replacer::replace(&union, template_result, codebase, interner);
296
297 let mut result = vec![];
298 for resolved_atomic in resolved_union.types {
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, interner);
310
311 vec![Assertion::InArray(resolved_union)]
312 }
313 Assertion::NotInArray(union) => {
314 let resolved_union = inferred_type_replacer::replace(union, template_result, codebase, interner);
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::HasAtMostCount(other_number) => other_number == number,
404 _ => false,
405 },
406 Assertion::HasAtMostCount(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::HasAtMostCount(*size),
461 Assertion::HasAtMostCount(size) => Assertion::HasAtLeastCount(*size),
462 Assertion::HasArrayKey(str) => Assertion::DoesNotHaveArrayKey(str.clone()),
463 Assertion::DoesNotHaveArrayKey(str) => Assertion::HasArrayKey(str.clone()),
464 Assertion::HasNonnullEntryForKey(str) => Assertion::DoesNotHaveNonnullEntryForKey(str.clone()),
465 Assertion::DoesNotHaveNonnullEntryForKey(str) => Assertion::HasNonnullEntryForKey(str.clone()),
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}