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