diff options
Diffstat (limited to 'crates/ra_hir_ty')
-rw-r--r-- | crates/ra_hir_ty/src/_match.rs | 350 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/expr.rs | 13 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/infer/pat.rs | 6 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/tests/patterns.rs | 3 |
4 files changed, 340 insertions, 32 deletions
diff --git a/crates/ra_hir_ty/src/_match.rs b/crates/ra_hir_ty/src/_match.rs index 9e9a9d047..a64be9848 100644 --- a/crates/ra_hir_ty/src/_match.rs +++ b/crates/ra_hir_ty/src/_match.rs | |||
@@ -289,7 +289,7 @@ impl PatStack { | |||
289 | Self::from_slice(&self.0[1..]) | 289 | Self::from_slice(&self.0[1..]) |
290 | } | 290 | } |
291 | 291 | ||
292 | fn replace_head_with(&self, pat_ids: &[PatId]) -> PatStack { | 292 | fn replace_head_with<T: Into<PatIdOrWild> + Copy>(&self, pat_ids: &[T]) -> PatStack { |
293 | let mut patterns: PatStackInner = smallvec![]; | 293 | let mut patterns: PatStackInner = smallvec![]; |
294 | for pat in pat_ids { | 294 | for pat in pat_ids { |
295 | patterns.push((*pat).into()); | 295 | patterns.push((*pat).into()); |
@@ -320,12 +320,14 @@ impl PatStack { | |||
320 | constructor: &Constructor, | 320 | constructor: &Constructor, |
321 | ) -> MatchCheckResult<Option<PatStack>> { | 321 | ) -> MatchCheckResult<Option<PatStack>> { |
322 | let result = match (self.head().as_pat(cx), constructor) { | 322 | let result = match (self.head().as_pat(cx), constructor) { |
323 | (Pat::Tuple(ref pat_ids), Constructor::Tuple { arity }) => { | 323 | (Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => { |
324 | debug_assert_eq!( | 324 | if ellipsis.is_some() { |
325 | pat_ids.len(), | 325 | // If there are ellipsis here, we should add the correct number of |
326 | *arity, | 326 | // Pat::Wild patterns to `pat_ids`. We should be able to use the |
327 | "we type check before calling this code, so we should never hit this case", | 327 | // constructors arity for this, but at the time of writing we aren't |
328 | ); | 328 | // correctly calculating this arity when ellipsis are present. |
329 | return Err(MatchCheckErr::NotImplemented); | ||
330 | } | ||
329 | 331 | ||
330 | Some(self.replace_head_with(pat_ids)) | 332 | Some(self.replace_head_with(pat_ids)) |
331 | } | 333 | } |
@@ -351,19 +353,47 @@ impl PatStack { | |||
351 | Some(self.to_tail()) | 353 | Some(self.to_tail()) |
352 | } | 354 | } |
353 | } | 355 | } |
354 | (Pat::TupleStruct { args: ref pat_ids, .. }, Constructor::Enum(enum_constructor)) => { | 356 | ( |
357 | Pat::TupleStruct { args: ref pat_ids, ellipsis, .. }, | ||
358 | Constructor::Enum(enum_constructor), | ||
359 | ) => { | ||
355 | let pat_id = self.head().as_id().expect("we know this isn't a wild"); | 360 | let pat_id = self.head().as_id().expect("we know this isn't a wild"); |
356 | if !enum_variant_matches(cx, pat_id, *enum_constructor) { | 361 | if !enum_variant_matches(cx, pat_id, *enum_constructor) { |
357 | None | 362 | None |
358 | } else { | 363 | } else { |
359 | // If the enum variant matches, then we need to confirm | 364 | let constructor_arity = constructor.arity(cx)?; |
360 | // that the number of patterns aligns with the expected | 365 | if let Some(ellipsis_position) = ellipsis { |
361 | // number of patterns for that enum variant. | 366 | // If there are ellipsis in the pattern, the ellipsis must take the place |
362 | if pat_ids.len() != constructor.arity(cx)? { | 367 | // of at least one sub-pattern, so `pat_ids` should be smaller than the |
363 | return Err(MatchCheckErr::MalformedMatchArm); | 368 | // constructor arity. |
369 | if pat_ids.len() < constructor_arity { | ||
370 | let mut new_patterns: Vec<PatIdOrWild> = vec![]; | ||
371 | |||
372 | for pat_id in &pat_ids[0..ellipsis_position] { | ||
373 | new_patterns.push((*pat_id).into()); | ||
374 | } | ||
375 | |||
376 | for _ in 0..(constructor_arity - pat_ids.len()) { | ||
377 | new_patterns.push(PatIdOrWild::Wild); | ||
378 | } | ||
379 | |||
380 | for pat_id in &pat_ids[ellipsis_position..pat_ids.len()] { | ||
381 | new_patterns.push((*pat_id).into()); | ||
382 | } | ||
383 | |||
384 | Some(self.replace_head_with(&new_patterns)) | ||
385 | } else { | ||
386 | return Err(MatchCheckErr::MalformedMatchArm); | ||
387 | } | ||
388 | } else { | ||
389 | // If there is no ellipsis in the tuple pattern, the number | ||
390 | // of patterns must equal the constructor arity. | ||
391 | if pat_ids.len() == constructor_arity { | ||
392 | Some(self.replace_head_with(pat_ids)) | ||
393 | } else { | ||
394 | return Err(MatchCheckErr::MalformedMatchArm); | ||
395 | } | ||
364 | } | 396 | } |
365 | |||
366 | Some(self.replace_head_with(pat_ids)) | ||
367 | } | 397 | } |
368 | } | 398 | } |
369 | (Pat::Or(_), _) => return Err(MatchCheckErr::NotImplemented), | 399 | (Pat::Or(_), _) => return Err(MatchCheckErr::NotImplemented), |
@@ -644,7 +674,11 @@ impl Constructor { | |||
644 | fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Option<Constructor>> { | 674 | fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Option<Constructor>> { |
645 | let res = match pat.as_pat(cx) { | 675 | let res = match pat.as_pat(cx) { |
646 | Pat::Wild => None, | 676 | Pat::Wild => None, |
647 | Pat::Tuple(pats) => Some(Constructor::Tuple { arity: pats.len() }), | 677 | // FIXME somehow create the Tuple constructor with the proper arity. If there are |
678 | // ellipsis, the arity is not equal to the number of patterns. | ||
679 | Pat::Tuple { args: pats, ellipsis } if ellipsis.is_none() => { | ||
680 | Some(Constructor::Tuple { arity: pats.len() }) | ||
681 | } | ||
648 | Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] { | 682 | Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] { |
649 | Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)), | 683 | Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)), |
650 | _ => return Err(MatchCheckErr::NotImplemented), | 684 | _ => return Err(MatchCheckErr::NotImplemented), |
@@ -973,6 +1007,47 @@ mod tests { | |||
973 | } | 1007 | } |
974 | 1008 | ||
975 | #[test] | 1009 | #[test] |
1010 | fn tuple_of_bools_with_ellipsis_at_end_no_diagnostic() { | ||
1011 | let content = r" | ||
1012 | fn test_fn() { | ||
1013 | match (false, true, false) { | ||
1014 | (false, ..) => {}, | ||
1015 | (true, ..) => {}, | ||
1016 | } | ||
1017 | } | ||
1018 | "; | ||
1019 | |||
1020 | check_no_diagnostic(content); | ||
1021 | } | ||
1022 | |||
1023 | #[test] | ||
1024 | fn tuple_of_bools_with_ellipsis_at_beginning_no_diagnostic() { | ||
1025 | let content = r" | ||
1026 | fn test_fn() { | ||
1027 | match (false, true, false) { | ||
1028 | (.., false) => {}, | ||
1029 | (.., true) => {}, | ||
1030 | } | ||
1031 | } | ||
1032 | "; | ||
1033 | |||
1034 | check_no_diagnostic(content); | ||
1035 | } | ||
1036 | |||
1037 | #[test] | ||
1038 | fn tuple_of_bools_with_ellipsis_no_diagnostic() { | ||
1039 | let content = r" | ||
1040 | fn test_fn() { | ||
1041 | match (false, true, false) { | ||
1042 | (..) => {}, | ||
1043 | } | ||
1044 | } | ||
1045 | "; | ||
1046 | |||
1047 | check_no_diagnostic(content); | ||
1048 | } | ||
1049 | |||
1050 | #[test] | ||
976 | fn tuple_of_tuple_and_bools_no_arms() { | 1051 | fn tuple_of_tuple_and_bools_no_arms() { |
977 | let content = r" | 1052 | let content = r" |
978 | fn test_fn() { | 1053 | fn test_fn() { |
@@ -1315,8 +1390,9 @@ mod tests { | |||
1315 | } | 1390 | } |
1316 | "; | 1391 | "; |
1317 | 1392 | ||
1318 | // Match arms with the incorrect type are filtered out. | 1393 | // Match statements with arms that don't match the |
1319 | check_diagnostic(content); | 1394 | // expression pattern do not fire this diagnostic. |
1395 | check_no_diagnostic(content); | ||
1320 | } | 1396 | } |
1321 | 1397 | ||
1322 | #[test] | 1398 | #[test] |
@@ -1330,8 +1406,9 @@ mod tests { | |||
1330 | } | 1406 | } |
1331 | "; | 1407 | "; |
1332 | 1408 | ||
1333 | // Match arms with the incorrect type are filtered out. | 1409 | // Match statements with arms that don't match the |
1334 | check_diagnostic(content); | 1410 | // expression pattern do not fire this diagnostic. |
1411 | check_no_diagnostic(content); | ||
1335 | } | 1412 | } |
1336 | 1413 | ||
1337 | #[test] | 1414 | #[test] |
@@ -1344,8 +1421,9 @@ mod tests { | |||
1344 | } | 1421 | } |
1345 | "; | 1422 | "; |
1346 | 1423 | ||
1347 | // Match arms with the incorrect type are filtered out. | 1424 | // Match statements with arms that don't match the |
1348 | check_diagnostic(content); | 1425 | // expression pattern do not fire this diagnostic. |
1426 | check_no_diagnostic(content); | ||
1349 | } | 1427 | } |
1350 | 1428 | ||
1351 | #[test] | 1429 | #[test] |
@@ -1383,6 +1461,163 @@ mod tests { | |||
1383 | // we don't create a diagnostic). | 1461 | // we don't create a diagnostic). |
1384 | check_no_diagnostic(content); | 1462 | check_no_diagnostic(content); |
1385 | } | 1463 | } |
1464 | |||
1465 | #[test] | ||
1466 | fn expr_diverges() { | ||
1467 | let content = r" | ||
1468 | enum Either { | ||
1469 | A, | ||
1470 | B, | ||
1471 | } | ||
1472 | fn test_fn() { | ||
1473 | match loop {} { | ||
1474 | Either::A => (), | ||
1475 | Either::B => (), | ||
1476 | } | ||
1477 | } | ||
1478 | "; | ||
1479 | |||
1480 | check_no_diagnostic(content); | ||
1481 | } | ||
1482 | |||
1483 | #[test] | ||
1484 | fn expr_loop_with_break() { | ||
1485 | let content = r" | ||
1486 | enum Either { | ||
1487 | A, | ||
1488 | B, | ||
1489 | } | ||
1490 | fn test_fn() { | ||
1491 | match loop { break Foo::A } { | ||
1492 | Either::A => (), | ||
1493 | Either::B => (), | ||
1494 | } | ||
1495 | } | ||
1496 | "; | ||
1497 | |||
1498 | check_no_diagnostic(content); | ||
1499 | } | ||
1500 | |||
1501 | #[test] | ||
1502 | fn expr_partially_diverges() { | ||
1503 | let content = r" | ||
1504 | enum Either<T> { | ||
1505 | A(T), | ||
1506 | B, | ||
1507 | } | ||
1508 | fn foo() -> Either<!> { | ||
1509 | Either::B | ||
1510 | } | ||
1511 | fn test_fn() -> u32 { | ||
1512 | match foo() { | ||
1513 | Either::A(val) => val, | ||
1514 | Either::B => 0, | ||
1515 | } | ||
1516 | } | ||
1517 | "; | ||
1518 | |||
1519 | check_no_diagnostic(content); | ||
1520 | } | ||
1521 | |||
1522 | #[test] | ||
1523 | fn enum_tuple_partial_ellipsis_no_diagnostic() { | ||
1524 | let content = r" | ||
1525 | enum Either { | ||
1526 | A(bool, bool, bool, bool), | ||
1527 | B, | ||
1528 | } | ||
1529 | fn test_fn() { | ||
1530 | match Either::B { | ||
1531 | Either::A(true, .., true) => {}, | ||
1532 | Either::A(true, .., false) => {}, | ||
1533 | Either::A(false, .., true) => {}, | ||
1534 | Either::A(false, .., false) => {}, | ||
1535 | Either::B => {}, | ||
1536 | } | ||
1537 | } | ||
1538 | "; | ||
1539 | |||
1540 | check_no_diagnostic(content); | ||
1541 | } | ||
1542 | |||
1543 | #[test] | ||
1544 | fn enum_tuple_partial_ellipsis_2_no_diagnostic() { | ||
1545 | let content = r" | ||
1546 | enum Either { | ||
1547 | A(bool, bool, bool, bool), | ||
1548 | B, | ||
1549 | } | ||
1550 | fn test_fn() { | ||
1551 | match Either::B { | ||
1552 | Either::A(true, .., true) => {}, | ||
1553 | Either::A(true, .., false) => {}, | ||
1554 | Either::A(.., true) => {}, | ||
1555 | Either::A(.., false) => {}, | ||
1556 | Either::B => {}, | ||
1557 | } | ||
1558 | } | ||
1559 | "; | ||
1560 | |||
1561 | check_no_diagnostic(content); | ||
1562 | } | ||
1563 | |||
1564 | #[test] | ||
1565 | fn enum_tuple_partial_ellipsis_missing_arm() { | ||
1566 | let content = r" | ||
1567 | enum Either { | ||
1568 | A(bool, bool, bool, bool), | ||
1569 | B, | ||
1570 | } | ||
1571 | fn test_fn() { | ||
1572 | match Either::B { | ||
1573 | Either::A(true, .., true) => {}, | ||
1574 | Either::A(true, .., false) => {}, | ||
1575 | Either::A(false, .., false) => {}, | ||
1576 | Either::B => {}, | ||
1577 | } | ||
1578 | } | ||
1579 | "; | ||
1580 | |||
1581 | check_diagnostic(content); | ||
1582 | } | ||
1583 | |||
1584 | #[test] | ||
1585 | fn enum_tuple_partial_ellipsis_2_missing_arm() { | ||
1586 | let content = r" | ||
1587 | enum Either { | ||
1588 | A(bool, bool, bool, bool), | ||
1589 | B, | ||
1590 | } | ||
1591 | fn test_fn() { | ||
1592 | match Either::B { | ||
1593 | Either::A(true, .., true) => {}, | ||
1594 | Either::A(true, .., false) => {}, | ||
1595 | Either::A(.., true) => {}, | ||
1596 | Either::B => {}, | ||
1597 | } | ||
1598 | } | ||
1599 | "; | ||
1600 | |||
1601 | check_diagnostic(content); | ||
1602 | } | ||
1603 | |||
1604 | #[test] | ||
1605 | fn enum_tuple_ellipsis_no_diagnostic() { | ||
1606 | let content = r" | ||
1607 | enum Either { | ||
1608 | A(bool, bool, bool, bool), | ||
1609 | B, | ||
1610 | } | ||
1611 | fn test_fn() { | ||
1612 | match Either::B { | ||
1613 | Either::A(..) => {}, | ||
1614 | Either::B => {}, | ||
1615 | } | ||
1616 | } | ||
1617 | "; | ||
1618 | |||
1619 | check_no_diagnostic(content); | ||
1620 | } | ||
1386 | } | 1621 | } |
1387 | 1622 | ||
1388 | #[cfg(test)] | 1623 | #[cfg(test)] |
@@ -1452,4 +1687,75 @@ mod false_negatives { | |||
1452 | // We do not currently handle patterns with internal `or`s. | 1687 | // We do not currently handle patterns with internal `or`s. |
1453 | check_no_diagnostic(content); | 1688 | check_no_diagnostic(content); |
1454 | } | 1689 | } |
1690 | |||
1691 | #[test] | ||
1692 | fn expr_diverges_missing_arm() { | ||
1693 | let content = r" | ||
1694 | enum Either { | ||
1695 | A, | ||
1696 | B, | ||
1697 | } | ||
1698 | fn test_fn() { | ||
1699 | match loop {} { | ||
1700 | Either::A => (), | ||
1701 | } | ||
1702 | } | ||
1703 | "; | ||
1704 | |||
1705 | // This is a false negative. | ||
1706 | // Even though the match expression diverges, rustc fails | ||
1707 | // to compile here since `Either::B` is missing. | ||
1708 | check_no_diagnostic(content); | ||
1709 | } | ||
1710 | |||
1711 | #[test] | ||
1712 | fn expr_loop_missing_arm() { | ||
1713 | let content = r" | ||
1714 | enum Either { | ||
1715 | A, | ||
1716 | B, | ||
1717 | } | ||
1718 | fn test_fn() { | ||
1719 | match loop { break Foo::A } { | ||
1720 | Either::A => (), | ||
1721 | } | ||
1722 | } | ||
1723 | "; | ||
1724 | |||
1725 | // This is a false negative. | ||
1726 | // We currently infer the type of `loop { break Foo::A }` to `!`, which | ||
1727 | // causes us to skip the diagnostic since `Either::A` doesn't type check | ||
1728 | // with `!`. | ||
1729 | check_no_diagnostic(content); | ||
1730 | } | ||
1731 | |||
1732 | #[test] | ||
1733 | fn tuple_of_bools_with_ellipsis_at_end_missing_arm() { | ||
1734 | let content = r" | ||
1735 | fn test_fn() { | ||
1736 | match (false, true, false) { | ||
1737 | (false, ..) => {}, | ||
1738 | } | ||
1739 | } | ||
1740 | "; | ||
1741 | |||
1742 | // This is a false negative. | ||
1743 | // We don't currently handle tuple patterns with ellipsis. | ||
1744 | check_no_diagnostic(content); | ||
1745 | } | ||
1746 | |||
1747 | #[test] | ||
1748 | fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() { | ||
1749 | let content = r" | ||
1750 | fn test_fn() { | ||
1751 | match (false, true, false) { | ||
1752 | (.., false) => {}, | ||
1753 | } | ||
1754 | } | ||
1755 | "; | ||
1756 | |||
1757 | // This is a false negative. | ||
1758 | // We don't currently handle tuple patterns with ellipsis. | ||
1759 | check_no_diagnostic(content); | ||
1760 | } | ||
1455 | } | 1761 | } |
diff --git a/crates/ra_hir_ty/src/expr.rs b/crates/ra_hir_ty/src/expr.rs index 69b527f74..21abbcf1e 100644 --- a/crates/ra_hir_ty/src/expr.rs +++ b/crates/ra_hir_ty/src/expr.rs | |||
@@ -161,12 +161,6 @@ impl<'a, 'b> ExprValidator<'a, 'b> { | |||
161 | 161 | ||
162 | let mut seen = Matrix::empty(); | 162 | let mut seen = Matrix::empty(); |
163 | for pat in pats { | 163 | for pat in pats { |
164 | // We skip any patterns whose type we cannot resolve. | ||
165 | // | ||
166 | // This could lead to false positives in this diagnostic, so | ||
167 | // it might be better to skip the entire diagnostic if we either | ||
168 | // cannot resolve a match arm or determine that the match arm has | ||
169 | // the wrong type. | ||
170 | if let Some(pat_ty) = infer.type_of_pat.get(pat) { | 164 | if let Some(pat_ty) = infer.type_of_pat.get(pat) { |
171 | // We only include patterns whose type matches the type | 165 | // We only include patterns whose type matches the type |
172 | // of the match expression. If we had a InvalidMatchArmPattern | 166 | // of the match expression. If we had a InvalidMatchArmPattern |
@@ -189,8 +183,15 @@ impl<'a, 'b> ExprValidator<'a, 'b> { | |||
189 | // to the matrix here. | 183 | // to the matrix here. |
190 | let v = PatStack::from_pattern(pat); | 184 | let v = PatStack::from_pattern(pat); |
191 | seen.push(&cx, v); | 185 | seen.push(&cx, v); |
186 | continue; | ||
192 | } | 187 | } |
193 | } | 188 | } |
189 | |||
190 | // If we can't resolve the type of a pattern, or the pattern type doesn't | ||
191 | // fit the match expression, we skip this diagnostic. Skipping the entire | ||
192 | // diagnostic rather than just not including this match arm is preferred | ||
193 | // to avoid the chance of false positives. | ||
194 | return; | ||
194 | } | 195 | } |
195 | 196 | ||
196 | match is_useful(&cx, &seen, &PatStack::from_wild()) { | 197 | match is_useful(&cx, &seen, &PatStack::from_wild()) { |
diff --git a/crates/ra_hir_ty/src/infer/pat.rs b/crates/ra_hir_ty/src/infer/pat.rs index 078476f76..8ec4d4ace 100644 --- a/crates/ra_hir_ty/src/infer/pat.rs +++ b/crates/ra_hir_ty/src/infer/pat.rs | |||
@@ -85,7 +85,7 @@ impl<'a> InferenceContext<'a> { | |||
85 | let body = Arc::clone(&self.body); // avoid borrow checker problem | 85 | let body = Arc::clone(&self.body); // avoid borrow checker problem |
86 | 86 | ||
87 | let is_non_ref_pat = match &body[pat] { | 87 | let is_non_ref_pat = match &body[pat] { |
88 | Pat::Tuple(..) | 88 | Pat::Tuple { .. } |
89 | | Pat::Or(..) | 89 | | Pat::Or(..) |
90 | | Pat::TupleStruct { .. } | 90 | | Pat::TupleStruct { .. } |
91 | | Pat::Record { .. } | 91 | | Pat::Record { .. } |
@@ -116,7 +116,7 @@ impl<'a> InferenceContext<'a> { | |||
116 | let expected = expected; | 116 | let expected = expected; |
117 | 117 | ||
118 | let ty = match &body[pat] { | 118 | let ty = match &body[pat] { |
119 | Pat::Tuple(ref args) => { | 119 | Pat::Tuple { ref args, .. } => { |
120 | let expectations = match expected.as_tuple() { | 120 | let expectations = match expected.as_tuple() { |
121 | Some(parameters) => &*parameters.0, | 121 | Some(parameters) => &*parameters.0, |
122 | _ => &[], | 122 | _ => &[], |
@@ -155,7 +155,7 @@ impl<'a> InferenceContext<'a> { | |||
155 | let subty = self.infer_pat(*pat, expectation, default_bm); | 155 | let subty = self.infer_pat(*pat, expectation, default_bm); |
156 | Ty::apply_one(TypeCtor::Ref(*mutability), subty) | 156 | Ty::apply_one(TypeCtor::Ref(*mutability), subty) |
157 | } | 157 | } |
158 | Pat::TupleStruct { path: p, args: subpats } => { | 158 | Pat::TupleStruct { path: p, args: subpats, .. } => { |
159 | self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat) | 159 | self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat) |
160 | } | 160 | } |
161 | Pat::Record { path: p, args: fields, ellipsis: _ } => { | 161 | Pat::Record { path: p, args: fields, ellipsis: _ } => { |
diff --git a/crates/ra_hir_ty/src/tests/patterns.rs b/crates/ra_hir_ty/src/tests/patterns.rs index 6e5d2247c..07cbc521a 100644 --- a/crates/ra_hir_ty/src/tests/patterns.rs +++ b/crates/ra_hir_ty/src/tests/patterns.rs | |||
@@ -1,7 +1,8 @@ | |||
1 | use super::{infer, infer_with_mismatches}; | ||
2 | use insta::assert_snapshot; | 1 | use insta::assert_snapshot; |
3 | use test_utils::covers; | 2 | use test_utils::covers; |
4 | 3 | ||
4 | use super::{infer, infer_with_mismatches}; | ||
5 | |||
5 | #[test] | 6 | #[test] |
6 | fn infer_pattern() { | 7 | fn infer_pattern() { |
7 | assert_snapshot!( | 8 | assert_snapshot!( |