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