mago_type_syntax/
lib.rs

1#![doc = include_str!("./../README.md")]
2
3use mago_span::Span;
4use mago_syntax_core::input::Input;
5
6use crate::ast::Type;
7use crate::error::ParseError;
8use crate::lexer::TypeLexer;
9
10pub mod ast;
11pub mod error;
12pub mod lexer;
13pub mod parser;
14pub mod token;
15
16/// Parses a string representation of a PHPDoc type into an Abstract Syntax Tree (AST).
17///
18/// This is the main entry point for the type parser. It takes the type string
19/// and its original `Span` (representing its location within the source file)
20/// and returns the parsed `Type` AST or a `ParseError`.
21///
22/// # Arguments
23///
24/// * `span` - The original `Span` of the `input` string slice within its source file.
25///   This is crucial for ensuring all AST nodes have correct, absolute positioning.
26/// * `input` - The `&str` containing the type string to parse (e.g., `"int|string"`, `"array<int, MyClass>"`).
27///
28/// # Returns
29///
30/// * `Ok(Type)` containing the root of the parsed AST on success.
31/// * `Err(ParseError)` if any lexing or parsing error occurs.
32pub fn parse_str(span: Span, input: &str) -> Result<Type<'_>, ParseError> {
33    // Create an Input anchored at the type string's original starting position.
34    let input = Input::anchored_at(span.file_id, input.as_bytes(), span.start);
35    // Create the type-specific lexer.
36    let lexer = TypeLexer::new(input);
37    // Construct the type AST using the lexer.
38    parser::construct(lexer)
39}
40
41#[cfg(test)]
42mod tests {
43    use mago_database::file::FileId;
44    use mago_span::HasSpan;
45    use mago_span::Position;
46    use mago_span::Span;
47
48    use crate::ast::*;
49
50    use super::*;
51
52    fn do_parse(input: &str) -> Result<Type<'_>, ParseError> {
53        parse_str(Span::new(FileId::zero(), Position::new(0), Position::new(input.len() as u32)), input)
54    }
55
56    #[test]
57    fn test_parse_simple_keyword() {
58        let result = do_parse("int");
59        assert!(result.is_ok());
60        match result.unwrap() {
61            Type::Int(k) => assert_eq!(k.value, "int"),
62            _ => panic!("Expected Type::Int"),
63        }
64    }
65
66    #[test]
67    fn test_parse_composite_keyword() {
68        let result = do_parse("non-empty-string");
69        assert!(result.is_ok());
70        match result.unwrap() {
71            Type::NonEmptyString(k) => assert_eq!(k.value, "non-empty-string"),
72            _ => panic!("Expected Type::NonEmptyString"),
73        }
74    }
75
76    #[test]
77    fn test_parse_literal_ints() {
78        let assert_parsed_literal_int = |input: &str, expected_value: u64| {
79            let result = do_parse(input);
80            assert!(result.is_ok());
81            match result.unwrap() {
82                Type::LiteralInt(LiteralIntType { value, .. }) => assert_eq!(
83                    value, expected_value,
84                    "Expected value to be {expected_value} for input {input}, but got {value}"
85                ),
86                _ => panic!("Expected Type::LiteralInt"),
87            }
88        };
89
90        assert_parsed_literal_int("0", 0);
91        assert_parsed_literal_int("1", 1);
92        assert_parsed_literal_int("123_345", 123345);
93        assert_parsed_literal_int("0b1", 1);
94        assert_parsed_literal_int("0o10", 8);
95        assert_parsed_literal_int("0x1", 1);
96        assert_parsed_literal_int("0x10", 16);
97        assert_parsed_literal_int("0xFF", 255);
98    }
99
100    #[test]
101    fn test_parse_literal_floats() {
102        let assert_parsed_literal_float = |input: &str, expected_value: f64| {
103            let result = do_parse(input);
104            assert!(result.is_ok());
105            match result.unwrap() {
106                Type::LiteralFloat(LiteralFloatType { value, .. }) => assert_eq!(
107                    value, expected_value,
108                    "Expected value to be {expected_value} for input {input}, but got {value}"
109                ),
110                _ => panic!("Expected Type::LiteralInt"),
111            }
112        };
113
114        assert_parsed_literal_float("0.0", 0.0);
115        assert_parsed_literal_float("1.0", 1.0);
116        assert_parsed_literal_float("0.1e1", 1.0);
117        assert_parsed_literal_float("0.1e-1", 0.01);
118        assert_parsed_literal_float("0.1E1", 1.0);
119        assert_parsed_literal_float("0.1E-1", 0.01);
120        assert_parsed_literal_float("0.1e+1", 1.0);
121        assert_parsed_literal_float(".1e+1", 1.0);
122    }
123
124    #[test]
125    fn test_parse_simple_union() {
126        match do_parse("int|string") {
127            Ok(ty) => match ty {
128                Type::Union(u) => {
129                    assert!(matches!(*u.left, Type::Int(_)));
130                    assert!(matches!(*u.right, Type::String(_)));
131                }
132                _ => panic!("Expected Type::Union"),
133            },
134            Err(err) => {
135                panic!("Failed to parse union type: {err:?}");
136            }
137        }
138    }
139
140    #[test]
141    fn test_parse_variable_union() {
142        match do_parse("$a|$b") {
143            Ok(ty) => match ty {
144                Type::Union(u) => {
145                    assert!(matches!(*u.left, Type::Variable(_)));
146                    assert!(matches!(*u.right, Type::Variable(_)));
147                }
148                _ => panic!("Expected Type::Union"),
149            },
150            Err(err) => {
151                panic!("Failed to parse union type: {err:?}");
152            }
153        }
154    }
155
156    #[test]
157    fn test_parse_nullable() {
158        let result = do_parse("?string");
159        assert!(result.is_ok());
160        match result.unwrap() {
161            Type::Nullable(n) => {
162                assert!(matches!(*n.inner, Type::String(_)));
163            }
164            _ => panic!("Expected Type::Nullable"),
165        }
166    }
167
168    #[test]
169    fn test_parse_generic_array() {
170        let result = do_parse("array<int, bool>");
171        assert!(result.is_ok());
172        match result.unwrap() {
173            Type::Array(a) => {
174                assert!(a.parameters.is_some());
175                let params = a.parameters.unwrap();
176                assert_eq!(params.entries.len(), 2);
177                assert!(matches!(params.entries[0].inner, Type::Int(_)));
178                assert!(matches!(params.entries[1].inner, Type::Bool(_)));
179            }
180            _ => panic!("Expected Type::Array"),
181        }
182    }
183
184    #[test]
185    fn test_parse_generic_array_one_param() {
186        match do_parse("array<string>") {
187            Ok(Type::Array(a)) => {
188                let params = a.parameters.expect("Expected generic parameters");
189                assert_eq!(params.entries.len(), 1);
190                assert!(matches!(params.entries[0].inner, Type::String(_)));
191            }
192            res => panic!("Expected Ok(Type::Array), got {res:?}"),
193        }
194    }
195
196    #[test]
197    fn test_parse_generic_list() {
198        match do_parse("list<string>") {
199            Ok(Type::List(l)) => {
200                let params = l.parameters.expect("Expected generic parameters");
201                assert_eq!(params.entries.len(), 1);
202                assert!(matches!(params.entries[0].inner, Type::String(_)));
203            }
204            res => panic!("Expected Ok(Type::List), got {res:?}"),
205        }
206    }
207
208    #[test]
209    fn test_parse_non_empty_array() {
210        match do_parse("non-empty-array<int, bool>") {
211            Ok(Type::NonEmptyArray(a)) => {
212                let params = a.parameters.expect("Expected generic parameters");
213                assert_eq!(params.entries.len(), 2);
214                assert!(matches!(params.entries[0].inner, Type::Int(_)));
215                assert!(matches!(params.entries[1].inner, Type::Bool(_)));
216            }
217            res => panic!("Expected Ok(Type::NonEmptyArray), got {res:?}"),
218        }
219    }
220
221    #[test]
222    fn test_parse_nested_generics() {
223        match do_parse("list<array<int, string>>") {
224            Ok(Type::List(l)) => {
225                let params = l.parameters.expect("Expected generic parameters");
226                assert_eq!(params.entries.len(), 1);
227                match &params.entries[0].inner {
228                    Type::Array(inner_array) => {
229                        let inner_params = inner_array.parameters.as_ref().expect("Inner array needs params");
230                        assert_eq!(inner_params.entries.len(), 2);
231                        assert!(matches!(inner_params.entries[0].inner, Type::Int(_)));
232                        assert!(matches!(inner_params.entries[1].inner, Type::String(_)));
233                    }
234                    _ => panic!("Expected inner type to be Type::Array"),
235                }
236            }
237            res => panic!("Expected Ok(Type::List), got {res:?}"),
238        }
239    }
240
241    #[test]
242    fn test_parse_simple_shape() {
243        let result = do_parse("array{'name': string}");
244        assert!(matches!(result, Ok(Type::Shape(_))));
245        let Ok(Type::Shape(shape)) = result else {
246            panic!("Expected Type::Shape");
247        };
248
249        assert_eq!(shape.kind, ShapeTypeKind::Array);
250        assert_eq!(shape.keyword.value, "array");
251        assert_eq!(shape.fields.len(), 1);
252        assert!(shape.additional_fields.is_none());
253
254        let field = &shape.fields[0];
255        assert!(matches!(field.key.as_ref().map(|k| &k.key), Some(ShapeKey::String { value: "name", .. })));
256        assert!(matches!(field.value.as_ref(), Type::String(_)));
257    }
258
259    #[test]
260    fn test_parse_int_key_shape() {
261        match do_parse("array{0: string, 1: bool}") {
262            Ok(Type::Shape(shape)) => {
263                assert_eq!(shape.fields.len(), 2);
264                let first_field = &shape.fields[0];
265                assert!(matches!(first_field.key.as_ref().map(|k| &k.key), Some(ShapeKey::Integer { value: 0, .. })));
266                assert!(matches!(first_field.value.as_ref(), Type::String(_)));
267                let second_field = &shape.fields[1];
268                assert!(matches!(second_field.key.as_ref().map(|k| &k.key), Some(ShapeKey::Integer { value: 1, .. })));
269                assert!(matches!(second_field.value.as_ref(), Type::Bool(_)));
270            }
271            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
272        }
273    }
274
275    #[test]
276    fn test_parse_optional_field_shape() {
277        match do_parse("array{name: string, age?: int, address: string}") {
278            Ok(Type::Shape(shape)) => {
279                assert_eq!(shape.fields.len(), 3);
280                assert!(!shape.fields[0].is_optional());
281                assert!(shape.fields[1].is_optional());
282                assert!(!shape.fields[2].is_optional());
283            }
284            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
285        }
286    }
287
288    #[test]
289    fn test_parse_unsealed_shape() {
290        match do_parse("array{name: string, ...}") {
291            Ok(Type::Shape(shape)) => {
292                assert_eq!(shape.fields.len(), 1);
293                assert!(shape.additional_fields.is_some());
294                assert!(shape.additional_fields.unwrap().parameters.is_none()); // No fallback specified
295            }
296            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
297        }
298    }
299
300    #[test]
301    fn test_parse_shape_with_keys_containing_special_chars() {
302        match do_parse("array{key-with-dash: int, key-with---multiple-dashes?: int}") {
303            Ok(Type::Shape(shape)) => {
304                assert_eq!(shape.fields.len(), 2);
305
306                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[0].key.as_ref().map(|k| &k.key) {
307                    assert_eq!(*s, "key-with-dash");
308                } else {
309                    panic!("Expected key to be a ShapeKey::String");
310                }
311
312                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[1].key.as_ref().map(|k| &k.key) {
313                    assert_eq!(*s, "key-with---multiple-dashes");
314                } else {
315                    panic!("Expected key to be a ShapeKey::String");
316                }
317            }
318            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
319        }
320    }
321
322    #[test]
323    fn test_parse_shape_with_keys_after_types() {
324        match do_parse("array{list: list<int>, int?: int, string: string, bool: bool}") {
325            Ok(Type::Shape(shape)) => {
326                assert_eq!(shape.fields.len(), 4);
327
328                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[0].key.as_ref().map(|k| &k.key) {
329                    assert_eq!(*s, "list");
330                } else {
331                    panic!("Expected key to be a ShapeKey::String");
332                }
333
334                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[1].key.as_ref().map(|k| &k.key) {
335                    assert_eq!(*s, "int");
336                } else {
337                    panic!("Expected key to be a ShapeKey::String");
338                }
339
340                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[2].key.as_ref().map(|k| &k.key) {
341                    assert_eq!(*s, "string");
342                } else {
343                    panic!("Expected key to be a ShapeKey::String");
344                }
345
346                if let Some(ShapeKey::String { value: s, .. }) = shape.fields[3].key.as_ref().map(|k| &k.key) {
347                    assert_eq!(*s, "bool");
348                } else {
349                    panic!("Expected key to be a ShapeKey::String");
350                }
351            }
352            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
353        }
354    }
355
356    #[test]
357    fn test_parse_unsealed_shape_with_fallback() {
358        match do_parse(
359            "array{
360                name: string, // This is a comment
361                ...<string, string>
362            }",
363        ) {
364            Ok(Type::Shape(shape)) => {
365                assert_eq!(shape.fields.len(), 1);
366                assert!(shape.additional_fields.as_ref().is_some_and(|a| a.parameters.is_some()));
367                let params = shape.additional_fields.unwrap().parameters.unwrap();
368                assert_eq!(params.entries.len(), 2);
369                assert!(matches!(params.entries[0].inner, Type::String(_)));
370                assert!(matches!(params.entries[1].inner, Type::String(_)));
371            }
372            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
373        }
374    }
375
376    #[test]
377    fn test_parse_empty_shape() {
378        match do_parse("array{}") {
379            Ok(Type::Shape(shape)) => {
380                assert_eq!(shape.fields.len(), 0);
381                assert!(shape.additional_fields.is_none());
382            }
383            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
384        }
385    }
386
387    #[test]
388    fn test_parse_error_unexpected_token() {
389        let result = do_parse("int|>");
390        assert!(result.is_err());
391        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedToken { .. }));
392    }
393
394    #[test]
395    fn test_parse_error_eof() {
396        let result = do_parse("array<int");
397        assert!(result.is_err());
398        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedEndOfFile { .. }));
399    }
400
401    #[test]
402    fn test_parse_error_trailing_token() {
403        let result = do_parse("int|string&");
404        assert!(result.is_err());
405        assert!(matches!(result.unwrap_err(), ParseError::UnexpectedEndOfFile { .. }));
406    }
407
408    #[test]
409    fn test_parse_intersection() {
410        match do_parse("Countable&Traversable") {
411            Ok(Type::Intersection(i)) => {
412                assert!(matches!(*i.left, Type::Reference(_)));
413                assert!(matches!(*i.right, Type::Reference(_)));
414
415                if let Type::Reference(r) = *i.left {
416                    assert_eq!(r.identifier.value, "Countable");
417                } else {
418                    panic!();
419                }
420
421                if let Type::Reference(r) = *i.right {
422                    assert_eq!(r.identifier.value, "Traversable");
423                } else {
424                    panic!();
425                }
426            }
427            res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
428        }
429    }
430
431    #[test]
432    fn test_parse_member_ref() {
433        match do_parse("MyClass::MY_CONST") {
434            Ok(Type::MemberReference(m)) => {
435                assert_eq!(m.class.value, "MyClass");
436                assert_eq!(m.member.to_string(), "MY_CONST");
437            }
438            res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
439        }
440
441        match do_parse("\\Fully\\Qualified::class") {
442            Ok(Type::MemberReference(m)) => {
443                assert_eq!(m.class.value, "\\Fully\\Qualified"); // Check if lexer keeps leading \
444                assert_eq!(m.member.to_string(), "class");
445            }
446            res => panic!("Expected Ok(Type::MemberReference), got {res:?}"),
447        }
448    }
449
450    #[test]
451    fn test_parse_iterable() {
452        match do_parse("iterable<int, string>") {
453            Ok(Type::Iterable(i)) => {
454                let params = i.parameters.expect("Expected generic parameters");
455                assert_eq!(params.entries.len(), 2);
456                assert!(matches!(params.entries[0].inner, Type::Int(_)));
457                assert!(matches!(params.entries[1].inner, Type::String(_)));
458            }
459            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
460        }
461
462        match do_parse("iterable<bool>") {
463            // Test single param case
464            Ok(Type::Iterable(i)) => {
465                let params = i.parameters.expect("Expected generic parameters");
466                assert_eq!(params.entries.len(), 1);
467                assert!(matches!(params.entries[0].inner, Type::Bool(_)));
468            }
469            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
470        }
471
472        match do_parse("iterable") {
473            Ok(Type::Iterable(i)) => {
474                assert!(i.parameters.is_none());
475            }
476            res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
477        }
478    }
479
480    #[test]
481    fn test_parse_negated_int() {
482        let assert_negated_int = |input: &str, expected_value: u64| {
483            let result = do_parse(input);
484            assert!(result.is_ok());
485            match result.unwrap() {
486                Type::Negated(n) => {
487                    assert!(matches!(n.number, LiteralIntOrFloatType::Int(_)));
488                    if let LiteralIntOrFloatType::Int(lit) = n.number {
489                        assert_eq!(lit.value, expected_value);
490                    } else {
491                        panic!()
492                    }
493                }
494                _ => panic!("Expected Type::Negated"),
495            }
496        };
497
498        assert_negated_int("-0", 0);
499        assert_negated_int("-1", 1);
500        assert_negated_int(
501            "-
502            // This is a comment
503            123_345",
504            123345,
505        );
506        assert_negated_int("-0b1", 1);
507    }
508
509    #[test]
510    fn test_parse_negated_float() {
511        let assert_negated_float = |input: &str, expected_value: f64| {
512            let result = do_parse(input);
513            assert!(result.is_ok());
514            match result.unwrap() {
515                Type::Negated(n) => {
516                    assert!(matches!(n.number, LiteralIntOrFloatType::Float(_)));
517                    if let LiteralIntOrFloatType::Float(lit) = n.number {
518                        assert_eq!(lit.value, expected_value);
519                    } else {
520                        panic!()
521                    }
522                }
523                _ => panic!("Expected Type::Negated"),
524            }
525        };
526
527        assert_negated_float("-0.0", 0.0);
528        assert_negated_float("-1.0", 1.0);
529        assert_negated_float("-0.1e1", 1.0);
530        assert_negated_float("-0.1e-1", 0.01);
531    }
532
533    #[test]
534    fn test_parse_negated_union() {
535        match do_parse("-1|-2.0|string") {
536            Ok(Type::Union(n)) => {
537                assert!(matches!(*n.left, Type::Negated(_)));
538                assert!(matches!(*n.right, Type::Union(_)));
539
540                if let Type::Negated(neg) = *n.left {
541                    assert!(matches!(neg.number, LiteralIntOrFloatType::Int(_)));
542                    if let LiteralIntOrFloatType::Int(lit) = neg.number {
543                        assert_eq!(lit.value, 1);
544                    } else {
545                        panic!()
546                    }
547                } else {
548                    panic!("Expected left side to be Type::Negated");
549                }
550
551                if let Type::Union(inner_union) = *n.right {
552                    assert!(matches!(*inner_union.left, Type::Negated(_)));
553                    assert!(matches!(*inner_union.right, Type::String(_)));
554
555                    if let Type::Negated(neg) = *inner_union.left {
556                        assert!(matches!(neg.number, LiteralIntOrFloatType::Float(_)));
557                        if let LiteralIntOrFloatType::Float(lit) = neg.number {
558                            assert_eq!(lit.value, 2.0);
559                        } else {
560                            panic!()
561                        }
562                    } else {
563                        panic!("Expected left side of inner union to be Type::Negated");
564                    }
565
566                    if let Type::String(s) = *inner_union.right {
567                        assert_eq!(s.value, "string");
568                    } else {
569                        panic!("Expected right side of inner union to be Type::String");
570                    }
571                } else {
572                    panic!("Expected right side to be Type::Union");
573                }
574            }
575            res => panic!("Expected Ok(Type::Negated), got {res:?}"),
576        }
577    }
578
579    #[test]
580    fn test_parse_callable_no_spec() {
581        match do_parse("callable") {
582            Ok(Type::Callable(c)) => {
583                assert!(c.specification.is_none());
584                assert_eq!(c.kind, CallableTypeKind::Callable);
585            }
586            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
587        }
588    }
589
590    #[test]
591    fn test_parse_callable_params_only() {
592        match do_parse("callable(int, ?string)") {
593            Ok(Type::Callable(c)) => {
594                let spec = c.specification.expect("Expected callable specification");
595                assert!(spec.return_type.is_none());
596                assert_eq!(spec.parameters.entries.len(), 2);
597                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Int(_))));
598                assert!(matches!(spec.parameters.entries[1].parameter_type, Some(Type::Nullable(_))));
599                assert!(spec.parameters.entries[0].ellipsis.is_none());
600                assert!(spec.parameters.entries[0].equals.is_none());
601            }
602            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
603        }
604    }
605
606    #[test]
607    fn test_parse_callable_return_only() {
608        match do_parse("callable(): void") {
609            Ok(Type::Callable(c)) => {
610                let spec = c.specification.expect("Expected callable specification");
611                assert!(spec.parameters.entries.is_empty());
612                assert!(spec.return_type.is_some());
613                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Void(_)));
614            }
615            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
616        }
617    }
618
619    #[test]
620    fn test_parse_pure_callable_full() {
621        match do_parse("pure-callable(bool): int") {
622            Ok(Type::Callable(c)) => {
623                assert_eq!(c.kind, CallableTypeKind::PureCallable);
624                let spec = c.specification.expect("Expected callable specification");
625                assert_eq!(spec.parameters.entries.len(), 1);
626                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Bool(_))));
627                assert!(spec.return_type.is_some());
628                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Int(_)));
629            }
630            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
631        }
632    }
633
634    #[test]
635    fn test_parse_closure_via_identifier() {
636        match do_parse("Closure(string): bool") {
637            Ok(Type::Callable(c)) => {
638                assert_eq!(c.kind, CallableTypeKind::Closure);
639                assert_eq!(c.keyword.value, "Closure");
640                let spec = c.specification.expect("Expected callable specification");
641                assert_eq!(spec.parameters.entries.len(), 1);
642                assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::String(_))));
643                assert!(spec.return_type.is_some());
644                assert!(matches!(*spec.return_type.unwrap().return_type, Type::Bool(_)));
645            }
646            res => panic!("Expected Ok(Type::Callable) for Closure, got {res:?}"),
647        }
648    }
649
650    #[test]
651    fn test_parse_complex_pure_callable() {
652        match do_parse("pure-callable(list<int>, ?Closure(): void=, int...): ((Simple&Iter<T>)|null)") {
653            Ok(Type::Callable(c)) => {
654                assert_eq!(c.kind, CallableTypeKind::PureCallable);
655                let spec = c.specification.expect("Expected callable specification");
656                assert_eq!(spec.parameters.entries.len(), 3);
657                assert!(spec.return_type.is_some());
658
659                let first_param = &spec.parameters.entries[0];
660                assert!(matches!(first_param.parameter_type, Some(Type::List(_))));
661                assert!(first_param.ellipsis.is_none());
662                assert!(first_param.equals.is_none());
663
664                let second_param = &spec.parameters.entries[1];
665                assert!(matches!(second_param.parameter_type, Some(Type::Nullable(_))));
666                assert!(second_param.ellipsis.is_none());
667                assert!(second_param.equals.is_some());
668
669                let third_param = &spec.parameters.entries[2];
670                assert!(matches!(third_param.parameter_type, Some(Type::Int(_))));
671                assert!(third_param.ellipsis.is_some());
672                assert!(third_param.equals.is_none());
673
674                if let Type::Parenthesized(p) = *spec.return_type.unwrap().return_type {
675                    assert!(matches!(*p.inner, Type::Union(_)));
676                    if let Type::Union(u) = *p.inner {
677                        assert!(matches!(u.left.as_ref(), Type::Parenthesized(_)));
678                        assert!(matches!(u.right.as_ref(), Type::Null(_)));
679                    }
680                } else {
681                    panic!("Expected Type::CallableReturnType");
682                }
683            }
684            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
685        }
686    }
687
688    #[test]
689    fn test_parse_conditional_type() {
690        match do_parse("int is not string ? array : int") {
691            Ok(Type::Conditional(c)) => {
692                assert!(matches!(*c.subject, Type::Int(_)));
693                assert!(c.not.is_some());
694                assert!(matches!(*c.target, Type::String(_)));
695                assert!(matches!(*c.then, Type::Array(_)));
696                assert!(matches!(*c.otherwise, Type::Int(_)));
697            }
698            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
699        }
700
701        match do_parse("$input is string ? array : int") {
702            Ok(Type::Conditional(c)) => {
703                assert!(matches!(*c.subject, Type::Variable(_)));
704                assert!(c.not.is_none());
705                assert!(matches!(*c.target, Type::String(_)));
706                assert!(matches!(*c.then, Type::Array(_)));
707                assert!(matches!(*c.otherwise, Type::Int(_)));
708            }
709            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
710        }
711
712        match do_parse("int is string ? array : (int is not $bar ? string : $baz)") {
713            Ok(Type::Conditional(c)) => {
714                assert!(matches!(*c.subject, Type::Int(_)));
715                assert!(c.not.is_none());
716                assert!(matches!(*c.target, Type::String(_)));
717                assert!(matches!(*c.then, Type::Array(_)));
718
719                let Type::Parenthesized(p) = *c.otherwise else {
720                    panic!("Expected Type::Parenthesized");
721                };
722
723                if let Type::Conditional(inner_conditional) = *p.inner {
724                    assert!(matches!(*inner_conditional.subject, Type::Int(_)));
725                    assert!(inner_conditional.not.is_some());
726                    assert!(matches!(*inner_conditional.target, Type::Variable(_)));
727                    assert!(matches!(*inner_conditional.then, Type::String(_)));
728                    assert!(matches!(*inner_conditional.otherwise, Type::Variable(_)));
729                } else {
730                    panic!("Expected Type::Conditional");
731                }
732            }
733            res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
734        }
735    }
736
737    #[test]
738    fn test_keyof() {
739        match do_parse("key-of<MyArray>") {
740            Ok(Type::KeyOf(k)) => {
741                assert_eq!(k.keyword.value, "key-of");
742                match &k.parameter.entry.inner {
743                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
744                    _ => panic!("Expected Type::Reference"),
745                }
746            }
747            res => panic!("Expected Ok(Type::KeyOf), got {res:?}"),
748        }
749    }
750
751    #[test]
752    fn test_valueof() {
753        match do_parse("value-of<MyArray>") {
754            Ok(Type::ValueOf(v)) => {
755                assert_eq!(v.keyword.value, "value-of");
756                match &v.parameter.entry.inner {
757                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
758                    _ => panic!("Expected Type::Reference"),
759                }
760            }
761            res => panic!("Expected Ok(Type::ValueOf), got {res:?}"),
762        }
763    }
764
765    #[test]
766    fn test_indexed_access() {
767        match do_parse("MyArray[MyKey]") {
768            Ok(Type::IndexAccess(i)) => {
769                match *i.target {
770                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
771                    _ => panic!("Expected Type::Reference"),
772                }
773                match *i.index {
774                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyKey"),
775                    _ => panic!("Expected Type::Reference"),
776                }
777            }
778            res => panic!("Expected Ok(Type::IndexAccess), got {res:?}"),
779        }
780    }
781
782    #[test]
783    fn test_slice_type() {
784        match do_parse("string[]") {
785            Ok(Type::Slice(s)) => {
786                assert!(matches!(*s.inner, Type::String(_)));
787            }
788            res => panic!("Expected Ok(Type::Slice), got {res:?}"),
789        }
790    }
791
792    #[test]
793    fn test_slice_of_slice_of_slice_type() {
794        match do_parse("string[][][]") {
795            Ok(Type::Slice(s)) => {
796                assert!(matches!(*s.inner, Type::Slice(_)));
797                if let Type::Slice(inner_slice) = *s.inner {
798                    assert!(matches!(*inner_slice.inner, Type::Slice(_)));
799                    if let Type::Slice(inner_inner_slice) = *inner_slice.inner {
800                        assert!(matches!(*inner_inner_slice.inner, Type::String(_)));
801                    } else {
802                        panic!("Expected inner slice to be a Slice");
803                    }
804                } else {
805                    panic!("Expected outer slice to be a Slice");
806                }
807            }
808            res => panic!("Expected Ok(Type::Slice), got {res:?}"),
809        }
810    }
811
812    #[test]
813    fn test_int_range() {
814        match do_parse("int<0, 100>") {
815            Ok(Type::IntRange(r)) => {
816                assert_eq!(r.keyword.value, "int");
817
818                match r.min {
819                    IntOrKeyword::Int(literal_int_type) => {
820                        assert_eq!(literal_int_type.value, 0);
821                    }
822                    _ => {
823                        panic!("Expected min to be a LiteralIntType, got `{}`", r.min)
824                    }
825                };
826
827                match r.max {
828                    IntOrKeyword::Int(literal_int_type) => {
829                        assert_eq!(literal_int_type.value, 100);
830                    }
831                    _ => {
832                        panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
833                    }
834                };
835            }
836            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
837        }
838
839        match do_parse("int<min, 0>") {
840            Ok(Type::IntRange(r)) => {
841                match r.min {
842                    IntOrKeyword::Keyword(keyword) => {
843                        assert_eq!(keyword.value, "min");
844                    }
845                    _ => {
846                        panic!("Expected min to be a Keyword, got `{}`", r.min)
847                    }
848                };
849
850                match r.max {
851                    IntOrKeyword::Int(literal_int_type) => {
852                        assert_eq!(literal_int_type.value, 0);
853                    }
854                    _ => {
855                        panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
856                    }
857                };
858            }
859            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
860        }
861
862        match do_parse("int<min, max>") {
863            Ok(Type::IntRange(r)) => {
864                match r.min {
865                    IntOrKeyword::Keyword(keyword) => {
866                        assert_eq!(keyword.value, "min");
867                    }
868                    _ => {
869                        panic!("Expected min to be a Keyword, got `{}`", r.min)
870                    }
871                };
872
873                match r.max {
874                    IntOrKeyword::Keyword(keyword) => {
875                        assert_eq!(keyword.value, "max");
876                    }
877                    _ => {
878                        panic!("Expected max to be a Keyword, got `{}`", r.max)
879                    }
880                };
881            }
882            res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
883        }
884    }
885
886    #[test]
887    fn test_properties_of() {
888        match do_parse("properties-of<MyClass>") {
889            Ok(Type::PropertiesOf(p)) => {
890                assert_eq!(p.keyword.value, "properties-of");
891                assert_eq!(p.filter, PropertiesOfFilter::All);
892                match &p.parameter.entry.inner {
893                    Type::Reference(r) => assert_eq!(r.identifier.value, "MyClass"),
894                    _ => panic!(),
895                }
896            }
897            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
898        }
899
900        match do_parse("protected-properties-of<T>") {
901            Ok(Type::PropertiesOf(p)) => {
902                assert_eq!(p.keyword.value, "protected-properties-of");
903                assert_eq!(p.filter, PropertiesOfFilter::Protected);
904                match &p.parameter.entry.inner {
905                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
906                    _ => panic!(),
907                }
908            }
909            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
910        }
911
912        match do_parse("private-properties-of<T>") {
913            Ok(Type::PropertiesOf(p)) => {
914                assert_eq!(p.keyword.value, "private-properties-of");
915                assert_eq!(p.filter, PropertiesOfFilter::Private);
916                match &p.parameter.entry.inner {
917                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
918                    _ => panic!(),
919                }
920            }
921            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
922        }
923
924        match do_parse("public-properties-of<T>") {
925            Ok(Type::PropertiesOf(p)) => {
926                assert_eq!(p.keyword.value, "public-properties-of");
927                assert_eq!(p.filter, PropertiesOfFilter::Public);
928                match &p.parameter.entry.inner {
929                    Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
930                    _ => panic!(),
931                }
932            }
933            res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
934        }
935    }
936
937    #[test]
938    fn test_variable() {
939        match do_parse("$myVar") {
940            Ok(Type::Variable(v)) => {
941                assert_eq!(v.value, "$myVar");
942            }
943            res => panic!("Expected Ok(Type::Variable), got {res:?}"),
944        }
945    }
946
947    #[test]
948    fn test_nullable_intersection() {
949        // Nullable applies only to the rightmost element of an intersection before parens
950        match do_parse("Countable&?Traversable") {
951            Ok(Type::Intersection(i)) => {
952                assert!(matches!(*i.left, Type::Reference(r) if r.identifier.value == "Countable"));
953                assert!(matches!(*i.right, Type::Nullable(_)));
954                if let Type::Nullable(n) = *i.right {
955                    assert!(matches!(*n.inner, Type::Reference(r) if r.identifier.value == "Traversable"));
956                } else {
957                    panic!();
958                }
959            }
960            res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
961        }
962    }
963
964    #[test]
965    fn test_parenthesized_nullable() {
966        match do_parse("?(Countable&Traversable)") {
967            Ok(Type::Nullable(n)) => {
968                assert!(matches!(*n.inner, Type::Parenthesized(_)));
969                if let Type::Parenthesized(p) = *n.inner {
970                    assert!(matches!(*p.inner, Type::Intersection(_)));
971                } else {
972                    panic!()
973                }
974            }
975            res => panic!("Expected Ok(Type::Nullable), got {res:?}"),
976        }
977    }
978
979    #[test]
980    fn test_positive_negative_int() {
981        match do_parse("positive-int|negative-int") {
982            Ok(Type::Union(u)) => {
983                assert!(matches!(*u.left, Type::PositiveInt(_)));
984                assert!(matches!(*u.right, Type::NegativeInt(_)));
985            }
986            res => panic!("Expected Ok(Type::Union), got {res:?}"),
987        }
988    }
989
990    #[test]
991    fn test_parse_float_alias() {
992        match do_parse("double") {
993            Ok(Type::Float(f)) => {
994                assert_eq!(f.value, "double");
995            }
996            res => panic!("Expected Ok(Type::Float), got {res:?}"),
997        }
998
999        match do_parse("real") {
1000            Ok(Type::Float(f)) => {
1001                assert_eq!(f.value, "real");
1002            }
1003            res => panic!("Expected Ok(Type::Float), got {res:?}"),
1004        }
1005
1006        match do_parse("float") {
1007            Ok(Type::Float(f)) => {
1008                assert_eq!(f.value, "float");
1009            }
1010            res => panic!("Expected Ok(Type::Float), got {res:?}"),
1011        }
1012    }
1013
1014    #[test]
1015    fn test_parse_bool_alias() {
1016        match do_parse("boolean") {
1017            Ok(Type::Bool(b)) => {
1018                assert_eq!(b.value, "boolean");
1019            }
1020            res => panic!("Expected Ok(Type::Bool), got {res:?}"),
1021        }
1022
1023        match do_parse("bool") {
1024            Ok(Type::Bool(b)) => {
1025                assert_eq!(b.value, "bool");
1026            }
1027            res => panic!("Expected Ok(Type::Bool), got {res:?}"),
1028        }
1029    }
1030
1031    #[test]
1032    fn test_parse_integer_alias() {
1033        match do_parse("integer") {
1034            Ok(Type::Int(i)) => {
1035                assert_eq!(i.value, "integer");
1036            }
1037            res => panic!("Expected Ok(Type::Int), got {res:?}"),
1038        }
1039
1040        match do_parse("int") {
1041            Ok(Type::Int(i)) => {
1042                assert_eq!(i.value, "int");
1043            }
1044            res => panic!("Expected Ok(Type::Int), got {res:?}"),
1045        }
1046    }
1047
1048    #[test]
1049    fn test_parse_callable_with_variables() {
1050        match do_parse("callable(string ...$names)") {
1051            Ok(Type::Callable(callable)) => {
1052                assert_eq!(callable.keyword.value, "callable");
1053                assert!(callable.specification.is_some());
1054
1055                let specification = callable.specification.unwrap();
1056
1057                assert!(specification.return_type.is_none());
1058                assert_eq!(specification.parameters.entries.len(), 1);
1059
1060                let first_parameter = specification.parameters.entries.first().unwrap();
1061                assert!(first_parameter.variable.is_some());
1062                assert!(first_parameter.ellipsis.is_some());
1063
1064                let variable = first_parameter.variable.unwrap();
1065                assert_eq!(variable.value, "$names");
1066            }
1067            res => panic!("Expected Ok(Type::Callable), got {res:?}"),
1068        }
1069    }
1070
1071    #[test]
1072    fn test_parse_string_or_lowercase_string_union() {
1073        match do_parse("string|lowercase-string") {
1074            Ok(Type::Union(u)) => {
1075                assert!(matches!(*u.left, Type::String(_)));
1076                assert!(matches!(*u.right, Type::LowercaseString(_)));
1077            }
1078            res => panic!("Expected Ok(Type::Union), got {res:?}"),
1079        }
1080    }
1081
1082    #[test]
1083    fn test_parse_optional_literal_string_shape_field() {
1084        match do_parse("array{'salt'?: int, 'cost'?: int, ...}") {
1085            Ok(Type::Shape(shape)) => {
1086                assert_eq!(shape.fields.len(), 2);
1087                assert!(shape.additional_fields.is_some());
1088
1089                let first_field = &shape.fields[0];
1090                assert!(first_field.is_optional());
1091                assert!(matches!(
1092                    first_field.key.as_ref().map(|k| &k.key),
1093                    Some(ShapeKey::String { value: "salt", .. })
1094                ));
1095                assert!(matches!(first_field.value.as_ref(), Type::Int(_)));
1096
1097                let second_field = &shape.fields[1];
1098                assert!(second_field.is_optional());
1099                assert!(matches!(
1100                    second_field.key.as_ref().map(|k| &k.key),
1101                    Some(ShapeKey::String { value: "cost", .. })
1102                ));
1103                assert!(matches!(second_field.value.as_ref(), Type::Int(_)));
1104            }
1105            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1106        }
1107    }
1108
1109    #[test]
1110    fn test_parse_keyword_keys() {
1111        match do_parse("array{string: int, bool: string, int: float, mixed: object}") {
1112            Ok(Type::Shape(shape)) => {
1113                assert_eq!(shape.fields.len(), 4);
1114
1115                assert!(matches!(
1116                    shape.fields[0].key.as_ref().map(|k| &k.key),
1117                    Some(ShapeKey::String { value: "string", .. })
1118                ));
1119                assert!(matches!(
1120                    shape.fields[1].key.as_ref().map(|k| &k.key),
1121                    Some(ShapeKey::String { value: "bool", .. })
1122                ));
1123                assert!(matches!(
1124                    shape.fields[2].key.as_ref().map(|k| &k.key),
1125                    Some(ShapeKey::String { value: "int", .. })
1126                ));
1127                assert!(matches!(
1128                    shape.fields[3].key.as_ref().map(|k| &k.key),
1129                    Some(ShapeKey::String { value: "mixed", .. })
1130                ));
1131            }
1132            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1133        }
1134    }
1135
1136    #[test]
1137    fn test_parse_negated_integer_keys() {
1138        match do_parse("array{-1: string, -42: int, +5: bool}") {
1139            Ok(Type::Shape(shape)) => {
1140                assert_eq!(shape.fields.len(), 3);
1141
1142                assert!(matches!(
1143                    shape.fields[0].key.as_ref().map(|k| &k.key),
1144                    Some(ShapeKey::Integer { value: -1, .. })
1145                ));
1146                assert!(matches!(
1147                    shape.fields[1].key.as_ref().map(|k| &k.key),
1148                    Some(ShapeKey::Integer { value: -42, .. })
1149                ));
1150                assert!(matches!(
1151                    shape.fields[2].key.as_ref().map(|k| &k.key),
1152                    Some(ShapeKey::Integer { value: 5, .. })
1153                ));
1154            }
1155            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1156        }
1157    }
1158
1159    #[test]
1160    fn test_parse_float_keys() {
1161        match do_parse("array{123.4: string, -1.2: int, +0.5: bool}") {
1162            Ok(Type::Shape(shape)) => {
1163                assert_eq!(shape.fields.len(), 3);
1164
1165                assert!(matches!(
1166                    shape.fields[0].key.as_ref().map(|k| &k.key),
1167                    Some(ShapeKey::String { value: "123.4", .. })
1168                ));
1169                assert!(matches!(
1170                    shape.fields[1].key.as_ref().map(|k| &k.key),
1171                    Some(ShapeKey::String { value: "-1.2", .. })
1172                ));
1173                assert!(matches!(
1174                    shape.fields[2].key.as_ref().map(|k| &k.key),
1175                    Some(ShapeKey::String { value: "+0.5", .. })
1176                ));
1177            }
1178            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1179        }
1180    }
1181
1182    #[test]
1183    fn test_parse_complex_identifier_keys() {
1184        match do_parse(
1185            "array{key_with_underscore: int, key-with-dash: string, key\\with\\backslash: bool, +key: mixed, -key: object, \\leading_backslash: int}",
1186        ) {
1187            Ok(Type::Shape(shape)) => {
1188                assert_eq!(shape.fields.len(), 6);
1189
1190                assert!(matches!(
1191                    shape.fields[0].key.as_ref().map(|k| &k.key),
1192                    Some(ShapeKey::String { value: "key_with_underscore", .. })
1193                ));
1194                assert!(matches!(
1195                    shape.fields[1].key.as_ref().map(|k| &k.key),
1196                    Some(ShapeKey::String { value: "key-with-dash", .. })
1197                ));
1198                assert!(matches!(
1199                    shape.fields[2].key.as_ref().map(|k| &k.key),
1200                    Some(ShapeKey::String { value: "key\\with\\backslash", .. })
1201                ));
1202                assert!(matches!(
1203                    shape.fields[3].key.as_ref().map(|k| &k.key),
1204                    Some(ShapeKey::String { value: "+key", .. })
1205                ));
1206                assert!(matches!(
1207                    shape.fields[4].key.as_ref().map(|k| &k.key),
1208                    Some(ShapeKey::String { value: "-key", .. })
1209                ));
1210                assert!(matches!(
1211                    shape.fields[5].key.as_ref().map(|k| &k.key),
1212                    Some(ShapeKey::String { value: "\\leading_backslash", .. })
1213                ));
1214            }
1215            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1216        }
1217    }
1218
1219    #[test]
1220    fn test_parse_optional_keys_with_question_mark_in_name() {
1221        match do_parse("array{key?name: int, regular?: string}") {
1222            Ok(Type::Shape(shape)) => {
1223                assert_eq!(shape.fields.len(), 2);
1224
1225                assert!(!shape.fields[0].is_optional());
1226                assert!(matches!(
1227                    shape.fields[0].key.as_ref().map(|k| &k.key),
1228                    Some(ShapeKey::String { value: "key?name", .. })
1229                ));
1230
1231                assert!(shape.fields[1].is_optional());
1232                assert!(matches!(
1233                    shape.fields[1].key.as_ref().map(|k| &k.key),
1234                    Some(ShapeKey::String { value: "regular", .. })
1235                ));
1236            }
1237            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1238        }
1239    }
1240
1241    #[test]
1242    fn test_parse_integer_formats() {
1243        match do_parse("array{42: string, 0x2A: int, 0b101010: bool, 0o52: mixed}") {
1244            Ok(Type::Shape(shape)) => {
1245                assert_eq!(shape.fields.len(), 4);
1246
1247                assert!(matches!(
1248                    shape.fields[0].key.as_ref().map(|k| &k.key),
1249                    Some(ShapeKey::Integer { value: 42, .. })
1250                ));
1251                assert!(matches!(
1252                    shape.fields[1].key.as_ref().map(|k| &k.key),
1253                    Some(ShapeKey::Integer { value: 42, .. })
1254                ));
1255                assert!(matches!(
1256                    shape.fields[2].key.as_ref().map(|k| &k.key),
1257                    Some(ShapeKey::Integer { value: 42, .. })
1258                ));
1259                assert!(matches!(
1260                    shape.fields[3].key.as_ref().map(|k| &k.key),
1261                    Some(ShapeKey::Integer { value: 42, .. })
1262                ));
1263            }
1264            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1265        }
1266    }
1267
1268    #[test]
1269    fn test_parse_quoted_vs_unquoted_keys() {
1270        match do_parse("array{'string': int, \"double\": bool, unquoted: mixed}") {
1271            Ok(Type::Shape(shape)) => {
1272                assert_eq!(shape.fields.len(), 3);
1273
1274                assert!(matches!(
1275                    shape.fields[0].key.as_ref().map(|k| &k.key),
1276                    Some(ShapeKey::String { value: "string", .. })
1277                ));
1278                assert!(matches!(
1279                    shape.fields[1].key.as_ref().map(|k| &k.key),
1280                    Some(ShapeKey::String { value: "double", .. })
1281                ));
1282                assert!(matches!(
1283                    shape.fields[2].key.as_ref().map(|k| &k.key),
1284                    Some(ShapeKey::String { value: "unquoted", .. })
1285                ));
1286            }
1287            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1288        }
1289    }
1290
1291    #[test]
1292    fn test_parse_all_keyword_types() {
1293        let keywords = vec![
1294            "list", "int", "integer", "string", "float", "double", "real", "bool", "boolean", "false", "true",
1295            "object", "callable", "array", "iterable", "null", "mixed", "resource", "void", "scalar", "numeric",
1296            "never", "nothing", "as", "is", "not", "min", "max",
1297        ];
1298
1299        for keyword in keywords {
1300            let input = format!("array{{{}: string}}", keyword);
1301            match do_parse(&input) {
1302                Ok(Type::Shape(shape)) => {
1303                    assert_eq!(shape.fields.len(), 1);
1304                    assert!(
1305                        matches!(
1306                            shape.fields[0].key.as_ref().map(|k| &k.key),
1307                            Some(ShapeKey::String { value, .. }) if *value == keyword
1308                        ),
1309                        "Failed for keyword: {}",
1310                        keyword
1311                    );
1312                }
1313                res => panic!("Expected Ok(Type::Shape) for keyword '{}', got {res:?}", keyword),
1314            }
1315        }
1316    }
1317
1318    #[test]
1319    fn test_parse_php_specific_keywords() {
1320        match do_parse("array{self: string, static: int, parent: bool, class: mixed, __CLASS__: object}") {
1321            Ok(Type::Shape(shape)) => {
1322                assert_eq!(shape.fields.len(), 5);
1323
1324                assert!(matches!(
1325                    shape.fields[0].key.as_ref().map(|k| &k.key),
1326                    Some(ShapeKey::String { value: "self", .. })
1327                ));
1328                assert!(matches!(
1329                    shape.fields[1].key.as_ref().map(|k| &k.key),
1330                    Some(ShapeKey::String { value: "static", .. })
1331                ));
1332                assert!(matches!(
1333                    shape.fields[2].key.as_ref().map(|k| &k.key),
1334                    Some(ShapeKey::String { value: "parent", .. })
1335                ));
1336                assert!(matches!(
1337                    shape.fields[3].key.as_ref().map(|k| &k.key),
1338                    Some(ShapeKey::String { value: "class", .. })
1339                ));
1340                assert!(matches!(
1341                    shape.fields[4].key.as_ref().map(|k| &k.key),
1342                    Some(ShapeKey::String { value: "__CLASS__", .. })
1343                ));
1344            }
1345            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1346        }
1347    }
1348
1349    #[test]
1350    fn test_shape_key_spans() {
1351        match do_parse("array{test: string}") {
1352            Ok(Type::Shape(shape)) => {
1353                assert_eq!(shape.fields.len(), 1);
1354                let field = &shape.fields[0];
1355
1356                if let Some(key) = &field.key {
1357                    let span = key.key.span();
1358                    assert!(span.start.offset < span.end.offset, "Span should have valid start/end");
1359
1360                    assert_eq!(span.end.offset - span.start.offset, 4, "Span should cover 'test' (4 characters)");
1361                }
1362            }
1363            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1364        }
1365    }
1366
1367    #[test]
1368    fn test_shape_key_spans_quoted() {
1369        match do_parse("array{'hello': string}") {
1370            Ok(Type::Shape(shape)) => {
1371                assert_eq!(shape.fields.len(), 1);
1372                let field = &shape.fields[0];
1373
1374                if let Some(key) = &field.key {
1375                    let span = key.key.span();
1376                    assert_eq!(span.end.offset - span.start.offset, 7, "Span should cover 'hello' including quotes");
1377
1378                    assert!(matches!(&key.key, ShapeKey::String { value: "hello", .. }));
1379                }
1380            }
1381            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1382        }
1383    }
1384
1385    #[test]
1386    fn test_shape_key_spans_integer() {
1387        match do_parse("array{42: string}") {
1388            Ok(Type::Shape(shape)) => {
1389                assert_eq!(shape.fields.len(), 1);
1390                let field = &shape.fields[0];
1391
1392                if let Some(key) = &field.key {
1393                    let span = key.key.span();
1394                    assert_eq!(span.end.offset - span.start.offset, 2, "Span should cover '42' (2 characters)");
1395
1396                    assert!(matches!(&key.key, ShapeKey::Integer { value: 42, .. }));
1397                }
1398            }
1399            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1400        }
1401    }
1402
1403    #[test]
1404    fn test_shape_key_spans_negated_integer() {
1405        match do_parse("array{-123: string}") {
1406            Ok(Type::Shape(shape)) => {
1407                assert_eq!(shape.fields.len(), 1);
1408                let field = &shape.fields[0];
1409
1410                if let Some(key) = &field.key {
1411                    let span = key.key.span();
1412                    assert_eq!(span.end.offset - span.start.offset, 4, "Span should cover '-123' (4 characters)");
1413
1414                    assert!(matches!(&key.key, ShapeKey::Integer { value: -123, .. }));
1415                }
1416            }
1417            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1418        }
1419    }
1420
1421    #[test]
1422    fn test_shape_key_spans_complex_identifiers() {
1423        match do_parse("array{complex-key_name: string}") {
1424            Ok(Type::Shape(shape)) => {
1425                assert_eq!(shape.fields.len(), 1);
1426                let field = &shape.fields[0];
1427
1428                if let Some(key) = &field.key {
1429                    let span = key.key.span();
1430                    assert_eq!(
1431                        span.end.offset - span.start.offset,
1432                        16,
1433                        "Span should cover 'complex-key_name' (16 characters)"
1434                    );
1435
1436                    assert!(matches!(&key.key, ShapeKey::String { value: "complex-key_name", .. }));
1437                }
1438            }
1439            res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1440        }
1441    }
1442}