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_unsealed_shape_with_fallback() {
312        match do_parse(
313            "array{
314                name: string, // This is a comment
315                ...<string, string>
316            }",
317        ) {
318            Ok(Type::Shape(shape)) => {
319                assert_eq!(shape.fields.len(), 1);
320                assert!(shape.additional_fields.as_ref().is_some_and(|a| a.parameters.is_some()));
321                let params = shape.additional_fields.unwrap().parameters.unwrap();
322                assert_eq!(params.entries.len(), 2);
323                assert!(matches!(params.entries[0].inner, Type::String(_)));
324                assert!(matches!(params.entries[1].inner, Type::String(_)));
325            }
326            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
327        }
328    }
329
330    #[test]
331    fn test_parse_empty_shape() {
332        match do_parse("array{}") {
333            Ok(Type::Shape(shape)) => {
334                assert_eq!(shape.fields.len(), 0);
335                assert!(shape.additional_fields.is_none());
336            }
337            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
338        }
339    }
340
341    #[test]
342    fn test_parse_error_unexpected_token() {
343        let result = do_parse("int|>");
344        assert!(result.is_err());
345        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedToken { .. }));
346    }
347
348    #[test]
349    fn test_parse_error_eof() {
350        let result = do_parse("array<int");
351        assert!(result.is_err());
352        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedEndOfFile { .. }));
353    }
354
355    #[test]
356    fn test_parse_error_trailing_token() {
357        let result = do_parse("int|string&");
358        assert!(result.is_err());
359        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedEndOfFile { .. }));
360    }
361
362    #[test]
363    fn test_parse_intersection() {
364        match do_parse("Countable&Traversable") {
365            Ok(Type::Intersection(i)) => {
366                assert!(matches!(*i.left, Type::Reference(_)));
367                assert!(matches!(*i.right, Type::Reference(_)));
368
369                if let Type::Reference(r) = *i.left {
370                    assert_eq!(r.identifier.value, "Countable");
371                } else {
372                    panic!();
373                }
374
375                if let Type::Reference(r) = *i.right {
376                    assert_eq!(r.identifier.value, "Traversable");
377                } else {
378                    panic!();
379                }
380            }
381            res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
382        }
383    }
384
385    #[test]
386    fn test_parse_member_ref() {
387        match do_parse("MyClass::MY_CONST") {
388            Ok(Type::MemberReference(m)) => {
389                assert_eq!(m.class.value, "MyClass");
390                assert_eq!(m.member.value, "MY_CONST");
391            }
392            res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
393        }
394
395        match do_parse("\\Fully\\Qualified::class") {
396            Ok(Type::MemberReference(m)) => {
397                assert_eq!(m.class.value, "\\Fully\\Qualified"); // Check if lexer keeps leading \
398                assert_eq!(m.member.value, "class");
399            }
400            res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
401        }
402    }
403
404    #[test]
405    fn test_parse_iterable() {
406        match do_parse("iterable<int, string>") {
407            Ok(Type::Iterable(i)) => {
408                let params = i.parameters.expect("Expected generic parameters");
409                assert_eq!(params.entries.len(), 2);
410                assert!(matches!(params.entries[0].inner, Type::Int(_)));
411                assert!(matches!(params.entries[1].inner, Type::String(_)));
412            }
413            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
414        }
415
416        match do_parse("iterable<bool>") {
417            // Test single param case
418            Ok(Type::Iterable(i)) => {
419                let params = i.parameters.expect("Expected generic parameters");
420                assert_eq!(params.entries.len(), 1);
421                assert!(matches!(params.entries[0].inner, Type::Bool(_)));
422            }
423            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
424        }
425
426        match do_parse("iterable") {
427            Ok(Type::Iterable(i)) => {
428                assert!(i.parameters.is_none());
429            }
430            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
431        }
432    }
433
434    #[test]
435    fn test_parse_negated_int() {
436        let assert_negated_int = |input: &str, expected_value: u64| {
437            let result = do_parse(input);
438            assert!(result.is_ok());
439            match result.unwrap() {
440                Type::Negated(n) => {
441                    assert!(matches!(*n.inner, Type::LiteralInt(_)));
442                    if let Type::LiteralInt(lit) = *n.inner {
443                        assert_eq!(lit.value, expected_value);
444                    } else {
445                        panic!()
446                    }
447                }
448                _ => panic!("Expected Type::Negated"),
449            }
450        };
451
452        assert_negated_int("-0", 0);
453        assert_negated_int("-1", 1);
454        assert_negated_int(
455            "-
456            // This is a comment
457            123_345",
458            123345,
459        );
460        assert_negated_int("-0b1", 1);
461    }
462
463    #[test]
464    fn test_parse_callable_no_spec() {
465        match do_parse("callable") {
466            Ok(Type::Callable(c)) => {
467                assert!(c.specification.is_none());
468                assert_eq!(c.kind, CallableTypeKind::Callable);
469            }
470            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
471        }
472    }
473
474    #[test]
475    fn test_parse_callable_params_only() {
476        match do_parse("callable(int, ?string)") {
477            Ok(Type::Callable(c)) => {
478                let spec = c.specification.expect("Expected callable specification");
479                assert!(spec.return_type.is_none());
480                assert_eq!(spec.parameters.entries.len(), 2);
481                assert!(matches!(*spec.parameters.entries[0].parameter_type, Type::Int(_)));
482                assert!(matches!(*spec.parameters.entries[1].parameter_type, Type::Nullable(_)));
483                assert!(spec.parameters.entries[0].ellipsis.is_none());
484                assert!(spec.parameters.entries[0].equals.is_none());
485            }
486            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
487        }
488    }
489
490    #[test]
491    fn test_parse_callable_return_only() {
492        match do_parse("callable(): void") {
493            Ok(Type::Callable(c)) => {
494                let spec = c.specification.expect("Expected callable specification");
495                assert!(spec.parameters.entries.is_empty());
496                assert!(spec.return_type.is_some());
497                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Void(_)));
498            }
499            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
500        }
501    }
502
503    #[test]
504    fn test_parse_pure_callable_full() {
505        match do_parse("pure-callable(bool): int") {
506            Ok(Type::Callable(c)) => {
507                assert_eq!(c.kind, CallableTypeKind::PureCallable);
508                let spec = c.specification.expect("Expected callable specification");
509                assert_eq!(spec.parameters.entries.len(), 1);
510                assert!(matches!(*spec.parameters.entries[0].parameter_type, Type::Bool(_)));
511                assert!(spec.return_type.is_some());
512                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Int(_)));
513            }
514            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
515        }
516    }
517
518    #[test]
519    fn test_parse_closure_via_identifier() {
520        match do_parse("Closure(string): bool") {
521            Ok(Type::Callable(c)) => {
522                assert_eq!(c.kind, CallableTypeKind::Closure);
523                assert_eq!(c.keyword.value, "Closure");
524                let spec = c.specification.expect("Expected callable specification");
525                assert_eq!(spec.parameters.entries.len(), 1);
526                assert!(matches!(*spec.parameters.entries[0].parameter_type, Type::String(_)));
527                assert!(spec.return_type.is_some());
528                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Bool(_)));
529            }
530            res => panic!("Expected Ok(Type::Callable) for Closure, got {res:?}"),
531        }
532    }
533
534    #[test]
535    fn test_parse_complex_pure_callable() {
536        match do_parse("pure-callable(list<int>, ?Closure(): void=, int...): ((Simple&Iter<T>)|null)") {
537            Ok(Type::Callable(c)) => {
538                assert_eq!(c.kind, CallableTypeKind::PureCallable);
539                let spec = c.specification.expect("Expected callable specification");
540                assert_eq!(spec.parameters.entries.len(), 3);
541                assert!(spec.return_type.is_some());
542
543                let first_param = &spec.parameters.entries[0];
544                assert!(matches!(*first_param.parameter_type, Type::List(_)));
545                assert!(first_param.ellipsis.is_none());
546                assert!(first_param.equals.is_none());
547
548                let second_param = &spec.parameters.entries[1];
549                assert!(matches!(*second_param.parameter_type, Type::Nullable(_)));
550                assert!(second_param.ellipsis.is_none());
551                assert!(second_param.equals.is_some());
552
553                let third_param = &spec.parameters.entries[2];
554                assert!(matches!(*third_param.parameter_type, Type::Int(_)));
555                assert!(third_param.ellipsis.is_some());
556                assert!(third_param.equals.is_none());
557
558                if let Type::Parenthesized(p) = *spec.return_type.unwrap().return_type {
559                    assert!(matches!(*p.inner, Type::Union(_)));
560                    if let Type::Union(u) = *p.inner {
561                        assert!(matches!(u.left.as_ref(), Type::Parenthesized(_)));
562                        assert!(matches!(u.right.as_ref(), Type::Null(_)));
563                    }
564                } else {
565                    panic!("Expected Type::CallableReturnType");
566                }
567            }
568            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
569        }
570    }
571
572    #[test]
573    fn test_parse_conditional_type() {
574        match do_parse("int is not string ? array : int") {
575            Ok(Type::Conditional(c)) => {
576                assert!(matches!(*c.subject, Type::Int(_)));
577                assert!(c.not.is_some());
578                assert!(matches!(*c.target, Type::String(_)));
579                assert!(matches!(*c.then, Type::Array(_)));
580                assert!(matches!(*c.otherwise, Type::Int(_)));
581            }
582            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
583        }
584
585        match do_parse("$input is string ? array : int") {
586            Ok(Type::Conditional(c)) => {
587                assert!(matches!(*c.subject, Type::Variable(_)));
588                assert!(c.not.is_none());
589                assert!(matches!(*c.target, Type::String(_)));
590                assert!(matches!(*c.then, Type::Array(_)));
591                assert!(matches!(*c.otherwise, Type::Int(_)));
592            }
593            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
594        }
595
596        match do_parse("int is string ? array : (int is not $bar ? string : $baz)") {
597            Ok(Type::Conditional(c)) => {
598                assert!(matches!(*c.subject, Type::Int(_)));
599                assert!(c.not.is_none());
600                assert!(matches!(*c.target, Type::String(_)));
601                assert!(matches!(*c.then, Type::Array(_)));
602
603                let Type::Parenthesized(p) = *c.otherwise else {
604                    panic!("Expected Type::Parenthesized");
605                };
606
607                if let Type::Conditional(inner_conditional) = *p.inner {
608                    assert!(matches!(*inner_conditional.subject, Type::Int(_)));
609                    assert!(inner_conditional.not.is_some());
610                    assert!(matches!(*inner_conditional.target, Type::Variable(_)));
611                    assert!(matches!(*inner_conditional.then, Type::String(_)));
612                    assert!(matches!(*inner_conditional.otherwise, Type::Variable(_)));
613                } else {
614                    panic!("Expected Type::Conditional");
615                }
616            }
617            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
618        }
619    }
620
621    #[test]
622    fn test_keyof() {
623        match do_parse("key-of<MyArray>") {
624            Ok(Type::KeyOf(k)) => {
625                assert_eq!(k.keyword.value, "key-of");
626                match &k.parameter.entry.inner {
627                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
628                    _ => panic!("Expected Type::Reference"),
629                }
630            }
631            res => panic!("Expected Ok(Type::KeyOf), got {res:?}"),
632        }
633    }
634
635    #[test]
636    fn test_valueof() {
637        match do_parse("value-of<MyArray>") {
638            Ok(Type::ValueOf(v)) => {
639                assert_eq!(v.keyword.value, "value-of");
640                match &v.parameter.entry.inner {
641                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
642                    _ => panic!("Expected Type::Reference"),
643                }
644            }
645            res => panic!("Expected Ok(Type::ValueOf), got {res:?}"),
646        }
647    }
648
649    #[test]
650    fn test_indexed_access() {
651        match do_parse("MyArray[MyKey]") {
652            Ok(Type::IndexAccess(i)) => {
653                match *i.target {
654                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
655                    _ => panic!("Expected Type::Reference"),
656                }
657                match *i.index {
658                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyKey"),
659                    _ => panic!("Expected Type::Reference"),
660                }
661            }
662            res => panic!("Expected Ok(Type::IndexAccess), got {res:?}"),
663        }
664    }
665
666    #[test]
667    fn test_slice_type() {
668        match do_parse("string[]") {
669            Ok(Type::Slice(s)) => {
670                assert!(matches!(*s.inner, Type::String(_)));
671            }
672            res => panic!("Expected Ok(Type::Slice), got {res:?}"),
673        }
674    }
675
676    #[test]
677    fn test_int_range() {
678        match do_parse("int<0, 100>") {
679            Ok(Type::IntRange(r)) => {
680                assert_eq!(r.keyword.value, "int");
681
682                match r.min {
683                    IntOrKeyword::Int(literal_int_type) => {
684                        assert_eq!(literal_int_type.value, 0);
685                    }
686                    _ => {
687                        panic!("Expected min to be a LiteralIntType, got `{}`", r.min)
688                    }
689                };
690
691                match r.max {
692                    IntOrKeyword::Int(literal_int_type) => {
693                        assert_eq!(literal_int_type.value, 100);
694                    }
695                    _ => {
696                        panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
697                    }
698                };
699            }
700            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
701        }
702
703        match do_parse("int<min, 0>") {
704            Ok(Type::IntRange(r)) => {
705                match r.min {
706                    IntOrKeyword::Keyword(keyword) => {
707                        assert_eq!(keyword.value, "min");
708                    }
709                    _ => {
710                        panic!("Expected min to be a Keyword, got `{}`", r.min)
711                    }
712                };
713
714                match r.max {
715                    IntOrKeyword::Int(literal_int_type) => {
716                        assert_eq!(literal_int_type.value, 0);
717                    }
718                    _ => {
719                        panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
720                    }
721                };
722            }
723            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
724        }
725
726        match do_parse("int<min, max>") {
727            Ok(Type::IntRange(r)) => {
728                match r.min {
729                    IntOrKeyword::Keyword(keyword) => {
730                        assert_eq!(keyword.value, "min");
731                    }
732                    _ => {
733                        panic!("Expected min to be a Keyword, got `{}`", r.min)
734                    }
735                };
736
737                match r.max {
738                    IntOrKeyword::Keyword(keyword) => {
739                        assert_eq!(keyword.value, "max");
740                    }
741                    _ => {
742                        panic!("Expected max to be a Keyword, got `{}`", r.max)
743                    }
744                };
745            }
746            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
747        }
748    }
749
750    #[test]
751    fn test_properties_of() {
752        match do_parse("properties-of<MyClass>") {
753            Ok(Type::PropertiesOf(p)) => {
754                assert_eq!(p.keyword.value, "properties-of");
755                assert_eq!(p.filter, PropertiesOfFilter::All);
756                match &p.parameter.entry.inner {
757                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyClass"),
758                    _ => panic!(),
759                }
760            }
761            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
762        }
763
764        match do_parse("protected-properties-of<T>") {
765            Ok(Type::PropertiesOf(p)) => {
766                assert_eq!(p.keyword.value, "protected-properties-of");
767                assert_eq!(p.filter, PropertiesOfFilter::Protected);
768                match &p.parameter.entry.inner {
769                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
770                    _ => panic!(),
771                }
772            }
773            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
774        }
775
776        match do_parse("private-properties-of<T>") {
777            Ok(Type::PropertiesOf(p)) => {
778                assert_eq!(p.keyword.value, "private-properties-of");
779                assert_eq!(p.filter, PropertiesOfFilter::Private);
780                match &p.parameter.entry.inner {
781                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
782                    _ => panic!(),
783                }
784            }
785            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
786        }
787
788        match do_parse("public-properties-of<T>") {
789            Ok(Type::PropertiesOf(p)) => {
790                assert_eq!(p.keyword.value, "public-properties-of");
791                assert_eq!(p.filter, PropertiesOfFilter::Public);
792                match &p.parameter.entry.inner {
793                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
794                    _ => panic!(),
795                }
796            }
797            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
798        }
799    }
800
801    #[test]
802    fn test_variable() {
803        match do_parse("$myVar") {
804            Ok(Type::Variable(v)) => {
805                assert_eq!(v.value, "$myVar");
806            }
807            res => panic!("Expected Ok(Type::Variable), got {res:?}"),
808        }
809    }
810
811    #[test]
812    fn test_nullable_intersection() {
813        // Nullable applies only to the rightmost element of an intersection before parens
814        match do_parse("Countable&?Traversable") {
815            Ok(Type::Intersection(i)) => {
816                assert!(matches!(*i.left, Type::Reference(r) if r.identifier.value == "Countable"));
817                assert!(matches!(*i.right, Type::Nullable(_)));
818                if let Type::Nullable(n) = *i.right {
819                    assert!(matches!(*n.inner, Type::Reference(r) if r.identifier.value == "Traversable"));
820                } else {
821                    panic!();
822                }
823            }
824            res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
825        }
826    }
827
828    #[test]
829    fn test_parenthesized_nullable() {
830        match do_parse("?(Countable&Traversable)") {
831            Ok(Type::Nullable(n)) => {
832                assert!(matches!(*n.inner, Type::Parenthesized(_)));
833                if let Type::Parenthesized(p) = *n.inner {
834                    assert!(matches!(*p.inner, Type::Intersection(_)));
835                } else {
836                    panic!()
837                }
838            }
839            res => panic!("Expected Ok(Type::Nullable), got {res:?}"),
840        }
841    }
842
843    #[test]
844    fn test_positive_negative_int() {
845        match do_parse("positive-int|negative-int") {
846            Ok(Type::Union(u)) => {
847                assert!(matches!(*u.left, Type::PositiveInt(_)));
848                assert!(matches!(*u.right, Type::NegativeInt(_)));
849            }
850            res => panic!("Expected Ok(Type::Union), got {res:?}"),
851        }
852    }
853}