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