mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-28 22:13:39 +00:00
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 <joshmcg88@gmail.com>
This commit is contained in:
commit
d075f49e6d
4 changed files with 141 additions and 58 deletions
|
@ -33,6 +33,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ExprSource, PatSource};
|
use super::{ExprSource, PatSource};
|
||||||
|
use ast::AstChildren;
|
||||||
|
|
||||||
pub(super) fn lower(
|
pub(super) fn lower(
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
|
@ -598,8 +599,8 @@ impl ExprCollector<'_> {
|
||||||
}
|
}
|
||||||
ast::Pat::TupleStructPat(p) => {
|
ast::Pat::TupleStructPat(p) => {
|
||||||
let path = p.path().and_then(|path| self.expander.parse_path(path));
|
let path = p.path().and_then(|path| self.expander.parse_path(path));
|
||||||
let args = p.args().map(|p| self.collect_pat(p)).collect();
|
let (args, ellipsis) = self.collect_tuple_pat(p.args());
|
||||||
Pat::TupleStruct { path, args }
|
Pat::TupleStruct { path, args, ellipsis }
|
||||||
}
|
}
|
||||||
ast::Pat::RefPat(p) => {
|
ast::Pat::RefPat(p) => {
|
||||||
let pat = self.collect_pat_opt(p.pat());
|
let pat = self.collect_pat_opt(p.pat());
|
||||||
|
@ -616,10 +617,10 @@ impl ExprCollector<'_> {
|
||||||
}
|
}
|
||||||
ast::Pat::ParenPat(p) => return self.collect_pat_opt(p.pat()),
|
ast::Pat::ParenPat(p) => return self.collect_pat_opt(p.pat()),
|
||||||
ast::Pat::TuplePat(p) => {
|
ast::Pat::TuplePat(p) => {
|
||||||
let args = p.args().map(|p| self.collect_pat(p)).collect();
|
let (args, ellipsis) = self.collect_tuple_pat(p.args());
|
||||||
Pat::Tuple(args)
|
Pat::Tuple { args, ellipsis }
|
||||||
}
|
}
|
||||||
ast::Pat::PlaceholderPat(_) | ast::Pat::DotDotPat(_) => Pat::Wild,
|
ast::Pat::PlaceholderPat(_) => Pat::Wild,
|
||||||
ast::Pat::RecordPat(p) => {
|
ast::Pat::RecordPat(p) => {
|
||||||
let path = p.path().and_then(|path| self.expander.parse_path(path));
|
let path = p.path().and_then(|path| self.expander.parse_path(path));
|
||||||
let record_field_pat_list =
|
let record_field_pat_list =
|
||||||
|
@ -665,6 +666,9 @@ impl ExprCollector<'_> {
|
||||||
Pat::Missing
|
Pat::Missing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ast::Pat::DotDotPat(_) => unreachable!(
|
||||||
|
"`DotDotPat` requires special handling and should not be mapped to a Pat."
|
||||||
|
),
|
||||||
// FIXME: implement
|
// FIXME: implement
|
||||||
ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
|
ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
|
||||||
};
|
};
|
||||||
|
@ -679,6 +683,19 @@ impl ExprCollector<'_> {
|
||||||
self.missing_pat()
|
self.missing_pat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Vec<PatId>, Option<usize>) {
|
||||||
|
// Find the location of the `..`, if there is one. Note that we do not
|
||||||
|
// consider the possiblity of there being multiple `..` here.
|
||||||
|
let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::DotDotPat(_)));
|
||||||
|
// We want to skip the `..` pattern here, since we account for it above.
|
||||||
|
let args = args
|
||||||
|
.filter(|p| !matches!(p, ast::Pat::DotDotPat(_)))
|
||||||
|
.map(|p| self.collect_pat(p))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
(args, ellipsis)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ast::BinOp> for BinaryOp {
|
impl From<ast::BinOp> for BinaryOp {
|
||||||
|
|
|
@ -374,7 +374,7 @@ pub struct RecordFieldPat {
|
||||||
pub enum Pat {
|
pub enum Pat {
|
||||||
Missing,
|
Missing,
|
||||||
Wild,
|
Wild,
|
||||||
Tuple(Vec<PatId>),
|
Tuple { args: Vec<PatId>, ellipsis: Option<usize> },
|
||||||
Or(Vec<PatId>),
|
Or(Vec<PatId>),
|
||||||
Record { path: Option<Path>, args: Vec<RecordFieldPat>, ellipsis: bool },
|
Record { path: Option<Path>, args: Vec<RecordFieldPat>, ellipsis: bool },
|
||||||
Range { start: ExprId, end: ExprId },
|
Range { start: ExprId, end: ExprId },
|
||||||
|
@ -382,7 +382,7 @@ pub enum Pat {
|
||||||
Path(Path),
|
Path(Path),
|
||||||
Lit(ExprId),
|
Lit(ExprId),
|
||||||
Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
|
Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
|
||||||
TupleStruct { path: Option<Path>, args: Vec<PatId> },
|
TupleStruct { path: Option<Path>, args: Vec<PatId>, ellipsis: Option<usize> },
|
||||||
Ref { pat: PatId, mutability: Mutability },
|
Ref { pat: PatId, mutability: Mutability },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ impl Pat {
|
||||||
Pat::Bind { subpat, .. } => {
|
Pat::Bind { subpat, .. } => {
|
||||||
subpat.iter().copied().for_each(f);
|
subpat.iter().copied().for_each(f);
|
||||||
}
|
}
|
||||||
Pat::Or(args) | Pat::Tuple(args) | Pat::TupleStruct { args, .. } => {
|
Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => {
|
||||||
args.iter().copied().for_each(f);
|
args.iter().copied().for_each(f);
|
||||||
}
|
}
|
||||||
Pat::Ref { pat, .. } => f(*pat),
|
Pat::Ref { pat, .. } => f(*pat),
|
||||||
|
|
|
@ -289,7 +289,7 @@ impl PatStack {
|
||||||
Self::from_slice(&self.0[1..])
|
Self::from_slice(&self.0[1..])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_head_with(&self, pat_ids: &[PatId]) -> PatStack {
|
fn replace_head_with<T: Into<PatIdOrWild> + Copy>(&self, pat_ids: &[T]) -> PatStack {
|
||||||
let mut patterns: PatStackInner = smallvec![];
|
let mut patterns: PatStackInner = smallvec![];
|
||||||
for pat in pat_ids {
|
for pat in pat_ids {
|
||||||
patterns.push((*pat).into());
|
patterns.push((*pat).into());
|
||||||
|
@ -320,12 +320,14 @@ impl PatStack {
|
||||||
constructor: &Constructor,
|
constructor: &Constructor,
|
||||||
) -> MatchCheckResult<Option<PatStack>> {
|
) -> MatchCheckResult<Option<PatStack>> {
|
||||||
let result = match (self.head().as_pat(cx), constructor) {
|
let result = match (self.head().as_pat(cx), constructor) {
|
||||||
(Pat::Tuple(ref pat_ids), Constructor::Tuple { arity }) => {
|
(Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => {
|
||||||
debug_assert_eq!(
|
if ellipsis.is_some() {
|
||||||
pat_ids.len(),
|
// If there are ellipsis here, we should add the correct number of
|
||||||
*arity,
|
// Pat::Wild patterns to `pat_ids`. We should be able to use the
|
||||||
"we type check before calling this code, so we should never hit this case",
|
// constructors arity for this, but at the time of writing we aren't
|
||||||
);
|
// correctly calculating this arity when ellipsis are present.
|
||||||
|
return Err(MatchCheckErr::NotImplemented);
|
||||||
|
}
|
||||||
|
|
||||||
Some(self.replace_head_with(pat_ids))
|
Some(self.replace_head_with(pat_ids))
|
||||||
}
|
}
|
||||||
|
@ -351,19 +353,47 @@ impl PatStack {
|
||||||
Some(self.to_tail())
|
Some(self.to_tail())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Pat::TupleStruct { args: ref pat_ids, .. }, Constructor::Enum(enum_constructor)) => {
|
(
|
||||||
|
Pat::TupleStruct { args: ref pat_ids, ellipsis, .. },
|
||||||
|
Constructor::Enum(enum_constructor),
|
||||||
|
) => {
|
||||||
let pat_id = self.head().as_id().expect("we know this isn't a wild");
|
let pat_id = self.head().as_id().expect("we know this isn't a wild");
|
||||||
if !enum_variant_matches(cx, pat_id, *enum_constructor) {
|
if !enum_variant_matches(cx, pat_id, *enum_constructor) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// If the enum variant matches, then we need to confirm
|
let constructor_arity = constructor.arity(cx)?;
|
||||||
// that the number of patterns aligns with the expected
|
if let Some(ellipsis_position) = ellipsis {
|
||||||
// number of patterns for that enum variant.
|
// If there are ellipsis in the pattern, the ellipsis must take the place
|
||||||
if pat_ids.len() != constructor.arity(cx)? {
|
// of at least one sub-pattern, so `pat_ids` should be smaller than the
|
||||||
return Err(MatchCheckErr::MalformedMatchArm);
|
// constructor arity.
|
||||||
|
if pat_ids.len() < constructor_arity {
|
||||||
|
let mut new_patterns: Vec<PatIdOrWild> = vec![];
|
||||||
|
|
||||||
|
for pat_id in &pat_ids[0..ellipsis_position] {
|
||||||
|
new_patterns.push((*pat_id).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _ in 0..(constructor_arity - pat_ids.len()) {
|
||||||
|
new_patterns.push(PatIdOrWild::Wild);
|
||||||
|
}
|
||||||
|
|
||||||
|
for pat_id in &pat_ids[ellipsis_position..pat_ids.len()] {
|
||||||
|
new_patterns.push((*pat_id).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(self.replace_head_with(&new_patterns))
|
||||||
|
} else {
|
||||||
|
return Err(MatchCheckErr::MalformedMatchArm);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If there is no ellipsis in the tuple pattern, the number
|
||||||
|
// of patterns must equal the constructor arity.
|
||||||
|
if pat_ids.len() == constructor_arity {
|
||||||
Some(self.replace_head_with(pat_ids))
|
Some(self.replace_head_with(pat_ids))
|
||||||
|
} else {
|
||||||
|
return Err(MatchCheckErr::MalformedMatchArm);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Pat::Or(_), _) => return Err(MatchCheckErr::NotImplemented),
|
(Pat::Or(_), _) => return Err(MatchCheckErr::NotImplemented),
|
||||||
|
@ -644,7 +674,11 @@ impl Constructor {
|
||||||
fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Option<Constructor>> {
|
fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Option<Constructor>> {
|
||||||
let res = match pat.as_pat(cx) {
|
let res = match pat.as_pat(cx) {
|
||||||
Pat::Wild => None,
|
Pat::Wild => None,
|
||||||
Pat::Tuple(pats) => Some(Constructor::Tuple { arity: pats.len() }),
|
// FIXME somehow create the Tuple constructor with the proper arity. If there are
|
||||||
|
// ellipsis, the arity is not equal to the number of patterns.
|
||||||
|
Pat::Tuple { args: pats, ellipsis } if ellipsis.is_none() => {
|
||||||
|
Some(Constructor::Tuple { arity: pats.len() })
|
||||||
|
}
|
||||||
Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] {
|
Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] {
|
||||||
Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)),
|
Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)),
|
||||||
_ => return Err(MatchCheckErr::NotImplemented),
|
_ => return Err(MatchCheckErr::NotImplemented),
|
||||||
|
@ -1506,6 +1540,67 @@ mod tests {
|
||||||
check_no_diagnostic(content);
|
check_no_diagnostic(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_tuple_partial_ellipsis_2_no_diagnostic() {
|
||||||
|
let content = r"
|
||||||
|
enum Either {
|
||||||
|
A(bool, bool, bool, bool),
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
fn test_fn() {
|
||||||
|
match Either::B {
|
||||||
|
Either::A(true, .., true) => {},
|
||||||
|
Either::A(true, .., false) => {},
|
||||||
|
Either::A(.., true) => {},
|
||||||
|
Either::A(.., false) => {},
|
||||||
|
Either::B => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
check_no_diagnostic(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_tuple_partial_ellipsis_missing_arm() {
|
||||||
|
let content = r"
|
||||||
|
enum Either {
|
||||||
|
A(bool, bool, bool, bool),
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
fn test_fn() {
|
||||||
|
match Either::B {
|
||||||
|
Either::A(true, .., true) => {},
|
||||||
|
Either::A(true, .., false) => {},
|
||||||
|
Either::A(false, .., false) => {},
|
||||||
|
Either::B => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
check_diagnostic(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_tuple_partial_ellipsis_2_missing_arm() {
|
||||||
|
let content = r"
|
||||||
|
enum Either {
|
||||||
|
A(bool, bool, bool, bool),
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
fn test_fn() {
|
||||||
|
match Either::B {
|
||||||
|
Either::A(true, .., true) => {},
|
||||||
|
Either::A(true, .., false) => {},
|
||||||
|
Either::A(.., true) => {},
|
||||||
|
Either::B => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
check_diagnostic(content);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn enum_tuple_ellipsis_no_diagnostic() {
|
fn enum_tuple_ellipsis_no_diagnostic() {
|
||||||
let content = r"
|
let content = r"
|
||||||
|
@ -1645,11 +1740,7 @@ mod false_negatives {
|
||||||
";
|
";
|
||||||
|
|
||||||
// This is a false negative.
|
// This is a false negative.
|
||||||
// The `..` pattern is currently lowered to a single `Pat::Wild`
|
// We don't currently handle tuple patterns with ellipsis.
|
||||||
// no matter how many fields the `..` pattern is covering. This
|
|
||||||
// causes the match arm in this test not to type check against
|
|
||||||
// the match expression, which causes this diagnostic not to
|
|
||||||
// fire.
|
|
||||||
check_no_diagnostic(content);
|
check_no_diagnostic(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1664,32 +1755,7 @@ mod false_negatives {
|
||||||
";
|
";
|
||||||
|
|
||||||
// This is a false negative.
|
// This is a false negative.
|
||||||
// See comments on `tuple_of_bools_with_ellipsis_at_end_missing_arm`.
|
// We don't currently handle tuple patterns with ellipsis.
|
||||||
check_no_diagnostic(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn enum_tuple_partial_ellipsis_missing_arm() {
|
|
||||||
let content = r"
|
|
||||||
enum Either {
|
|
||||||
A(bool, bool, bool, bool),
|
|
||||||
B,
|
|
||||||
}
|
|
||||||
fn test_fn() {
|
|
||||||
match Either::B {
|
|
||||||
Either::A(true, .., true) => {},
|
|
||||||
Either::A(true, .., false) => {},
|
|
||||||
Either::A(false, .., false) => {},
|
|
||||||
Either::B => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
// This is a false negative.
|
|
||||||
// The `..` pattern is currently lowered to a single `Pat::Wild`
|
|
||||||
// no matter how many fields the `..` pattern is covering. This
|
|
||||||
// causes us to return a `MatchCheckErr::MalformedMatchArm` in
|
|
||||||
// `Pat::specialize_constructor`.
|
|
||||||
check_no_diagnostic(content);
|
check_no_diagnostic(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ impl<'a> InferenceContext<'a> {
|
||||||
let body = Arc::clone(&self.body); // avoid borrow checker problem
|
let body = Arc::clone(&self.body); // avoid borrow checker problem
|
||||||
|
|
||||||
let is_non_ref_pat = match &body[pat] {
|
let is_non_ref_pat = match &body[pat] {
|
||||||
Pat::Tuple(..)
|
Pat::Tuple { .. }
|
||||||
| Pat::Or(..)
|
| Pat::Or(..)
|
||||||
| Pat::TupleStruct { .. }
|
| Pat::TupleStruct { .. }
|
||||||
| Pat::Record { .. }
|
| Pat::Record { .. }
|
||||||
|
@ -116,7 +116,7 @@ impl<'a> InferenceContext<'a> {
|
||||||
let expected = expected;
|
let expected = expected;
|
||||||
|
|
||||||
let ty = match &body[pat] {
|
let ty = match &body[pat] {
|
||||||
Pat::Tuple(ref args) => {
|
Pat::Tuple { ref args, .. } => {
|
||||||
let expectations = match expected.as_tuple() {
|
let expectations = match expected.as_tuple() {
|
||||||
Some(parameters) => &*parameters.0,
|
Some(parameters) => &*parameters.0,
|
||||||
_ => &[],
|
_ => &[],
|
||||||
|
@ -155,7 +155,7 @@ impl<'a> InferenceContext<'a> {
|
||||||
let subty = self.infer_pat(*pat, expectation, default_bm);
|
let subty = self.infer_pat(*pat, expectation, default_bm);
|
||||||
Ty::apply_one(TypeCtor::Ref(*mutability), subty)
|
Ty::apply_one(TypeCtor::Ref(*mutability), subty)
|
||||||
}
|
}
|
||||||
Pat::TupleStruct { path: p, args: subpats } => {
|
Pat::TupleStruct { path: p, args: subpats, .. } => {
|
||||||
self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat)
|
self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat)
|
||||||
}
|
}
|
||||||
Pat::Record { path: p, args: fields, ellipsis: _ } => {
|
Pat::Record { path: p, args: fields, ellipsis: _ } => {
|
||||||
|
|
Loading…
Reference in a new issue