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_nullable() {
143        let result = do_parse("?string");
144        assert!(result.is_ok());
145        match result.unwrap() {
146            Type::Nullable(n) => {
147                assert!(matches!(*n.inner, Type::String(_)));
148            }
149            _ => panic!("Expected Type::Nullable"),
150        }
151    }
152
153    #[test]
154    fn test_parse_generic_array() {
155        let result = do_parse("array<int, bool>");
156        assert!(result.is_ok());
157        match result.unwrap() {
158            Type::Array(a) => {
159                assert!(a.parameters.is_some());
160                let params = a.parameters.unwrap();
161                assert_eq!(params.entries.len(), 2);
162                assert!(matches!(params.entries[0].inner, Type::Int(_)));
163                assert!(matches!(params.entries[1].inner, Type::Bool(_)));
164            }
165            _ => panic!("Expected Type::Array"),
166        }
167    }
168
169    #[test]
170    fn test_parse_generic_array_one_param() {
171        match do_parse("array<string>") {
172            Ok(Type::Array(a)) => {
173                let params = a.parameters.expect("Expected generic parameters");
174                assert_eq!(params.entries.len(), 1);
175                assert!(matches!(params.entries[0].inner, Type::String(_)));
176            }
177            res => panic!("Expected Ok(Type::Array), got {res:?}"),
178        }
179    }
180
181    #[test]
182    fn test_parse_generic_list() {
183        match do_parse("list<string>") {
184            Ok(Type::List(l)) => {
185                let params = l.parameters.expect("Expected generic parameters");
186                assert_eq!(params.entries.len(), 1);
187                assert!(matches!(params.entries[0].inner, Type::String(_)));
188            }
189            res => panic!("Expected Ok(Type::List), got {res:?}"),
190        }
191    }
192
193    #[test]
194    fn test_parse_non_empty_array() {
195        match do_parse("non-empty-array<int, bool>") {
196            Ok(Type::NonEmptyArray(a)) => {
197                let params = a.parameters.expect("Expected generic parameters");
198                assert_eq!(params.entries.len(), 2);
199                assert!(matches!(params.entries[0].inner, Type::Int(_)));
200                assert!(matches!(params.entries[1].inner, Type::Bool(_)));
201            }
202            res => panic!("Expected Ok(Type::NonEmptyArray), got {res:?}"),
203        }
204    }
205
206    #[test]
207    fn test_parse_nested_generics() {
208        match do_parse("list<array<int, string>>") {
209            Ok(Type::List(l)) => {
210                let params = l.parameters.expect("Expected generic parameters");
211                assert_eq!(params.entries.len(), 1);
212                match &params.entries[0].inner {
213                    Type::Array(inner_array) => {
214                        let inner_params = inner_array.parameters.as_ref().expect("Inner array needs params");
215                        assert_eq!(inner_params.entries.len(), 2);
216                        assert!(matches!(inner_params.entries[0].inner, Type::Int(_)));
217                        assert!(matches!(inner_params.entries[1].inner, Type::String(_)));
218                    }
219                    _ => panic!("Expected inner type to be Type::Array"),
220                }
221            }
222            res => panic!("Expected Ok(Type::List), got {res:?}"),
223        }
224    }
225
226    #[test]
227    fn test_parse_simple_shape() {
228        let result = do_parse("array{'name': string}");
229        assert!(matches!(result, Ok(Type::Shape(_))));
230        let Ok(Type::Shape(shape)) = result else {
231            panic!("Expected Type::Shape");
232        };
233
234        assert_eq!(shape.kind, ShapeTypeKind::Array);
235        assert_eq!(shape.keyword.value, "array");
236        assert_eq!(shape.fields.len(), 1);
237        assert!(shape.additional_fields.is_none());
238
239        let field = &shape.fields[0];
240        assert!(matches!(
241            field.key.as_ref().map(|k| k.name.as_ref()),
242            Some(Type::LiteralString(LiteralStringType { raw: "'name'", value: "name", .. }))
243        ));
244        assert!(matches!(field.value.as_ref(), Type::String(_)));
245    }
246
247    #[test]
248    fn test_parse_int_key_shape() {
249        match do_parse("array{0: string, 1: bool}") {
250            Ok(Type::Shape(shape)) => {
251                assert_eq!(shape.fields.len(), 2);
252                let first_field = &shape.fields[0];
253                assert!(matches!(
254                    first_field.key.as_ref().map(|k| k.name.as_ref()),
255                    Some(Type::LiteralInt(LiteralIntType { value: 0, .. }))
256                ));
257                assert!(matches!(first_field.value.as_ref(), Type::String(_)));
258                let second_field = &shape.fields[1];
259                assert!(matches!(
260                    second_field.key.as_ref().map(|k| k.name.as_ref()),
261                    Some(Type::LiteralInt(LiteralIntType { value: 1, .. }))
262                ));
263                assert!(matches!(second_field.value.as_ref(), Type::Bool(_)));
264            }
265            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
266        }
267    }
268
269    #[test]
270    fn test_parse_optional_field_shape() {
271        match do_parse("array{name: string, age?: int, address: string}") {
272            Ok(Type::Shape(shape)) => {
273                assert_eq!(shape.fields.len(), 3);
274                assert!(!shape.fields[0].is_optional());
275                assert!(shape.fields[1].is_optional());
276                assert!(!shape.fields[2].is_optional());
277            }
278            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
279        }
280    }
281
282    #[test]
283    fn test_parse_unsealed_shape() {
284        match do_parse("array{name: string, ...}") {
285            Ok(Type::Shape(shape)) => {
286                assert_eq!(shape.fields.len(), 1);
287                assert!(shape.additional_fields.is_some());
288                assert!(shape.additional_fields.unwrap().parameters.is_none()); // No fallback specified
289            }
290            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
291        }
292    }
293
294    #[test]
295    fn test_parse_unsealed_shape_with_fallback() {
296        match do_parse(
297            "array{
298                name: string, // This is a comment
299                ...<string, string>
300            }",
301        ) {
302            Ok(Type::Shape(shape)) => {
303                assert_eq!(shape.fields.len(), 1);
304                assert!(shape.additional_fields.as_ref().is_some_and(|a| a.parameters.is_some()));
305                let params = shape.additional_fields.unwrap().parameters.unwrap();
306                assert_eq!(params.entries.len(), 2);
307                assert!(matches!(params.entries[0].inner, Type::String(_)));
308                assert!(matches!(params.entries[1].inner, Type::String(_)));
309            }
310            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
311        }
312    }
313
314    #[test]
315    fn test_parse_empty_shape() {
316        match do_parse("array{}") {
317            Ok(Type::Shape(shape)) => {
318                assert_eq!(shape.fields.len(), 0);
319                assert!(shape.additional_fields.is_none());
320            }
321            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
322        }
323    }
324
325    #[test]
326    fn test_parse_error_unexpected_token() {
327        let result = do_parse("int|>");
328        assert!(result.is_err());
329        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedToken { .. }));
330    }
331
332    #[test]
333    fn test_parse_error_eof() {
334        let result = do_parse("array<int");
335        assert!(result.is_err());
336        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedEndOfFile { .. }));
337    }
338
339    #[test]
340    fn test_parse_error_trailing_token() {
341        let result = do_parse("int|string&");
342        assert!(result.is_err());
343        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedEndOfFile { .. }));
344    }
345
346    #[test]
347    fn test_parse_intersection() {
348        match do_parse("Countable&Traversable") {
349            Ok(Type::Intersection(i)) => {
350                assert!(matches!(*i.left, Type::Reference(_)));
351                assert!(matches!(*i.right, Type::Reference(_)));
352
353                if let Type::Reference(r) = *i.left {
354                    assert_eq!(r.identifier.value, "Countable");
355                } else {
356                    panic!();
357                }
358
359                if let Type::Reference(r) = *i.right {
360                    assert_eq!(r.identifier.value, "Traversable");
361                } else {
362                    panic!();
363                }
364            }
365            res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
366        }
367    }
368
369    #[test]
370    fn test_parse_member_ref() {
371        match do_parse("MyClass::MY_CONST") {
372            Ok(Type::MemberReference(m)) => {
373                assert_eq!(m.class.value, "MyClass");
374                assert_eq!(m.member.value, "MY_CONST");
375            }
376            res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
377        }
378
379        match do_parse("\\Fully\\Qualified::class") {
380            Ok(Type::MemberReference(m)) => {
381                assert_eq!(m.class.value, "\\Fully\\Qualified"); // Check if lexer keeps leading \
382                assert_eq!(m.member.value, "class");
383            }
384            res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
385        }
386    }
387
388    #[test]
389    fn test_parse_iterable() {
390        match do_parse("iterable<int, string>") {
391            Ok(Type::Iterable(i)) => {
392                let params = i.parameters.expect("Expected generic parameters");
393                assert_eq!(params.entries.len(), 2);
394                assert!(matches!(params.entries[0].inner, Type::Int(_)));
395                assert!(matches!(params.entries[1].inner, Type::String(_)));
396            }
397            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
398        }
399
400        match do_parse("iterable<bool>") {
401            // Test single param case
402            Ok(Type::Iterable(i)) => {
403                let params = i.parameters.expect("Expected generic parameters");
404                assert_eq!(params.entries.len(), 1);
405                assert!(matches!(params.entries[0].inner, Type::Bool(_)));
406            }
407            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
408        }
409
410        match do_parse("iterable") {
411            Ok(Type::Iterable(i)) => {
412                assert!(i.parameters.is_none());
413            }
414            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
415        }
416    }
417
418    #[test]
419    fn test_parse_negated_int() {
420        let assert_negated_int = |input: &str, expected_value: u64| {
421            let result = do_parse(input);
422            assert!(result.is_ok());
423            match result.unwrap() {
424                Type::Negated(n) => {
425                    assert!(matches!(*n.inner, Type::LiteralInt(_)));
426                    if let Type::LiteralInt(lit) = *n.inner {
427                        assert_eq!(lit.value, expected_value);
428                    } else {
429                        panic!()
430                    }
431                }
432                _ => panic!("Expected Type::Negated"),
433            }
434        };
435
436        assert_negated_int("-0", 0);
437        assert_negated_int("-1", 1);
438        assert_negated_int(
439            "-
440            // This is a comment
441            123_345",
442            123345,
443        );
444        assert_negated_int("-0b1", 1);
445    }
446
447    #[test]
448    fn test_parse_callable_no_spec() {
449        match do_parse("callable") {
450            Ok(Type::Callable(c)) => {
451                assert!(c.specification.is_none());
452                assert_eq!(c.kind, CallableTypeKind::Callable);
453            }
454            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
455        }
456    }
457
458    #[test]
459    fn test_parse_callable_params_only() {
460        match do_parse("callable(int, ?string)") {
461            Ok(Type::Callable(c)) => {
462                let spec = c.specification.expect("Expected callable specification");
463                assert!(spec.return_type.is_none());
464                assert_eq!(spec.parameters.entries.len(), 2);
465                assert!(matches!(*spec.parameters.entries[0].parameter_type, Type::Int(_)));
466                assert!(matches!(*spec.parameters.entries[1].parameter_type, Type::Nullable(_)));
467                assert!(spec.parameters.entries[0].ellipsis.is_none());
468                assert!(spec.parameters.entries[0].equals.is_none());
469            }
470            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
471        }
472    }
473
474    #[test]
475    fn test_parse_callable_return_only() {
476        match do_parse("callable(): void") {
477            Ok(Type::Callable(c)) => {
478                let spec = c.specification.expect("Expected callable specification");
479                assert!(spec.parameters.entries.is_empty());
480                assert!(spec.return_type.is_some());
481                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Void(_)));
482            }
483            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
484        }
485    }
486
487    #[test]
488    fn test_parse_pure_callable_full() {
489        match do_parse("pure-callable(bool): int") {
490            Ok(Type::Callable(c)) => {
491                assert_eq!(c.kind, CallableTypeKind::PureCallable);
492                let spec = c.specification.expect("Expected callable specification");
493                assert_eq!(spec.parameters.entries.len(), 1);
494                assert!(matches!(*spec.parameters.entries[0].parameter_type, Type::Bool(_)));
495                assert!(spec.return_type.is_some());
496                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Int(_)));
497            }
498            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
499        }
500    }
501
502    #[test]
503    fn test_parse_closure_via_identifier() {
504        match do_parse("Closure(string): bool") {
505            Ok(Type::Callable(c)) => {
506                assert_eq!(c.kind, CallableTypeKind::Closure);
507                assert_eq!(c.keyword.value, "Closure");
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::String(_)));
511                assert!(spec.return_type.is_some());
512                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Bool(_)));
513            }
514            res => panic!("Expected Ok(Type::Callable) for Closure, got {res:?}"),
515        }
516    }
517
518    #[test]
519    fn test_parse_complex_pure_callable() {
520        match do_parse("pure-callable(list<int>, ?Closure(): void=, int...): ((Simple&Iter<T>)|null)") {
521            Ok(Type::Callable(c)) => {
522                assert_eq!(c.kind, CallableTypeKind::PureCallable);
523                let spec = c.specification.expect("Expected callable specification");
524                assert_eq!(spec.parameters.entries.len(), 3);
525                assert!(spec.return_type.is_some());
526
527                let first_param = &spec.parameters.entries[0];
528                assert!(matches!(*first_param.parameter_type, Type::List(_)));
529                assert!(first_param.ellipsis.is_none());
530                assert!(first_param.equals.is_none());
531
532                let second_param = &spec.parameters.entries[1];
533                assert!(matches!(*second_param.parameter_type, Type::Nullable(_)));
534                assert!(second_param.ellipsis.is_none());
535                assert!(second_param.equals.is_some());
536
537                let third_param = &spec.parameters.entries[2];
538                assert!(matches!(*third_param.parameter_type, Type::Int(_)));
539                assert!(third_param.ellipsis.is_some());
540                assert!(third_param.equals.is_none());
541
542                if let Type::Parenthesized(p) = *spec.return_type.unwrap().return_type {
543                    assert!(matches!(*p.inner, Type::Union(_)));
544                    if let Type::Union(u) = *p.inner {
545                        assert!(matches!(u.left.as_ref(), Type::Parenthesized(_)));
546                        assert!(matches!(u.right.as_ref(), Type::Null(_)));
547                    }
548                } else {
549                    panic!("Expected Type::CallableReturnType");
550                }
551            }
552            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
553        }
554    }
555
556    #[test]
557    fn test_parse_conditional_type() {
558        match do_parse("int is not string ? array : int") {
559            Ok(Type::Conditional(c)) => {
560                assert!(matches!(*c.subject, Type::Int(_)));
561                assert!(c.not.is_some());
562                assert!(matches!(*c.target, Type::String(_)));
563                assert!(matches!(*c.then, Type::Array(_)));
564                assert!(matches!(*c.otherwise, Type::Int(_)));
565            }
566            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
567        }
568
569        match do_parse("$input is string ? array : int") {
570            Ok(Type::Conditional(c)) => {
571                assert!(matches!(*c.subject, Type::Variable(_)));
572                assert!(c.not.is_none());
573                assert!(matches!(*c.target, Type::String(_)));
574                assert!(matches!(*c.then, Type::Array(_)));
575                assert!(matches!(*c.otherwise, Type::Int(_)));
576            }
577            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
578        }
579
580        match do_parse("int is string ? array : (int is not $bar ? string : $baz)") {
581            Ok(Type::Conditional(c)) => {
582                assert!(matches!(*c.subject, Type::Int(_)));
583                assert!(c.not.is_none());
584                assert!(matches!(*c.target, Type::String(_)));
585                assert!(matches!(*c.then, Type::Array(_)));
586
587                let Type::Parenthesized(p) = *c.otherwise else {
588                    panic!("Expected Type::Parenthesized");
589                };
590
591                if let Type::Conditional(inner_conditional) = *p.inner {
592                    assert!(matches!(*inner_conditional.subject, Type::Int(_)));
593                    assert!(inner_conditional.not.is_some());
594                    assert!(matches!(*inner_conditional.target, Type::Variable(_)));
595                    assert!(matches!(*inner_conditional.then, Type::String(_)));
596                    assert!(matches!(*inner_conditional.otherwise, Type::Variable(_)));
597                } else {
598                    panic!("Expected Type::Conditional");
599                }
600            }
601            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
602        }
603    }
604
605    #[test]
606    fn test_keyof() {
607        match do_parse("key-of<MyArray>") {
608            Ok(Type::KeyOf(k)) => {
609                assert_eq!(k.keyword.value, "key-of");
610                match &k.parameter.entry.inner {
611                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
612                    _ => panic!("Expected Type::Reference"),
613                }
614            }
615            res => panic!("Expected Ok(Type::KeyOf), got {res:?}"),
616        }
617    }
618
619    #[test]
620    fn test_valueof() {
621        match do_parse("value-of<MyArray>") {
622            Ok(Type::ValueOf(v)) => {
623                assert_eq!(v.keyword.value, "value-of");
624                match &v.parameter.entry.inner {
625                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
626                    _ => panic!("Expected Type::Reference"),
627                }
628            }
629            res => panic!("Expected Ok(Type::ValueOf), got {res:?}"),
630        }
631    }
632
633    #[test]
634    fn test_indexed_access() {
635        match do_parse("MyArray[MyKey]") {
636            Ok(Type::IndexAccess(i)) => {
637                match *i.target {
638                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
639                    _ => panic!("Expected Type::Reference"),
640                }
641                match *i.index {
642                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyKey"),
643                    _ => panic!("Expected Type::Reference"),
644                }
645            }
646            res => panic!("Expected Ok(Type::IndexAccess), got {res:?}"),
647        }
648    }
649
650    #[test]
651    fn test_int_range() {
652        match do_parse("int<0, 100>") {
653            Ok(Type::IntRange(r)) => {
654                assert_eq!(r.keyword.value, "int");
655
656                match r.min {
657                    LiteralIntOrKeyword::LiteralInt(literal_int_type) => {
658                        assert_eq!(literal_int_type.value, 0);
659                    }
660                    LiteralIntOrKeyword::Keyword(keyword) => {
661                        panic!("Expected min to be a LiteralIntType, got Keyword with value {}", keyword.value,)
662                    }
663                };
664
665                match r.max {
666                    LiteralIntOrKeyword::LiteralInt(literal_int_type) => {
667                        assert_eq!(literal_int_type.value, 100);
668                    }
669                    LiteralIntOrKeyword::Keyword(keyword) => {
670                        panic!("Expected max to be a Keyword, got Keyword with value {}", keyword.value,)
671                    }
672                };
673            }
674            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
675        }
676
677        match do_parse("int<min, 0>") {
678            Ok(Type::IntRange(r)) => {
679                match r.min {
680                    LiteralIntOrKeyword::LiteralInt(literal_int_type) => {
681                        panic!("Expected min to be a keyword, got LiteralIntType with value {}", literal_int_type.value,)
682                    }
683                    LiteralIntOrKeyword::Keyword(keyword) => {
684                        assert_eq!(keyword.value, "min");
685                    }
686                };
687
688                match r.max {
689                    LiteralIntOrKeyword::LiteralInt(literal_int_type) => {
690                        assert_eq!(literal_int_type.value, 0);
691                    }
692                    LiteralIntOrKeyword::Keyword(keyword) => {
693                        panic!("Expected max to be a LiteralIntType, got Keyword with value {}", keyword.value,)
694                    }
695                };
696            }
697            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
698        }
699
700        match do_parse("int<min, max>") {
701            Ok(Type::IntRange(r)) => {
702                match r.min {
703                    LiteralIntOrKeyword::LiteralInt(literal_int_type) => {
704                        panic!("Expected min to be a keyword, got LiteralIntType with value {}", literal_int_type.value,)
705                    }
706                    LiteralIntOrKeyword::Keyword(keyword) => {
707                        assert_eq!(keyword.value, "min");
708                    }
709                };
710
711                match r.max {
712                    LiteralIntOrKeyword::LiteralInt(literal_int_type) => {
713                        panic!("Expected max to be a keyword, got LiteralIntType with value {}", literal_int_type.value,)
714                    }
715                    LiteralIntOrKeyword::Keyword(keyword) => {
716                        assert_eq!(keyword.value, "max");
717                    }
718                };
719            }
720            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
721        }
722    }
723
724    #[test]
725    fn test_properties_of() {
726        match do_parse("properties-of<MyClass>") {
727            Ok(Type::PropertiesOf(p)) => {
728                assert_eq!(p.keyword.value, "properties-of");
729                assert_eq!(p.filter, PropertiesOfFilter::All);
730                match &p.parameter.entry.inner {
731                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyClass"),
732                    _ => panic!(),
733                }
734            }
735            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
736        }
737
738        match do_parse("protected-properties-of<T>") {
739            Ok(Type::PropertiesOf(p)) => {
740                assert_eq!(p.keyword.value, "protected-properties-of");
741                assert_eq!(p.filter, PropertiesOfFilter::Protected);
742                match &p.parameter.entry.inner {
743                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
744                    _ => panic!(),
745                }
746            }
747            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
748        }
749
750        match do_parse("private-properties-of<T>") {
751            Ok(Type::PropertiesOf(p)) => {
752                assert_eq!(p.keyword.value, "private-properties-of");
753                assert_eq!(p.filter, PropertiesOfFilter::Private);
754                match &p.parameter.entry.inner {
755                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
756                    _ => panic!(),
757                }
758            }
759            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
760        }
761
762        match do_parse("public-properties-of<T>") {
763            Ok(Type::PropertiesOf(p)) => {
764                assert_eq!(p.keyword.value, "public-properties-of");
765                assert_eq!(p.filter, PropertiesOfFilter::Public);
766                match &p.parameter.entry.inner {
767                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
768                    _ => panic!(),
769                }
770            }
771            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
772        }
773    }
774
775    #[test]
776    fn test_variable() {
777        match do_parse("$myVar") {
778            Ok(Type::Variable(v)) => {
779                assert_eq!(v.value, "$myVar");
780            }
781            res => panic!("Expected Ok(Type::Variable), got {res:?}"),
782        }
783    }
784
785    #[test]
786    fn test_nullable_intersection() {
787        // Nullable applies only to the rightmost element of an intersection before parens
788        match do_parse("Countable&?Traversable") {
789            Ok(Type::Intersection(i)) => {
790                assert!(matches!(*i.left, Type::Reference(r) if r.identifier.value == "Countable"));
791                assert!(matches!(*i.right, Type::Nullable(_)));
792                if let Type::Nullable(n) = *i.right {
793                    assert!(matches!(*n.inner, Type::Reference(r) if r.identifier.value == "Traversable"));
794                } else {
795                    panic!();
796                }
797            }
798            res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
799        }
800    }
801
802    #[test]
803    fn test_parenthesized_nullable() {
804        match do_parse("?(Countable&Traversable)") {
805            Ok(Type::Nullable(n)) => {
806                assert!(matches!(*n.inner, Type::Parenthesized(_)));
807                if let Type::Parenthesized(p) = *n.inner {
808                    assert!(matches!(*p.inner, Type::Intersection(_)));
809                } else {
810                    panic!()
811                }
812            }
813            res => panic!("Expected Ok(Type::Nullable), got {res:?}"),
814        }
815    }
816
817    #[test]
818    fn test_positive_negative_int() {
819        match do_parse("positive-int|negative-int") {
820            Ok(Type::Union(u)) => {
821                assert!(matches!(*u.left, Type::PositiveInt(_)));
822                assert!(matches!(*u.right, Type::NegativeInt(_)));
823            }
824            res => panic!("Expected Ok(Type::Union), got {res:?}"),
825        }
826    }
827}