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