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