1#![doc = include_str!("./../README.md")]
2
3use mago_span::Span;
4use mago_syntax_core::input::Input;
5
6use crate::ast::Type;
7use crate::error::ParseError;
8use crate::lexer::TypeLexer;
9
10pub mod ast;
11pub mod error;
12pub mod lexer;
13pub mod parser;
14pub mod token;
15
16pub fn parse_str(span: Span, input: &str) -> Result<Type<'_>, ParseError> {
33 let input = Input::anchored_at(input.as_bytes(), span.start);
35 let lexer = TypeLexer::new(input);
37 parser::construct(lexer)
39}
40
41#[cfg(test)]
42mod tests {
43 use mago_span::Span;
44
45 use crate::ast::*;
46
47 use super::*;
48
49 fn do_parse(input: &str) -> Result<Type<'_>, ParseError> {
50 parse_str(Span::dummy(0, input.len()), input)
51 }
52
53 #[test]
54 fn test_parse_simple_keyword() {
55 let result = do_parse("int");
56 assert!(result.is_ok());
57 match result.unwrap() {
58 Type::Int(k) => assert_eq!(k.value, "int"),
59 _ => panic!("Expected Type::Int"),
60 }
61 }
62
63 #[test]
64 fn test_parse_composite_keyword() {
65 let result = do_parse("non-empty-string");
66 assert!(result.is_ok());
67 match result.unwrap() {
68 Type::NonEmptyString(k) => assert_eq!(k.value, "non-empty-string"),
69 _ => panic!("Expected Type::NonEmptyString"),
70 }
71 }
72
73 #[test]
74 fn test_parse_literal_ints() {
75 let assert_parsed_literal_int = |input: &str, expected_value: u64| {
76 let result = do_parse(input);
77 assert!(result.is_ok());
78 match result.unwrap() {
79 Type::LiteralInt(LiteralIntType { value, .. }) => assert_eq!(
80 value, expected_value,
81 "Expected value to be {expected_value} for input {input}, but got {value}"
82 ),
83 _ => panic!("Expected Type::LiteralInt"),
84 }
85 };
86
87 assert_parsed_literal_int("0", 0);
88 assert_parsed_literal_int("1", 1);
89 assert_parsed_literal_int("123_345", 123345);
90 assert_parsed_literal_int("0b1", 1);
91 assert_parsed_literal_int("0o10", 8);
92 assert_parsed_literal_int("0x1", 1);
93 assert_parsed_literal_int("0x10", 16);
94 assert_parsed_literal_int("0xFF", 255);
95 }
96
97 #[test]
98 fn test_parse_literal_floats() {
99 let assert_parsed_literal_float = |input: &str, expected_value: f64| {
100 let result = do_parse(input);
101 assert!(result.is_ok());
102 match result.unwrap() {
103 Type::LiteralFloat(LiteralFloatType { value, .. }) => assert_eq!(
104 value, expected_value,
105 "Expected value to be {expected_value} for input {input}, but got {value}"
106 ),
107 _ => panic!("Expected Type::LiteralInt"),
108 }
109 };
110
111 assert_parsed_literal_float("0.0", 0.0);
112 assert_parsed_literal_float("1.0", 1.0);
113 assert_parsed_literal_float("0.1e1", 1.0);
114 assert_parsed_literal_float("0.1e-1", 0.01);
115 assert_parsed_literal_float("0.1E1", 1.0);
116 assert_parsed_literal_float("0.1E-1", 0.01);
117 assert_parsed_literal_float("0.1e+1", 1.0);
118 assert_parsed_literal_float(".1e+1", 1.0);
119 }
120
121 #[test]
122 fn test_parse_simple_union() {
123 match do_parse("int|string") {
124 Ok(ty) => match ty {
125 Type::Union(u) => {
126 assert!(matches!(*u.left, Type::Int(_)));
127 assert!(matches!(*u.right, Type::String(_)));
128 }
129 _ => panic!("Expected Type::Union"),
130 },
131 Err(err) => {
132 panic!("Failed to parse union type: {err:?}");
133 }
134 }
135 }
136
137 #[test]
138 fn test_parse_variable_union() {
139 match do_parse("$a|$b") {
140 Ok(ty) => match ty {
141 Type::Union(u) => {
142 assert!(matches!(*u.left, Type::Variable(_)));
143 assert!(matches!(*u.right, Type::Variable(_)));
144 }
145 _ => panic!("Expected Type::Union"),
146 },
147 Err(err) => {
148 panic!("Failed to parse union type: {err:?}");
149 }
150 }
151 }
152
153 #[test]
154 fn test_parse_nullable() {
155 let result = do_parse("?string");
156 assert!(result.is_ok());
157 match result.unwrap() {
158 Type::Nullable(n) => {
159 assert!(matches!(*n.inner, Type::String(_)));
160 }
161 _ => panic!("Expected Type::Nullable"),
162 }
163 }
164
165 #[test]
166 fn test_parse_generic_array() {
167 let result = do_parse("array<int, bool>");
168 assert!(result.is_ok());
169 match result.unwrap() {
170 Type::Array(a) => {
171 assert!(a.parameters.is_some());
172 let params = a.parameters.unwrap();
173 assert_eq!(params.entries.len(), 2);
174 assert!(matches!(params.entries[0].inner, Type::Int(_)));
175 assert!(matches!(params.entries[1].inner, Type::Bool(_)));
176 }
177 _ => panic!("Expected Type::Array"),
178 }
179 }
180
181 #[test]
182 fn test_parse_generic_array_one_param() {
183 match do_parse("array<string>") {
184 Ok(Type::Array(a)) => {
185 let params = a.parameters.expect("Expected generic parameters");
186 assert_eq!(params.entries.len(), 1);
187 assert!(matches!(params.entries[0].inner, Type::String(_)));
188 }
189 res => panic!("Expected Ok(Type::Array), got {res:?}"),
190 }
191 }
192
193 #[test]
194 fn test_parse_generic_list() {
195 match do_parse("list<string>") {
196 Ok(Type::List(l)) => {
197 let params = l.parameters.expect("Expected generic parameters");
198 assert_eq!(params.entries.len(), 1);
199 assert!(matches!(params.entries[0].inner, Type::String(_)));
200 }
201 res => panic!("Expected Ok(Type::List), got {res:?}"),
202 }
203 }
204
205 #[test]
206 fn test_parse_non_empty_array() {
207 match do_parse("non-empty-array<int, bool>") {
208 Ok(Type::NonEmptyArray(a)) => {
209 let params = a.parameters.expect("Expected generic parameters");
210 assert_eq!(params.entries.len(), 2);
211 assert!(matches!(params.entries[0].inner, Type::Int(_)));
212 assert!(matches!(params.entries[1].inner, Type::Bool(_)));
213 }
214 res => panic!("Expected Ok(Type::NonEmptyArray), got {res:?}"),
215 }
216 }
217
218 #[test]
219 fn test_parse_nested_generics() {
220 match do_parse("list<array<int, string>>") {
221 Ok(Type::List(l)) => {
222 let params = l.parameters.expect("Expected generic parameters");
223 assert_eq!(params.entries.len(), 1);
224 match ¶ms.entries[0].inner {
225 Type::Array(inner_array) => {
226 let inner_params = inner_array.parameters.as_ref().expect("Inner array needs params");
227 assert_eq!(inner_params.entries.len(), 2);
228 assert!(matches!(inner_params.entries[0].inner, Type::Int(_)));
229 assert!(matches!(inner_params.entries[1].inner, Type::String(_)));
230 }
231 _ => panic!("Expected inner type to be Type::Array"),
232 }
233 }
234 res => panic!("Expected Ok(Type::List), got {res:?}"),
235 }
236 }
237
238 #[test]
239 fn test_parse_simple_shape() {
240 let result = do_parse("array{'name': string}");
241 assert!(matches!(result, Ok(Type::Shape(_))));
242 let Ok(Type::Shape(shape)) = result else {
243 panic!("Expected Type::Shape");
244 };
245
246 assert_eq!(shape.kind, ShapeTypeKind::Array);
247 assert_eq!(shape.keyword.value, "array");
248 assert_eq!(shape.fields.len(), 1);
249 assert!(shape.additional_fields.is_none());
250
251 let field = &shape.fields[0];
252 assert!(matches!(
253 field.key.as_ref().map(|k| k.name.as_ref()),
254 Some(Type::LiteralString(LiteralStringType { raw: "'name'", value: "name", .. }))
255 ));
256 assert!(matches!(field.value.as_ref(), Type::String(_)));
257 }
258
259 #[test]
260 fn test_parse_int_key_shape() {
261 match do_parse("array{0: string, 1: bool}") {
262 Ok(Type::Shape(shape)) => {
263 assert_eq!(shape.fields.len(), 2);
264 let first_field = &shape.fields[0];
265 assert!(matches!(
266 first_field.key.as_ref().map(|k| k.name.as_ref()),
267 Some(Type::LiteralInt(LiteralIntType { value: 0, .. }))
268 ));
269 assert!(matches!(first_field.value.as_ref(), Type::String(_)));
270 let second_field = &shape.fields[1];
271 assert!(matches!(
272 second_field.key.as_ref().map(|k| k.name.as_ref()),
273 Some(Type::LiteralInt(LiteralIntType { value: 1, .. }))
274 ));
275 assert!(matches!(second_field.value.as_ref(), Type::Bool(_)));
276 }
277 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
278 }
279 }
280
281 #[test]
282 fn test_parse_optional_field_shape() {
283 match do_parse("array{name: string, age?: int, address: string}") {
284 Ok(Type::Shape(shape)) => {
285 assert_eq!(shape.fields.len(), 3);
286 assert!(!shape.fields[0].is_optional());
287 assert!(shape.fields[1].is_optional());
288 assert!(!shape.fields[2].is_optional());
289 }
290 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
291 }
292 }
293
294 #[test]
295 fn test_parse_unsealed_shape() {
296 match do_parse("array{name: string, ...}") {
297 Ok(Type::Shape(shape)) => {
298 assert_eq!(shape.fields.len(), 1);
299 assert!(shape.additional_fields.is_some());
300 assert!(shape.additional_fields.unwrap().parameters.is_none()); }
302 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
303 }
304 }
305
306 #[test]
307 fn test_parse_shape_with_keys_containing_special_chars() {
308 match do_parse("array{key-with-dash: int, key-with---multiple-dashes?: int}") {
309 Ok(Type::Shape(shape)) => {
310 assert_eq!(shape.fields.len(), 2);
311
312 if let Some(Type::Reference(r)) = shape.fields[0].key.as_ref().map(|k| k.name.as_ref()) {
313 assert_eq!(r.identifier.value, "key-with-dash");
314 } else {
315 panic!("Expected key to be a Type::Reference");
316 }
317
318 if let Some(Type::Reference(r)) = shape.fields[1].key.as_ref().map(|k| k.name.as_ref()) {
319 assert_eq!(r.identifier.value, "key-with---multiple-dashes");
320 } else {
321 panic!("Expected key to be a Type::Reference");
322 }
323 }
324 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
325 }
326 }
327
328 #[test]
329 fn test_parse_shape_with_keys_after_types() {
330 match do_parse("array{list: list<int>, int?: int, string: string, bool: bool}") {
331 Ok(Type::Shape(shape)) => {
332 assert_eq!(shape.fields.len(), 4);
333
334 if let Some(Type::Reference(r)) = shape.fields[0].key.as_ref().map(|k| k.name.as_ref()) {
335 assert_eq!(r.identifier.value, "list");
336 } else {
337 panic!("Expected key to be a Type::Reference");
338 }
339
340 if let Some(Type::Reference(r)) = shape.fields[1].key.as_ref().map(|k| k.name.as_ref()) {
341 assert_eq!(r.identifier.value, "int");
342 } else {
343 panic!("Expected key to be a Type::Reference");
344 }
345
346 if let Some(Type::Reference(r)) = shape.fields[2].key.as_ref().map(|k| k.name.as_ref()) {
347 assert_eq!(r.identifier.value, "string");
348 } else {
349 panic!("Expected key to be a Type::Reference");
350 }
351
352 if let Some(Type::Reference(r)) = shape.fields[3].key.as_ref().map(|k| k.name.as_ref()) {
353 assert_eq!(r.identifier.value, "bool");
354 } else {
355 panic!("Expected key to be a Type::Reference");
356 }
357 }
358 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
359 }
360 }
361
362 #[test]
363 fn test_parse_unsealed_shape_with_fallback() {
364 match do_parse(
365 "array{
366 name: string, // This is a comment
367 ...<string, string>
368 }",
369 ) {
370 Ok(Type::Shape(shape)) => {
371 assert_eq!(shape.fields.len(), 1);
372 assert!(shape.additional_fields.as_ref().is_some_and(|a| a.parameters.is_some()));
373 let params = shape.additional_fields.unwrap().parameters.unwrap();
374 assert_eq!(params.entries.len(), 2);
375 assert!(matches!(params.entries[0].inner, Type::String(_)));
376 assert!(matches!(params.entries[1].inner, Type::String(_)));
377 }
378 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
379 }
380 }
381
382 #[test]
383 fn test_parse_empty_shape() {
384 match do_parse("array{}") {
385 Ok(Type::Shape(shape)) => {
386 assert_eq!(shape.fields.len(), 0);
387 assert!(shape.additional_fields.is_none());
388 }
389 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
390 }
391 }
392
393 #[test]
394 fn test_parse_error_unexpected_token() {
395 let result = do_parse("int|>");
396 assert!(result.is_err());
397 assert!(matches!(result.unwrap_err(), ParseError::UnexpectedToken { .. }));
398 }
399
400 #[test]
401 fn test_parse_error_eof() {
402 let result = do_parse("array<int");
403 assert!(result.is_err());
404 assert!(matches!(result.unwrap_err(), ParseError::UnexpectedEndOfFile { .. }));
405 }
406
407 #[test]
408 fn test_parse_error_trailing_token() {
409 let result = do_parse("int|string&");
410 assert!(result.is_err());
411 assert!(matches!(result.unwrap_err(), ParseError::UnexpectedEndOfFile { .. }));
412 }
413
414 #[test]
415 fn test_parse_intersection() {
416 match do_parse("Countable&Traversable") {
417 Ok(Type::Intersection(i)) => {
418 assert!(matches!(*i.left, Type::Reference(_)));
419 assert!(matches!(*i.right, Type::Reference(_)));
420
421 if let Type::Reference(r) = *i.left {
422 assert_eq!(r.identifier.value, "Countable");
423 } else {
424 panic!();
425 }
426
427 if let Type::Reference(r) = *i.right {
428 assert_eq!(r.identifier.value, "Traversable");
429 } else {
430 panic!();
431 }
432 }
433 res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
434 }
435 }
436
437 #[test]
438 fn test_parse_member_ref() {
439 match do_parse("MyClass::MY_CONST") {
440 Ok(Type::MemberReference(m)) => {
441 assert_eq!(m.class.value, "MyClass");
442 assert_eq!(m.member.to_string(), "MY_CONST");
443 }
444 res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
445 }
446
447 match do_parse("\\Fully\\Qualified::class") {
448 Ok(Type::MemberReference(m)) => {
449 assert_eq!(m.class.value, "\\Fully\\Qualified"); assert_eq!(m.member.to_string(), "class");
451 }
452 res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
453 }
454 }
455
456 #[test]
457 fn test_parse_iterable() {
458 match do_parse("iterable<int, string>") {
459 Ok(Type::Iterable(i)) => {
460 let params = i.parameters.expect("Expected generic parameters");
461 assert_eq!(params.entries.len(), 2);
462 assert!(matches!(params.entries[0].inner, Type::Int(_)));
463 assert!(matches!(params.entries[1].inner, Type::String(_)));
464 }
465 res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
466 }
467
468 match do_parse("iterable<bool>") {
469 Ok(Type::Iterable(i)) => {
471 let params = i.parameters.expect("Expected generic parameters");
472 assert_eq!(params.entries.len(), 1);
473 assert!(matches!(params.entries[0].inner, Type::Bool(_)));
474 }
475 res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
476 }
477
478 match do_parse("iterable") {
479 Ok(Type::Iterable(i)) => {
480 assert!(i.parameters.is_none());
481 }
482 res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
483 }
484 }
485
486 #[test]
487 fn test_parse_negated_int() {
488 let assert_negated_int = |input: &str, expected_value: u64| {
489 let result = do_parse(input);
490 assert!(result.is_ok());
491 match result.unwrap() {
492 Type::Negated(n) => {
493 assert!(matches!(*n.inner, Type::LiteralInt(_)));
494 if let Type::LiteralInt(lit) = *n.inner {
495 assert_eq!(lit.value, expected_value);
496 } else {
497 panic!()
498 }
499 }
500 _ => panic!("Expected Type::Negated"),
501 }
502 };
503
504 assert_negated_int("-0", 0);
505 assert_negated_int("-1", 1);
506 assert_negated_int(
507 "-
508 // This is a comment
509 123_345",
510 123345,
511 );
512 assert_negated_int("-0b1", 1);
513 }
514
515 #[test]
516 fn test_parse_callable_no_spec() {
517 match do_parse("callable") {
518 Ok(Type::Callable(c)) => {
519 assert!(c.specification.is_none());
520 assert_eq!(c.kind, CallableTypeKind::Callable);
521 }
522 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
523 }
524 }
525
526 #[test]
527 fn test_parse_callable_params_only() {
528 match do_parse("callable(int, ?string)") {
529 Ok(Type::Callable(c)) => {
530 let spec = c.specification.expect("Expected callable specification");
531 assert!(spec.return_type.is_none());
532 assert_eq!(spec.parameters.entries.len(), 2);
533 assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Int(_))));
534 assert!(matches!(spec.parameters.entries[1].parameter_type, Some(Type::Nullable(_))));
535 assert!(spec.parameters.entries[0].ellipsis.is_none());
536 assert!(spec.parameters.entries[0].equals.is_none());
537 }
538 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
539 }
540 }
541
542 #[test]
543 fn test_parse_callable_return_only() {
544 match do_parse("callable(): void") {
545 Ok(Type::Callable(c)) => {
546 let spec = c.specification.expect("Expected callable specification");
547 assert!(spec.parameters.entries.is_empty());
548 assert!(spec.return_type.is_some());
549 assert!(matches!(*spec.return_type.unwrap().return_type, Type::Void(_)));
550 }
551 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
552 }
553 }
554
555 #[test]
556 fn test_parse_pure_callable_full() {
557 match do_parse("pure-callable(bool): int") {
558 Ok(Type::Callable(c)) => {
559 assert_eq!(c.kind, CallableTypeKind::PureCallable);
560 let spec = c.specification.expect("Expected callable specification");
561 assert_eq!(spec.parameters.entries.len(), 1);
562 assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Bool(_))));
563 assert!(spec.return_type.is_some());
564 assert!(matches!(*spec.return_type.unwrap().return_type, Type::Int(_)));
565 }
566 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
567 }
568 }
569
570 #[test]
571 fn test_parse_closure_via_identifier() {
572 match do_parse("Closure(string): bool") {
573 Ok(Type::Callable(c)) => {
574 assert_eq!(c.kind, CallableTypeKind::Closure);
575 assert_eq!(c.keyword.value, "Closure");
576 let spec = c.specification.expect("Expected callable specification");
577 assert_eq!(spec.parameters.entries.len(), 1);
578 assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::String(_))));
579 assert!(spec.return_type.is_some());
580 assert!(matches!(*spec.return_type.unwrap().return_type, Type::Bool(_)));
581 }
582 res => panic!("Expected Ok(Type::Callable) for Closure, got {res:?}"),
583 }
584 }
585
586 #[test]
587 fn test_parse_complex_pure_callable() {
588 match do_parse("pure-callable(list<int>, ?Closure(): void=, int...): ((Simple&Iter<T>)|null)") {
589 Ok(Type::Callable(c)) => {
590 assert_eq!(c.kind, CallableTypeKind::PureCallable);
591 let spec = c.specification.expect("Expected callable specification");
592 assert_eq!(spec.parameters.entries.len(), 3);
593 assert!(spec.return_type.is_some());
594
595 let first_param = &spec.parameters.entries[0];
596 assert!(matches!(first_param.parameter_type, Some(Type::List(_))));
597 assert!(first_param.ellipsis.is_none());
598 assert!(first_param.equals.is_none());
599
600 let second_param = &spec.parameters.entries[1];
601 assert!(matches!(second_param.parameter_type, Some(Type::Nullable(_))));
602 assert!(second_param.ellipsis.is_none());
603 assert!(second_param.equals.is_some());
604
605 let third_param = &spec.parameters.entries[2];
606 assert!(matches!(third_param.parameter_type, Some(Type::Int(_))));
607 assert!(third_param.ellipsis.is_some());
608 assert!(third_param.equals.is_none());
609
610 if let Type::Parenthesized(p) = *spec.return_type.unwrap().return_type {
611 assert!(matches!(*p.inner, Type::Union(_)));
612 if let Type::Union(u) = *p.inner {
613 assert!(matches!(u.left.as_ref(), Type::Parenthesized(_)));
614 assert!(matches!(u.right.as_ref(), Type::Null(_)));
615 }
616 } else {
617 panic!("Expected Type::CallableReturnType");
618 }
619 }
620 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
621 }
622 }
623
624 #[test]
625 fn test_parse_conditional_type() {
626 match do_parse("int is not string ? array : int") {
627 Ok(Type::Conditional(c)) => {
628 assert!(matches!(*c.subject, Type::Int(_)));
629 assert!(c.not.is_some());
630 assert!(matches!(*c.target, Type::String(_)));
631 assert!(matches!(*c.then, Type::Array(_)));
632 assert!(matches!(*c.otherwise, Type::Int(_)));
633 }
634 res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
635 }
636
637 match do_parse("$input is string ? array : int") {
638 Ok(Type::Conditional(c)) => {
639 assert!(matches!(*c.subject, Type::Variable(_)));
640 assert!(c.not.is_none());
641 assert!(matches!(*c.target, Type::String(_)));
642 assert!(matches!(*c.then, Type::Array(_)));
643 assert!(matches!(*c.otherwise, Type::Int(_)));
644 }
645 res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
646 }
647
648 match do_parse("int is string ? array : (int is not $bar ? string : $baz)") {
649 Ok(Type::Conditional(c)) => {
650 assert!(matches!(*c.subject, Type::Int(_)));
651 assert!(c.not.is_none());
652 assert!(matches!(*c.target, Type::String(_)));
653 assert!(matches!(*c.then, Type::Array(_)));
654
655 let Type::Parenthesized(p) = *c.otherwise else {
656 panic!("Expected Type::Parenthesized");
657 };
658
659 if let Type::Conditional(inner_conditional) = *p.inner {
660 assert!(matches!(*inner_conditional.subject, Type::Int(_)));
661 assert!(inner_conditional.not.is_some());
662 assert!(matches!(*inner_conditional.target, Type::Variable(_)));
663 assert!(matches!(*inner_conditional.then, Type::String(_)));
664 assert!(matches!(*inner_conditional.otherwise, Type::Variable(_)));
665 } else {
666 panic!("Expected Type::Conditional");
667 }
668 }
669 res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
670 }
671 }
672
673 #[test]
674 fn test_keyof() {
675 match do_parse("key-of<MyArray>") {
676 Ok(Type::KeyOf(k)) => {
677 assert_eq!(k.keyword.value, "key-of");
678 match &k.parameter.entry.inner {
679 Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
680 _ => panic!("Expected Type::Reference"),
681 }
682 }
683 res => panic!("Expected Ok(Type::KeyOf), got {res:?}"),
684 }
685 }
686
687 #[test]
688 fn test_valueof() {
689 match do_parse("value-of<MyArray>") {
690 Ok(Type::ValueOf(v)) => {
691 assert_eq!(v.keyword.value, "value-of");
692 match &v.parameter.entry.inner {
693 Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
694 _ => panic!("Expected Type::Reference"),
695 }
696 }
697 res => panic!("Expected Ok(Type::ValueOf), got {res:?}"),
698 }
699 }
700
701 #[test]
702 fn test_indexed_access() {
703 match do_parse("MyArray[MyKey]") {
704 Ok(Type::IndexAccess(i)) => {
705 match *i.target {
706 Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
707 _ => panic!("Expected Type::Reference"),
708 }
709 match *i.index {
710 Type::Reference(r) => assert_eq!(r.identifier.value, "MyKey"),
711 _ => panic!("Expected Type::Reference"),
712 }
713 }
714 res => panic!("Expected Ok(Type::IndexAccess), got {res:?}"),
715 }
716 }
717
718 #[test]
719 fn test_slice_type() {
720 match do_parse("string[]") {
721 Ok(Type::Slice(s)) => {
722 assert!(matches!(*s.inner, Type::String(_)));
723 }
724 res => panic!("Expected Ok(Type::Slice), got {res:?}"),
725 }
726 }
727
728 #[test]
729 fn test_slice_of_slice_of_slice_type() {
730 match do_parse("string[][][]") {
731 Ok(Type::Slice(s)) => {
732 assert!(matches!(*s.inner, Type::Slice(_)));
733 if let Type::Slice(inner_slice) = *s.inner {
734 assert!(matches!(*inner_slice.inner, Type::Slice(_)));
735 if let Type::Slice(inner_inner_slice) = *inner_slice.inner {
736 assert!(matches!(*inner_inner_slice.inner, Type::String(_)));
737 } else {
738 panic!("Expected inner slice to be a Slice");
739 }
740 } else {
741 panic!("Expected outer slice to be a Slice");
742 }
743 }
744 res => panic!("Expected Ok(Type::Slice), got {res:?}"),
745 }
746 }
747
748 #[test]
749 fn test_int_range() {
750 match do_parse("int<0, 100>") {
751 Ok(Type::IntRange(r)) => {
752 assert_eq!(r.keyword.value, "int");
753
754 match r.min {
755 IntOrKeyword::Int(literal_int_type) => {
756 assert_eq!(literal_int_type.value, 0);
757 }
758 _ => {
759 panic!("Expected min to be a LiteralIntType, got `{}`", r.min)
760 }
761 };
762
763 match r.max {
764 IntOrKeyword::Int(literal_int_type) => {
765 assert_eq!(literal_int_type.value, 100);
766 }
767 _ => {
768 panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
769 }
770 };
771 }
772 res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
773 }
774
775 match do_parse("int<min, 0>") {
776 Ok(Type::IntRange(r)) => {
777 match r.min {
778 IntOrKeyword::Keyword(keyword) => {
779 assert_eq!(keyword.value, "min");
780 }
781 _ => {
782 panic!("Expected min to be a Keyword, got `{}`", r.min)
783 }
784 };
785
786 match r.max {
787 IntOrKeyword::Int(literal_int_type) => {
788 assert_eq!(literal_int_type.value, 0);
789 }
790 _ => {
791 panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
792 }
793 };
794 }
795 res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
796 }
797
798 match do_parse("int<min, max>") {
799 Ok(Type::IntRange(r)) => {
800 match r.min {
801 IntOrKeyword::Keyword(keyword) => {
802 assert_eq!(keyword.value, "min");
803 }
804 _ => {
805 panic!("Expected min to be a Keyword, got `{}`", r.min)
806 }
807 };
808
809 match r.max {
810 IntOrKeyword::Keyword(keyword) => {
811 assert_eq!(keyword.value, "max");
812 }
813 _ => {
814 panic!("Expected max to be a Keyword, got `{}`", r.max)
815 }
816 };
817 }
818 res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
819 }
820 }
821
822 #[test]
823 fn test_properties_of() {
824 match do_parse("properties-of<MyClass>") {
825 Ok(Type::PropertiesOf(p)) => {
826 assert_eq!(p.keyword.value, "properties-of");
827 assert_eq!(p.filter, PropertiesOfFilter::All);
828 match &p.parameter.entry.inner {
829 Type::Reference(r) => assert_eq!(r.identifier.value, "MyClass"),
830 _ => panic!(),
831 }
832 }
833 res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
834 }
835
836 match do_parse("protected-properties-of<T>") {
837 Ok(Type::PropertiesOf(p)) => {
838 assert_eq!(p.keyword.value, "protected-properties-of");
839 assert_eq!(p.filter, PropertiesOfFilter::Protected);
840 match &p.parameter.entry.inner {
841 Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
842 _ => panic!(),
843 }
844 }
845 res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
846 }
847
848 match do_parse("private-properties-of<T>") {
849 Ok(Type::PropertiesOf(p)) => {
850 assert_eq!(p.keyword.value, "private-properties-of");
851 assert_eq!(p.filter, PropertiesOfFilter::Private);
852 match &p.parameter.entry.inner {
853 Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
854 _ => panic!(),
855 }
856 }
857 res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
858 }
859
860 match do_parse("public-properties-of<T>") {
861 Ok(Type::PropertiesOf(p)) => {
862 assert_eq!(p.keyword.value, "public-properties-of");
863 assert_eq!(p.filter, PropertiesOfFilter::Public);
864 match &p.parameter.entry.inner {
865 Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
866 _ => panic!(),
867 }
868 }
869 res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
870 }
871 }
872
873 #[test]
874 fn test_variable() {
875 match do_parse("$myVar") {
876 Ok(Type::Variable(v)) => {
877 assert_eq!(v.value, "$myVar");
878 }
879 res => panic!("Expected Ok(Type::Variable), got {res:?}"),
880 }
881 }
882
883 #[test]
884 fn test_nullable_intersection() {
885 match do_parse("Countable&?Traversable") {
887 Ok(Type::Intersection(i)) => {
888 assert!(matches!(*i.left, Type::Reference(r) if r.identifier.value == "Countable"));
889 assert!(matches!(*i.right, Type::Nullable(_)));
890 if let Type::Nullable(n) = *i.right {
891 assert!(matches!(*n.inner, Type::Reference(r) if r.identifier.value == "Traversable"));
892 } else {
893 panic!();
894 }
895 }
896 res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
897 }
898 }
899
900 #[test]
901 fn test_parenthesized_nullable() {
902 match do_parse("?(Countable&Traversable)") {
903 Ok(Type::Nullable(n)) => {
904 assert!(matches!(*n.inner, Type::Parenthesized(_)));
905 if let Type::Parenthesized(p) = *n.inner {
906 assert!(matches!(*p.inner, Type::Intersection(_)));
907 } else {
908 panic!()
909 }
910 }
911 res => panic!("Expected Ok(Type::Nullable), got {res:?}"),
912 }
913 }
914
915 #[test]
916 fn test_positive_negative_int() {
917 match do_parse("positive-int|negative-int") {
918 Ok(Type::Union(u)) => {
919 assert!(matches!(*u.left, Type::PositiveInt(_)));
920 assert!(matches!(*u.right, Type::NegativeInt(_)));
921 }
922 res => panic!("Expected Ok(Type::Union), got {res:?}"),
923 }
924 }
925
926 #[test]
927 fn test_parse_float_alias() {
928 match do_parse("double") {
929 Ok(Type::Float(f)) => {
930 assert_eq!(f.value, "double");
931 }
932 res => panic!("Expected Ok(Type::Float), got {res:?}"),
933 }
934
935 match do_parse("real") {
936 Ok(Type::Float(f)) => {
937 assert_eq!(f.value, "real");
938 }
939 res => panic!("Expected Ok(Type::Float), got {res:?}"),
940 }
941
942 match do_parse("float") {
943 Ok(Type::Float(f)) => {
944 assert_eq!(f.value, "float");
945 }
946 res => panic!("Expected Ok(Type::Float), got {res:?}"),
947 }
948 }
949
950 #[test]
951 fn test_parse_bool_alias() {
952 match do_parse("boolean") {
953 Ok(Type::Bool(b)) => {
954 assert_eq!(b.value, "boolean");
955 }
956 res => panic!("Expected Ok(Type::Bool), got {res:?}"),
957 }
958
959 match do_parse("bool") {
960 Ok(Type::Bool(b)) => {
961 assert_eq!(b.value, "bool");
962 }
963 res => panic!("Expected Ok(Type::Bool), got {res:?}"),
964 }
965 }
966
967 #[test]
968 fn test_parse_integer_alias() {
969 match do_parse("integer") {
970 Ok(Type::Int(i)) => {
971 assert_eq!(i.value, "integer");
972 }
973 res => panic!("Expected Ok(Type::Int), got {res:?}"),
974 }
975
976 match do_parse("int") {
977 Ok(Type::Int(i)) => {
978 assert_eq!(i.value, "int");
979 }
980 res => panic!("Expected Ok(Type::Int), got {res:?}"),
981 }
982 }
983
984 #[test]
985 fn test_parse_callable_with_variables() {
986 match do_parse("callable(string ...$names)") {
987 Ok(Type::Callable(callable)) => {
988 assert_eq!(callable.keyword.value, "callable");
989 assert!(callable.specification.is_some());
990
991 let specification = callable.specification.unwrap();
992
993 assert!(specification.return_type.is_none());
994 assert_eq!(specification.parameters.entries.len(), 1);
995
996 let first_parameter = specification.parameters.entries.first().unwrap();
997 assert!(first_parameter.variable.is_some());
998 assert!(first_parameter.ellipsis.is_some());
999
1000 let variable = first_parameter.variable.unwrap();
1001 assert_eq!(variable.value, "$names");
1002 }
1003 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
1004 }
1005 }
1006
1007 #[test]
1008 fn test_parse_string_or_lowercase_string_union() {
1009 match do_parse("string|lowercase-string") {
1010 Ok(Type::Union(u)) => {
1011 assert!(matches!(*u.left, Type::String(_)));
1012 assert!(matches!(*u.right, Type::LowercaseString(_)));
1013 }
1014 res => panic!("Expected Ok(Type::Union), got {res:?}"),
1015 }
1016 }
1017
1018 #[test]
1019 fn test_parse_optional_literal_string_shape_field() {
1020 match do_parse("array{'salt'?: int, 'cost'?: int, ...}") {
1021 Ok(Type::Shape(shape)) => {
1022 assert_eq!(shape.fields.len(), 2);
1023 assert!(shape.additional_fields.is_some());
1024
1025 let first_field = &shape.fields[0];
1026 assert!(first_field.is_optional());
1027 assert!(matches!(
1028 first_field.key.as_ref().map(|k| k.name.as_ref()),
1029 Some(Type::LiteralString(LiteralStringType { raw: "'salt'", value: "salt", .. }))
1030 ));
1031 assert!(matches!(first_field.value.as_ref(), Type::Int(_)));
1032
1033 let second_field = &shape.fields[1];
1034 assert!(second_field.is_optional());
1035 assert!(matches!(
1036 second_field.key.as_ref().map(|k| k.name.as_ref()),
1037 Some(Type::LiteralString(LiteralStringType { raw: "'cost'", value: "cost", .. }))
1038 ));
1039 assert!(matches!(second_field.value.as_ref(), Type::Int(_)));
1040 }
1041 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1042 }
1043 }
1044}