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