mago_type_syntax/
lib.rs

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
16/// Parses a string representation of a PHPDoc type into an Abstract Syntax Tree (AST).
17///
18/// This is the main entry point for the type parser. It takes the type string
19/// and its original `Span` (representing its location within the source file)
20/// and returns the parsed `Type` AST or a `ParseError`.
21///
22/// # Arguments
23///
24/// * `span` - The original `Span` of the `input` string slice within its source file.
25///   This is crucial for ensuring all AST nodes have correct, absolute positioning.
26/// * `input` - The `&str` containing the type string to parse (e.g., `"int|string"`, `"array<int, MyClass>"`).
27///
28/// # Returns
29///
30/// * `Ok(Type)` containing the root of the parsed AST on success.
31/// * `Err(ParseError)` if any lexing or parsing error occurs.
32pub fn parse_str(span: Span, input: &str) -> Result<Type<'_>, ParseError> {
33    // Create an Input anchored at the type string's original starting position.
34    let input = Input::anchored_at(input.as_bytes(), span.start);
35    // Create the type-specific lexer.
36    let lexer = TypeLexer::new(input);
37    // Construct the type AST using the lexer.
38    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 &params.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()); // No fallback specified
301            }
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"); // Check if lexer keeps leading \
450                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            // Test single param case
470            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        // Nullable applies only to the rightmost element of an intersection before parens
886        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}