Skip to content

Commit d075f49

Browse files
Merge #3960
3960: ellipsis in tuple patterns r=JoshMcguigan a=JoshMcguigan This PR lowers ellipsis in tuple patterns. It fixes a bug in the way ellipsis were previously lowered (by replacing the ellipsis with a single `Pat::Wild` no matter how many items the `..` was taking the place of). It also uses this new information to properly handle `..` in tuple struct patterns when perform match statement exhaustiveness checks. While this PR provides the building blocks for match statement exhaustiveness checks for tuples, there are some additional challenges there, so that is still unimplemented (unlike tuple structs). Co-authored-by: Josh Mcguigan <[email protected]>
2 parents c388130 + ee822d1 commit d075f49

File tree

4 files changed

+141
-58
lines changed

4 files changed

+141
-58
lines changed

crates/ra_hir_def/src/body/lower.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use crate::{
3333
};
3434

3535
use super::{ExprSource, PatSource};
36+
use ast::AstChildren;
3637

3738
pub(super) fn lower(
3839
db: &dyn DefDatabase,
@@ -598,8 +599,8 @@ impl ExprCollector<'_> {
598599
}
599600
ast::Pat::TupleStructPat(p) => {
600601
let path = p.path().and_then(|path| self.expander.parse_path(path));
601-
let args = p.args().map(|p| self.collect_pat(p)).collect();
602-
Pat::TupleStruct { path, args }
602+
let (args, ellipsis) = self.collect_tuple_pat(p.args());
603+
Pat::TupleStruct { path, args, ellipsis }
603604
}
604605
ast::Pat::RefPat(p) => {
605606
let pat = self.collect_pat_opt(p.pat());
@@ -616,10 +617,10 @@ impl ExprCollector<'_> {
616617
}
617618
ast::Pat::ParenPat(p) => return self.collect_pat_opt(p.pat()),
618619
ast::Pat::TuplePat(p) => {
619-
let args = p.args().map(|p| self.collect_pat(p)).collect();
620-
Pat::Tuple(args)
620+
let (args, ellipsis) = self.collect_tuple_pat(p.args());
621+
Pat::Tuple { args, ellipsis }
621622
}
622-
ast::Pat::PlaceholderPat(_) | ast::Pat::DotDotPat(_) => Pat::Wild,
623+
ast::Pat::PlaceholderPat(_) => Pat::Wild,
623624
ast::Pat::RecordPat(p) => {
624625
let path = p.path().and_then(|path| self.expander.parse_path(path));
625626
let record_field_pat_list =
@@ -665,6 +666,9 @@ impl ExprCollector<'_> {
665666
Pat::Missing
666667
}
667668
}
669+
ast::Pat::DotDotPat(_) => unreachable!(
670+
"`DotDotPat` requires special handling and should not be mapped to a Pat."
671+
),
668672
// FIXME: implement
669673
ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
670674
};
@@ -679,6 +683,19 @@ impl ExprCollector<'_> {
679683
self.missing_pat()
680684
}
681685
}
686+
687+
fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Vec<PatId>, Option<usize>) {
688+
// Find the location of the `..`, if there is one. Note that we do not
689+
// consider the possiblity of there being multiple `..` here.
690+
let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::DotDotPat(_)));
691+
// We want to skip the `..` pattern here, since we account for it above.
692+
let args = args
693+
.filter(|p| !matches!(p, ast::Pat::DotDotPat(_)))
694+
.map(|p| self.collect_pat(p))
695+
.collect();
696+
697+
(args, ellipsis)
698+
}
682699
}
683700

684701
impl From<ast::BinOp> for BinaryOp {

crates/ra_hir_def/src/expr.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,15 +374,15 @@ pub struct RecordFieldPat {
374374
pub enum Pat {
375375
Missing,
376376
Wild,
377-
Tuple(Vec<PatId>),
377+
Tuple { args: Vec<PatId>, ellipsis: Option<usize> },
378378
Or(Vec<PatId>),
379379
Record { path: Option<Path>, args: Vec<RecordFieldPat>, ellipsis: bool },
380380
Range { start: ExprId, end: ExprId },
381381
Slice { prefix: Vec<PatId>, slice: Option<PatId>, suffix: Vec<PatId> },
382382
Path(Path),
383383
Lit(ExprId),
384384
Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
385-
TupleStruct { path: Option<Path>, args: Vec<PatId> },
385+
TupleStruct { path: Option<Path>, args: Vec<PatId>, ellipsis: Option<usize> },
386386
Ref { pat: PatId, mutability: Mutability },
387387
}
388388

@@ -393,7 +393,7 @@ impl Pat {
393393
Pat::Bind { subpat, .. } => {
394394
subpat.iter().copied().for_each(f);
395395
}
396-
Pat::Or(args) | Pat::Tuple(args) | Pat::TupleStruct { args, .. } => {
396+
Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => {
397397
args.iter().copied().for_each(f);
398398
}
399399
Pat::Ref { pat, .. } => f(*pat),

crates/ra_hir_ty/src/_match.rs

Lines changed: 113 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ impl PatStack {
289289
Self::from_slice(&self.0[1..])
290290
}
291291

292-
fn replace_head_with(&self, pat_ids: &[PatId]) -> PatStack {
292+
fn replace_head_with<T: Into<PatIdOrWild> + Copy>(&self, pat_ids: &[T]) -> PatStack {
293293
let mut patterns: PatStackInner = smallvec![];
294294
for pat in pat_ids {
295295
patterns.push((*pat).into());
@@ -320,12 +320,14 @@ impl PatStack {
320320
constructor: &Constructor,
321321
) -> MatchCheckResult<Option<PatStack>> {
322322
let result = match (self.head().as_pat(cx), constructor) {
323-
(Pat::Tuple(ref pat_ids), Constructor::Tuple { arity }) => {
324-
debug_assert_eq!(
325-
pat_ids.len(),
326-
*arity,
327-
"we type check before calling this code, so we should never hit this case",
328-
);
323+
(Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => {
324+
if ellipsis.is_some() {
325+
// If there are ellipsis here, we should add the correct number of
326+
// Pat::Wild patterns to `pat_ids`. We should be able to use the
327+
// constructors arity for this, but at the time of writing we aren't
328+
// correctly calculating this arity when ellipsis are present.
329+
return Err(MatchCheckErr::NotImplemented);
330+
}
329331

330332
Some(self.replace_head_with(pat_ids))
331333
}
@@ -351,19 +353,47 @@ impl PatStack {
351353
Some(self.to_tail())
352354
}
353355
}
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+
) => {
355360
let pat_id = self.head().as_id().expect("we know this isn't a wild");
356361
if !enum_variant_matches(cx, pat_id, *enum_constructor) {
357362
None
358363
} else {
359-
// If the enum variant matches, then we need to confirm
360-
// that the number of patterns aligns with the expected
361-
// number of patterns for that enum variant.
362-
if pat_ids.len() != constructor.arity(cx)? {
363-
return Err(MatchCheckErr::MalformedMatchArm);
364+
let constructor_arity = constructor.arity(cx)?;
365+
if let Some(ellipsis_position) = ellipsis {
366+
// If there are ellipsis in the pattern, the ellipsis must take the place
367+
// of at least one sub-pattern, so `pat_ids` should be smaller than the
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+
}
364396
}
365-
366-
Some(self.replace_head_with(pat_ids))
367397
}
368398
}
369399
(Pat::Or(_), _) => return Err(MatchCheckErr::NotImplemented),
@@ -644,7 +674,11 @@ impl Constructor {
644674
fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Option<Constructor>> {
645675
let res = match pat.as_pat(cx) {
646676
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+
}
648682
Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] {
649683
Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)),
650684
_ => return Err(MatchCheckErr::NotImplemented),
@@ -1506,6 +1540,67 @@ mod tests {
15061540
check_no_diagnostic(content);
15071541
}
15081542

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+
15091604
#[test]
15101605
fn enum_tuple_ellipsis_no_diagnostic() {
15111606
let content = r"
@@ -1645,11 +1740,7 @@ mod false_negatives {
16451740
";
16461741

16471742
// This is a false negative.
1648-
// The `..` pattern is currently lowered to a single `Pat::Wild`
1649-
// no matter how many fields the `..` pattern is covering. This
1650-
// causes the match arm in this test not to type check against
1651-
// the match expression, which causes this diagnostic not to
1652-
// fire.
1743+
// We don't currently handle tuple patterns with ellipsis.
16531744
check_no_diagnostic(content);
16541745
}
16551746

@@ -1664,32 +1755,7 @@ mod false_negatives {
16641755
";
16651756

16661757
// This is a false negative.
1667-
// See comments on `tuple_of_bools_with_ellipsis_at_end_missing_arm`.
1668-
check_no_diagnostic(content);
1669-
}
1670-
1671-
#[test]
1672-
fn enum_tuple_partial_ellipsis_missing_arm() {
1673-
let content = r"
1674-
enum Either {
1675-
A(bool, bool, bool, bool),
1676-
B,
1677-
}
1678-
fn test_fn() {
1679-
match Either::B {
1680-
Either::A(true, .., true) => {},
1681-
Either::A(true, .., false) => {},
1682-
Either::A(false, .., false) => {},
1683-
Either::B => {},
1684-
}
1685-
}
1686-
";
1687-
1688-
// This is a false negative.
1689-
// The `..` pattern is currently lowered to a single `Pat::Wild`
1690-
// no matter how many fields the `..` pattern is covering. This
1691-
// causes us to return a `MatchCheckErr::MalformedMatchArm` in
1692-
// `Pat::specialize_constructor`.
1758+
// We don't currently handle tuple patterns with ellipsis.
16931759
check_no_diagnostic(content);
16941760
}
16951761
}

crates/ra_hir_ty/src/infer/pat.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ impl<'a> InferenceContext<'a> {
8585
let body = Arc::clone(&self.body); // avoid borrow checker problem
8686

8787
let is_non_ref_pat = match &body[pat] {
88-
Pat::Tuple(..)
88+
Pat::Tuple { .. }
8989
| Pat::Or(..)
9090
| Pat::TupleStruct { .. }
9191
| Pat::Record { .. }
@@ -116,7 +116,7 @@ impl<'a> InferenceContext<'a> {
116116
let expected = expected;
117117

118118
let ty = match &body[pat] {
119-
Pat::Tuple(ref args) => {
119+
Pat::Tuple { ref args, .. } => {
120120
let expectations = match expected.as_tuple() {
121121
Some(parameters) => &*parameters.0,
122122
_ => &[],
@@ -155,7 +155,7 @@ impl<'a> InferenceContext<'a> {
155155
let subty = self.infer_pat(*pat, expectation, default_bm);
156156
Ty::apply_one(TypeCtor::Ref(*mutability), subty)
157157
}
158-
Pat::TupleStruct { path: p, args: subpats } => {
158+
Pat::TupleStruct { path: p, args: subpats, .. } => {
159159
self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat)
160160
}
161161
Pat::Record { path: p, args: fields, ellipsis: _ } => {

0 commit comments

Comments
 (0)