Skip to content

Commit ee822d1

Browse files
committed
handle tuple patterns with ellipsis
1 parent 268b798 commit ee822d1

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)