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.inner, Type::LiteralInt(_)));
496                    if let Type::LiteralInt(lit) = *n.inner {
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_callable_no_spec() {
519        match do_parse("callable") {
520            Ok(Type::Callable(c)) => {
521                assert!(c.specification.is_none());
522                assert_eq!(c.kind, CallableTypeKind::Callable);
523            }
524            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
525        }
526    }
527
528    #[test]
529    fn test_parse_callable_params_only() {
530        match do_parse("callable(int, ?string)") {
531            Ok(Type::Callable(c)) => {
532                let spec = c.specification.expect("Expected callable specification");
533                assert!(spec.return_type.is_none());
534                assert_eq!(spec.parameters.entries.len(), 2);
535                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Int(_))));
536                assert!(matches!(spec.parameters.entries[1].parameter_type, Some(Type::Nullable(_))));
537                assert!(spec.parameters.entries[0].ellipsis.is_none());
538                assert!(spec.parameters.entries[0].equals.is_none());
539            }
540            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
541        }
542    }
543
544    #[test]
545    fn test_parse_callable_return_only() {
546        match do_parse("callable(): void") {
547            Ok(Type::Callable(c)) => {
548                let spec = c.specification.expect("Expected callable specification");
549                assert!(spec.parameters.entries.is_empty());
550                assert!(spec.return_type.is_some());
551                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Void(_)));
552            }
553            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
554        }
555    }
556
557    #[test]
558    fn test_parse_pure_callable_full() {
559        match do_parse("pure-callable(bool): int") {
560            Ok(Type::Callable(c)) => {
561                assert_eq!(c.kind, CallableTypeKind::PureCallable);
562                let spec = c.specification.expect("Expected callable specification");
563                assert_eq!(spec.parameters.entries.len(), 1);
564                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Bool(_))));
565                assert!(spec.return_type.is_some());
566                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Int(_)));
567            }
568            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
569        }
570    }
571
572    #[test]
573    fn test_parse_closure_via_identifier() {
574        match do_parse("Closure(string): bool") {
575            Ok(Type::Callable(c)) => {
576                assert_eq!(c.kind, CallableTypeKind::Closure);
577                assert_eq!(c.keyword.value, "Closure");
578                let spec = c.specification.expect("Expected callable specification");
579                assert_eq!(spec.parameters.entries.len(), 1);
580                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::String(_))));
581                assert!(spec.return_type.is_some());
582                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Bool(_)));
583            }
584            res => panic!("Expected Ok(Type::Callable) for Closure, got {res:?}"),
585        }
586    }
587
588    #[test]
589    fn test_parse_complex_pure_callable() {
590        match do_parse("pure-callable(list<int>, ?Closure(): void=, int...): ((Simple&Iter<T>)|null)") {
591            Ok(Type::Callable(c)) => {
592                assert_eq!(c.kind, CallableTypeKind::PureCallable);
593                let spec = c.specification.expect("Expected callable specification");
594                assert_eq!(spec.parameters.entries.len(), 3);
595                assert!(spec.return_type.is_some());
596
597                let first_param = &spec.parameters.entries[0];
598                assert!(matches!(first_param.parameter_type, Some(Type::List(_))));
599                assert!(first_param.ellipsis.is_none());
600                assert!(first_param.equals.is_none());
601
602                let second_param = &spec.parameters.entries[1];
603                assert!(matches!(second_param.parameter_type, Some(Type::Nullable(_))));
604                assert!(second_param.ellipsis.is_none());
605                assert!(second_param.equals.is_some());
606
607                let third_param = &spec.parameters.entries[2];
608                assert!(matches!(third_param.parameter_type, Some(Type::Int(_))));
609                assert!(third_param.ellipsis.is_some());
610                assert!(third_param.equals.is_none());
611
612                if let Type::Parenthesized(p) = *spec.return_type.unwrap().return_type {
613                    assert!(matches!(*p.inner, Type::Union(_)));
614                    if let Type::Union(u) = *p.inner {
615                        assert!(matches!(u.left.as_ref(), Type::Parenthesized(_)));
616                        assert!(matches!(u.right.as_ref(), Type::Null(_)));
617                    }
618                } else {
619                    panic!("Expected Type::CallableReturnType");
620                }
621            }
622            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
623        }
624    }
625
626    #[test]
627    fn test_parse_conditional_type() {
628        match do_parse("int is not string ? array : int") {
629            Ok(Type::Conditional(c)) => {
630                assert!(matches!(*c.subject, Type::Int(_)));
631                assert!(c.not.is_some());
632                assert!(matches!(*c.target, Type::String(_)));
633                assert!(matches!(*c.then, Type::Array(_)));
634                assert!(matches!(*c.otherwise, Type::Int(_)));
635            }
636            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
637        }
638
639        match do_parse("$input is string ? array : int") {
640            Ok(Type::Conditional(c)) => {
641                assert!(matches!(*c.subject, Type::Variable(_)));
642                assert!(c.not.is_none());
643                assert!(matches!(*c.target, Type::String(_)));
644                assert!(matches!(*c.then, Type::Array(_)));
645                assert!(matches!(*c.otherwise, Type::Int(_)));
646            }
647            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
648        }
649
650        match do_parse("int is string ? array : (int is not $bar ? string : $baz)") {
651            Ok(Type::Conditional(c)) => {
652                assert!(matches!(*c.subject, Type::Int(_)));
653                assert!(c.not.is_none());
654                assert!(matches!(*c.target, Type::String(_)));
655                assert!(matches!(*c.then, Type::Array(_)));
656
657                let Type::Parenthesized(p) = *c.otherwise else {
658                    panic!("Expected Type::Parenthesized");
659                };
660
661                if let Type::Conditional(inner_conditional) = *p.inner {
662                    assert!(matches!(*inner_conditional.subject, Type::Int(_)));
663                    assert!(inner_conditional.not.is_some());
664                    assert!(matches!(*inner_conditional.target, Type::Variable(_)));
665                    assert!(matches!(*inner_conditional.then, Type::String(_)));
666                    assert!(matches!(*inner_conditional.otherwise, Type::Variable(_)));
667                } else {
668                    panic!("Expected Type::Conditional");
669                }
670            }
671            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
672        }
673    }
674
675    #[test]
676    fn test_keyof() {
677        match do_parse("key-of<MyArray>") {
678            Ok(Type::KeyOf(k)) => {
679                assert_eq!(k.keyword.value, "key-of");
680                match &k.parameter.entry.inner {
681                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
682                    _ => panic!("Expected Type::Reference"),
683                }
684            }
685            res => panic!("Expected Ok(Type::KeyOf), got {res:?}"),
686        }
687    }
688
689    #[test]
690    fn test_valueof() {
691        match do_parse("value-of<MyArray>") {
692            Ok(Type::ValueOf(v)) => {
693                assert_eq!(v.keyword.value, "value-of");
694                match &v.parameter.entry.inner {
695                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
696                    _ => panic!("Expected Type::Reference"),
697                }
698            }
699            res => panic!("Expected Ok(Type::ValueOf), got {res:?}"),
700        }
701    }
702
703    #[test]
704    fn test_indexed_access() {
705        match do_parse("MyArray[MyKey]") {
706            Ok(Type::IndexAccess(i)) => {
707                match *i.target {
708                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
709                    _ => panic!("Expected Type::Reference"),
710                }
711                match *i.index {
712                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyKey"),
713                    _ => panic!("Expected Type::Reference"),
714                }
715            }
716            res => panic!("Expected Ok(Type::IndexAccess), got {res:?}"),
717        }
718    }
719
720    #[test]
721    fn test_slice_type() {
722        match do_parse("string[]") {
723            Ok(Type::Slice(s)) => {
724                assert!(matches!(*s.inner, Type::String(_)));
725            }
726            res => panic!("Expected Ok(Type::Slice), got {res:?}"),
727        }
728    }
729
730    #[test]
731    fn test_slice_of_slice_of_slice_type() {
732        match do_parse("string[][][]") {
733            Ok(Type::Slice(s)) => {
734                assert!(matches!(*s.inner, Type::Slice(_)));
735                if let Type::Slice(inner_slice) = *s.inner {
736                    assert!(matches!(*inner_slice.inner, Type::Slice(_)));
737                    if let Type::Slice(inner_inner_slice) = *inner_slice.inner {
738                        assert!(matches!(*inner_inner_slice.inner, Type::String(_)));
739                    } else {
740                        panic!("Expected inner slice to be a Slice");
741                    }
742                } else {
743                    panic!("Expected outer slice to be a Slice");
744                }
745            }
746            res => panic!("Expected Ok(Type::Slice), got {res:?}"),
747        }
748    }
749
750    #[test]
751    fn test_int_range() {
752        match do_parse("int<0, 100>") {
753            Ok(Type::IntRange(r)) => {
754                assert_eq!(r.keyword.value, "int");
755
756                match r.min {
757                    IntOrKeyword::Int(literal_int_type) => {
758                        assert_eq!(literal_int_type.value, 0);
759                    }
760                    _ => {
761                        panic!("Expected min to be a LiteralIntType, got `{}`", r.min)
762                    }
763                };
764
765                match r.max {
766                    IntOrKeyword::Int(literal_int_type) => {
767                        assert_eq!(literal_int_type.value, 100);
768                    }
769                    _ => {
770                        panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
771                    }
772                };
773            }
774            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
775        }
776
777        match do_parse("int<min, 0>") {
778            Ok(Type::IntRange(r)) => {
779                match r.min {
780                    IntOrKeyword::Keyword(keyword) => {
781                        assert_eq!(keyword.value, "min");
782                    }
783                    _ => {
784                        panic!("Expected min to be a Keyword, got `{}`", r.min)
785                    }
786                };
787
788                match r.max {
789                    IntOrKeyword::Int(literal_int_type) => {
790                        assert_eq!(literal_int_type.value, 0);
791                    }
792                    _ => {
793                        panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
794                    }
795                };
796            }
797            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
798        }
799
800        match do_parse("int<min, max>") {
801            Ok(Type::IntRange(r)) => {
802                match r.min {
803                    IntOrKeyword::Keyword(keyword) => {
804                        assert_eq!(keyword.value, "min");
805                    }
806                    _ => {
807                        panic!("Expected min to be a Keyword, got `{}`", r.min)
808                    }
809                };
810
811                match r.max {
812                    IntOrKeyword::Keyword(keyword) => {
813                        assert_eq!(keyword.value, "max");
814                    }
815                    _ => {
816                        panic!("Expected max to be a Keyword, got `{}`", r.max)
817                    }
818                };
819            }
820            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
821        }
822    }
823
824    #[test]
825    fn test_properties_of() {
826        match do_parse("properties-of<MyClass>") {
827            Ok(Type::PropertiesOf(p)) => {
828                assert_eq!(p.keyword.value, "properties-of");
829                assert_eq!(p.filter, PropertiesOfFilter::All);
830                match &p.parameter.entry.inner {
831                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyClass"),
832                    _ => panic!(),
833                }
834            }
835            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
836        }
837
838        match do_parse("protected-properties-of<T>") {
839            Ok(Type::PropertiesOf(p)) => {
840                assert_eq!(p.keyword.value, "protected-properties-of");
841                assert_eq!(p.filter, PropertiesOfFilter::Protected);
842                match &p.parameter.entry.inner {
843                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
844                    _ => panic!(),
845                }
846            }
847            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
848        }
849
850        match do_parse("private-properties-of<T>") {
851            Ok(Type::PropertiesOf(p)) => {
852                assert_eq!(p.keyword.value, "private-properties-of");
853                assert_eq!(p.filter, PropertiesOfFilter::Private);
854                match &p.parameter.entry.inner {
855                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
856                    _ => panic!(),
857                }
858            }
859            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
860        }
861
862        match do_parse("public-properties-of<T>") {
863            Ok(Type::PropertiesOf(p)) => {
864                assert_eq!(p.keyword.value, "public-properties-of");
865                assert_eq!(p.filter, PropertiesOfFilter::Public);
866                match &p.parameter.entry.inner {
867                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
868                    _ => panic!(),
869                }
870            }
871            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
872        }
873    }
874
875    #[test]
876    fn test_variable() {
877        match do_parse("$myVar") {
878            Ok(Type::Variable(v)) => {
879                assert_eq!(v.value, "$myVar");
880            }
881            res => panic!("Expected Ok(Type::Variable), got {res:?}"),
882        }
883    }
884
885    #[test]
886    fn test_nullable_intersection() {
887        // Nullable applies only to the rightmost element of an intersection before parens
888        match do_parse("Countable&?Traversable") {
889            Ok(Type::Intersection(i)) => {
890                assert!(matches!(*i.left, Type::Reference(r) if r.identifier.value == "Countable"));
891                assert!(matches!(*i.right, Type::Nullable(_)));
892                if let Type::Nullable(n) = *i.right {
893                    assert!(matches!(*n.inner, Type::Reference(r) if r.identifier.value == "Traversable"));
894                } else {
895                    panic!();
896                }
897            }
898            res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
899        }
900    }
901
902    #[test]
903    fn test_parenthesized_nullable() {
904        match do_parse("?(Countable&Traversable)") {
905            Ok(Type::Nullable(n)) => {
906                assert!(matches!(*n.inner, Type::Parenthesized(_)));
907                if let Type::Parenthesized(p) = *n.inner {
908                    assert!(matches!(*p.inner, Type::Intersection(_)));
909                } else {
910                    panic!()
911                }
912            }
913            res => panic!("Expected Ok(Type::Nullable), got {res:?}"),
914        }
915    }
916
917    #[test]
918    fn test_positive_negative_int() {
919        match do_parse("positive-int|negative-int") {
920            Ok(Type::Union(u)) => {
921                assert!(matches!(*u.left, Type::PositiveInt(_)));
922                assert!(matches!(*u.right, Type::NegativeInt(_)));
923            }
924            res => panic!("Expected Ok(Type::Union), got {res:?}"),
925        }
926    }
927
928    #[test]
929    fn test_parse_float_alias() {
930        match do_parse("double") {
931            Ok(Type::Float(f)) => {
932                assert_eq!(f.value, "double");
933            }
934            res => panic!("Expected Ok(Type::Float), got {res:?}"),
935        }
936
937        match do_parse("real") {
938            Ok(Type::Float(f)) => {
939                assert_eq!(f.value, "real");
940            }
941            res => panic!("Expected Ok(Type::Float), got {res:?}"),
942        }
943
944        match do_parse("float") {
945            Ok(Type::Float(f)) => {
946                assert_eq!(f.value, "float");
947            }
948            res => panic!("Expected Ok(Type::Float), got {res:?}"),
949        }
950    }
951
952    #[test]
953    fn test_parse_bool_alias() {
954        match do_parse("boolean") {
955            Ok(Type::Bool(b)) => {
956                assert_eq!(b.value, "boolean");
957            }
958            res => panic!("Expected Ok(Type::Bool), got {res:?}"),
959        }
960
961        match do_parse("bool") {
962            Ok(Type::Bool(b)) => {
963                assert_eq!(b.value, "bool");
964            }
965            res => panic!("Expected Ok(Type::Bool), got {res:?}"),
966        }
967    }
968
969    #[test]
970    fn test_parse_integer_alias() {
971        match do_parse("integer") {
972            Ok(Type::Int(i)) => {
973                assert_eq!(i.value, "integer");
974            }
975            res => panic!("Expected Ok(Type::Int), got {res:?}"),
976        }
977
978        match do_parse("int") {
979            Ok(Type::Int(i)) => {
980                assert_eq!(i.value, "int");
981            }
982            res => panic!("Expected Ok(Type::Int), got {res:?}"),
983        }
984    }
985
986    #[test]
987    fn test_parse_callable_with_variables() {
988        match do_parse("callable(string ...$names)") {
989            Ok(Type::Callable(callable)) => {
990                assert_eq!(callable.keyword.value, "callable");
991                assert!(callable.specification.is_some());
992
993                let specification = callable.specification.unwrap();
994
995                assert!(specification.return_type.is_none());
996                assert_eq!(specification.parameters.entries.len(), 1);
997
998                let first_parameter = specification.parameters.entries.first().unwrap();
999                assert!(first_parameter.variable.is_some());
1000                assert!(first_parameter.ellipsis.is_some());
1001
1002                let variable = first_parameter.variable.unwrap();
1003                assert_eq!(variable.value, "$names");
1004            }
1005            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
1006        }
1007    }
1008
1009    #[test]
1010    fn test_parse_string_or_lowercase_string_union() {
1011        match do_parse("string|lowercase-string") {
1012            Ok(Type::Union(u)) => {
1013                assert!(matches!(*u.left, Type::String(_)));
1014                assert!(matches!(*u.right, Type::LowercaseString(_)));
1015            }
1016            res => panic!("Expected Ok(Type::Union), got {res:?}"),
1017        }
1018    }
1019
1020    #[test]
1021    fn test_parse_optional_literal_string_shape_field() {
1022        match do_parse("array{'salt'?: int, 'cost'?: int, ...}") {
1023            Ok(Type::Shape(shape)) => {
1024                assert_eq!(shape.fields.len(), 2);
1025                assert!(shape.additional_fields.is_some());
1026
1027                let first_field = &shape.fields[0];
1028                assert!(first_field.is_optional());
1029                assert!(matches!(
1030                    first_field.key.as_ref().map(|k| k.name.as_ref()),
1031                    Some(Type::LiteralString(LiteralStringType { raw: "'salt'", value: "salt", .. }))
1032                ));
1033                assert!(matches!(first_field.value.as_ref(), Type::Int(_)));
1034
1035                let second_field = &shape.fields[1];
1036                assert!(second_field.is_optional());
1037                assert!(matches!(
1038                    second_field.key.as_ref().map(|k| k.name.as_ref()),
1039                    Some(Type::LiteralString(LiteralStringType { raw: "'cost'", value: "cost", .. }))
1040                ));
1041                assert!(matches!(second_field.value.as_ref(), Type::Int(_)));
1042            }
1043            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1044        }
1045    }
1046}