Skip to main content

mago_type_syntax/
lib.rs

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