Skip to content

Commit 35aeecb

Browse files
committed
Auto merge of #51201 - estebank:dotdot, r=petrochenkov
Accept `..` in incorrect position to avoid further errors We currently give a specific message when encountering a `..` anywhere other than the end of a pattern. Modify the parser to accept it (while still emitting the error) so that we don't also trigger "missing fields in pattern" errors afterwards. Add suggestions to either remove trailing `,` or moving the `..` to the end. Follow up to #49268.
2 parents 9ac3725 + d66d35b commit 35aeecb

File tree

4 files changed

+169
-78
lines changed

4 files changed

+169
-78
lines changed

src/libsyntax/parse/parser.rs

+135-69
Original file line numberDiff line numberDiff line change
@@ -3710,26 +3710,89 @@ impl<'a> Parser<'a> {
37103710
Ok((before, slice, after))
37113711
}
37123712

3713+
fn parse_pat_field(
3714+
&mut self,
3715+
lo: Span,
3716+
attrs: Vec<Attribute>
3717+
) -> PResult<'a, codemap::Spanned<ast::FieldPat>> {
3718+
// Check if a colon exists one ahead. This means we're parsing a fieldname.
3719+
let hi;
3720+
let (subpat, fieldname, is_shorthand) = if self.look_ahead(1, |t| t == &token::Colon) {
3721+
// Parsing a pattern of the form "fieldname: pat"
3722+
let fieldname = self.parse_field_name()?;
3723+
self.bump();
3724+
let pat = self.parse_pat()?;
3725+
hi = pat.span;
3726+
(pat, fieldname, false)
3727+
} else {
3728+
// Parsing a pattern of the form "(box) (ref) (mut) fieldname"
3729+
let is_box = self.eat_keyword(keywords::Box);
3730+
let boxed_span = self.span;
3731+
let is_ref = self.eat_keyword(keywords::Ref);
3732+
let is_mut = self.eat_keyword(keywords::Mut);
3733+
let fieldname = self.parse_ident()?;
3734+
hi = self.prev_span;
3735+
3736+
let bind_type = match (is_ref, is_mut) {
3737+
(true, true) => BindingMode::ByRef(Mutability::Mutable),
3738+
(true, false) => BindingMode::ByRef(Mutability::Immutable),
3739+
(false, true) => BindingMode::ByValue(Mutability::Mutable),
3740+
(false, false) => BindingMode::ByValue(Mutability::Immutable),
3741+
};
3742+
let fieldpat = P(Pat {
3743+
id: ast::DUMMY_NODE_ID,
3744+
node: PatKind::Ident(bind_type, fieldname, None),
3745+
span: boxed_span.to(hi),
3746+
});
3747+
3748+
let subpat = if is_box {
3749+
P(Pat {
3750+
id: ast::DUMMY_NODE_ID,
3751+
node: PatKind::Box(fieldpat),
3752+
span: lo.to(hi),
3753+
})
3754+
} else {
3755+
fieldpat
3756+
};
3757+
(subpat, fieldname, true)
3758+
};
3759+
3760+
Ok(codemap::Spanned {
3761+
span: lo.to(hi),
3762+
node: ast::FieldPat {
3763+
ident: fieldname,
3764+
pat: subpat,
3765+
is_shorthand,
3766+
attrs: attrs.into(),
3767+
}
3768+
})
3769+
}
3770+
37133771
/// Parse the fields of a struct-like pattern
37143772
fn parse_pat_fields(&mut self) -> PResult<'a, (Vec<codemap::Spanned<ast::FieldPat>>, bool)> {
37153773
let mut fields = Vec::new();
37163774
let mut etc = false;
3717-
let mut first = true;
3718-
while self.token != token::CloseDelim(token::Brace) {
3719-
if first {
3720-
first = false;
3721-
} else {
3722-
self.expect(&token::Comma)?;
3723-
// accept trailing commas
3724-
if self.check(&token::CloseDelim(token::Brace)) { break }
3725-
}
3775+
let mut ate_comma = true;
3776+
let mut delayed_err: Option<DiagnosticBuilder<'a>> = None;
3777+
let mut etc_span = None;
37263778

3779+
while self.token != token::CloseDelim(token::Brace) {
37273780
let attrs = self.parse_outer_attributes()?;
37283781
let lo = self.span;
3729-
let hi;
3782+
3783+
// check that a comma comes after every field
3784+
if !ate_comma {
3785+
let err = self.struct_span_err(self.prev_span, "expected `,`");
3786+
return Err(err);
3787+
}
3788+
ate_comma = false;
37303789

37313790
if self.check(&token::DotDot) || self.token == token::DotDotDot {
3791+
etc = true;
3792+
let mut etc_sp = self.span;
3793+
37323794
if self.token == token::DotDotDot { // Issue #46718
3795+
// Accept `...` as if it were `..` to avoid further errors
37333796
let mut err = self.struct_span_err(self.span,
37343797
"expected field pattern, found `...`");
37353798
err.span_suggestion_with_applicability(
@@ -3740,73 +3803,76 @@ impl<'a> Parser<'a> {
37403803
);
37413804
err.emit();
37423805
}
3806+
self.bump(); // `..` || `...`:w
37433807

3744-
self.bump();
3745-
if self.token != token::CloseDelim(token::Brace) {
3746-
let token_str = self.this_token_to_string();
3747-
let mut err = self.fatal(&format!("expected `{}`, found `{}`", "}", token_str));
3748-
if self.token == token::Comma { // Issue #49257
3749-
err.span_label(self.span,
3750-
"`..` must be in the last position, \
3751-
and cannot have a trailing comma");
3808+
if self.token == token::CloseDelim(token::Brace) {
3809+
etc_span = Some(etc_sp);
3810+
break;
3811+
}
3812+
let token_str = self.this_token_to_string();
3813+
let mut err = self.fatal(&format!("expected `}}`, found `{}`", token_str));
3814+
3815+
err.span_label(self.span, "expected `}`");
3816+
let mut comma_sp = None;
3817+
if self.token == token::Comma { // Issue #49257
3818+
etc_sp = etc_sp.to(self.sess.codemap().span_until_non_whitespace(self.span));
3819+
err.span_label(etc_sp,
3820+
"`..` must be at the end and cannot have a trailing comma");
3821+
comma_sp = Some(self.span);
3822+
self.bump();
3823+
ate_comma = true;
3824+
}
3825+
3826+
etc_span = Some(etc_sp);
3827+
if self.token == token::CloseDelim(token::Brace) {
3828+
// If the struct looks otherwise well formed, recover and continue.
3829+
if let Some(sp) = comma_sp {
3830+
err.span_suggestion_short(sp, "remove this comma", "".into());
3831+
}
3832+
err.emit();
3833+
break;
3834+
} else if self.token.is_ident() && ate_comma {
3835+
// Accept fields coming after `..,`.
3836+
// This way we avoid "pattern missing fields" errors afterwards.
3837+
// We delay this error until the end in order to have a span for a
3838+
// suggested fix.
3839+
if let Some(mut delayed_err) = delayed_err {
3840+
delayed_err.emit();
3841+
return Err(err);
37523842
} else {
3753-
err.span_label(self.span, "expected `}`");
3843+
delayed_err = Some(err);
3844+
}
3845+
} else {
3846+
if let Some(mut err) = delayed_err {
3847+
err.emit();
37543848
}
37553849
return Err(err);
37563850
}
3757-
etc = true;
3758-
break;
37593851
}
37603852

3761-
// Check if a colon exists one ahead. This means we're parsing a fieldname.
3762-
let (subpat, fieldname, is_shorthand) = if self.look_ahead(1, |t| t == &token::Colon) {
3763-
// Parsing a pattern of the form "fieldname: pat"
3764-
let fieldname = self.parse_field_name()?;
3765-
self.bump();
3766-
let pat = self.parse_pat()?;
3767-
hi = pat.span;
3768-
(pat, fieldname, false)
3769-
} else {
3770-
// Parsing a pattern of the form "(box) (ref) (mut) fieldname"
3771-
let is_box = self.eat_keyword(keywords::Box);
3772-
let boxed_span = self.span;
3773-
let is_ref = self.eat_keyword(keywords::Ref);
3774-
let is_mut = self.eat_keyword(keywords::Mut);
3775-
let fieldname = self.parse_ident()?;
3776-
hi = self.prev_span;
3777-
3778-
let bind_type = match (is_ref, is_mut) {
3779-
(true, true) => BindingMode::ByRef(Mutability::Mutable),
3780-
(true, false) => BindingMode::ByRef(Mutability::Immutable),
3781-
(false, true) => BindingMode::ByValue(Mutability::Mutable),
3782-
(false, false) => BindingMode::ByValue(Mutability::Immutable),
3783-
};
3784-
let fieldpat = P(Pat {
3785-
id: ast::DUMMY_NODE_ID,
3786-
node: PatKind::Ident(bind_type, fieldname, None),
3787-
span: boxed_span.to(hi),
3788-
});
3789-
3790-
let subpat = if is_box {
3791-
P(Pat {
3792-
id: ast::DUMMY_NODE_ID,
3793-
node: PatKind::Box(fieldpat),
3794-
span: lo.to(hi),
3795-
})
3796-
} else {
3797-
fieldpat
3798-
};
3799-
(subpat, fieldname, true)
3800-
};
3801-
3802-
fields.push(codemap::Spanned { span: lo.to(hi),
3803-
node: ast::FieldPat {
3804-
ident: fieldname,
3805-
pat: subpat,
3806-
is_shorthand,
3807-
attrs: attrs.into(),
3808-
}
3853+
fields.push(match self.parse_pat_field(lo, attrs) {
3854+
Ok(field) => field,
3855+
Err(err) => {
3856+
if let Some(mut delayed_err) = delayed_err {
3857+
delayed_err.emit();
3858+
}
3859+
return Err(err);
3860+
}
38093861
});
3862+
ate_comma = self.eat(&token::Comma);
3863+
}
3864+
3865+
if let Some(mut err) = delayed_err {
3866+
if let Some(etc_span) = etc_span {
3867+
err.multipart_suggestion(
3868+
"move the `..` to the end of the field list",
3869+
vec![
3870+
(etc_span, "".into()),
3871+
(self.span, format!("{}.. }}", if ate_comma { "" } else { ", " })),
3872+
],
3873+
);
3874+
}
3875+
err.emit();
38103876
}
38113877
return Ok((fields, etc));
38123878
}

src/test/parse-fail/bind-struct-early-modifiers.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
fn main() {
1414
struct Foo { x: isize }
1515
match (Foo { x: 10 }) {
16-
Foo { ref x: ref x } => {}, //~ ERROR expected `,`, found `:`
16+
Foo { ref x: ref x } => {}, //~ ERROR expected `,`
1717
_ => {}
1818
}
1919
}

src/test/ui/issue-49257.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ struct Point { x: u8, y: u8 }
1717

1818
fn main() {
1919
let p = Point { x: 0, y: 0 };
20+
let Point { .., y, } = p; //~ ERROR expected `}`, found `,`
2021
let Point { .., y } = p; //~ ERROR expected `}`, found `,`
21-
//~| ERROR pattern does not mention fields `x`, `y`
22+
let Point { .., } = p; //~ ERROR expected `}`, found `,`
23+
let Point { .. } = p;
2224
}

src/test/ui/issue-49257.stderr

+30-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,38 @@
11
error: expected `}`, found `,`
22
--> $DIR/issue-49257.rs:20:19
33
|
4-
LL | let Point { .., y } = p; //~ ERROR expected `}`, found `,`
5-
| ^ `..` must be in the last position, and cannot have a trailing comma
4+
LL | let Point { .., y, } = p; //~ ERROR expected `}`, found `,`
5+
| --^
6+
| | |
7+
| | expected `}`
8+
| `..` must be at the end and cannot have a trailing comma
9+
help: move the `..` to the end of the field list
10+
|
11+
LL | let Point { y, .. } = p; //~ ERROR expected `}`, found `,`
12+
| -- ^^^^
613

7-
error[E0027]: pattern does not mention fields `x`, `y`
8-
--> $DIR/issue-49257.rs:20:9
14+
error: expected `}`, found `,`
15+
--> $DIR/issue-49257.rs:21:19
916
|
1017
LL | let Point { .., y } = p; //~ ERROR expected `}`, found `,`
11-
| ^^^^^^^^^^^^^^^ missing fields `x`, `y`
18+
| --^
19+
| | |
20+
| | expected `}`
21+
| `..` must be at the end and cannot have a trailing comma
22+
help: move the `..` to the end of the field list
23+
|
24+
LL | let Point { y , .. } = p; //~ ERROR expected `}`, found `,`
25+
| -- ^^^^^^
26+
27+
error: expected `}`, found `,`
28+
--> $DIR/issue-49257.rs:22:19
29+
|
30+
LL | let Point { .., } = p; //~ ERROR expected `}`, found `,`
31+
| --^
32+
| | |
33+
| | expected `}`
34+
| | help: remove this comma
35+
| `..` must be at the end and cannot have a trailing comma
1236

13-
error: aborting due to 2 previous errors
37+
error: aborting due to 3 previous errors
1438

15-
For more information about this error, try `rustc --explain E0027`.

0 commit comments

Comments
 (0)