Skip to main content

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/// # Errors
29///
30/// Returns a [`ParseError`] if any lexing or parsing error occurs.
31pub fn parse_str(span: Span, input: &str) -> Result<Type<'_>, ParseError> {
32    // Create an Input anchored at the type string's original starting position.
33    let input = Input::anchored_at(span.file_id, input.as_bytes(), span.start);
34    // Create the type-specific lexer.
35    let lexer = TypeLexer::new(input);
36    // Construct the type AST using the lexer.
37    parser::construct(lexer)
38}
39
40#[cfg(test)]
41mod tests {
42    use mago_database::file::FileId;
43    use mago_span::HasSpan;
44    use mago_span::Position;
45    use mago_span::Span;
46
47    use crate::ast::*;
48
49    use super::*;
50
51    fn do_parse(input: &str) -> Result<Type<'_>, ParseError> {
52        parse_str(Span::new(FileId::zero(), Position::new(0), Position::new(input.len() as u32)), input)
53    }
54
55    #[test]
56    fn test_parse_simple_keyword() {
57        let result = do_parse("int");
58        assert!(result.is_ok());
59        match result.unwrap() {
60            Type::Int(k) => assert_eq!(k.value, "int"),
61            _ => panic!("Expected Type::Int"),
62        }
63    }
64
65    #[test]
66    fn test_parse_composite_keyword() {
67        let result = do_parse("non-empty-string");
68        assert!(result.is_ok());
69        match result.unwrap() {
70            Type::NonEmptyString(k) => assert_eq!(k.value, "non-empty-string"),
71            _ => panic!("Expected Type::NonEmptyString"),
72        }
73    }
74
75    #[test]
76    fn test_parse_literal_ints() {
77        let assert_parsed_literal_int = |input: &str, expected_value: u64| {
78            let result = do_parse(input);
79            assert!(result.is_ok());
80            match result.unwrap() {
81                Type::LiteralInt(LiteralIntType { value, .. }) => assert_eq!(
82                    value, expected_value,
83                    "Expected value to be {expected_value} for input {input}, but got {value}"
84                ),
85                _ => panic!("Expected Type::LiteralInt"),
86            }
87        };
88
89        assert_parsed_literal_int("0", 0);
90        assert_parsed_literal_int("1", 1);
91        assert_parsed_literal_int("123_345", 123_345);
92        assert_parsed_literal_int("0b1", 1);
93        assert_parsed_literal_int("0o10", 8);
94        assert_parsed_literal_int("0x1", 1);
95        assert_parsed_literal_int("0x10", 16);
96        assert_parsed_literal_int("0xFF", 255);
97    }
98
99    #[test]
100    fn test_parse_literal_floats() {
101        let assert_parsed_literal_float = |input: &str, expected_value: f64| {
102            let result = do_parse(input);
103            assert!(result.is_ok());
104            match result.unwrap() {
105                Type::LiteralFloat(LiteralFloatType { value, .. }) => assert_eq!(
106                    value, expected_value,
107                    "Expected value to be {expected_value} for input {input}, but got {value}"
108                ),
109                _ => panic!("Expected Type::LiteralInt"),
110            }
111        };
112
113        assert_parsed_literal_float("0.0", 0.0);
114        assert_parsed_literal_float("1.0", 1.0);
115        assert_parsed_literal_float("0.1e1", 1.0);
116        assert_parsed_literal_float("0.1e-1", 0.01);
117        assert_parsed_literal_float("0.1E1", 1.0);
118        assert_parsed_literal_float("0.1E-1", 0.01);
119        assert_parsed_literal_float("0.1e+1", 1.0);
120        assert_parsed_literal_float(".1e+1", 1.0);
121    }
122
123    #[test]
124    fn test_parse_simple_union() {
125        match do_parse("int|string") {
126            Ok(ty) => match ty {
127                Type::Union(u) => {
128                    assert!(matches!(*u.left, Type::Int(_)));
129                    assert!(matches!(*u.right, Type::String(_)));
130                }
131                _ => panic!("Expected Type::Union"),
132            },
133            Err(err) => {
134                panic!("Failed to parse union type: {err:?}");
135            }
136        }
137    }
138
139    #[test]
140    fn test_parse_variable_union() {
141        match do_parse("$a|$b") {
142            Ok(ty) => match ty {
143                Type::Union(u) => {
144                    assert!(matches!(*u.left, Type::Variable(_)));
145                    assert!(matches!(*u.right, Type::Variable(_)));
146                }
147                _ => panic!("Expected Type::Union"),
148            },
149            Err(err) => {
150                panic!("Failed to parse union type: {err:?}");
151            }
152        }
153    }
154
155    #[test]
156    fn test_parse_nullable() {
157        let result = do_parse("?string");
158        assert!(result.is_ok());
159        match result.unwrap() {
160            Type::Nullable(n) => {
161                assert!(matches!(*n.inner, Type::String(_)));
162            }
163            _ => panic!("Expected Type::Nullable"),
164        }
165    }
166
167    #[test]
168    fn test_parse_generic_array() {
169        let result = do_parse("array<int, bool>");
170        assert!(result.is_ok());
171        match result.unwrap() {
172            Type::Array(a) => {
173                assert!(a.parameters.is_some());
174                let params = a.parameters.unwrap();
175                assert_eq!(params.entries.len(), 2);
176                assert!(matches!(params.entries[0].inner, Type::Int(_)));
177                assert!(matches!(params.entries[1].inner, Type::Bool(_)));
178            }
179            _ => panic!("Expected Type::Array"),
180        }
181    }
182
183    #[test]
184    fn test_parse_generic_array_one_param() {
185        match do_parse("array<string>") {
186            Ok(Type::Array(a)) => {
187                let params = a.parameters.expect("Expected generic parameters");
188                assert_eq!(params.entries.len(), 1);
189                assert!(matches!(params.entries[0].inner, Type::String(_)));
190            }
191            res => panic!("Expected Ok(Type::Array), got {res:?}"),
192        }
193    }
194
195    #[test]
196    fn test_parse_generic_list() {
197        match do_parse("list<string>") {
198            Ok(Type::List(l)) => {
199                let params = l.parameters.expect("Expected generic parameters");
200                assert_eq!(params.entries.len(), 1);
201                assert!(matches!(params.entries[0].inner, Type::String(_)));
202            }
203            res => panic!("Expected Ok(Type::List), got {res:?}"),
204        }
205    }
206
207    #[test]
208    fn test_parse_non_empty_array() {
209        match do_parse("non-empty-array<int, bool>") {
210            Ok(Type::NonEmptyArray(a)) => {
211                let params = a.parameters.expect("Expected generic parameters");
212                assert_eq!(params.entries.len(), 2);
213                assert!(matches!(params.entries[0].inner, Type::Int(_)));
214                assert!(matches!(params.entries[1].inner, Type::Bool(_)));
215            }
216            res => panic!("Expected Ok(Type::NonEmptyArray), got {res:?}"),
217        }
218    }
219
220    #[test]
221    fn test_parse_nested_generics() {
222        match do_parse("list<array<int, string>>") {
223            Ok(Type::List(l)) => {
224                let params = l.parameters.expect("Expected generic parameters");
225                assert_eq!(params.entries.len(), 1);
226                match &params.entries[0].inner {
227                    Type::Array(inner_array) => {
228                        let inner_params = inner_array.parameters.as_ref().expect("Inner array needs params");
229                        assert_eq!(inner_params.entries.len(), 2);
230                        assert!(matches!(inner_params.entries[0].inner, Type::Int(_)));
231                        assert!(matches!(inner_params.entries[1].inner, Type::String(_)));
232                    }
233                    _ => panic!("Expected inner type to be Type::Array"),
234                }
235            }
236            res => panic!("Expected Ok(Type::List), got {res:?}"),
237        }
238    }
239
240    #[test]
241    fn test_parse_simple_shape() {
242        let result = do_parse("array{'name': string}");
243        assert!(matches!(result, Ok(Type::Shape(_))));
244        let Ok(Type::Shape(shape)) = result else {
245            panic!("Expected Type::Shape");
246        };
247
248        assert_eq!(shape.kind, ShapeTypeKind::Array);
249        assert_eq!(shape.keyword.value, "array");
250        assert_eq!(shape.fields.len(), 1);
251        assert!(shape.additional_fields.is_none());
252
253        let field = &shape.fields[0];
254        assert!(matches!(field.key.as_ref().map(|k| &k.key), Some(ShapeKey::String { value: "name", .. })));
255        assert!(matches!(field.value.as_ref(), Type::String(_)));
256    }
257
258    #[test]
259    fn test_parse_int_key_shape() {
260        match do_parse("array{0: string, 1: bool}") {
261            Ok(Type::Shape(shape)) => {
262                assert_eq!(shape.fields.len(), 2);
263                let first_field = &shape.fields[0];
264                assert!(matches!(first_field.key.as_ref().map(|k| &k.key), Some(ShapeKey::Integer { value: 0, .. })));
265                assert!(matches!(first_field.value.as_ref(), Type::String(_)));
266                let second_field = &shape.fields[1];
267                assert!(matches!(second_field.key.as_ref().map(|k| &k.key), Some(ShapeKey::Integer { value: 1, .. })));
268                assert!(matches!(second_field.value.as_ref(), Type::Bool(_)));
269            }
270            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
271        }
272    }
273
274    #[test]
275    fn test_parse_optional_field_shape() {
276        match do_parse("array{name: string, age?: int, address: string}") {
277            Ok(Type::Shape(shape)) => {
278                assert_eq!(shape.fields.len(), 3);
279                assert!(!shape.fields[0].is_optional());
280                assert!(shape.fields[1].is_optional());
281                assert!(!shape.fields[2].is_optional());
282            }
283            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
284        }
285    }
286
287    #[test]
288    fn test_parse_unsealed_shape() {
289        match do_parse("array{name: string, ...}") {
290            Ok(Type::Shape(shape)) => {
291                assert_eq!(shape.fields.len(), 1);
292                assert!(shape.additional_fields.is_some());
293                assert!(shape.additional_fields.unwrap().parameters.is_none()); // No fallback specified
294            }
295            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
296        }
297    }
298
299    #[test]
300    fn test_parse_shape_with_keys_containing_special_chars() {
301        match do_parse("array{key-with-dash: int, key-with---multiple-dashes?: int}") {
302            Ok(Type::Shape(shape)) => {
303                assert_eq!(shape.fields.len(), 2);
304
305                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[0].key.as_ref().map(|k| &k.key) {
306                    assert_eq!(*s, "key-with-dash");
307                } else {
308                    panic!("Expected key to be a ShapeKey::String");
309                }
310
311                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[1].key.as_ref().map(|k| &k.key) {
312                    assert_eq!(*s, "key-with---multiple-dashes");
313                } else {
314                    panic!("Expected key to be a ShapeKey::String");
315                }
316            }
317            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
318        }
319    }
320
321    #[test]
322    fn test_parse_shape_with_keys_after_types() {
323        match do_parse("array{list: list<int>, int?: int, string: string, bool: bool}") {
324            Ok(Type::Shape(shape)) => {
325                assert_eq!(shape.fields.len(), 4);
326
327                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[0].key.as_ref().map(|k| &k.key) {
328                    assert_eq!(*s, "list");
329                } else {
330                    panic!("Expected key to be a ShapeKey::String");
331                }
332
333                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[1].key.as_ref().map(|k| &k.key) {
334                    assert_eq!(*s, "int");
335                } else {
336                    panic!("Expected key to be a ShapeKey::String");
337                }
338
339                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[2].key.as_ref().map(|k| &k.key) {
340                    assert_eq!(*s, "string");
341                } else {
342                    panic!("Expected key to be a ShapeKey::String");
343                }
344
345                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[3].key.as_ref().map(|k| &k.key) {
346                    assert_eq!(*s, "bool");
347                } else {
348                    panic!("Expected key to be a ShapeKey::String");
349                }
350            }
351            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
352        }
353    }
354
355    #[test]
356    fn test_parse_unsealed_shape_with_fallback() {
357        match do_parse(
358            "array{
359                name: string, // This is a comment
360                ...<string, string>
361            }",
362        ) {
363            Ok(Type::Shape(shape)) => {
364                assert_eq!(shape.fields.len(), 1);
365                assert!(shape.additional_fields.as_ref().is_some_and(|a| a.parameters.is_some()));
366                let params = shape.additional_fields.unwrap().parameters.unwrap();
367                assert_eq!(params.entries.len(), 2);
368                assert!(matches!(params.entries[0].inner, Type::String(_)));
369                assert!(matches!(params.entries[1].inner, Type::String(_)));
370            }
371            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
372        }
373    }
374
375    #[test]
376    fn test_parse_empty_shape() {
377        match do_parse("array{}") {
378            Ok(Type::Shape(shape)) => {
379                assert_eq!(shape.fields.len(), 0);
380                assert!(shape.additional_fields.is_none());
381            }
382            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
383        }
384    }
385
386    #[test]
387    fn test_parse_nested_spread_singleline() {
388        // Test nested spreads on single line - this should work
389        match do_parse("array{a?: int, ...<string, array{b?: int, ...<string, int>}>}") {
390            Ok(Type::Shape(shape)) => {
391                assert_eq!(shape.fields.len(), 1);
392                assert!(shape.additional_fields.is_some());
393            }
394            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
395        }
396    }
397
398    #[test]
399    fn test_parse_nested_spread_multiline() {
400        match do_parse(
401            "array{
402                a?: int,
403                ...<string, array{
404                    b?: int,
405                    ...<string, int>,
406                }>
407            }",
408        ) {
409            Ok(Type::Shape(shape)) => {
410                assert_eq!(shape.fields.len(), 1);
411                assert!(shape.additional_fields.is_some());
412            }
413            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
414        }
415    }
416
417    #[test]
418    fn test_parse_spread_with_trailing_comma() {
419        match do_parse("array{a?: int, ...<string, int>,}") {
420            Ok(Type::Shape(shape)) => {
421                assert_eq!(shape.fields.len(), 1);
422                assert!(shape.additional_fields.is_some());
423            }
424            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
425        }
426    }
427
428    #[test]
429    fn test_parse_error_unexpected_token() {
430        let result = do_parse("int|>");
431        assert!(result.is_err());
432        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedToken { .. }));
433    }
434
435    #[test]
436    fn test_parse_error_eof() {
437        let result = do_parse("array<int");
438        assert!(result.is_err());
439        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedEndOfFile { .. }));
440    }
441
442    #[test]
443    fn test_parse_error_trailing_token() {
444        let result = do_parse("int|string&");
445        assert!(result.is_err());
446        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedEndOfFile { .. }));
447    }
448
449    #[test]
450    fn test_parse_intersection() {
451        match do_parse("Countable&Traversable") {
452            Ok(Type::Intersection(i)) => {
453                assert!(matches!(*i.left, Type::Reference(_)));
454                assert!(matches!(*i.right, Type::Reference(_)));
455
456                if let Type::Reference(r) = *i.left {
457                    assert_eq!(r.identifier.value, "Countable");
458                } else {
459                    panic!();
460                }
461
462                if let Type::Reference(r) = *i.right {
463                    assert_eq!(r.identifier.value, "Traversable");
464                } else {
465                    panic!();
466                }
467            }
468            res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
469        }
470    }
471
472    #[test]
473    fn test_parse_member_ref() {
474        match do_parse("MyClass::MY_CONST") {
475            Ok(Type::MemberReference(m)) => {
476                assert_eq!(m.class.value, "MyClass");
477                assert_eq!(m.member.to_string(), "MY_CONST");
478            }
479            res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
480        }
481
482        match do_parse("\\Fully\\Qualified::class") {
483            Ok(Type::MemberReference(m)) => {
484                assert_eq!(m.class.value, "\\Fully\\Qualified"); // Check if lexer keeps leading \
485                assert_eq!(m.member.to_string(), "class");
486            }
487            res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
488        }
489    }
490
491    #[test]
492    fn test_parse_member_ref_named_new() {
493        match do_parse("Action::NEW") {
494            Ok(Type::MemberReference(m)) => {
495                assert_eq!(m.class.value, "Action");
496                assert_eq!(m.member.to_string(), "NEW");
497            }
498            res => panic!("Expected Ok(Type::MemberReference) for Action::NEW, got {res:?}"),
499        }
500
501        match do_parse("Action::new") {
502            Ok(Type::MemberReference(m)) => {
503                assert_eq!(m.class.value, "Action");
504                assert_eq!(m.member.to_string(), "new");
505            }
506            res => panic!("Expected Ok(Type::MemberReference) for Action::new, got {res:?}"),
507        }
508
509        match do_parse("Action::DELETE|Action::NEW") {
510            Ok(Type::Union(u)) => match (&*u.left, &*u.right) {
511                (Type::MemberReference(lhs), Type::MemberReference(rhs)) => {
512                    assert_eq!(lhs.member.to_string(), "DELETE");
513                    assert_eq!(rhs.member.to_string(), "NEW");
514                }
515                other => panic!("Expected two member references, got {other:?}"),
516            },
517            res => panic!("Expected Ok(Type::Union), got {res:?}"),
518        }
519
520        match do_parse("\\App\\Action::NEW") {
521            Ok(Type::MemberReference(m)) => {
522                assert_eq!(m.member.to_string(), "NEW");
523            }
524            res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
525        }
526
527        match do_parse("App\\Action::NEW") {
528            Ok(Type::MemberReference(m)) => {
529                assert_eq!(m.member.to_string(), "NEW");
530            }
531            res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
532        }
533
534        match do_parse("Action::new*") {
535            Ok(Type::MemberReference(m)) => {
536                assert_eq!(m.class.value, "Action");
537                assert!(matches!(m.member, MemberReferenceSelector::StartsWith(..)));
538            }
539            res => panic!("Expected Ok(Type::MemberReference) for Action::new*, got {res:?}"),
540        }
541
542        match do_parse("Action::*new") {
543            Ok(Type::MemberReference(m)) => {
544                assert_eq!(m.class.value, "Action");
545                assert!(matches!(m.member, MemberReferenceSelector::EndsWith(..)));
546            }
547            res => panic!("Expected Ok(Type::MemberReference) for Action::*new, got {res:?}"),
548        }
549
550        match do_parse("new<Foo>") {
551            Ok(Type::New(_)) => {}
552            res => panic!("Expected Ok(Type::New), got {res:?}"),
553        }
554    }
555
556    #[test]
557    fn test_parse_new_in_other_identifier_contexts() {
558        match do_parse("array{new: int}") {
559            Ok(Type::Shape(_)) => {}
560            res => panic!("Expected Ok(Type::Shape) for array{{new: int}}, got {res:?}"),
561        }
562
563        match do_parse("array{new?: int}") {
564            Ok(Type::Shape(_)) => {}
565            res => panic!("Expected Ok(Type::Shape) for array{{new?: int}}, got {res:?}"),
566        }
567
568        match do_parse("array{Foo::NEW: int}") {
569            Ok(Type::Shape(_)) => {}
570            res => panic!("Expected Ok(Type::Shape) for array{{Foo::NEW: int}}, got {res:?}"),
571        }
572
573        match do_parse("object{new: int}") {
574            Ok(Type::Object(_)) => {}
575            res => panic!("Expected Ok(Type::Object) for object{{new: int}}, got {res:?}"),
576        }
577
578        match do_parse("!Foo::new") {
579            Ok(Type::AliasReference(_)) => {}
580            res => panic!("Expected Ok(Type::AliasReference) for !Foo::new, got {res:?}"),
581        }
582    }
583
584    #[test]
585    fn test_parse_iterable() {
586        match do_parse("iterable<int, string>") {
587            Ok(Type::Iterable(i)) => {
588                let params = i.parameters.expect("Expected generic parameters");
589                assert_eq!(params.entries.len(), 2);
590                assert!(matches!(params.entries[0].inner, Type::Int(_)));
591                assert!(matches!(params.entries[1].inner, Type::String(_)));
592            }
593            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
594        }
595
596        match do_parse("iterable<bool>") {
597            // Test single param case
598            Ok(Type::Iterable(i)) => {
599                let params = i.parameters.expect("Expected generic parameters");
600                assert_eq!(params.entries.len(), 1);
601                assert!(matches!(params.entries[0].inner, Type::Bool(_)));
602            }
603            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
604        }
605
606        match do_parse("iterable") {
607            Ok(Type::Iterable(i)) => {
608                assert!(i.parameters.is_none());
609            }
610            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
611        }
612    }
613
614    #[test]
615    fn test_parse_negated_int() {
616        let assert_negated_int = |input: &str, expected_value: u64| {
617            let result = do_parse(input);
618            assert!(result.is_ok());
619            match result.unwrap() {
620                Type::Negated(n) => {
621                    assert!(matches!(n.number, LiteralIntOrFloatType::Int(_)));
622                    if let LiteralIntOrFloatType::Int(lit) = n.number {
623                        assert_eq!(lit.value, expected_value);
624                    } else {
625                        panic!()
626                    }
627                }
628                _ => panic!("Expected Type::Negated"),
629            }
630        };
631
632        assert_negated_int("-0", 0);
633        assert_negated_int("-1", 1);
634        assert_negated_int(
635            "-
636            // This is a comment
637            123_345",
638            123_345,
639        );
640        assert_negated_int("-0b1", 1);
641    }
642
643    #[test]
644    fn test_parse_negated_float() {
645        let assert_negated_float = |input: &str, expected_value: f64| {
646            let result = do_parse(input);
647            assert!(result.is_ok());
648            match result.unwrap() {
649                Type::Negated(n) => {
650                    assert!(matches!(n.number, LiteralIntOrFloatType::Float(_)));
651                    if let LiteralIntOrFloatType::Float(lit) = n.number {
652                        assert_eq!(lit.value, expected_value);
653                    } else {
654                        panic!()
655                    }
656                }
657                _ => panic!("Expected Type::Negated"),
658            }
659        };
660
661        assert_negated_float("-0.0", 0.0);
662        assert_negated_float("-1.0", 1.0);
663        assert_negated_float("-0.1e1", 1.0);
664        assert_negated_float("-0.1e-1", 0.01);
665    }
666
667    #[test]
668    fn test_parse_negated_union() {
669        match do_parse("-1|-2.0|string") {
670            Ok(Type::Union(n)) => {
671                assert!(matches!(*n.left, Type::Negated(_)));
672                assert!(matches!(*n.right, Type::Union(_)));
673
674                if let Type::Negated(neg) = *n.left {
675                    assert!(matches!(neg.number, LiteralIntOrFloatType::Int(_)));
676                    if let LiteralIntOrFloatType::Int(lit) = neg.number {
677                        assert_eq!(lit.value, 1);
678                    } else {
679                        panic!()
680                    }
681                } else {
682                    panic!("Expected left side to be Type::Negated");
683                }
684
685                if let Type::Union(inner_union) = *n.right {
686                    assert!(matches!(*inner_union.left, Type::Negated(_)));
687                    assert!(matches!(*inner_union.right, Type::String(_)));
688
689                    if let Type::Negated(neg) = *inner_union.left {
690                        assert!(matches!(neg.number, LiteralIntOrFloatType::Float(_)));
691                        if let LiteralIntOrFloatType::Float(lit) = neg.number {
692                            assert_eq!(lit.value, 2.0);
693                        } else {
694                            panic!()
695                        }
696                    } else {
697                        panic!("Expected left side of inner union to be Type::Negated");
698                    }
699
700                    if let Type::String(s) = *inner_union.right {
701                        assert_eq!(s.value, "string");
702                    } else {
703                        panic!("Expected right side of inner union to be Type::String");
704                    }
705                } else {
706                    panic!("Expected right side to be Type::Union");
707                }
708            }
709            res => panic!("Expected Ok(Type::Negated), got {res:?}"),
710        }
711    }
712
713    #[test]
714    fn test_parse_callable_no_spec() {
715        match do_parse("callable") {
716            Ok(Type::Callable(c)) => {
717                assert!(c.specification.is_none());
718                assert_eq!(c.kind, CallableTypeKind::Callable);
719            }
720            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
721        }
722    }
723
724    #[test]
725    fn test_parse_callable_params_only() {
726        match do_parse("callable(int, ?string)") {
727            Ok(Type::Callable(c)) => {
728                let spec = c.specification.expect("Expected callable specification");
729                assert!(spec.return_type.is_none());
730                assert_eq!(spec.parameters.entries.len(), 2);
731                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Int(_))));
732                assert!(matches!(spec.parameters.entries[1].parameter_type, Some(Type::Nullable(_))));
733                assert!(spec.parameters.entries[0].ellipsis.is_none());
734                assert!(spec.parameters.entries[0].equals.is_none());
735            }
736            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
737        }
738    }
739
740    #[test]
741    fn test_parse_callable_return_only() {
742        match do_parse("callable(): void") {
743            Ok(Type::Callable(c)) => {
744                let spec = c.specification.expect("Expected callable specification");
745                assert!(spec.parameters.entries.is_empty());
746                assert!(spec.return_type.is_some());
747                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Void(_)));
748            }
749            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
750        }
751    }
752
753    #[test]
754    fn test_parse_pure_callable_full() {
755        match do_parse("pure-callable(bool): int") {
756            Ok(Type::Callable(c)) => {
757                assert_eq!(c.kind, CallableTypeKind::PureCallable);
758                let spec = c.specification.expect("Expected callable specification");
759                assert_eq!(spec.parameters.entries.len(), 1);
760                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Bool(_))));
761                assert!(spec.return_type.is_some());
762                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Int(_)));
763            }
764            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
765        }
766    }
767
768    #[test]
769    fn test_parse_closure_via_identifier() {
770        match do_parse("Closure(string): bool") {
771            Ok(Type::Callable(c)) => {
772                assert_eq!(c.kind, CallableTypeKind::Closure);
773                assert_eq!(c.keyword.value, "Closure");
774                let spec = c.specification.expect("Expected callable specification");
775                assert_eq!(spec.parameters.entries.len(), 1);
776                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::String(_))));
777                assert!(spec.return_type.is_some());
778                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Bool(_)));
779            }
780            res => panic!("Expected Ok(Type::Callable) for Closure, got {res:?}"),
781        }
782    }
783
784    #[test]
785    fn test_parse_complex_pure_callable() {
786        match do_parse("pure-callable(list<int>, ?Closure(): void=, int...): ((Simple&Iter<T>)|null)") {
787            Ok(Type::Callable(c)) => {
788                assert_eq!(c.kind, CallableTypeKind::PureCallable);
789                let spec = c.specification.expect("Expected callable specification");
790                assert_eq!(spec.parameters.entries.len(), 3);
791                assert!(spec.return_type.is_some());
792
793                let first_param = &spec.parameters.entries[0];
794                assert!(matches!(first_param.parameter_type, Some(Type::List(_))));
795                assert!(first_param.ellipsis.is_none());
796                assert!(first_param.equals.is_none());
797
798                let second_param = &spec.parameters.entries[1];
799                assert!(matches!(second_param.parameter_type, Some(Type::Nullable(_))));
800                assert!(second_param.ellipsis.is_none());
801                assert!(second_param.equals.is_some());
802
803                let third_param = &spec.parameters.entries[2];
804                assert!(matches!(third_param.parameter_type, Some(Type::Int(_))));
805                assert!(third_param.ellipsis.is_some());
806                assert!(third_param.equals.is_none());
807
808                if let Type::Parenthesized(p) = *spec.return_type.unwrap().return_type {
809                    assert!(matches!(*p.inner, Type::Union(_)));
810                    if let Type::Union(u) = *p.inner {
811                        assert!(matches!(u.left.as_ref(), Type::Parenthesized(_)));
812                        assert!(matches!(u.right.as_ref(), Type::Null(_)));
813                    }
814                } else {
815                    panic!("Expected Type::CallableReturnType");
816                }
817            }
818            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
819        }
820    }
821
822    #[test]
823    fn test_parse_conditional_type() {
824        match do_parse("int is not string ? array : int") {
825            Ok(Type::Conditional(c)) => {
826                assert!(matches!(*c.subject, Type::Int(_)));
827                assert!(c.not.is_some());
828                assert!(matches!(*c.target, Type::String(_)));
829                assert!(matches!(*c.then, Type::Array(_)));
830                assert!(matches!(*c.otherwise, Type::Int(_)));
831            }
832            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
833        }
834
835        match do_parse("$input is string ? array : int") {
836            Ok(Type::Conditional(c)) => {
837                assert!(matches!(*c.subject, Type::Variable(_)));
838                assert!(c.not.is_none());
839                assert!(matches!(*c.target, Type::String(_)));
840                assert!(matches!(*c.then, Type::Array(_)));
841                assert!(matches!(*c.otherwise, Type::Int(_)));
842            }
843            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
844        }
845
846        match do_parse("int is string ? array : (int is not $bar ? string : $baz)") {
847            Ok(Type::Conditional(c)) => {
848                assert!(matches!(*c.subject, Type::Int(_)));
849                assert!(c.not.is_none());
850                assert!(matches!(*c.target, Type::String(_)));
851                assert!(matches!(*c.then, Type::Array(_)));
852
853                let Type::Parenthesized(p) = *c.otherwise else {
854                    panic!("Expected Type::Parenthesized");
855                };
856
857                if let Type::Conditional(inner_conditional) = *p.inner {
858                    assert!(matches!(*inner_conditional.subject, Type::Int(_)));
859                    assert!(inner_conditional.not.is_some());
860                    assert!(matches!(*inner_conditional.target, Type::Variable(_)));
861                    assert!(matches!(*inner_conditional.then, Type::String(_)));
862                    assert!(matches!(*inner_conditional.otherwise, Type::Variable(_)));
863                } else {
864                    panic!("Expected Type::Conditional");
865                }
866            }
867            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
868        }
869    }
870
871    #[test]
872    fn test_keyof() {
873        match do_parse("key-of<MyArray>") {
874            Ok(Type::KeyOf(k)) => {
875                assert_eq!(k.keyword.value, "key-of");
876                match &k.parameter.entry.inner {
877                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
878                    _ => panic!("Expected Type::Reference"),
879                }
880            }
881            res => panic!("Expected Ok(Type::KeyOf), got {res:?}"),
882        }
883    }
884
885    #[test]
886    fn test_valueof() {
887        match do_parse("value-of<MyArray>") {
888            Ok(Type::ValueOf(v)) => {
889                assert_eq!(v.keyword.value, "value-of");
890                match &v.parameter.entry.inner {
891                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
892                    _ => panic!("Expected Type::Reference"),
893                }
894            }
895            res => panic!("Expected Ok(Type::ValueOf), got {res:?}"),
896        }
897    }
898
899    #[test]
900    fn test_indexed_access() {
901        match do_parse("MyArray[MyKey]") {
902            Ok(Type::IndexAccess(i)) => {
903                match *i.target {
904                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
905                    _ => panic!("Expected Type::Reference"),
906                }
907                match *i.index {
908                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyKey"),
909                    _ => panic!("Expected Type::Reference"),
910                }
911            }
912            res => panic!("Expected Ok(Type::IndexAccess), got {res:?}"),
913        }
914    }
915
916    #[test]
917    fn test_slice_type() {
918        match do_parse("string[]") {
919            Ok(Type::Slice(s)) => {
920                assert!(matches!(*s.inner, Type::String(_)));
921            }
922            res => panic!("Expected Ok(Type::Slice), got {res:?}"),
923        }
924    }
925
926    #[test]
927    fn test_slice_of_slice_of_slice_type() {
928        match do_parse("string[][][]") {
929            Ok(Type::Slice(s)) => {
930                assert!(matches!(*s.inner, Type::Slice(_)));
931                if let Type::Slice(inner_slice) = *s.inner {
932                    assert!(matches!(*inner_slice.inner, Type::Slice(_)));
933                    if let Type::Slice(inner_inner_slice) = *inner_slice.inner {
934                        assert!(matches!(*inner_inner_slice.inner, Type::String(_)));
935                    } else {
936                        panic!("Expected inner slice to be a Slice");
937                    }
938                } else {
939                    panic!("Expected outer slice to be a Slice");
940                }
941            }
942            res => panic!("Expected Ok(Type::Slice), got {res:?}"),
943        }
944    }
945
946    #[test]
947    fn test_int_range() {
948        match do_parse("int<0, 100>") {
949            Ok(Type::IntRange(r)) => {
950                assert_eq!(r.keyword.value, "int");
951
952                match r.min {
953                    IntOrKeyword::Int(literal_int_type) => {
954                        assert_eq!(literal_int_type.value, 0);
955                    }
956                    _ => {
957                        panic!("Expected min to be a LiteralIntType, got `{}`", r.min)
958                    }
959                }
960
961                match r.max {
962                    IntOrKeyword::Int(literal_int_type) => {
963                        assert_eq!(literal_int_type.value, 100);
964                    }
965                    _ => {
966                        panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
967                    }
968                }
969            }
970            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
971        }
972
973        match do_parse("int<min, 0>") {
974            Ok(Type::IntRange(r)) => {
975                match r.min {
976                    IntOrKeyword::Keyword(keyword) => {
977                        assert_eq!(keyword.value, "min");
978                    }
979                    _ => {
980                        panic!("Expected min to be a Keyword, got `{}`", r.min)
981                    }
982                }
983
984                match r.max {
985                    IntOrKeyword::Int(literal_int_type) => {
986                        assert_eq!(literal_int_type.value, 0);
987                    }
988                    _ => {
989                        panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
990                    }
991                }
992            }
993            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
994        }
995
996        match do_parse("int<min, max>") {
997            Ok(Type::IntRange(r)) => {
998                match r.min {
999                    IntOrKeyword::Keyword(keyword) => {
1000                        assert_eq!(keyword.value, "min");
1001                    }
1002                    _ => {
1003                        panic!("Expected min to be a Keyword, got `{}`", r.min)
1004                    }
1005                }
1006
1007                match r.max {
1008                    IntOrKeyword::Keyword(keyword) => {
1009                        assert_eq!(keyword.value, "max");
1010                    }
1011                    _ => {
1012                        panic!("Expected max to be a Keyword, got `{}`", r.max)
1013                    }
1014                }
1015            }
1016            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
1017        }
1018    }
1019
1020    #[test]
1021    fn test_properties_of() {
1022        match do_parse("properties-of<MyClass>") {
1023            Ok(Type::PropertiesOf(p)) => {
1024                assert_eq!(p.keyword.value, "properties-of");
1025                assert_eq!(p.filter, PropertiesOfFilter::All);
1026                match &p.parameter.entry.inner {
1027                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyClass"),
1028                    _ => panic!(),
1029                }
1030            }
1031            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
1032        }
1033
1034        match do_parse("protected-properties-of<T>") {
1035            Ok(Type::PropertiesOf(p)) => {
1036                assert_eq!(p.keyword.value, "protected-properties-of");
1037                assert_eq!(p.filter, PropertiesOfFilter::Protected);
1038                match &p.parameter.entry.inner {
1039                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
1040                    _ => panic!(),
1041                }
1042            }
1043            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
1044        }
1045
1046        match do_parse("private-properties-of<T>") {
1047            Ok(Type::PropertiesOf(p)) => {
1048                assert_eq!(p.keyword.value, "private-properties-of");
1049                assert_eq!(p.filter, PropertiesOfFilter::Private);
1050                match &p.parameter.entry.inner {
1051                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
1052                    _ => panic!(),
1053                }
1054            }
1055            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
1056        }
1057
1058        match do_parse("public-properties-of<T>") {
1059            Ok(Type::PropertiesOf(p)) => {
1060                assert_eq!(p.keyword.value, "public-properties-of");
1061                assert_eq!(p.filter, PropertiesOfFilter::Public);
1062                match &p.parameter.entry.inner {
1063                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
1064                    _ => panic!(),
1065                }
1066            }
1067            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
1068        }
1069    }
1070
1071    #[test]
1072    fn test_variable() {
1073        match do_parse("$myVar") {
1074            Ok(Type::Variable(v)) => {
1075                assert_eq!(v.value, "$myVar");
1076            }
1077            res => panic!("Expected Ok(Type::Variable), got {res:?}"),
1078        }
1079    }
1080
1081    #[test]
1082    fn test_nullable_intersection() {
1083        // Nullable applies only to the rightmost element of an intersection before parens
1084        match do_parse("Countable&?Traversable") {
1085            Ok(Type::Intersection(i)) => {
1086                assert!(matches!(*i.left, Type::Reference(r) if r.identifier.value == "Countable"));
1087                assert!(matches!(*i.right, Type::Nullable(_)));
1088                if let Type::Nullable(n) = *i.right {
1089                    assert!(matches!(*n.inner, Type::Reference(r) if r.identifier.value == "Traversable"));
1090                } else {
1091                    panic!();
1092                }
1093            }
1094            res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
1095        }
1096    }
1097
1098    #[test]
1099    fn test_parenthesized_nullable() {
1100        match do_parse("?(Countable&Traversable)") {
1101            Ok(Type::Nullable(n)) => {
1102                assert!(matches!(*n.inner, Type::Parenthesized(_)));
1103                if let Type::Parenthesized(p) = *n.inner {
1104                    assert!(matches!(*p.inner, Type::Intersection(_)));
1105                } else {
1106                    panic!()
1107                }
1108            }
1109            res => panic!("Expected Ok(Type::Nullable), got {res:?}"),
1110        }
1111    }
1112
1113    #[test]
1114    fn test_positive_negative_int() {
1115        match do_parse("positive-int|negative-int") {
1116            Ok(Type::Union(u)) => {
1117                assert!(matches!(*u.left, Type::PositiveInt(_)));
1118                assert!(matches!(*u.right, Type::NegativeInt(_)));
1119            }
1120            res => panic!("Expected Ok(Type::Union), got {res:?}"),
1121        }
1122    }
1123
1124    #[test]
1125    fn test_parse_float_alias() {
1126        match do_parse("double") {
1127            Ok(Type::Float(f)) => {
1128                assert_eq!(f.value, "double");
1129            }
1130            res => panic!("Expected Ok(Type::Float), got {res:?}"),
1131        }
1132
1133        match do_parse("real") {
1134            Ok(Type::Float(f)) => {
1135                assert_eq!(f.value, "real");
1136            }
1137            res => panic!("Expected Ok(Type::Float), got {res:?}"),
1138        }
1139
1140        match do_parse("float") {
1141            Ok(Type::Float(f)) => {
1142                assert_eq!(f.value, "float");
1143            }
1144            res => panic!("Expected Ok(Type::Float), got {res:?}"),
1145        }
1146    }
1147
1148    #[test]
1149    fn test_parse_bool_alias() {
1150        match do_parse("boolean") {
1151            Ok(Type::Bool(b)) => {
1152                assert_eq!(b.value, "boolean");
1153            }
1154            res => panic!("Expected Ok(Type::Bool), got {res:?}"),
1155        }
1156
1157        match do_parse("bool") {
1158            Ok(Type::Bool(b)) => {
1159                assert_eq!(b.value, "bool");
1160            }
1161            res => panic!("Expected Ok(Type::Bool), got {res:?}"),
1162        }
1163    }
1164
1165    #[test]
1166    fn test_parse_integer_alias() {
1167        match do_parse("integer") {
1168            Ok(Type::Int(i)) => {
1169                assert_eq!(i.value, "integer");
1170            }
1171            res => panic!("Expected Ok(Type::Int), got {res:?}"),
1172        }
1173
1174        match do_parse("int") {
1175            Ok(Type::Int(i)) => {
1176                assert_eq!(i.value, "int");
1177            }
1178            res => panic!("Expected Ok(Type::Int), got {res:?}"),
1179        }
1180    }
1181
1182    #[test]
1183    fn test_parse_callable_with_variables() {
1184        match do_parse("callable(string ...$names)") {
1185            Ok(Type::Callable(callable)) => {
1186                assert_eq!(callable.keyword.value, "callable");
1187                assert!(callable.specification.is_some());
1188
1189                let specification = callable.specification.unwrap();
1190
1191                assert!(specification.return_type.is_none());
1192                assert_eq!(specification.parameters.entries.len(), 1);
1193
1194                let first_parameter = specification.parameters.entries.first().unwrap();
1195                assert!(first_parameter.variable.is_some());
1196                assert!(first_parameter.ellipsis.is_some());
1197
1198                let variable = first_parameter.variable.unwrap();
1199                assert_eq!(variable.value, "$names");
1200            }
1201            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
1202        }
1203    }
1204
1205    #[test]
1206    fn test_parse_string_or_lowercase_string_union() {
1207        match do_parse("string|lowercase-string") {
1208            Ok(Type::Union(u)) => {
1209                assert!(matches!(*u.left, Type::String(_)));
1210                assert!(matches!(*u.right, Type::LowercaseString(_)));
1211            }
1212            res => panic!("Expected Ok(Type::Union), got {res:?}"),
1213        }
1214    }
1215
1216    #[test]
1217    fn test_parse_optional_literal_string_shape_field() {
1218        match do_parse("array{'salt'?: int, 'cost'?: int, ...}") {
1219            Ok(Type::Shape(shape)) => {
1220                assert_eq!(shape.fields.len(), 2);
1221                assert!(shape.additional_fields.is_some());
1222
1223                let first_field = &shape.fields[0];
1224                assert!(first_field.is_optional());
1225                assert!(matches!(
1226                    first_field.key.as_ref().map(|k| &k.key),
1227                    Some(ShapeKey::String { value: "salt", .. })
1228                ));
1229                assert!(matches!(first_field.value.as_ref(), Type::Int(_)));
1230
1231                let second_field = &shape.fields[1];
1232                assert!(second_field.is_optional());
1233                assert!(matches!(
1234                    second_field.key.as_ref().map(|k| &k.key),
1235                    Some(ShapeKey::String { value: "cost", .. })
1236                ));
1237                assert!(matches!(second_field.value.as_ref(), Type::Int(_)));
1238            }
1239            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1240        }
1241    }
1242
1243    #[test]
1244    fn test_parse_keyword_keys() {
1245        match do_parse("array{string: int, bool: string, int: float, mixed: object}") {
1246            Ok(Type::Shape(shape)) => {
1247                assert_eq!(shape.fields.len(), 4);
1248
1249                assert!(matches!(
1250                    shape.fields[0].key.as_ref().map(|k| &k.key),
1251                    Some(ShapeKey::String { value: "string", .. })
1252                ));
1253                assert!(matches!(
1254                    shape.fields[1].key.as_ref().map(|k| &k.key),
1255                    Some(ShapeKey::String { value: "bool", .. })
1256                ));
1257                assert!(matches!(
1258                    shape.fields[2].key.as_ref().map(|k| &k.key),
1259                    Some(ShapeKey::String { value: "int", .. })
1260                ));
1261                assert!(matches!(
1262                    shape.fields[3].key.as_ref().map(|k| &k.key),
1263                    Some(ShapeKey::String { value: "mixed", .. })
1264                ));
1265            }
1266            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1267        }
1268    }
1269
1270    #[test]
1271    fn test_parse_negated_integer_keys() {
1272        match do_parse("array{-1: string, -42: int, +5: bool}") {
1273            Ok(Type::Shape(shape)) => {
1274                assert_eq!(shape.fields.len(), 3);
1275
1276                assert!(matches!(
1277                    shape.fields[0].key.as_ref().map(|k| &k.key),
1278                    Some(ShapeKey::Integer { value: -1, .. })
1279                ));
1280                assert!(matches!(
1281                    shape.fields[1].key.as_ref().map(|k| &k.key),
1282                    Some(ShapeKey::Integer { value: -42, .. })
1283                ));
1284                assert!(matches!(
1285                    shape.fields[2].key.as_ref().map(|k| &k.key),
1286                    Some(ShapeKey::Integer { value: 5, .. })
1287                ));
1288            }
1289            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1290        }
1291    }
1292
1293    #[test]
1294    fn test_parse_float_keys() {
1295        match do_parse("array{123.4: string, -1.2: int, +0.5: bool}") {
1296            Ok(Type::Shape(shape)) => {
1297                assert_eq!(shape.fields.len(), 3);
1298
1299                assert!(matches!(
1300                    shape.fields[0].key.as_ref().map(|k| &k.key),
1301                    Some(ShapeKey::String { value: "123.4", .. })
1302                ));
1303                assert!(matches!(
1304                    shape.fields[1].key.as_ref().map(|k| &k.key),
1305                    Some(ShapeKey::String { value: "-1.2", .. })
1306                ));
1307                assert!(matches!(
1308                    shape.fields[2].key.as_ref().map(|k| &k.key),
1309                    Some(ShapeKey::String { value: "+0.5", .. })
1310                ));
1311            }
1312            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1313        }
1314    }
1315
1316    #[test]
1317    fn test_parse_complex_identifier_keys() {
1318        match do_parse(
1319            "array{key_with_underscore: int, key-with-dash: string, key\\with\\backslash: bool, +key: mixed, -key: object, \\leading_backslash: int}",
1320        ) {
1321            Ok(Type::Shape(shape)) => {
1322                assert_eq!(shape.fields.len(), 6);
1323
1324                assert!(matches!(
1325                    shape.fields[0].key.as_ref().map(|k| &k.key),
1326                    Some(ShapeKey::String { value: "key_with_underscore", .. })
1327                ));
1328                assert!(matches!(
1329                    shape.fields[1].key.as_ref().map(|k| &k.key),
1330                    Some(ShapeKey::String { value: "key-with-dash", .. })
1331                ));
1332                assert!(matches!(
1333                    shape.fields[2].key.as_ref().map(|k| &k.key),
1334                    Some(ShapeKey::String { value: "key\\with\\backslash", .. })
1335                ));
1336                assert!(matches!(
1337                    shape.fields[3].key.as_ref().map(|k| &k.key),
1338                    Some(ShapeKey::String { value: "+key", .. })
1339                ));
1340                assert!(matches!(
1341                    shape.fields[4].key.as_ref().map(|k| &k.key),
1342                    Some(ShapeKey::String { value: "-key", .. })
1343                ));
1344                assert!(matches!(
1345                    shape.fields[5].key.as_ref().map(|k| &k.key),
1346                    Some(ShapeKey::String { value: "\\leading_backslash", .. })
1347                ));
1348            }
1349            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1350        }
1351    }
1352
1353    #[test]
1354    fn test_parse_optional_keys_with_question_mark_in_name() {
1355        match do_parse("array{key?name: int, regular?: string}") {
1356            Ok(Type::Shape(shape)) => {
1357                assert_eq!(shape.fields.len(), 2);
1358
1359                assert!(!shape.fields[0].is_optional());
1360                assert!(matches!(
1361                    shape.fields[0].key.as_ref().map(|k| &k.key),
1362                    Some(ShapeKey::String { value: "key?name", .. })
1363                ));
1364
1365                assert!(shape.fields[1].is_optional());
1366                assert!(matches!(
1367                    shape.fields[1].key.as_ref().map(|k| &k.key),
1368                    Some(ShapeKey::String { value: "regular", .. })
1369                ));
1370            }
1371            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1372        }
1373    }
1374
1375    #[test]
1376    fn test_parse_integer_formats() {
1377        match do_parse("array{42: string, 0x2A: int, 0b101010: bool, 0o52: mixed}") {
1378            Ok(Type::Shape(shape)) => {
1379                assert_eq!(shape.fields.len(), 4);
1380
1381                assert!(matches!(
1382                    shape.fields[0].key.as_ref().map(|k| &k.key),
1383                    Some(ShapeKey::Integer { value: 42, .. })
1384                ));
1385                assert!(matches!(
1386                    shape.fields[1].key.as_ref().map(|k| &k.key),
1387                    Some(ShapeKey::Integer { value: 42, .. })
1388                ));
1389                assert!(matches!(
1390                    shape.fields[2].key.as_ref().map(|k| &k.key),
1391                    Some(ShapeKey::Integer { value: 42, .. })
1392                ));
1393                assert!(matches!(
1394                    shape.fields[3].key.as_ref().map(|k| &k.key),
1395                    Some(ShapeKey::Integer { value: 42, .. })
1396                ));
1397            }
1398            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1399        }
1400    }
1401
1402    #[test]
1403    fn test_parse_quoted_vs_unquoted_keys() {
1404        match do_parse("array{'string': int, \"double\": bool, unquoted: mixed}") {
1405            Ok(Type::Shape(shape)) => {
1406                assert_eq!(shape.fields.len(), 3);
1407
1408                assert!(matches!(
1409                    shape.fields[0].key.as_ref().map(|k| &k.key),
1410                    Some(ShapeKey::String { value: "string", .. })
1411                ));
1412                assert!(matches!(
1413                    shape.fields[1].key.as_ref().map(|k| &k.key),
1414                    Some(ShapeKey::String { value: "double", .. })
1415                ));
1416                assert!(matches!(
1417                    shape.fields[2].key.as_ref().map(|k| &k.key),
1418                    Some(ShapeKey::String { value: "unquoted", .. })
1419                ));
1420            }
1421            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1422        }
1423    }
1424
1425    #[test]
1426    fn test_parse_all_keyword_types() {
1427        let keywords = vec![
1428            "list", "int", "integer", "string", "float", "double", "real", "bool", "boolean", "false", "true",
1429            "object", "callable", "array", "iterable", "null", "mixed", "resource", "void", "scalar", "numeric",
1430            "never", "nothing", "as", "is", "not", "min", "max",
1431        ];
1432
1433        for keyword in keywords {
1434            let input = format!("array{{{keyword}: string}}");
1435            match do_parse(&input) {
1436                Ok(Type::Shape(shape)) => {
1437                    assert_eq!(shape.fields.len(), 1);
1438                    assert!(
1439                        matches!(
1440                            shape.fields[0].key.as_ref().map(|k| &k.key),
1441                            Some(ShapeKey::String { value, .. }) if *value == keyword
1442                        ),
1443                        "Failed for keyword: {keyword}"
1444                    );
1445                }
1446                res => panic!("Expected Ok(Type::Shape) for keyword '{keyword}', got {res:?}"),
1447            }
1448        }
1449    }
1450
1451    #[test]
1452    fn test_parse_php_specific_keywords() {
1453        match do_parse("array{self: string, static: int, parent: bool, class: mixed, __CLASS__: object}") {
1454            Ok(Type::Shape(shape)) => {
1455                assert_eq!(shape.fields.len(), 5);
1456
1457                assert!(matches!(
1458                    shape.fields[0].key.as_ref().map(|k| &k.key),
1459                    Some(ShapeKey::String { value: "self", .. })
1460                ));
1461                assert!(matches!(
1462                    shape.fields[1].key.as_ref().map(|k| &k.key),
1463                    Some(ShapeKey::String { value: "static", .. })
1464                ));
1465                assert!(matches!(
1466                    shape.fields[2].key.as_ref().map(|k| &k.key),
1467                    Some(ShapeKey::String { value: "parent", .. })
1468                ));
1469                assert!(matches!(
1470                    shape.fields[3].key.as_ref().map(|k| &k.key),
1471                    Some(ShapeKey::String { value: "class", .. })
1472                ));
1473                assert!(matches!(
1474                    shape.fields[4].key.as_ref().map(|k| &k.key),
1475                    Some(ShapeKey::String { value: "__CLASS__", .. })
1476                ));
1477            }
1478            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1479        }
1480    }
1481
1482    #[test]
1483    fn test_shape_key_spans() {
1484        match do_parse("array{test: string}") {
1485            Ok(Type::Shape(shape)) => {
1486                assert_eq!(shape.fields.len(), 1);
1487                let field = &shape.fields[0];
1488
1489                if let Some(key) = &field.key {
1490                    let span = key.key.span();
1491                    assert!(span.start.offset < span.end.offset, "Span should have valid start/end");
1492
1493                    assert_eq!(span.end.offset - span.start.offset, 4, "Span should cover 'test' (4 characters)");
1494                }
1495            }
1496            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1497        }
1498    }
1499
1500    #[test]
1501    fn test_shape_key_spans_quoted() {
1502        match do_parse("array{'hello': string}") {
1503            Ok(Type::Shape(shape)) => {
1504                assert_eq!(shape.fields.len(), 1);
1505                let field = &shape.fields[0];
1506
1507                if let Some(key) = &field.key {
1508                    let span = key.key.span();
1509                    assert_eq!(span.end.offset - span.start.offset, 7, "Span should cover 'hello' including quotes");
1510
1511                    assert!(matches!(&key.key, ShapeKey::String { value: "hello", .. }));
1512                }
1513            }
1514            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1515        }
1516    }
1517
1518    #[test]
1519    fn test_shape_key_spans_integer() {
1520        match do_parse("array{42: string}") {
1521            Ok(Type::Shape(shape)) => {
1522                assert_eq!(shape.fields.len(), 1);
1523                let field = &shape.fields[0];
1524
1525                if let Some(key) = &field.key {
1526                    let span = key.key.span();
1527                    assert_eq!(span.end.offset - span.start.offset, 2, "Span should cover '42' (2 characters)");
1528
1529                    assert!(matches!(&key.key, ShapeKey::Integer { value: 42, .. }));
1530                }
1531            }
1532            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1533        }
1534    }
1535
1536    #[test]
1537    fn test_shape_key_spans_negated_integer() {
1538        match do_parse("array{-123: string}") {
1539            Ok(Type::Shape(shape)) => {
1540                assert_eq!(shape.fields.len(), 1);
1541                let field = &shape.fields[0];
1542
1543                if let Some(key) = &field.key {
1544                    let span = key.key.span();
1545                    assert_eq!(span.end.offset - span.start.offset, 4, "Span should cover '-123' (4 characters)");
1546
1547                    assert!(matches!(&key.key, ShapeKey::Integer { value: -123, .. }));
1548                }
1549            }
1550            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1551        }
1552    }
1553
1554    #[test]
1555    fn test_shape_key_spans_complex_identifiers() {
1556        match do_parse("array{complex-key_name: string}") {
1557            Ok(Type::Shape(shape)) => {
1558                assert_eq!(shape.fields.len(), 1);
1559                let field = &shape.fields[0];
1560
1561                if let Some(key) = &field.key {
1562                    let span = key.key.span();
1563                    assert_eq!(
1564                        span.end.offset - span.start.offset,
1565                        16,
1566                        "Span should cover 'complex-key_name' (16 characters)"
1567                    );
1568
1569                    assert!(matches!(&key.key, ShapeKey::String { value: "complex-key_name", .. }));
1570                }
1571            }
1572            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1573        }
1574    }
1575
1576    #[test]
1577    fn test_parse_shape_key_overflow_unsigned() {
1578        let result = do_parse("array{9223372036854775808: string}");
1579        assert!(result.is_err(), "Expected parse error for shape key > i64::MAX, got: {result:?}");
1580    }
1581
1582    #[test]
1583    fn test_parse_shape_key_overflow_negated() {
1584        let result = do_parse("array{-9223372036854775808: string}");
1585        assert!(result.is_err(), "Expected parse error for negated shape key overflow, got: {result:?}");
1586    }
1587
1588    #[test]
1589    fn test_parse_wildcard_asterisk() {
1590        let result = do_parse("*");
1591        assert!(result.is_ok(), "Expected successful parse for wildcard, got: {result:?}");
1592        match result.unwrap() {
1593            Type::Wildcard(w) => assert_eq!(w.kind, WildcardKind::Asterisk),
1594            other => panic!("Expected Type::Wildcard, got: {other:?}"),
1595        }
1596    }
1597
1598    #[test]
1599    fn test_parse_wildcard_underscore() {
1600        let result = do_parse("_");
1601        assert!(result.is_ok(), "Expected successful parse for underscore wildcard, got: {result:?}");
1602        match result.unwrap() {
1603            Type::Wildcard(w) => assert_eq!(w.kind, WildcardKind::Underscore),
1604            other => panic!("Expected Type::Wildcard, got: {other:?}"),
1605        }
1606    }
1607
1608    #[test]
1609    fn test_parse_wildcard_in_generic() {
1610        let result = do_parse("array<string, *>");
1611        assert!(result.is_ok(), "Expected successful parse for wildcard in generic, got: {result:?}");
1612
1613        let result = do_parse("array<string, _>");
1614        assert!(result.is_ok(), "Expected successful parse for underscore wildcard in generic, got: {result:?}");
1615    }
1616
1617    #[test]
1618    fn test_parse_wildcard_display() {
1619        assert_eq!(do_parse("*").unwrap().to_string(), "*");
1620        assert_eq!(do_parse("_").unwrap().to_string(), "_");
1621    }
1622
1623    #[test]
1624    fn test_parse_non_zero_int() {
1625        match do_parse("non-zero-int") {
1626            Ok(Type::NonZeroInt(k)) => assert_eq!(k.value, "non-zero-int"),
1627            other => panic!("Expected Type::NonZeroInt, got: {other:?}"),
1628        }
1629    }
1630
1631    #[test]
1632    fn test_parse_int_range_int_keyword_max() {
1633        match do_parse("int<0, int>") {
1634            Ok(Type::IntRange(range)) => {
1635                assert!(matches!(range.min, IntOrKeyword::Int(LiteralIntType { value: 0, .. })));
1636                match range.max {
1637                    IntOrKeyword::Keyword(ref keyword) => assert!(keyword.value.eq_ignore_ascii_case("int")),
1638                    other => panic!("Expected IntOrKeyword::Keyword, got: {other:?}"),
1639                }
1640            }
1641            other => panic!("Expected Type::IntRange, got: {other:?}"),
1642        }
1643    }
1644
1645    #[test]
1646    fn test_parse_int_range_int_keyword_min() {
1647        match do_parse("int<int, 0>") {
1648            Ok(Type::IntRange(range)) => {
1649                match range.min {
1650                    IntOrKeyword::Keyword(ref keyword) => assert!(keyword.value.eq_ignore_ascii_case("int")),
1651                    other => panic!("Expected IntOrKeyword::Keyword, got: {other:?}"),
1652                }
1653                assert!(matches!(range.max, IntOrKeyword::Int(LiteralIntType { value: 0, .. })));
1654            }
1655            other => panic!("Expected Type::IntRange, got: {other:?}"),
1656        }
1657    }
1658
1659    #[test]
1660    fn test_parse_member_reference_reserved_keywords() {
1661        for name in [
1662            "NULL", "ARRAY", "INT", "STRING", "FLOAT", "TRUE", "FALSE", "MIXED", "CALLABLE", "ITERABLE", "RESOURCE",
1663            "BOOL", "OBJECT", "NEVER", "VOID", "NUMERIC", "SCALAR",
1664        ] {
1665            let input = format!("TypeIdentifier::{name}");
1666            match do_parse(&input) {
1667                Ok(Type::MemberReference(r)) => match r.member {
1668                    MemberReferenceSelector::Identifier(ident) => {
1669                        assert!(
1670                            ident.value.eq_ignore_ascii_case(name),
1671                            "Expected member name {name}, got {}",
1672                            ident.value,
1673                        );
1674                    }
1675                    other => panic!("Expected Identifier selector for {input}, got {other:?}"),
1676                },
1677                other => panic!("Expected Type::MemberReference for {input}, got: {other:?}"),
1678            }
1679        }
1680    }
1681
1682    #[test]
1683    fn test_parse_member_reference_reserved_prefix_wildcard() {
1684        match do_parse("Foo::INT*") {
1685            Ok(Type::MemberReference(r)) => match r.member {
1686                MemberReferenceSelector::StartsWith(ident, _) => {
1687                    assert!(ident.value.eq_ignore_ascii_case("INT"), "expected INT prefix, got {}", ident.value);
1688                }
1689                other => panic!("Expected StartsWith selector, got {other:?}"),
1690            },
1691            other => panic!("Expected Type::MemberReference, got: {other:?}"),
1692        }
1693    }
1694
1695    #[test]
1696    fn test_parse_nested_generic_with_reserved_const() {
1697        match do_parse("UnionType<T|Foo::NULL>") {
1698            Ok(Type::Reference(r)) => {
1699                let params = r.parameters.expect("Expected generic parameters");
1700                assert_eq!(params.entries.len(), 1);
1701                match &params.entries[0].inner {
1702                    Type::Union(u) => {
1703                        assert!(matches!(*u.left, Type::Reference(_)));
1704                        assert!(matches!(*u.right, Type::MemberReference(_)));
1705                    }
1706                    other => panic!("Expected inner Union, got {other:?}"),
1707                }
1708            }
1709            other => panic!("Expected Type::Reference, got: {other:?}"),
1710        }
1711    }
1712
1713    #[test]
1714    fn test_parse_builtin_type_identifier_union() {
1715        let input = "BuiltinType<TypeIdentifier::ARRAY>|BuiltinType<TypeIdentifier::ITERABLE>|ObjectType|GenericType";
1716        assert!(do_parse(input).is_ok(), "expected successful parse for {input}");
1717    }
1718
1719    #[test]
1720    fn test_parse_collection_type_with_reserved_identifier() {
1721        let input = "CollectionType<BuiltinType<TypeIdentifier::ITERABLE>>";
1722        assert!(do_parse(input).is_ok(), "expected successful parse for {input}");
1723    }
1724
1725    #[test]
1726    fn test_parse_trailing_pipe() {
1727        match do_parse("int|string|") {
1728            Ok(Type::TrailingPipe(trailing)) => assert!(matches!(*trailing.inner, Type::Union(_))),
1729            other => panic!("Expected Type::TrailingPipe, got: {other:?}"),
1730        }
1731    }
1732
1733    #[test]
1734    fn test_parse_trailing_pipe_single() {
1735        match do_parse("int|") {
1736            Ok(Type::TrailingPipe(trailing)) => assert!(matches!(*trailing.inner, Type::Int(_))),
1737            other => panic!("Expected Type::TrailingPipe, got: {other:?}"),
1738        }
1739    }
1740
1741    #[test]
1742    fn test_parse_trailing_pipe_in_shape_value() {
1743        match do_parse("array{0: int|string|}") {
1744            Ok(Type::Shape(shape)) => {
1745                assert_eq!(shape.fields.len(), 1);
1746                assert!(matches!(shape.fields[0].value.as_ref(), Type::TrailingPipe(_)));
1747            }
1748            other => panic!("Expected Type::Shape, got: {other:?}"),
1749        }
1750    }
1751
1752    #[test]
1753    fn test_parse_trailing_pipe_in_generic_shape_value() {
1754        let input = "iterable<array{0: int|array<string, mixed>|}>";
1755        match do_parse(input) {
1756            Ok(Type::Iterable(iter)) => {
1757                let params = iter.parameters.expect("expected generic parameters");
1758                assert_eq!(params.entries.len(), 1);
1759                match &params.entries[0].inner {
1760                    Type::Shape(shape) => {
1761                        assert_eq!(shape.fields.len(), 1);
1762                        assert!(matches!(shape.fields[0].value.as_ref(), Type::TrailingPipe(_)));
1763                    }
1764                    other => panic!("Expected Type::Shape, got {other:?}"),
1765                }
1766            }
1767            other => panic!("Expected Type::Iterable, got: {other:?}"),
1768        }
1769    }
1770
1771    #[test]
1772    fn test_parse_global_wildcard_starts_with() {
1773        match do_parse("FILTER_FLAG_*") {
1774            Ok(Type::GlobalWildcardReference(g)) => match g.selector {
1775                GlobalWildcardSelector::StartsWith(identifier, _) => {
1776                    assert_eq!(identifier.value, "FILTER_FLAG_");
1777                }
1778                other => panic!("Expected StartsWith selector, got {other:?}"),
1779            },
1780            other => panic!("Expected Type::GlobalWildcardReference, got: {other:?}"),
1781        }
1782    }
1783
1784    #[test]
1785    fn test_parse_global_wildcard_ends_with() {
1786        match do_parse("*_SUFFIX") {
1787            Ok(Type::GlobalWildcardReference(g)) => match g.selector {
1788                GlobalWildcardSelector::EndsWith(_, identifier) => {
1789                    assert_eq!(identifier.value, "_SUFFIX");
1790                }
1791                other => panic!("Expected EndsWith selector, got {other:?}"),
1792            },
1793            other => panic!("Expected Type::GlobalWildcardReference, got: {other:?}"),
1794        }
1795    }
1796
1797    #[test]
1798    fn test_parse_global_wildcard_in_int_mask_of() {
1799        let input = "int-mask-of<FILTER_FLAG_*>";
1800        match do_parse(input) {
1801            Ok(Type::IntMaskOf(mask)) => {
1802                assert!(matches!(mask.parameter.entry.inner, Type::GlobalWildcardReference(_)));
1803            }
1804            other => panic!("Expected Type::IntMaskOf, got: {other:?}"),
1805        }
1806    }
1807
1808    #[test]
1809    fn test_parse_int_mask_of_class_wildcard_regression() {
1810        let input = "int-mask-of<Ulid::FORMAT_*>";
1811        match do_parse(input) {
1812            Ok(Type::IntMaskOf(mask)) => match &mask.parameter.entry.inner {
1813                Type::MemberReference(r) => {
1814                    assert!(matches!(r.member, MemberReferenceSelector::StartsWith(_, _)));
1815                }
1816                other => panic!("Expected MemberReference, got {other:?}"),
1817            },
1818            other => panic!("Expected Type::IntMaskOf, got: {other:?}"),
1819        }
1820    }
1821}