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