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_iterable() {
493 match do_parse("iterable<int, string>") {
494 Ok(Type::Iterable(i)) => {
495 let params = i.parameters.expect("Expected generic parameters");
496 assert_eq!(params.entries.len(), 2);
497 assert!(matches!(params.entries[0].inner, Type::Int(_)));
498 assert!(matches!(params.entries[1].inner, Type::String(_)));
499 }
500 res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
501 }
502
503 match do_parse("iterable<bool>") {
504 Ok(Type::Iterable(i)) => {
506 let params = i.parameters.expect("Expected generic parameters");
507 assert_eq!(params.entries.len(), 1);
508 assert!(matches!(params.entries[0].inner, Type::Bool(_)));
509 }
510 res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
511 }
512
513 match do_parse("iterable") {
514 Ok(Type::Iterable(i)) => {
515 assert!(i.parameters.is_none());
516 }
517 res => panic!("Expected Ok(Type::Iterable), got {res:?}"),
518 }
519 }
520
521 #[test]
522 fn test_parse_negated_int() {
523 let assert_negated_int = |input: &str, expected_value: u64| {
524 let result = do_parse(input);
525 assert!(result.is_ok());
526 match result.unwrap() {
527 Type::Negated(n) => {
528 assert!(matches!(n.number, LiteralIntOrFloatType::Int(_)));
529 if let LiteralIntOrFloatType::Int(lit) = n.number {
530 assert_eq!(lit.value, expected_value);
531 } else {
532 panic!()
533 }
534 }
535 _ => panic!("Expected Type::Negated"),
536 }
537 };
538
539 assert_negated_int("-0", 0);
540 assert_negated_int("-1", 1);
541 assert_negated_int(
542 "-
543 // This is a comment
544 123_345",
545 123_345,
546 );
547 assert_negated_int("-0b1", 1);
548 }
549
550 #[test]
551 fn test_parse_negated_float() {
552 let assert_negated_float = |input: &str, expected_value: f64| {
553 let result = do_parse(input);
554 assert!(result.is_ok());
555 match result.unwrap() {
556 Type::Negated(n) => {
557 assert!(matches!(n.number, LiteralIntOrFloatType::Float(_)));
558 if let LiteralIntOrFloatType::Float(lit) = n.number {
559 assert_eq!(lit.value, expected_value);
560 } else {
561 panic!()
562 }
563 }
564 _ => panic!("Expected Type::Negated"),
565 }
566 };
567
568 assert_negated_float("-0.0", 0.0);
569 assert_negated_float("-1.0", 1.0);
570 assert_negated_float("-0.1e1", 1.0);
571 assert_negated_float("-0.1e-1", 0.01);
572 }
573
574 #[test]
575 fn test_parse_negated_union() {
576 match do_parse("-1|-2.0|string") {
577 Ok(Type::Union(n)) => {
578 assert!(matches!(*n.left, Type::Negated(_)));
579 assert!(matches!(*n.right, Type::Union(_)));
580
581 if let Type::Negated(neg) = *n.left {
582 assert!(matches!(neg.number, LiteralIntOrFloatType::Int(_)));
583 if let LiteralIntOrFloatType::Int(lit) = neg.number {
584 assert_eq!(lit.value, 1);
585 } else {
586 panic!()
587 }
588 } else {
589 panic!("Expected left side to be Type::Negated");
590 }
591
592 if let Type::Union(inner_union) = *n.right {
593 assert!(matches!(*inner_union.left, Type::Negated(_)));
594 assert!(matches!(*inner_union.right, Type::String(_)));
595
596 if let Type::Negated(neg) = *inner_union.left {
597 assert!(matches!(neg.number, LiteralIntOrFloatType::Float(_)));
598 if let LiteralIntOrFloatType::Float(lit) = neg.number {
599 assert_eq!(lit.value, 2.0);
600 } else {
601 panic!()
602 }
603 } else {
604 panic!("Expected left side of inner union to be Type::Negated");
605 }
606
607 if let Type::String(s) = *inner_union.right {
608 assert_eq!(s.value, "string");
609 } else {
610 panic!("Expected right side of inner union to be Type::String");
611 }
612 } else {
613 panic!("Expected right side to be Type::Union");
614 }
615 }
616 res => panic!("Expected Ok(Type::Negated), got {res:?}"),
617 }
618 }
619
620 #[test]
621 fn test_parse_callable_no_spec() {
622 match do_parse("callable") {
623 Ok(Type::Callable(c)) => {
624 assert!(c.specification.is_none());
625 assert_eq!(c.kind, CallableTypeKind::Callable);
626 }
627 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
628 }
629 }
630
631 #[test]
632 fn test_parse_callable_params_only() {
633 match do_parse("callable(int, ?string)") {
634 Ok(Type::Callable(c)) => {
635 let spec = c.specification.expect("Expected callable specification");
636 assert!(spec.return_type.is_none());
637 assert_eq!(spec.parameters.entries.len(), 2);
638 assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Int(_))));
639 assert!(matches!(spec.parameters.entries[1].parameter_type, Some(Type::Nullable(_))));
640 assert!(spec.parameters.entries[0].ellipsis.is_none());
641 assert!(spec.parameters.entries[0].equals.is_none());
642 }
643 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
644 }
645 }
646
647 #[test]
648 fn test_parse_callable_return_only() {
649 match do_parse("callable(): void") {
650 Ok(Type::Callable(c)) => {
651 let spec = c.specification.expect("Expected callable specification");
652 assert!(spec.parameters.entries.is_empty());
653 assert!(spec.return_type.is_some());
654 assert!(matches!(*spec.return_type.unwrap().return_type, Type::Void(_)));
655 }
656 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
657 }
658 }
659
660 #[test]
661 fn test_parse_pure_callable_full() {
662 match do_parse("pure-callable(bool): int") {
663 Ok(Type::Callable(c)) => {
664 assert_eq!(c.kind, CallableTypeKind::PureCallable);
665 let spec = c.specification.expect("Expected callable specification");
666 assert_eq!(spec.parameters.entries.len(), 1);
667 assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::Bool(_))));
668 assert!(spec.return_type.is_some());
669 assert!(matches!(*spec.return_type.unwrap().return_type, Type::Int(_)));
670 }
671 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
672 }
673 }
674
675 #[test]
676 fn test_parse_closure_via_identifier() {
677 match do_parse("Closure(string): bool") {
678 Ok(Type::Callable(c)) => {
679 assert_eq!(c.kind, CallableTypeKind::Closure);
680 assert_eq!(c.keyword.value, "Closure");
681 let spec = c.specification.expect("Expected callable specification");
682 assert_eq!(spec.parameters.entries.len(), 1);
683 assert!(matches!(spec.parameters.entries[0].parameter_type, Some(Type::String(_))));
684 assert!(spec.return_type.is_some());
685 assert!(matches!(*spec.return_type.unwrap().return_type, Type::Bool(_)));
686 }
687 res => panic!("Expected Ok(Type::Callable) for Closure, got {res:?}"),
688 }
689 }
690
691 #[test]
692 fn test_parse_complex_pure_callable() {
693 match do_parse("pure-callable(list<int>, ?Closure(): void=, int...): ((Simple&Iter<T>)|null)") {
694 Ok(Type::Callable(c)) => {
695 assert_eq!(c.kind, CallableTypeKind::PureCallable);
696 let spec = c.specification.expect("Expected callable specification");
697 assert_eq!(spec.parameters.entries.len(), 3);
698 assert!(spec.return_type.is_some());
699
700 let first_param = &spec.parameters.entries[0];
701 assert!(matches!(first_param.parameter_type, Some(Type::List(_))));
702 assert!(first_param.ellipsis.is_none());
703 assert!(first_param.equals.is_none());
704
705 let second_param = &spec.parameters.entries[1];
706 assert!(matches!(second_param.parameter_type, Some(Type::Nullable(_))));
707 assert!(second_param.ellipsis.is_none());
708 assert!(second_param.equals.is_some());
709
710 let third_param = &spec.parameters.entries[2];
711 assert!(matches!(third_param.parameter_type, Some(Type::Int(_))));
712 assert!(third_param.ellipsis.is_some());
713 assert!(third_param.equals.is_none());
714
715 if let Type::Parenthesized(p) = *spec.return_type.unwrap().return_type {
716 assert!(matches!(*p.inner, Type::Union(_)));
717 if let Type::Union(u) = *p.inner {
718 assert!(matches!(u.left.as_ref(), Type::Parenthesized(_)));
719 assert!(matches!(u.right.as_ref(), Type::Null(_)));
720 }
721 } else {
722 panic!("Expected Type::CallableReturnType");
723 }
724 }
725 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
726 }
727 }
728
729 #[test]
730 fn test_parse_conditional_type() {
731 match do_parse("int is not string ? array : int") {
732 Ok(Type::Conditional(c)) => {
733 assert!(matches!(*c.subject, Type::Int(_)));
734 assert!(c.not.is_some());
735 assert!(matches!(*c.target, Type::String(_)));
736 assert!(matches!(*c.then, Type::Array(_)));
737 assert!(matches!(*c.otherwise, Type::Int(_)));
738 }
739 res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
740 }
741
742 match do_parse("$input is string ? array : int") {
743 Ok(Type::Conditional(c)) => {
744 assert!(matches!(*c.subject, Type::Variable(_)));
745 assert!(c.not.is_none());
746 assert!(matches!(*c.target, Type::String(_)));
747 assert!(matches!(*c.then, Type::Array(_)));
748 assert!(matches!(*c.otherwise, Type::Int(_)));
749 }
750 res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
751 }
752
753 match do_parse("int is string ? array : (int is not $bar ? string : $baz)") {
754 Ok(Type::Conditional(c)) => {
755 assert!(matches!(*c.subject, Type::Int(_)));
756 assert!(c.not.is_none());
757 assert!(matches!(*c.target, Type::String(_)));
758 assert!(matches!(*c.then, Type::Array(_)));
759
760 let Type::Parenthesized(p) = *c.otherwise else {
761 panic!("Expected Type::Parenthesized");
762 };
763
764 if let Type::Conditional(inner_conditional) = *p.inner {
765 assert!(matches!(*inner_conditional.subject, Type::Int(_)));
766 assert!(inner_conditional.not.is_some());
767 assert!(matches!(*inner_conditional.target, Type::Variable(_)));
768 assert!(matches!(*inner_conditional.then, Type::String(_)));
769 assert!(matches!(*inner_conditional.otherwise, Type::Variable(_)));
770 } else {
771 panic!("Expected Type::Conditional");
772 }
773 }
774 res => panic!("Expected Ok(Type::Conditional), got {res:?}"),
775 }
776 }
777
778 #[test]
779 fn test_keyof() {
780 match do_parse("key-of<MyArray>") {
781 Ok(Type::KeyOf(k)) => {
782 assert_eq!(k.keyword.value, "key-of");
783 match &k.parameter.entry.inner {
784 Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
785 _ => panic!("Expected Type::Reference"),
786 }
787 }
788 res => panic!("Expected Ok(Type::KeyOf), got {res:?}"),
789 }
790 }
791
792 #[test]
793 fn test_valueof() {
794 match do_parse("value-of<MyArray>") {
795 Ok(Type::ValueOf(v)) => {
796 assert_eq!(v.keyword.value, "value-of");
797 match &v.parameter.entry.inner {
798 Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
799 _ => panic!("Expected Type::Reference"),
800 }
801 }
802 res => panic!("Expected Ok(Type::ValueOf), got {res:?}"),
803 }
804 }
805
806 #[test]
807 fn test_indexed_access() {
808 match do_parse("MyArray[MyKey]") {
809 Ok(Type::IndexAccess(i)) => {
810 match *i.target {
811 Type::Reference(r) => assert_eq!(r.identifier.value, "MyArray"),
812 _ => panic!("Expected Type::Reference"),
813 }
814 match *i.index {
815 Type::Reference(r) => assert_eq!(r.identifier.value, "MyKey"),
816 _ => panic!("Expected Type::Reference"),
817 }
818 }
819 res => panic!("Expected Ok(Type::IndexAccess), got {res:?}"),
820 }
821 }
822
823 #[test]
824 fn test_slice_type() {
825 match do_parse("string[]") {
826 Ok(Type::Slice(s)) => {
827 assert!(matches!(*s.inner, Type::String(_)));
828 }
829 res => panic!("Expected Ok(Type::Slice), got {res:?}"),
830 }
831 }
832
833 #[test]
834 fn test_slice_of_slice_of_slice_type() {
835 match do_parse("string[][][]") {
836 Ok(Type::Slice(s)) => {
837 assert!(matches!(*s.inner, Type::Slice(_)));
838 if let Type::Slice(inner_slice) = *s.inner {
839 assert!(matches!(*inner_slice.inner, Type::Slice(_)));
840 if let Type::Slice(inner_inner_slice) = *inner_slice.inner {
841 assert!(matches!(*inner_inner_slice.inner, Type::String(_)));
842 } else {
843 panic!("Expected inner slice to be a Slice");
844 }
845 } else {
846 panic!("Expected outer slice to be a Slice");
847 }
848 }
849 res => panic!("Expected Ok(Type::Slice), got {res:?}"),
850 }
851 }
852
853 #[test]
854 fn test_int_range() {
855 match do_parse("int<0, 100>") {
856 Ok(Type::IntRange(r)) => {
857 assert_eq!(r.keyword.value, "int");
858
859 match r.min {
860 IntOrKeyword::Int(literal_int_type) => {
861 assert_eq!(literal_int_type.value, 0);
862 }
863 _ => {
864 panic!("Expected min to be a LiteralIntType, got `{}`", r.min)
865 }
866 }
867
868 match r.max {
869 IntOrKeyword::Int(literal_int_type) => {
870 assert_eq!(literal_int_type.value, 100);
871 }
872 _ => {
873 panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
874 }
875 }
876 }
877 res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
878 }
879
880 match do_parse("int<min, 0>") {
881 Ok(Type::IntRange(r)) => {
882 match r.min {
883 IntOrKeyword::Keyword(keyword) => {
884 assert_eq!(keyword.value, "min");
885 }
886 _ => {
887 panic!("Expected min to be a Keyword, got `{}`", r.min)
888 }
889 }
890
891 match r.max {
892 IntOrKeyword::Int(literal_int_type) => {
893 assert_eq!(literal_int_type.value, 0);
894 }
895 _ => {
896 panic!("Expected max to be a LiteralIntType, got `{}`", r.max)
897 }
898 }
899 }
900 res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
901 }
902
903 match do_parse("int<min, max>") {
904 Ok(Type::IntRange(r)) => {
905 match r.min {
906 IntOrKeyword::Keyword(keyword) => {
907 assert_eq!(keyword.value, "min");
908 }
909 _ => {
910 panic!("Expected min to be a Keyword, got `{}`", r.min)
911 }
912 }
913
914 match r.max {
915 IntOrKeyword::Keyword(keyword) => {
916 assert_eq!(keyword.value, "max");
917 }
918 _ => {
919 panic!("Expected max to be a Keyword, got `{}`", r.max)
920 }
921 }
922 }
923 res => panic!("Expected Ok(Type::IntRange), got {res:?}"),
924 }
925 }
926
927 #[test]
928 fn test_properties_of() {
929 match do_parse("properties-of<MyClass>") {
930 Ok(Type::PropertiesOf(p)) => {
931 assert_eq!(p.keyword.value, "properties-of");
932 assert_eq!(p.filter, PropertiesOfFilter::All);
933 match &p.parameter.entry.inner {
934 Type::Reference(r) => assert_eq!(r.identifier.value, "MyClass"),
935 _ => panic!(),
936 }
937 }
938 res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
939 }
940
941 match do_parse("protected-properties-of<T>") {
942 Ok(Type::PropertiesOf(p)) => {
943 assert_eq!(p.keyword.value, "protected-properties-of");
944 assert_eq!(p.filter, PropertiesOfFilter::Protected);
945 match &p.parameter.entry.inner {
946 Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
947 _ => panic!(),
948 }
949 }
950 res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
951 }
952
953 match do_parse("private-properties-of<T>") {
954 Ok(Type::PropertiesOf(p)) => {
955 assert_eq!(p.keyword.value, "private-properties-of");
956 assert_eq!(p.filter, PropertiesOfFilter::Private);
957 match &p.parameter.entry.inner {
958 Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
959 _ => panic!(),
960 }
961 }
962 res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
963 }
964
965 match do_parse("public-properties-of<T>") {
966 Ok(Type::PropertiesOf(p)) => {
967 assert_eq!(p.keyword.value, "public-properties-of");
968 assert_eq!(p.filter, PropertiesOfFilter::Public);
969 match &p.parameter.entry.inner {
970 Type::Reference(r) => assert_eq!(r.identifier.value, "T"),
971 _ => panic!(),
972 }
973 }
974 res => panic!("Expected Ok(Type::PropertiesOf), got {res:?}"),
975 }
976 }
977
978 #[test]
979 fn test_variable() {
980 match do_parse("$myVar") {
981 Ok(Type::Variable(v)) => {
982 assert_eq!(v.value, "$myVar");
983 }
984 res => panic!("Expected Ok(Type::Variable), got {res:?}"),
985 }
986 }
987
988 #[test]
989 fn test_nullable_intersection() {
990 match do_parse("Countable&?Traversable") {
992 Ok(Type::Intersection(i)) => {
993 assert!(matches!(*i.left, Type::Reference(r) if r.identifier.value == "Countable"));
994 assert!(matches!(*i.right, Type::Nullable(_)));
995 if let Type::Nullable(n) = *i.right {
996 assert!(matches!(*n.inner, Type::Reference(r) if r.identifier.value == "Traversable"));
997 } else {
998 panic!();
999 }
1000 }
1001 res => panic!("Expected Ok(Type::Intersection), got {res:?}"),
1002 }
1003 }
1004
1005 #[test]
1006 fn test_parenthesized_nullable() {
1007 match do_parse("?(Countable&Traversable)") {
1008 Ok(Type::Nullable(n)) => {
1009 assert!(matches!(*n.inner, Type::Parenthesized(_)));
1010 if let Type::Parenthesized(p) = *n.inner {
1011 assert!(matches!(*p.inner, Type::Intersection(_)));
1012 } else {
1013 panic!()
1014 }
1015 }
1016 res => panic!("Expected Ok(Type::Nullable), got {res:?}"),
1017 }
1018 }
1019
1020 #[test]
1021 fn test_positive_negative_int() {
1022 match do_parse("positive-int|negative-int") {
1023 Ok(Type::Union(u)) => {
1024 assert!(matches!(*u.left, Type::PositiveInt(_)));
1025 assert!(matches!(*u.right, Type::NegativeInt(_)));
1026 }
1027 res => panic!("Expected Ok(Type::Union), got {res:?}"),
1028 }
1029 }
1030
1031 #[test]
1032 fn test_parse_float_alias() {
1033 match do_parse("double") {
1034 Ok(Type::Float(f)) => {
1035 assert_eq!(f.value, "double");
1036 }
1037 res => panic!("Expected Ok(Type::Float), got {res:?}"),
1038 }
1039
1040 match do_parse("real") {
1041 Ok(Type::Float(f)) => {
1042 assert_eq!(f.value, "real");
1043 }
1044 res => panic!("Expected Ok(Type::Float), got {res:?}"),
1045 }
1046
1047 match do_parse("float") {
1048 Ok(Type::Float(f)) => {
1049 assert_eq!(f.value, "float");
1050 }
1051 res => panic!("Expected Ok(Type::Float), got {res:?}"),
1052 }
1053 }
1054
1055 #[test]
1056 fn test_parse_bool_alias() {
1057 match do_parse("boolean") {
1058 Ok(Type::Bool(b)) => {
1059 assert_eq!(b.value, "boolean");
1060 }
1061 res => panic!("Expected Ok(Type::Bool), got {res:?}"),
1062 }
1063
1064 match do_parse("bool") {
1065 Ok(Type::Bool(b)) => {
1066 assert_eq!(b.value, "bool");
1067 }
1068 res => panic!("Expected Ok(Type::Bool), got {res:?}"),
1069 }
1070 }
1071
1072 #[test]
1073 fn test_parse_integer_alias() {
1074 match do_parse("integer") {
1075 Ok(Type::Int(i)) => {
1076 assert_eq!(i.value, "integer");
1077 }
1078 res => panic!("Expected Ok(Type::Int), got {res:?}"),
1079 }
1080
1081 match do_parse("int") {
1082 Ok(Type::Int(i)) => {
1083 assert_eq!(i.value, "int");
1084 }
1085 res => panic!("Expected Ok(Type::Int), got {res:?}"),
1086 }
1087 }
1088
1089 #[test]
1090 fn test_parse_callable_with_variables() {
1091 match do_parse("callable(string ...$names)") {
1092 Ok(Type::Callable(callable)) => {
1093 assert_eq!(callable.keyword.value, "callable");
1094 assert!(callable.specification.is_some());
1095
1096 let specification = callable.specification.unwrap();
1097
1098 assert!(specification.return_type.is_none());
1099 assert_eq!(specification.parameters.entries.len(), 1);
1100
1101 let first_parameter = specification.parameters.entries.first().unwrap();
1102 assert!(first_parameter.variable.is_some());
1103 assert!(first_parameter.ellipsis.is_some());
1104
1105 let variable = first_parameter.variable.unwrap();
1106 assert_eq!(variable.value, "$names");
1107 }
1108 res => panic!("Expected Ok(Type::Callable), got {res:?}"),
1109 }
1110 }
1111
1112 #[test]
1113 fn test_parse_string_or_lowercase_string_union() {
1114 match do_parse("string|lowercase-string") {
1115 Ok(Type::Union(u)) => {
1116 assert!(matches!(*u.left, Type::String(_)));
1117 assert!(matches!(*u.right, Type::LowercaseString(_)));
1118 }
1119 res => panic!("Expected Ok(Type::Union), got {res:?}"),
1120 }
1121 }
1122
1123 #[test]
1124 fn test_parse_optional_literal_string_shape_field() {
1125 match do_parse("array{'salt'?: int, 'cost'?: int, ...}") {
1126 Ok(Type::Shape(shape)) => {
1127 assert_eq!(shape.fields.len(), 2);
1128 assert!(shape.additional_fields.is_some());
1129
1130 let first_field = &shape.fields[0];
1131 assert!(first_field.is_optional());
1132 assert!(matches!(
1133 first_field.key.as_ref().map(|k| &k.key),
1134 Some(ShapeKey::String { value: "salt", .. })
1135 ));
1136 assert!(matches!(first_field.value.as_ref(), Type::Int(_)));
1137
1138 let second_field = &shape.fields[1];
1139 assert!(second_field.is_optional());
1140 assert!(matches!(
1141 second_field.key.as_ref().map(|k| &k.key),
1142 Some(ShapeKey::String { value: "cost", .. })
1143 ));
1144 assert!(matches!(second_field.value.as_ref(), Type::Int(_)));
1145 }
1146 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1147 }
1148 }
1149
1150 #[test]
1151 fn test_parse_keyword_keys() {
1152 match do_parse("array{string: int, bool: string, int: float, mixed: object}") {
1153 Ok(Type::Shape(shape)) => {
1154 assert_eq!(shape.fields.len(), 4);
1155
1156 assert!(matches!(
1157 shape.fields[0].key.as_ref().map(|k| &k.key),
1158 Some(ShapeKey::String { value: "string", .. })
1159 ));
1160 assert!(matches!(
1161 shape.fields[1].key.as_ref().map(|k| &k.key),
1162 Some(ShapeKey::String { value: "bool", .. })
1163 ));
1164 assert!(matches!(
1165 shape.fields[2].key.as_ref().map(|k| &k.key),
1166 Some(ShapeKey::String { value: "int", .. })
1167 ));
1168 assert!(matches!(
1169 shape.fields[3].key.as_ref().map(|k| &k.key),
1170 Some(ShapeKey::String { value: "mixed", .. })
1171 ));
1172 }
1173 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1174 }
1175 }
1176
1177 #[test]
1178 fn test_parse_negated_integer_keys() {
1179 match do_parse("array{-1: string, -42: int, +5: bool}") {
1180 Ok(Type::Shape(shape)) => {
1181 assert_eq!(shape.fields.len(), 3);
1182
1183 assert!(matches!(
1184 shape.fields[0].key.as_ref().map(|k| &k.key),
1185 Some(ShapeKey::Integer { value: -1, .. })
1186 ));
1187 assert!(matches!(
1188 shape.fields[1].key.as_ref().map(|k| &k.key),
1189 Some(ShapeKey::Integer { value: -42, .. })
1190 ));
1191 assert!(matches!(
1192 shape.fields[2].key.as_ref().map(|k| &k.key),
1193 Some(ShapeKey::Integer { value: 5, .. })
1194 ));
1195 }
1196 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1197 }
1198 }
1199
1200 #[test]
1201 fn test_parse_float_keys() {
1202 match do_parse("array{123.4: string, -1.2: int, +0.5: bool}") {
1203 Ok(Type::Shape(shape)) => {
1204 assert_eq!(shape.fields.len(), 3);
1205
1206 assert!(matches!(
1207 shape.fields[0].key.as_ref().map(|k| &k.key),
1208 Some(ShapeKey::String { value: "123.4", .. })
1209 ));
1210 assert!(matches!(
1211 shape.fields[1].key.as_ref().map(|k| &k.key),
1212 Some(ShapeKey::String { value: "-1.2", .. })
1213 ));
1214 assert!(matches!(
1215 shape.fields[2].key.as_ref().map(|k| &k.key),
1216 Some(ShapeKey::String { value: "+0.5", .. })
1217 ));
1218 }
1219 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1220 }
1221 }
1222
1223 #[test]
1224 fn test_parse_complex_identifier_keys() {
1225 match do_parse(
1226 "array{key_with_underscore: int, key-with-dash: string, key\\with\\backslash: bool, +key: mixed, -key: object, \\leading_backslash: int}",
1227 ) {
1228 Ok(Type::Shape(shape)) => {
1229 assert_eq!(shape.fields.len(), 6);
1230
1231 assert!(matches!(
1232 shape.fields[0].key.as_ref().map(|k| &k.key),
1233 Some(ShapeKey::String { value: "key_with_underscore", .. })
1234 ));
1235 assert!(matches!(
1236 shape.fields[1].key.as_ref().map(|k| &k.key),
1237 Some(ShapeKey::String { value: "key-with-dash", .. })
1238 ));
1239 assert!(matches!(
1240 shape.fields[2].key.as_ref().map(|k| &k.key),
1241 Some(ShapeKey::String { value: "key\\with\\backslash", .. })
1242 ));
1243 assert!(matches!(
1244 shape.fields[3].key.as_ref().map(|k| &k.key),
1245 Some(ShapeKey::String { value: "+key", .. })
1246 ));
1247 assert!(matches!(
1248 shape.fields[4].key.as_ref().map(|k| &k.key),
1249 Some(ShapeKey::String { value: "-key", .. })
1250 ));
1251 assert!(matches!(
1252 shape.fields[5].key.as_ref().map(|k| &k.key),
1253 Some(ShapeKey::String { value: "\\leading_backslash", .. })
1254 ));
1255 }
1256 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1257 }
1258 }
1259
1260 #[test]
1261 fn test_parse_optional_keys_with_question_mark_in_name() {
1262 match do_parse("array{key?name: int, regular?: string}") {
1263 Ok(Type::Shape(shape)) => {
1264 assert_eq!(shape.fields.len(), 2);
1265
1266 assert!(!shape.fields[0].is_optional());
1267 assert!(matches!(
1268 shape.fields[0].key.as_ref().map(|k| &k.key),
1269 Some(ShapeKey::String { value: "key?name", .. })
1270 ));
1271
1272 assert!(shape.fields[1].is_optional());
1273 assert!(matches!(
1274 shape.fields[1].key.as_ref().map(|k| &k.key),
1275 Some(ShapeKey::String { value: "regular", .. })
1276 ));
1277 }
1278 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1279 }
1280 }
1281
1282 #[test]
1283 fn test_parse_integer_formats() {
1284 match do_parse("array{42: string, 0x2A: int, 0b101010: bool, 0o52: mixed}") {
1285 Ok(Type::Shape(shape)) => {
1286 assert_eq!(shape.fields.len(), 4);
1287
1288 assert!(matches!(
1289 shape.fields[0].key.as_ref().map(|k| &k.key),
1290 Some(ShapeKey::Integer { value: 42, .. })
1291 ));
1292 assert!(matches!(
1293 shape.fields[1].key.as_ref().map(|k| &k.key),
1294 Some(ShapeKey::Integer { value: 42, .. })
1295 ));
1296 assert!(matches!(
1297 shape.fields[2].key.as_ref().map(|k| &k.key),
1298 Some(ShapeKey::Integer { value: 42, .. })
1299 ));
1300 assert!(matches!(
1301 shape.fields[3].key.as_ref().map(|k| &k.key),
1302 Some(ShapeKey::Integer { value: 42, .. })
1303 ));
1304 }
1305 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1306 }
1307 }
1308
1309 #[test]
1310 fn test_parse_quoted_vs_unquoted_keys() {
1311 match do_parse("array{'string': int, \"double\": bool, unquoted: mixed}") {
1312 Ok(Type::Shape(shape)) => {
1313 assert_eq!(shape.fields.len(), 3);
1314
1315 assert!(matches!(
1316 shape.fields[0].key.as_ref().map(|k| &k.key),
1317 Some(ShapeKey::String { value: "string", .. })
1318 ));
1319 assert!(matches!(
1320 shape.fields[1].key.as_ref().map(|k| &k.key),
1321 Some(ShapeKey::String { value: "double", .. })
1322 ));
1323 assert!(matches!(
1324 shape.fields[2].key.as_ref().map(|k| &k.key),
1325 Some(ShapeKey::String { value: "unquoted", .. })
1326 ));
1327 }
1328 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1329 }
1330 }
1331
1332 #[test]
1333 fn test_parse_all_keyword_types() {
1334 let keywords = vec![
1335 "list", "int", "integer", "string", "float", "double", "real", "bool", "boolean", "false", "true",
1336 "object", "callable", "array", "iterable", "null", "mixed", "resource", "void", "scalar", "numeric",
1337 "never", "nothing", "as", "is", "not", "min", "max",
1338 ];
1339
1340 for keyword in keywords {
1341 let input = format!("array{{{keyword}: string}}");
1342 match do_parse(&input) {
1343 Ok(Type::Shape(shape)) => {
1344 assert_eq!(shape.fields.len(), 1);
1345 assert!(
1346 matches!(
1347 shape.fields[0].key.as_ref().map(|k| &k.key),
1348 Some(ShapeKey::String { value, .. }) if *value == keyword
1349 ),
1350 "Failed for keyword: {keyword}"
1351 );
1352 }
1353 res => panic!("Expected Ok(Type::Shape) for keyword '{keyword}', got {res:?}"),
1354 }
1355 }
1356 }
1357
1358 #[test]
1359 fn test_parse_php_specific_keywords() {
1360 match do_parse("array{self: string, static: int, parent: bool, class: mixed, __CLASS__: object}") {
1361 Ok(Type::Shape(shape)) => {
1362 assert_eq!(shape.fields.len(), 5);
1363
1364 assert!(matches!(
1365 shape.fields[0].key.as_ref().map(|k| &k.key),
1366 Some(ShapeKey::String { value: "self", .. })
1367 ));
1368 assert!(matches!(
1369 shape.fields[1].key.as_ref().map(|k| &k.key),
1370 Some(ShapeKey::String { value: "static", .. })
1371 ));
1372 assert!(matches!(
1373 shape.fields[2].key.as_ref().map(|k| &k.key),
1374 Some(ShapeKey::String { value: "parent", .. })
1375 ));
1376 assert!(matches!(
1377 shape.fields[3].key.as_ref().map(|k| &k.key),
1378 Some(ShapeKey::String { value: "class", .. })
1379 ));
1380 assert!(matches!(
1381 shape.fields[4].key.as_ref().map(|k| &k.key),
1382 Some(ShapeKey::String { value: "__CLASS__", .. })
1383 ));
1384 }
1385 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1386 }
1387 }
1388
1389 #[test]
1390 fn test_shape_key_spans() {
1391 match do_parse("array{test: string}") {
1392 Ok(Type::Shape(shape)) => {
1393 assert_eq!(shape.fields.len(), 1);
1394 let field = &shape.fields[0];
1395
1396 if let Some(key) = &field.key {
1397 let span = key.key.span();
1398 assert!(span.start.offset < span.end.offset, "Span should have valid start/end");
1399
1400 assert_eq!(span.end.offset - span.start.offset, 4, "Span should cover 'test' (4 characters)");
1401 }
1402 }
1403 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1404 }
1405 }
1406
1407 #[test]
1408 fn test_shape_key_spans_quoted() {
1409 match do_parse("array{'hello': string}") {
1410 Ok(Type::Shape(shape)) => {
1411 assert_eq!(shape.fields.len(), 1);
1412 let field = &shape.fields[0];
1413
1414 if let Some(key) = &field.key {
1415 let span = key.key.span();
1416 assert_eq!(span.end.offset - span.start.offset, 7, "Span should cover 'hello' including quotes");
1417
1418 assert!(matches!(&key.key, ShapeKey::String { value: "hello", .. }));
1419 }
1420 }
1421 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1422 }
1423 }
1424
1425 #[test]
1426 fn test_shape_key_spans_integer() {
1427 match do_parse("array{42: string}") {
1428 Ok(Type::Shape(shape)) => {
1429 assert_eq!(shape.fields.len(), 1);
1430 let field = &shape.fields[0];
1431
1432 if let Some(key) = &field.key {
1433 let span = key.key.span();
1434 assert_eq!(span.end.offset - span.start.offset, 2, "Span should cover '42' (2 characters)");
1435
1436 assert!(matches!(&key.key, ShapeKey::Integer { value: 42, .. }));
1437 }
1438 }
1439 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1440 }
1441 }
1442
1443 #[test]
1444 fn test_shape_key_spans_negated_integer() {
1445 match do_parse("array{-123: string}") {
1446 Ok(Type::Shape(shape)) => {
1447 assert_eq!(shape.fields.len(), 1);
1448 let field = &shape.fields[0];
1449
1450 if let Some(key) = &field.key {
1451 let span = key.key.span();
1452 assert_eq!(span.end.offset - span.start.offset, 4, "Span should cover '-123' (4 characters)");
1453
1454 assert!(matches!(&key.key, ShapeKey::Integer { value: -123, .. }));
1455 }
1456 }
1457 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1458 }
1459 }
1460
1461 #[test]
1462 fn test_shape_key_spans_complex_identifiers() {
1463 match do_parse("array{complex-key_name: string}") {
1464 Ok(Type::Shape(shape)) => {
1465 assert_eq!(shape.fields.len(), 1);
1466 let field = &shape.fields[0];
1467
1468 if let Some(key) = &field.key {
1469 let span = key.key.span();
1470 assert_eq!(
1471 span.end.offset - span.start.offset,
1472 16,
1473 "Span should cover 'complex-key_name' (16 characters)"
1474 );
1475
1476 assert!(matches!(&key.key, ShapeKey::String { value: "complex-key_name", .. }));
1477 }
1478 }
1479 res => panic!("Expected Ok(Type::Shape), got {res:?}"),
1480 }
1481 }
1482}