Skip to main content

mago_type_syntax/
lib.rs

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