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_iterable() {
493        match do_parse("iterable<int, string>") {
494            Ok(Type::Iterable(i)) => {
495                let params = i.parameters.expect("Expected generic parameters");
496                assert_eq!(params.entries.len(), 2);
497                assert!(matches!(params.entries[0].inner, Type::Int(_)));
498                assert!(matches!(params.entries[1].inner, Type::String(_)));
499            }
500            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
501        }
502
503        match do_parse("iterable<bool>") {
504            // Test single param case
505            Ok(Type::Iterable(i)) => {
506                let params = i.parameters.expect("Expected generic parameters");
507                assert_eq!(params.entries.len(), 1);
508                assert!(matches!(params.entries[0].inner, Type::Bool(_)));
509            }
510            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
511        }
512
513        match do_parse("iterable") {
514            Ok(Type::Iterable(i)) => {
515                assert!(i.parameters.is_none());
516            }
517            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
518        }
519    }
520
521    #[test]
522    fn test_parse_negated_int() {
523        let assert_negated_int = |input: &str, expected_value: u64| {
524            let result = do_parse(input);
525            assert!(result.is_ok());
526            match result.unwrap() {
527                Type::Negated(n) => {
528                    assert!(matches!(n.number, LiteralIntOrFloatType::Int(_)));
529                    if let LiteralIntOrFloatType::Int(lit) = n.number {
530                        assert_eq!(lit.value, expected_value);
531                    } else {
532                        panic!()
533                    }
534                }
535                _ => panic!("Expected Type::Negated"),
536            }
537        };
538
539        assert_negated_int("-0", 0);
540        assert_negated_int("-1", 1);
541        assert_negated_int(
542            "-
543            // This is a comment
544            123_345",
545            123_345,
546        );
547        assert_negated_int("-0b1", 1);
548    }
549
550    #[test]
551    fn test_parse_negated_float() {
552        let assert_negated_float = |input: &str, expected_value: f64| {
553            let result = do_parse(input);
554            assert!(result.is_ok());
555            match result.unwrap() {
556                Type::Negated(n) => {
557                    assert!(matches!(n.number, LiteralIntOrFloatType::Float(_)));
558                    if let LiteralIntOrFloatType::Float(lit) = n.number {
559                        assert_eq!(lit.value, expected_value);
560                    } else {
561                        panic!()
562                    }
563                }
564                _ => panic!("Expected Type::Negated"),
565            }
566        };
567
568        assert_negated_float("-0.0", 0.0);
569        assert_negated_float("-1.0", 1.0);
570        assert_negated_float("-0.1e1", 1.0);
571        assert_negated_float("-0.1e-1", 0.01);
572    }
573
574    #[test]
575    fn test_parse_negated_union() {
576        match do_parse("-1|-2.0|string") {
577            Ok(Type::Union(n)) => {
578                assert!(matches!(*n.left, Type::Negated(_)));
579                assert!(matches!(*n.right, Type::Union(_)));
580
581                if let Type::Negated(neg) = *n.left {
582                    assert!(matches!(neg.number, LiteralIntOrFloatType::Int(_)));
583                    if let LiteralIntOrFloatType::Int(lit) = neg.number {
584                        assert_eq!(lit.value, 1);
585                    } else {
586                        panic!()
587                    }
588                } else {
589                    panic!("Expected left side to be Type::Negated");
590                }
591
592                if let Type::Union(inner_union) = *n.right {
593                    assert!(matches!(*inner_union.left, Type::Negated(_)));
594                    assert!(matches!(*inner_union.right, Type::String(_)));
595
596                    if let Type::Negated(neg) = *inner_union.left {
597                        assert!(matches!(neg.number, LiteralIntOrFloatType::Float(_)));
598                        if let LiteralIntOrFloatType::Float(lit) = neg.number {
599                            assert_eq!(lit.value, 2.0);
600                        } else {
601                            panic!()
602                        }
603                    } else {
604                        panic!("Expected left side of inner union to be Type::Negated");
605                    }
606
607                    if let Type::String(s) = *inner_union.right {
608                        assert_eq!(s.value, "string");
609                    } else {
610                        panic!("Expected right side of inner union to be Type::String");
611                    }
612                } else {
613                    panic!("Expected right side to be Type::Union");
614                }
615            }
616            res => panic!("Expected Ok(Type::Negated), got {res:?}"),
617        }
618    }
619
620    #[test]
621    fn test_parse_callable_no_spec() {
622        match do_parse("callable") {
623            Ok(Type::Callable(c)) => {
624                assert!(c.specification.is_none());
625                assert_eq!(c.kind, CallableTypeKind::Callable);
626            }
627            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
628        }
629    }
630
631    #[test]
632    fn test_parse_callable_params_only() {
633        match do_parse("callable(int, ?string)") {
634            Ok(Type::Callable(c)) => {
635                let spec = c.specification.expect("Expected callable specification");
636                assert!(spec.return_type.is_none());
637                assert_eq!(spec.parameters.entries.len(), 2);
638                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Int(_))));
639                assert!(matches!(spec.parameters.entries[1].parameter_type, Some(Type::Nullable(_))));
640                assert!(spec.parameters.entries[0].ellipsis.is_none());
641                assert!(spec.parameters.entries[0].equals.is_none());
642            }
643            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
644        }
645    }
646
647    #[test]
648    fn test_parse_callable_return_only() {
649        match do_parse("callable(): void") {
650            Ok(Type::Callable(c)) => {
651                let spec = c.specification.expect("Expected callable specification");
652                assert!(spec.parameters.entries.is_empty());
653                assert!(spec.return_type.is_some());
654                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Void(_)));
655            }
656            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
657        }
658    }
659
660    #[test]
661    fn test_parse_pure_callable_full() {
662        match do_parse("pure-callable(bool): int") {
663            Ok(Type::Callable(c)) => {
664                assert_eq!(c.kind, CallableTypeKind::PureCallable);
665                let spec = c.specification.expect("Expected callable specification");
666                assert_eq!(spec.parameters.entries.len(), 1);
667                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Bool(_))));
668                assert!(spec.return_type.is_some());
669                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Int(_)));
670            }
671            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
672        }
673    }
674
675    #[test]
676    fn test_parse_closure_via_identifier() {
677        match do_parse("Closure(string): bool") {
678            Ok(Type::Callable(c)) => {
679                assert_eq!(c.kind, CallableTypeKind::Closure);
680                assert_eq!(c.keyword.value, "Closure");
681                let spec = c.specification.expect("Expected callable specification");
682                assert_eq!(spec.parameters.entries.len(), 1);
683                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::String(_))));
684                assert!(spec.return_type.is_some());
685                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Bool(_)));
686            }
687            res => panic!("Expected Ok(Type::Callable) for Closure, got {res:?}"),
688        }
689    }
690
691    #[test]
692    fn test_parse_complex_pure_callable() {
693        match do_parse("pure-callable(list<int>, ?Closure(): void=, int...): ((Simple&Iter<T>)|null)") {
694            Ok(Type::Callable(c)) => {
695                assert_eq!(c.kind, CallableTypeKind::PureCallable);
696                let spec = c.specification.expect("Expected callable specification");
697                assert_eq!(spec.parameters.entries.len(), 3);
698                assert!(spec.return_type.is_some());
699
700                let first_param = &spec.parameters.entries[0];
701                assert!(matches!(first_param.parameter_type, Some(Type::List(_))));
702                assert!(first_param.ellipsis.is_none());
703                assert!(first_param.equals.is_none());
704
705                let second_param = &spec.parameters.entries[1];
706                assert!(matches!(second_param.parameter_type, Some(Type::Nullable(_))));
707                assert!(second_param.ellipsis.is_none());
708                assert!(second_param.equals.is_some());
709
710                let third_param = &spec.parameters.entries[2];
711                assert!(matches!(third_param.parameter_type, Some(Type::Int(_))));
712                assert!(third_param.ellipsis.is_some());
713                assert!(third_param.equals.is_none());
714
715                if let Type::Parenthesized(p) = *spec.return_type.unwrap().return_type {
716                    assert!(matches!(*p.inner, Type::Union(_)));
717                    if let Type::Union(u) = *p.inner {
718                        assert!(matches!(u.left.as_ref(), Type::Parenthesized(_)));
719                        assert!(matches!(u.right.as_ref(), Type::Null(_)));
720                    }
721                } else {
722                    panic!("Expected Type::CallableReturnType");
723                }
724            }
725            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
726        }
727    }
728
729    #[test]
730    fn test_parse_conditional_type() {
731        match do_parse("int is not string ? array : int") {
732            Ok(Type::Conditional(c)) => {
733                assert!(matches!(*c.subject, Type::Int(_)));
734                assert!(c.not.is_some());
735                assert!(matches!(*c.target, Type::String(_)));
736                assert!(matches!(*c.then, Type::Array(_)));
737                assert!(matches!(*c.otherwise, Type::Int(_)));
738            }
739            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
740        }
741
742        match do_parse("$input is string ? array : int") {
743            Ok(Type::Conditional(c)) => {
744                assert!(matches!(*c.subject, Type::Variable(_)));
745                assert!(c.not.is_none());
746                assert!(matches!(*c.target, Type::String(_)));
747                assert!(matches!(*c.then, Type::Array(_)));
748                assert!(matches!(*c.otherwise, Type::Int(_)));
749            }
750            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
751        }
752
753        match do_parse("int is string ? array : (int is not $bar ? string : $baz)") {
754            Ok(Type::Conditional(c)) => {
755                assert!(matches!(*c.subject, Type::Int(_)));
756                assert!(c.not.is_none());
757                assert!(matches!(*c.target, Type::String(_)));
758                assert!(matches!(*c.then, Type::Array(_)));
759
760                let Type::Parenthesized(p) = *c.otherwise else {
761                    panic!("Expected Type::Parenthesized");
762                };
763
764                if let Type::Conditional(inner_conditional) = *p.inner {
765                    assert!(matches!(*inner_conditional.subject, Type::Int(_)));
766                    assert!(inner_conditional.not.is_some());
767                    assert!(matches!(*inner_conditional.target, Type::Variable(_)));
768                    assert!(matches!(*inner_conditional.then, Type::String(_)));
769                    assert!(matches!(*inner_conditional.otherwise, Type::Variable(_)));
770                } else {
771                    panic!("Expected Type::Conditional");
772                }
773            }
774            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
775        }
776    }
777
778    #[test]
779    fn test_keyof() {
780        match do_parse("key-of<MyArray>") {
781            Ok(Type::KeyOf(k)) => {
782                assert_eq!(k.keyword.value, "key-of");
783                match &k.parameter.entry.inner {
784                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
785                    _ => panic!("Expected Type::Reference"),
786                }
787            }
788            res => panic!("Expected Ok(Type::KeyOf), got {res:?}"),
789        }
790    }
791
792    #[test]
793    fn test_valueof() {
794        match do_parse("value-of<MyArray>") {
795            Ok(Type::ValueOf(v)) => {
796                assert_eq!(v.keyword.value, "value-of");
797                match &v.parameter.entry.inner {
798                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
799                    _ => panic!("Expected Type::Reference"),
800                }
801            }
802            res => panic!("Expected Ok(Type::ValueOf), got {res:?}"),
803        }
804    }
805
806    #[test]
807    fn test_indexed_access() {
808        match do_parse("MyArray[MyKey]") {
809            Ok(Type::IndexAccess(i)) => {
810                match *i.target {
811                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
812                    _ => panic!("Expected Type::Reference"),
813                }
814                match *i.index {
815                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyKey"),
816                    _ => panic!("Expected Type::Reference"),
817                }
818            }
819            res => panic!("Expected Ok(Type::IndexAccess), got {res:?}"),
820        }
821    }
822
823    #[test]
824    fn test_slice_type() {
825        match do_parse("string[]") {
826            Ok(Type::Slice(s)) => {
827                assert!(matches!(*s.inner, Type::String(_)));
828            }
829            res => panic!("Expected Ok(Type::Slice), got {res:?}"),
830        }
831    }
832
833    #[test]
834    fn test_slice_of_slice_of_slice_type() {
835        match do_parse("string[][][]") {
836            Ok(Type::Slice(s)) => {
837                assert!(matches!(*s.inner, Type::Slice(_)));
838                if let Type::Slice(inner_slice) = *s.inner {
839                    assert!(matches!(*inner_slice.inner, Type::Slice(_)));
840                    if let Type::Slice(inner_inner_slice) = *inner_slice.inner {
841                        assert!(matches!(*inner_inner_slice.inner, Type::String(_)));
842                    } else {
843                        panic!("Expected inner slice to be a Slice");
844                    }
845                } else {
846                    panic!("Expected outer slice to be a Slice");
847                }
848            }
849            res => panic!("Expected Ok(Type::Slice), got {res:?}"),
850        }
851    }
852
853    #[test]
854    fn test_int_range() {
855        match do_parse("int<0, 100>") {
856            Ok(Type::IntRange(r)) => {
857                assert_eq!(r.keyword.value, "int");
858
859                match r.min {
860                    IntOrKeyword::Int(literal_int_type) => {
861                        assert_eq!(literal_int_type.value, 0);
862                    }
863                    _ => {
864                        panic!("Expected min to be a LiteralIntType, got `{}`", r.min)
865                    }
866                }
867
868                match r.max {
869                    IntOrKeyword::Int(literal_int_type) => {
870                        assert_eq!(literal_int_type.value, 100);
871                    }
872                    _ => {
873                        panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
874                    }
875                }
876            }
877            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
878        }
879
880        match do_parse("int<min, 0>") {
881            Ok(Type::IntRange(r)) => {
882                match r.min {
883                    IntOrKeyword::Keyword(keyword) => {
884                        assert_eq!(keyword.value, "min");
885                    }
886                    _ => {
887                        panic!("Expected min to be a Keyword, got `{}`", r.min)
888                    }
889                }
890
891                match r.max {
892                    IntOrKeyword::Int(literal_int_type) => {
893                        assert_eq!(literal_int_type.value, 0);
894                    }
895                    _ => {
896                        panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
897                    }
898                }
899            }
900            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
901        }
902
903        match do_parse("int<min, max>") {
904            Ok(Type::IntRange(r)) => {
905                match r.min {
906                    IntOrKeyword::Keyword(keyword) => {
907                        assert_eq!(keyword.value, "min");
908                    }
909                    _ => {
910                        panic!("Expected min to be a Keyword, got `{}`", r.min)
911                    }
912                }
913
914                match r.max {
915                    IntOrKeyword::Keyword(keyword) => {
916                        assert_eq!(keyword.value, "max");
917                    }
918                    _ => {
919                        panic!("Expected max to be a Keyword, got `{}`", r.max)
920                    }
921                }
922            }
923            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
924        }
925    }
926
927    #[test]
928    fn test_properties_of() {
929        match do_parse("properties-of<MyClass>") {
930            Ok(Type::PropertiesOf(p)) => {
931                assert_eq!(p.keyword.value, "properties-of");
932                assert_eq!(p.filter, PropertiesOfFilter::All);
933                match &p.parameter.entry.inner {
934                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyClass"),
935                    _ => panic!(),
936                }
937            }
938            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
939        }
940
941        match do_parse("protected-properties-of<T>") {
942            Ok(Type::PropertiesOf(p)) => {
943                assert_eq!(p.keyword.value, "protected-properties-of");
944                assert_eq!(p.filter, PropertiesOfFilter::Protected);
945                match &p.parameter.entry.inner {
946                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
947                    _ => panic!(),
948                }
949            }
950            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
951        }
952
953        match do_parse("private-properties-of<T>") {
954            Ok(Type::PropertiesOf(p)) => {
955                assert_eq!(p.keyword.value, "private-properties-of");
956                assert_eq!(p.filter, PropertiesOfFilter::Private);
957                match &p.parameter.entry.inner {
958                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
959                    _ => panic!(),
960                }
961            }
962            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
963        }
964
965        match do_parse("public-properties-of<T>") {
966            Ok(Type::PropertiesOf(p)) => {
967                assert_eq!(p.keyword.value, "public-properties-of");
968                assert_eq!(p.filter, PropertiesOfFilter::Public);
969                match &p.parameter.entry.inner {
970                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
971                    _ => panic!(),
972                }
973            }
974            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
975        }
976    }
977
978    #[test]
979    fn test_variable() {
980        match do_parse("$myVar") {
981            Ok(Type::Variable(v)) => {
982                assert_eq!(v.value, "$myVar");
983            }
984            res => panic!("Expected Ok(Type::Variable), got {res:?}"),
985        }
986    }
987
988    #[test]
989    fn test_nullable_intersection() {
990        // Nullable applies only to the rightmost element of an intersection before parens
991        match do_parse("Countable&?Traversable") {
992            Ok(Type::Intersection(i)) => {
993                assert!(matches!(*i.left, Type::Reference(r) if r.identifier.value == "Countable"));
994                assert!(matches!(*i.right, Type::Nullable(_)));
995                if let Type::Nullable(n) = *i.right {
996                    assert!(matches!(*n.inner, Type::Reference(r) if r.identifier.value == "Traversable"));
997                } else {
998                    panic!();
999                }
1000            }
1001            res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
1002        }
1003    }
1004
1005    #[test]
1006    fn test_parenthesized_nullable() {
1007        match do_parse("?(Countable&Traversable)") {
1008            Ok(Type::Nullable(n)) => {
1009                assert!(matches!(*n.inner, Type::Parenthesized(_)));
1010                if let Type::Parenthesized(p) = *n.inner {
1011                    assert!(matches!(*p.inner, Type::Intersection(_)));
1012                } else {
1013                    panic!()
1014                }
1015            }
1016            res => panic!("Expected Ok(Type::Nullable), got {res:?}"),
1017        }
1018    }
1019
1020    #[test]
1021    fn test_positive_negative_int() {
1022        match do_parse("positive-int|negative-int") {
1023            Ok(Type::Union(u)) => {
1024                assert!(matches!(*u.left, Type::PositiveInt(_)));
1025                assert!(matches!(*u.right, Type::NegativeInt(_)));
1026            }
1027            res => panic!("Expected Ok(Type::Union), got {res:?}"),
1028        }
1029    }
1030
1031    #[test]
1032    fn test_parse_float_alias() {
1033        match do_parse("double") {
1034            Ok(Type::Float(f)) => {
1035                assert_eq!(f.value, "double");
1036            }
1037            res => panic!("Expected Ok(Type::Float), got {res:?}"),
1038        }
1039
1040        match do_parse("real") {
1041            Ok(Type::Float(f)) => {
1042                assert_eq!(f.value, "real");
1043            }
1044            res => panic!("Expected Ok(Type::Float), got {res:?}"),
1045        }
1046
1047        match do_parse("float") {
1048            Ok(Type::Float(f)) => {
1049                assert_eq!(f.value, "float");
1050            }
1051            res => panic!("Expected Ok(Type::Float), got {res:?}"),
1052        }
1053    }
1054
1055    #[test]
1056    fn test_parse_bool_alias() {
1057        match do_parse("boolean") {
1058            Ok(Type::Bool(b)) => {
1059                assert_eq!(b.value, "boolean");
1060            }
1061            res => panic!("Expected Ok(Type::Bool), got {res:?}"),
1062        }
1063
1064        match do_parse("bool") {
1065            Ok(Type::Bool(b)) => {
1066                assert_eq!(b.value, "bool");
1067            }
1068            res => panic!("Expected Ok(Type::Bool), got {res:?}"),
1069        }
1070    }
1071
1072    #[test]
1073    fn test_parse_integer_alias() {
1074        match do_parse("integer") {
1075            Ok(Type::Int(i)) => {
1076                assert_eq!(i.value, "integer");
1077            }
1078            res => panic!("Expected Ok(Type::Int), got {res:?}"),
1079        }
1080
1081        match do_parse("int") {
1082            Ok(Type::Int(i)) => {
1083                assert_eq!(i.value, "int");
1084            }
1085            res => panic!("Expected Ok(Type::Int), got {res:?}"),
1086        }
1087    }
1088
1089    #[test]
1090    fn test_parse_callable_with_variables() {
1091        match do_parse("callable(string ...$names)") {
1092            Ok(Type::Callable(callable)) => {
1093                assert_eq!(callable.keyword.value, "callable");
1094                assert!(callable.specification.is_some());
1095
1096                let specification = callable.specification.unwrap();
1097
1098                assert!(specification.return_type.is_none());
1099                assert_eq!(specification.parameters.entries.len(), 1);
1100
1101                let first_parameter = specification.parameters.entries.first().unwrap();
1102                assert!(first_parameter.variable.is_some());
1103                assert!(first_parameter.ellipsis.is_some());
1104
1105                let variable = first_parameter.variable.unwrap();
1106                assert_eq!(variable.value, "$names");
1107            }
1108            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
1109        }
1110    }
1111
1112    #[test]
1113    fn test_parse_string_or_lowercase_string_union() {
1114        match do_parse("string|lowercase-string") {
1115            Ok(Type::Union(u)) => {
1116                assert!(matches!(*u.left, Type::String(_)));
1117                assert!(matches!(*u.right, Type::LowercaseString(_)));
1118            }
1119            res => panic!("Expected Ok(Type::Union), got {res:?}"),
1120        }
1121    }
1122
1123    #[test]
1124    fn test_parse_optional_literal_string_shape_field() {
1125        match do_parse("array{'salt'?: int, 'cost'?: int, ...}") {
1126            Ok(Type::Shape(shape)) => {
1127                assert_eq!(shape.fields.len(), 2);
1128                assert!(shape.additional_fields.is_some());
1129
1130                let first_field = &shape.fields[0];
1131                assert!(first_field.is_optional());
1132                assert!(matches!(
1133                    first_field.key.as_ref().map(|k| &k.key),
1134                    Some(ShapeKey::String { value: "salt", .. })
1135                ));
1136                assert!(matches!(first_field.value.as_ref(), Type::Int(_)));
1137
1138                let second_field = &shape.fields[1];
1139                assert!(second_field.is_optional());
1140                assert!(matches!(
1141                    second_field.key.as_ref().map(|k| &k.key),
1142                    Some(ShapeKey::String { value: "cost", .. })
1143                ));
1144                assert!(matches!(second_field.value.as_ref(), Type::Int(_)));
1145            }
1146            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1147        }
1148    }
1149
1150    #[test]
1151    fn test_parse_keyword_keys() {
1152        match do_parse("array{string: int, bool: string, int: float, mixed: object}") {
1153            Ok(Type::Shape(shape)) => {
1154                assert_eq!(shape.fields.len(), 4);
1155
1156                assert!(matches!(
1157                    shape.fields[0].key.as_ref().map(|k| &k.key),
1158                    Some(ShapeKey::String { value: "string", .. })
1159                ));
1160                assert!(matches!(
1161                    shape.fields[1].key.as_ref().map(|k| &k.key),
1162                    Some(ShapeKey::String { value: "bool", .. })
1163                ));
1164                assert!(matches!(
1165                    shape.fields[2].key.as_ref().map(|k| &k.key),
1166                    Some(ShapeKey::String { value: "int", .. })
1167                ));
1168                assert!(matches!(
1169                    shape.fields[3].key.as_ref().map(|k| &k.key),
1170                    Some(ShapeKey::String { value: "mixed", .. })
1171                ));
1172            }
1173            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1174        }
1175    }
1176
1177    #[test]
1178    fn test_parse_negated_integer_keys() {
1179        match do_parse("array{-1: string, -42: int, +5: bool}") {
1180            Ok(Type::Shape(shape)) => {
1181                assert_eq!(shape.fields.len(), 3);
1182
1183                assert!(matches!(
1184                    shape.fields[0].key.as_ref().map(|k| &k.key),
1185                    Some(ShapeKey::Integer { value: -1, .. })
1186                ));
1187                assert!(matches!(
1188                    shape.fields[1].key.as_ref().map(|k| &k.key),
1189                    Some(ShapeKey::Integer { value: -42, .. })
1190                ));
1191                assert!(matches!(
1192                    shape.fields[2].key.as_ref().map(|k| &k.key),
1193                    Some(ShapeKey::Integer { value: 5, .. })
1194                ));
1195            }
1196            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1197        }
1198    }
1199
1200    #[test]
1201    fn test_parse_float_keys() {
1202        match do_parse("array{123.4: string, -1.2: int, +0.5: bool}") {
1203            Ok(Type::Shape(shape)) => {
1204                assert_eq!(shape.fields.len(), 3);
1205
1206                assert!(matches!(
1207                    shape.fields[0].key.as_ref().map(|k| &k.key),
1208                    Some(ShapeKey::String { value: "123.4", .. })
1209                ));
1210                assert!(matches!(
1211                    shape.fields[1].key.as_ref().map(|k| &k.key),
1212                    Some(ShapeKey::String { value: "-1.2", .. })
1213                ));
1214                assert!(matches!(
1215                    shape.fields[2].key.as_ref().map(|k| &k.key),
1216                    Some(ShapeKey::String { value: "+0.5", .. })
1217                ));
1218            }
1219            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1220        }
1221    }
1222
1223    #[test]
1224    fn test_parse_complex_identifier_keys() {
1225        match do_parse(
1226            "array{key_with_underscore: int, key-with-dash: string, key\\with\\backslash: bool, +key: mixed, -key: object, \\leading_backslash: int}",
1227        ) {
1228            Ok(Type::Shape(shape)) => {
1229                assert_eq!(shape.fields.len(), 6);
1230
1231                assert!(matches!(
1232                    shape.fields[0].key.as_ref().map(|k| &k.key),
1233                    Some(ShapeKey::String { value: "key_with_underscore", .. })
1234                ));
1235                assert!(matches!(
1236                    shape.fields[1].key.as_ref().map(|k| &k.key),
1237                    Some(ShapeKey::String { value: "key-with-dash", .. })
1238                ));
1239                assert!(matches!(
1240                    shape.fields[2].key.as_ref().map(|k| &k.key),
1241                    Some(ShapeKey::String { value: "key\\with\\backslash", .. })
1242                ));
1243                assert!(matches!(
1244                    shape.fields[3].key.as_ref().map(|k| &k.key),
1245                    Some(ShapeKey::String { value: "+key", .. })
1246                ));
1247                assert!(matches!(
1248                    shape.fields[4].key.as_ref().map(|k| &k.key),
1249                    Some(ShapeKey::String { value: "-key", .. })
1250                ));
1251                assert!(matches!(
1252                    shape.fields[5].key.as_ref().map(|k| &k.key),
1253                    Some(ShapeKey::String { value: "\\leading_backslash", .. })
1254                ));
1255            }
1256            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1257        }
1258    }
1259
1260    #[test]
1261    fn test_parse_optional_keys_with_question_mark_in_name() {
1262        match do_parse("array{key?name: int, regular?: string}") {
1263            Ok(Type::Shape(shape)) => {
1264                assert_eq!(shape.fields.len(), 2);
1265
1266                assert!(!shape.fields[0].is_optional());
1267                assert!(matches!(
1268                    shape.fields[0].key.as_ref().map(|k| &k.key),
1269                    Some(ShapeKey::String { value: "key?name", .. })
1270                ));
1271
1272                assert!(shape.fields[1].is_optional());
1273                assert!(matches!(
1274                    shape.fields[1].key.as_ref().map(|k| &k.key),
1275                    Some(ShapeKey::String { value: "regular", .. })
1276                ));
1277            }
1278            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1279        }
1280    }
1281
1282    #[test]
1283    fn test_parse_integer_formats() {
1284        match do_parse("array{42: string, 0x2A: int, 0b101010: bool, 0o52: mixed}") {
1285            Ok(Type::Shape(shape)) => {
1286                assert_eq!(shape.fields.len(), 4);
1287
1288                assert!(matches!(
1289                    shape.fields[0].key.as_ref().map(|k| &k.key),
1290                    Some(ShapeKey::Integer { value: 42, .. })
1291                ));
1292                assert!(matches!(
1293                    shape.fields[1].key.as_ref().map(|k| &k.key),
1294                    Some(ShapeKey::Integer { value: 42, .. })
1295                ));
1296                assert!(matches!(
1297                    shape.fields[2].key.as_ref().map(|k| &k.key),
1298                    Some(ShapeKey::Integer { value: 42, .. })
1299                ));
1300                assert!(matches!(
1301                    shape.fields[3].key.as_ref().map(|k| &k.key),
1302                    Some(ShapeKey::Integer { value: 42, .. })
1303                ));
1304            }
1305            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1306        }
1307    }
1308
1309    #[test]
1310    fn test_parse_quoted_vs_unquoted_keys() {
1311        match do_parse("array{'string': int, \"double\": bool, unquoted: mixed}") {
1312            Ok(Type::Shape(shape)) => {
1313                assert_eq!(shape.fields.len(), 3);
1314
1315                assert!(matches!(
1316                    shape.fields[0].key.as_ref().map(|k| &k.key),
1317                    Some(ShapeKey::String { value: "string", .. })
1318                ));
1319                assert!(matches!(
1320                    shape.fields[1].key.as_ref().map(|k| &k.key),
1321                    Some(ShapeKey::String { value: "double", .. })
1322                ));
1323                assert!(matches!(
1324                    shape.fields[2].key.as_ref().map(|k| &k.key),
1325                    Some(ShapeKey::String { value: "unquoted", .. })
1326                ));
1327            }
1328            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1329        }
1330    }
1331
1332    #[test]
1333    fn test_parse_all_keyword_types() {
1334        let keywords = vec![
1335            "list", "int", "integer", "string", "float", "double", "real", "bool", "boolean", "false", "true",
1336            "object", "callable", "array", "iterable", "null", "mixed", "resource", "void", "scalar", "numeric",
1337            "never", "nothing", "as", "is", "not", "min", "max",
1338        ];
1339
1340        for keyword in keywords {
1341            let input = format!("array{{{keyword}: string}}");
1342            match do_parse(&input) {
1343                Ok(Type::Shape(shape)) => {
1344                    assert_eq!(shape.fields.len(), 1);
1345                    assert!(
1346                        matches!(
1347                            shape.fields[0].key.as_ref().map(|k| &k.key),
1348                            Some(ShapeKey::String { value, .. }) if *value == keyword
1349                        ),
1350                        "Failed for keyword: {keyword}"
1351                    );
1352                }
1353                res => panic!("Expected Ok(Type::Shape) for keyword '{keyword}', got {res:?}"),
1354            }
1355        }
1356    }
1357
1358    #[test]
1359    fn test_parse_php_specific_keywords() {
1360        match do_parse("array{self: string, static: int, parent: bool, class: mixed, __CLASS__: object}") {
1361            Ok(Type::Shape(shape)) => {
1362                assert_eq!(shape.fields.len(), 5);
1363
1364                assert!(matches!(
1365                    shape.fields[0].key.as_ref().map(|k| &k.key),
1366                    Some(ShapeKey::String { value: "self", .. })
1367                ));
1368                assert!(matches!(
1369                    shape.fields[1].key.as_ref().map(|k| &k.key),
1370                    Some(ShapeKey::String { value: "static", .. })
1371                ));
1372                assert!(matches!(
1373                    shape.fields[2].key.as_ref().map(|k| &k.key),
1374                    Some(ShapeKey::String { value: "parent", .. })
1375                ));
1376                assert!(matches!(
1377                    shape.fields[3].key.as_ref().map(|k| &k.key),
1378                    Some(ShapeKey::String { value: "class", .. })
1379                ));
1380                assert!(matches!(
1381                    shape.fields[4].key.as_ref().map(|k| &k.key),
1382                    Some(ShapeKey::String { value: "__CLASS__", .. })
1383                ));
1384            }
1385            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1386        }
1387    }
1388
1389    #[test]
1390    fn test_shape_key_spans() {
1391        match do_parse("array{test: string}") {
1392            Ok(Type::Shape(shape)) => {
1393                assert_eq!(shape.fields.len(), 1);
1394                let field = &shape.fields[0];
1395
1396                if let Some(key) = &field.key {
1397                    let span = key.key.span();
1398                    assert!(span.start.offset < span.end.offset, "Span should have valid start/end");
1399
1400                    assert_eq!(span.end.offset - span.start.offset, 4, "Span should cover 'test' (4 characters)");
1401                }
1402            }
1403            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1404        }
1405    }
1406
1407    #[test]
1408    fn test_shape_key_spans_quoted() {
1409        match do_parse("array{'hello': string}") {
1410            Ok(Type::Shape(shape)) => {
1411                assert_eq!(shape.fields.len(), 1);
1412                let field = &shape.fields[0];
1413
1414                if let Some(key) = &field.key {
1415                    let span = key.key.span();
1416                    assert_eq!(span.end.offset - span.start.offset, 7, "Span should cover 'hello' including quotes");
1417
1418                    assert!(matches!(&key.key, ShapeKey::String { value: "hello", .. }));
1419                }
1420            }
1421            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1422        }
1423    }
1424
1425    #[test]
1426    fn test_shape_key_spans_integer() {
1427        match do_parse("array{42: string}") {
1428            Ok(Type::Shape(shape)) => {
1429                assert_eq!(shape.fields.len(), 1);
1430                let field = &shape.fields[0];
1431
1432                if let Some(key) = &field.key {
1433                    let span = key.key.span();
1434                    assert_eq!(span.end.offset - span.start.offset, 2, "Span should cover '42' (2 characters)");
1435
1436                    assert!(matches!(&key.key, ShapeKey::Integer { value: 42, .. }));
1437                }
1438            }
1439            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1440        }
1441    }
1442
1443    #[test]
1444    fn test_shape_key_spans_negated_integer() {
1445        match do_parse("array{-123: string}") {
1446            Ok(Type::Shape(shape)) => {
1447                assert_eq!(shape.fields.len(), 1);
1448                let field = &shape.fields[0];
1449
1450                if let Some(key) = &field.key {
1451                    let span = key.key.span();
1452                    assert_eq!(span.end.offset - span.start.offset, 4, "Span should cover '-123' (4 characters)");
1453
1454                    assert!(matches!(&key.key, ShapeKey::Integer { value: -123, .. }));
1455                }
1456            }
1457            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1458        }
1459    }
1460
1461    #[test]
1462    fn test_shape_key_spans_complex_identifiers() {
1463        match do_parse("array{complex-key_name: string}") {
1464            Ok(Type::Shape(shape)) => {
1465                assert_eq!(shape.fields.len(), 1);
1466                let field = &shape.fields[0];
1467
1468                if let Some(key) = &field.key {
1469                    let span = key.key.span();
1470                    assert_eq!(
1471                        span.end.offset - span.start.offset,
1472                        16,
1473                        "Span should cover 'complex-key_name' (16 characters)"
1474                    );
1475
1476                    assert!(matches!(&key.key, ShapeKey::String { value: "complex-key_name", .. }));
1477                }
1478            }
1479            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1480        }
1481    }
1482}