aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_ty')
-rw-r--r--crates/hir_ty/src/diagnostics/match_check.rs248
-rw-r--r--crates/hir_ty/src/infer/pat.rs46
-rw-r--r--crates/hir_ty/src/tests/patterns.rs95
3 files changed, 304 insertions, 85 deletions
diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs
index 5bd03f2ac..62c329731 100644
--- a/crates/hir_ty/src/diagnostics/match_check.rs
+++ b/crates/hir_ty/src/diagnostics/match_check.rs
@@ -216,14 +216,14 @@
216//! U(P, p) := U(P, (r_1, p_2, .., p_n)) 216//! U(P, p) := U(P, (r_1, p_2, .., p_n))
217//! || U(P, (r_2, p_2, .., p_n)) 217//! || U(P, (r_2, p_2, .., p_n))
218//! ``` 218//! ```
219use std::sync::Arc; 219use std::{iter, sync::Arc};
220 220
221use arena::Idx; 221use arena::Idx;
222use hir_def::{ 222use hir_def::{
223 adt::VariantData, 223 adt::VariantData,
224 body::Body, 224 body::Body,
225 expr::{Expr, Literal, Pat, PatId}, 225 expr::{Expr, Literal, Pat, PatId},
226 AdtId, EnumVariantId, VariantId, 226 AdtId, EnumVariantId, StructId, VariantId,
227}; 227};
228use smallvec::{smallvec, SmallVec}; 228use smallvec::{smallvec, SmallVec};
229 229
@@ -366,16 +366,17 @@ impl PatStack {
366 366
367 let head_pat = head.as_pat(cx); 367 let head_pat = head.as_pat(cx);
368 let result = match (head_pat, constructor) { 368 let result = match (head_pat, constructor) {
369 (Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => { 369 (Pat::Tuple { args: pat_ids, ellipsis }, &Constructor::Tuple { arity }) => {
370 if ellipsis.is_some() { 370 if let Some(ellipsis) = ellipsis {
371 // If there are ellipsis here, we should add the correct number of 371 let (pre, post) = pat_ids.split_at(ellipsis);
372 // Pat::Wild patterns to `pat_ids`. We should be able to use the 372 let n_wild_pats = arity.saturating_sub(pat_ids.len());
373 // constructors arity for this, but at the time of writing we aren't 373 let pre_iter = pre.iter().map(Into::into);
374 // correctly calculating this arity when ellipsis are present. 374 let wildcards = iter::repeat(PatIdOrWild::Wild).take(n_wild_pats);
375 return Err(MatchCheckErr::NotImplemented); 375 let post_iter = post.iter().map(Into::into);
376 Some(self.replace_head_with(pre_iter.chain(wildcards).chain(post_iter)))
377 } else {
378 Some(self.replace_head_with(pat_ids.iter()))
376 } 379 }
377
378 Some(self.replace_head_with(pat_ids.iter()))
379 } 380 }
380 (Pat::Lit(lit_expr), Constructor::Bool(constructor_val)) => { 381 (Pat::Lit(lit_expr), Constructor::Bool(constructor_val)) => {
381 match cx.body.exprs[lit_expr] { 382 match cx.body.exprs[lit_expr] {
@@ -390,21 +391,28 @@ impl PatStack {
390 } 391 }
391 } 392 }
392 (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?), 393 (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?),
393 (Pat::Path(_), Constructor::Enum(constructor)) => { 394 (Pat::Path(_), constructor) => {
394 // unit enum variants become `Pat::Path` 395 // unit enum variants become `Pat::Path`
395 let pat_id = head.as_id().expect("we know this isn't a wild"); 396 let pat_id = head.as_id().expect("we know this isn't a wild");
396 if !enum_variant_matches(cx, pat_id, *constructor) { 397 let variant_id: VariantId = match constructor {
398 &Constructor::Enum(e) => e.into(),
399 &Constructor::Struct(s) => s.into(),
400 _ => return Err(MatchCheckErr::NotImplemented),
401 };
402 if Some(variant_id) != cx.infer.variant_resolution_for_pat(pat_id) {
397 None 403 None
398 } else { 404 } else {
399 Some(self.to_tail()) 405 Some(self.to_tail())
400 } 406 }
401 } 407 }
402 ( 408 (Pat::TupleStruct { args: ref pat_ids, ellipsis, .. }, constructor) => {
403 Pat::TupleStruct { args: ref pat_ids, ellipsis, .. },
404 Constructor::Enum(enum_constructor),
405 ) => {
406 let pat_id = head.as_id().expect("we know this isn't a wild"); 409 let pat_id = head.as_id().expect("we know this isn't a wild");
407 if !enum_variant_matches(cx, pat_id, *enum_constructor) { 410 let variant_id: VariantId = match constructor {
411 &Constructor::Enum(e) => e.into(),
412 &Constructor::Struct(s) => s.into(),
413 _ => return Err(MatchCheckErr::MalformedMatchArm),
414 };
415 if Some(variant_id) != cx.infer.variant_resolution_for_pat(pat_id) {
408 None 416 None
409 } else { 417 } else {
410 let constructor_arity = constructor.arity(cx)?; 418 let constructor_arity = constructor.arity(cx)?;
@@ -442,12 +450,22 @@ impl PatStack {
442 } 450 }
443 } 451 }
444 } 452 }
445 (Pat::Record { args: ref arg_patterns, .. }, Constructor::Enum(e)) => { 453 (Pat::Record { args: ref arg_patterns, .. }, constructor) => {
446 let pat_id = head.as_id().expect("we know this isn't a wild"); 454 let pat_id = head.as_id().expect("we know this isn't a wild");
447 if !enum_variant_matches(cx, pat_id, *e) { 455 let (variant_id, variant_data) = match constructor {
456 &Constructor::Enum(e) => (
457 e.into(),
458 cx.db.enum_data(e.parent).variants[e.local_id].variant_data.clone(),
459 ),
460 &Constructor::Struct(s) => {
461 (s.into(), cx.db.struct_data(s).variant_data.clone())
462 }
463 _ => return Err(MatchCheckErr::MalformedMatchArm),
464 };
465 if Some(variant_id) != cx.infer.variant_resolution_for_pat(pat_id) {
448 None 466 None
449 } else { 467 } else {
450 match cx.db.enum_data(e.parent).variants[e.local_id].variant_data.as_ref() { 468 match variant_data.as_ref() {
451 VariantData::Record(struct_field_arena) => { 469 VariantData::Record(struct_field_arena) => {
452 // Here we treat any missing fields in the record as the wild pattern, as 470 // Here we treat any missing fields in the record as the wild pattern, as
453 // if the record has ellipsis. We want to do this here even if the 471 // if the record has ellipsis. We want to do this here even if the
@@ -726,6 +744,7 @@ enum Constructor {
726 Bool(bool), 744 Bool(bool),
727 Tuple { arity: usize }, 745 Tuple { arity: usize },
728 Enum(EnumVariantId), 746 Enum(EnumVariantId),
747 Struct(StructId),
729} 748}
730 749
731impl Constructor { 750impl Constructor {
@@ -740,6 +759,11 @@ impl Constructor {
740 VariantData::Unit => 0, 759 VariantData::Unit => 0,
741 } 760 }
742 } 761 }
762 &Constructor::Struct(s) => match cx.db.struct_data(s).variant_data.as_ref() {
763 VariantData::Tuple(struct_field_data) => struct_field_data.len(),
764 VariantData::Record(struct_field_data) => struct_field_data.len(),
765 VariantData::Unit => 0,
766 },
743 }; 767 };
744 768
745 Ok(arity) 769 Ok(arity)
@@ -748,7 +772,7 @@ impl Constructor {
748 fn all_constructors(&self, cx: &MatchCheckCtx) -> Vec<Constructor> { 772 fn all_constructors(&self, cx: &MatchCheckCtx) -> Vec<Constructor> {
749 match self { 773 match self {
750 Constructor::Bool(_) => vec![Constructor::Bool(true), Constructor::Bool(false)], 774 Constructor::Bool(_) => vec![Constructor::Bool(true), Constructor::Bool(false)],
751 Constructor::Tuple { .. } => vec![*self], 775 Constructor::Tuple { .. } | Constructor::Struct(_) => vec![*self],
752 Constructor::Enum(e) => cx 776 Constructor::Enum(e) => cx
753 .db 777 .db
754 .enum_data(e.parent) 778 .enum_data(e.parent)
@@ -767,10 +791,11 @@ impl Constructor {
767fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Option<Constructor>> { 791fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Option<Constructor>> {
768 let res = match pat.as_pat(cx) { 792 let res = match pat.as_pat(cx) {
769 Pat::Wild => None, 793 Pat::Wild => None,
770 // FIXME somehow create the Tuple constructor with the proper arity. If there are 794 Pat::Tuple { .. } => {
771 // ellipsis, the arity is not equal to the number of patterns. 795 let pat_id = pat.as_id().expect("we already know this pattern is not a wild");
772 Pat::Tuple { args: pats, ellipsis } if ellipsis.is_none() => { 796 Some(Constructor::Tuple {
773 Some(Constructor::Tuple { arity: pats.len() }) 797 arity: cx.infer.type_of_pat[pat_id].as_tuple().ok_or(MatchCheckErr::Unknown)?.len(),
798 })
774 } 799 }
775 Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] { 800 Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] {
776 Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)), 801 Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)),
@@ -784,6 +809,7 @@ fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Opt
784 VariantId::EnumVariantId(enum_variant_id) => { 809 VariantId::EnumVariantId(enum_variant_id) => {
785 Some(Constructor::Enum(enum_variant_id)) 810 Some(Constructor::Enum(enum_variant_id))
786 } 811 }
812 VariantId::StructId(struct_id) => Some(Constructor::Struct(struct_id)),
787 _ => return Err(MatchCheckErr::NotImplemented), 813 _ => return Err(MatchCheckErr::NotImplemented),
788 } 814 }
789 } 815 }
@@ -828,13 +854,13 @@ fn all_constructors_covered(
828 854
829 false 855 false
830 }), 856 }),
857 &Constructor::Struct(s) => used_constructors.iter().any(|constructor| match constructor {
858 &Constructor::Struct(sid) => sid == s,
859 _ => false,
860 }),
831 } 861 }
832} 862}
833 863
834fn enum_variant_matches(cx: &MatchCheckCtx, pat_id: PatId, enum_variant_id: EnumVariantId) -> bool {
835 Some(enum_variant_id.into()) == cx.infer.variant_resolution_for_pat(pat_id)
836}
837
838#[cfg(test)] 864#[cfg(test)]
839mod tests { 865mod tests {
840 use crate::diagnostics::tests::check_diagnostics; 866 use crate::diagnostics::tests::check_diagnostics;
@@ -846,8 +872,8 @@ mod tests {
846fn main() { 872fn main() {
847 match () { } 873 match () { }
848 //^^ Missing match arm 874 //^^ Missing match arm
849 match (()) { } 875 match (()) { }
850 //^^^^ Missing match arm 876 //^^^^ Missing match arm
851 877
852 match () { _ => (), } 878 match () { _ => (), }
853 match () { () => (), } 879 match () { () => (), }
@@ -1352,6 +1378,123 @@ fn main() {
1352 ); 1378 );
1353 } 1379 }
1354 1380
1381 #[test]
1382 fn tuple_of_bools_with_ellipsis_at_end_missing_arm() {
1383 check_diagnostics(
1384 r#"
1385fn main() {
1386 match (false, true, false) {
1387 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1388 (false, ..) => (),
1389 }
1390}"#,
1391 );
1392 }
1393
1394 #[test]
1395 fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() {
1396 check_diagnostics(
1397 r#"
1398fn main() {
1399 match (false, true, false) {
1400 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1401 (.., false) => (),
1402 }
1403}"#,
1404 );
1405 }
1406
1407 #[test]
1408 fn tuple_of_bools_with_ellipsis_in_middle_missing_arm() {
1409 check_diagnostics(
1410 r#"
1411fn main() {
1412 match (false, true, false) {
1413 //^^^^^^^^^^^^^^^^^^^^ Missing match arm
1414 (true, .., false) => (),
1415 }
1416}"#,
1417 );
1418 }
1419
1420 #[test]
1421 fn record_struct() {
1422 check_diagnostics(
1423 r#"struct Foo { a: bool }
1424fn main(f: Foo) {
1425 match f {}
1426 //^ Missing match arm
1427 match f { Foo { a: true } => () }
1428 //^ Missing match arm
1429 match &f { Foo { a: true } => () }
1430 //^^ Missing match arm
1431 match f { Foo { a: _ } => () }
1432 match f {
1433 Foo { a: true } => (),
1434 Foo { a: false } => (),
1435 }
1436 match &f {
1437 Foo { a: true } => (),
1438 Foo { a: false } => (),
1439 }
1440}
1441"#,
1442 );
1443 }
1444
1445 #[test]
1446 fn tuple_struct() {
1447 check_diagnostics(
1448 r#"struct Foo(bool);
1449fn main(f: Foo) {
1450 match f {}
1451 //^ Missing match arm
1452 match f { Foo(true) => () }
1453 //^ Missing match arm
1454 match f {
1455 Foo(true) => (),
1456 Foo(false) => (),
1457 }
1458}
1459"#,
1460 );
1461 }
1462
1463 #[test]
1464 fn unit_struct() {
1465 check_diagnostics(
1466 r#"struct Foo;
1467fn main(f: Foo) {
1468 match f {}
1469 //^ Missing match arm
1470 match f { Foo => () }
1471}
1472"#,
1473 );
1474 }
1475
1476 #[test]
1477 fn record_struct_ellipsis() {
1478 check_diagnostics(
1479 r#"struct Foo { foo: bool, bar: bool }
1480fn main(f: Foo) {
1481 match f { Foo { foo: true, .. } => () }
1482 //^ Missing match arm
1483 match f {
1484 //^ Missing match arm
1485 Foo { foo: true, .. } => (),
1486 Foo { bar: false, .. } => ()
1487 }
1488 match f { Foo { .. } => () }
1489 match f {
1490 Foo { foo: true, .. } => (),
1491 Foo { foo: false, .. } => ()
1492 }
1493}
1494"#,
1495 );
1496 }
1497
1355 mod false_negatives { 1498 mod false_negatives {
1356 //! The implementation of match checking here is a work in progress. As we roll this out, we 1499 //! The implementation of match checking here is a work in progress. As we roll this out, we
1357 //! prefer false negatives to false positives (ideally there would be no false positives). This 1500 //! prefer false negatives to false positives (ideally there would be no false positives). This
@@ -1393,46 +1536,5 @@ fn main() {
1393"#, 1536"#,
1394 ); 1537 );
1395 } 1538 }
1396
1397 #[test]
1398 fn tuple_of_bools_with_ellipsis_at_end_missing_arm() {
1399 // We don't currently handle tuple patterns with ellipsis.
1400 check_diagnostics(
1401 r#"
1402fn main() {
1403 match (false, true, false) {
1404 (false, ..) => (),
1405 }
1406}
1407"#,
1408 );
1409 }
1410
1411 #[test]
1412 fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() {
1413 // We don't currently handle tuple patterns with ellipsis.
1414 check_diagnostics(
1415 r#"
1416fn main() {
1417 match (false, true, false) {
1418 (.., false) => (),
1419 }
1420}
1421"#,
1422 );
1423 }
1424
1425 #[test]
1426 fn struct_missing_arm() {
1427 // We don't currently handle structs.
1428 check_diagnostics(
1429 r#"
1430struct Foo { a: bool }
1431fn main(f: Foo) {
1432 match f { Foo { a: true } => () }
1433}
1434"#,
1435 );
1436 }
1437 } 1539 }
1438} 1540}
diff --git a/crates/hir_ty/src/infer/pat.rs b/crates/hir_ty/src/infer/pat.rs
index cde2ab82b..b70ec55eb 100644
--- a/crates/hir_ty/src/infer/pat.rs
+++ b/crates/hir_ty/src/infer/pat.rs
@@ -23,6 +23,7 @@ impl<'a> InferenceContext<'a> {
23 expected: &Ty, 23 expected: &Ty,
24 default_bm: BindingMode, 24 default_bm: BindingMode,
25 id: PatId, 25 id: PatId,
26 ellipsis: Option<usize>,
26 ) -> Ty { 27 ) -> Ty {
27 let (ty, def) = self.resolve_variant(path); 28 let (ty, def) = self.resolve_variant(path);
28 let var_data = def.map(|it| variant_data(self.db.upcast(), it)); 29 let var_data = def.map(|it| variant_data(self.db.upcast(), it));
@@ -34,8 +35,15 @@ impl<'a> InferenceContext<'a> {
34 let substs = ty.substs().unwrap_or_else(Substs::empty); 35 let substs = ty.substs().unwrap_or_else(Substs::empty);
35 36
36 let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default(); 37 let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default();
38 let (pre, post) = match ellipsis {
39 Some(idx) => subpats.split_at(idx),
40 None => (&subpats[..], &[][..]),
41 };
42 let post_idx_offset = field_tys.iter().count() - post.len();
37 43
38 for (i, &subpat) in subpats.iter().enumerate() { 44 let pre_iter = pre.iter().enumerate();
45 let post_iter = (post_idx_offset..).zip(post.iter());
46 for (i, &subpat) in pre_iter.chain(post_iter) {
39 let expected_ty = var_data 47 let expected_ty = var_data
40 .as_ref() 48 .as_ref()
41 .and_then(|d| d.field(&Name::new_tuple_field(i))) 49 .and_then(|d| d.field(&Name::new_tuple_field(i)))
@@ -111,20 +119,29 @@ impl<'a> InferenceContext<'a> {
111 let expected = expected; 119 let expected = expected;
112 120
113 let ty = match &body[pat] { 121 let ty = match &body[pat] {
114 Pat::Tuple { ref args, .. } => { 122 &Pat::Tuple { ref args, ellipsis } => {
115 let expectations = match expected.as_tuple() { 123 let expectations = match expected.as_tuple() {
116 Some(parameters) => &*parameters.0, 124 Some(parameters) => &*parameters.0,
117 _ => &[], 125 _ => &[],
118 }; 126 };
119 let expectations_iter = expectations.iter().chain(repeat(&Ty::Unknown));
120 127
121 let inner_tys = args 128 let (pre, post) = match ellipsis {
122 .iter() 129 Some(idx) => args.split_at(idx),
123 .zip(expectations_iter) 130 None => (&args[..], &[][..]),
124 .map(|(&pat, ty)| self.infer_pat(pat, ty, default_bm)) 131 };
125 .collect(); 132 let n_uncovered_patterns = expectations.len().saturating_sub(args.len());
133 let mut expectations_iter = expectations.iter().chain(repeat(&Ty::Unknown));
134 let mut infer_pat = |(&pat, ty)| self.infer_pat(pat, ty, default_bm);
135
136 let mut inner_tys = Vec::with_capacity(n_uncovered_patterns + args.len());
137 inner_tys.extend(pre.iter().zip(expectations_iter.by_ref()).map(&mut infer_pat));
138 inner_tys.extend(expectations_iter.by_ref().take(n_uncovered_patterns).cloned());
139 inner_tys.extend(post.iter().zip(expectations_iter).map(infer_pat));
126 140
127 Ty::apply(TypeCtor::Tuple { cardinality: args.len() as u16 }, Substs(inner_tys)) 141 Ty::apply(
142 TypeCtor::Tuple { cardinality: inner_tys.len() as u16 },
143 Substs(inner_tys.into()),
144 )
128 } 145 }
129 Pat::Or(ref pats) => { 146 Pat::Or(ref pats) => {
130 if let Some((first_pat, rest)) = pats.split_first() { 147 if let Some((first_pat, rest)) = pats.split_first() {
@@ -150,9 +167,14 @@ impl<'a> InferenceContext<'a> {
150 let subty = self.infer_pat(*pat, expectation, default_bm); 167 let subty = self.infer_pat(*pat, expectation, default_bm);
151 Ty::apply_one(TypeCtor::Ref(*mutability), subty) 168 Ty::apply_one(TypeCtor::Ref(*mutability), subty)
152 } 169 }
153 Pat::TupleStruct { path: p, args: subpats, .. } => { 170 Pat::TupleStruct { path: p, args: subpats, ellipsis } => self.infer_tuple_struct_pat(
154 self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat) 171 p.as_ref(),
155 } 172 subpats,
173 expected,
174 default_bm,
175 pat,
176 *ellipsis,
177 ),
156 Pat::Record { path: p, args: fields, ellipsis: _ } => { 178 Pat::Record { path: p, args: fields, ellipsis: _ } => {
157 self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat) 179 self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat)
158 } 180 }
diff --git a/crates/hir_ty/src/tests/patterns.rs b/crates/hir_ty/src/tests/patterns.rs
index 6a965ac4f..5a5f48fd0 100644
--- a/crates/hir_ty/src/tests/patterns.rs
+++ b/crates/hir_ty/src/tests/patterns.rs
@@ -679,3 +679,98 @@ fn box_pattern() {
679 "#]], 679 "#]],
680 ); 680 );
681} 681}
682
683#[test]
684fn tuple_ellipsis_pattern() {
685 check_infer(
686 r#"
687fn foo(tuple: (u8, i16, f32)) {
688 match tuple {
689 (.., b, c) => {},
690 (a, .., c) => {},
691 (a, b, ..) => {},
692 (a, b) => {/*too short*/}
693 (a, b, c, d) => {/*too long*/}
694 _ => {}
695 }
696}"#,
697 expect![[r#"
698 7..12 'tuple': (u8, i16, f32)
699 30..224 '{ ... } }': ()
700 36..222 'match ... }': ()
701 42..47 'tuple': (u8, i16, f32)
702 58..68 '(.., b, c)': (u8, i16, f32)
703 63..64 'b': i16
704 66..67 'c': f32
705 72..74 '{}': ()
706 84..94 '(a, .., c)': (u8, i16, f32)
707 85..86 'a': u8
708 92..93 'c': f32
709 98..100 '{}': ()
710 110..120 '(a, b, ..)': (u8, i16, f32)
711 111..112 'a': u8
712 114..115 'b': i16
713 124..126 '{}': ()
714 136..142 '(a, b)': (u8, i16, f32)
715 137..138 'a': u8
716 140..141 'b': i16
717 146..161 '{/*too short*/}': ()
718 170..182 '(a, b, c, d)': (u8, i16, f32, {unknown})
719 171..172 'a': u8
720 174..175 'b': i16
721 177..178 'c': f32
722 180..181 'd': {unknown}
723 186..200 '{/*too long*/}': ()
724 209..210 '_': (u8, i16, f32)
725 214..216 '{}': ()
726 "#]],
727 );
728}
729
730#[test]
731fn tuple_struct_ellipsis_pattern() {
732 check_infer(
733 r#"
734struct Tuple(u8, i16, f32);
735fn foo(tuple: Tuple) {
736 match tuple {
737 Tuple(.., b, c) => {},
738 Tuple(a, .., c) => {},
739 Tuple(a, b, ..) => {},
740 Tuple(a, b) => {/*too short*/}
741 Tuple(a, b, c, d) => {/*too long*/}
742 _ => {}
743 }
744}"#,
745 expect![[r#"
746 35..40 'tuple': Tuple
747 49..268 '{ ... } }': ()
748 55..266 'match ... }': ()
749 61..66 'tuple': Tuple
750 77..92 'Tuple(.., b, c)': Tuple
751 87..88 'b': i16
752 90..91 'c': f32
753 96..98 '{}': ()
754 108..123 'Tuple(a, .., c)': Tuple
755 114..115 'a': u8
756 121..122 'c': f32
757 127..129 '{}': ()
758 139..154 'Tuple(a, b, ..)': Tuple
759 145..146 'a': u8
760 148..149 'b': i16
761 158..160 '{}': ()
762 170..181 'Tuple(a, b)': Tuple
763 176..177 'a': u8
764 179..180 'b': i16
765 185..200 '{/*too short*/}': ()
766 209..226 'Tuple(... c, d)': Tuple
767 215..216 'a': u8
768 218..219 'b': i16
769 221..222 'c': f32
770 224..225 'd': {unknown}
771 230..244 '{/*too long*/}': ()
772 253..254 '_': Tuple
773 258..260 '{}': ()
774 "#]],
775 );
776}