Skip to content

Commit 8ec9f4c

Browse files
Rollup merge of rust-lang#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 rust-lang#49268.
2 parents 8e9a5e9 + d66d35b commit 8ec9f4c

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
@@ -3719,26 +3719,89 @@ impl<'a> Parser<'a> {
37193719
Ok((before, slice, after))
37203720
}
37213721

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

3788+
while self.token != token::CloseDelim(token::Brace) {
37363789
let attrs = self.parse_outer_attributes()?;
37373790
let lo = self.span;
3738-
let hi;
3791+
3792+
// check that a comma comes after every field
3793+
if !ate_comma {
3794+
let err = self.struct_span_err(self.prev_span, "expected `,`");
3795+
return Err(err);
3796+
}
3797+
ate_comma = false;
37393798

37403799
if self.check(&token::DotDot) || self.token == token::DotDotDot {
3800+
etc = true;
3801+
let mut etc_sp = self.span;
3802+
37413803
if self.token == token::DotDotDot { // Issue #46718
3804+
// Accept `...` as if it were `..` to avoid further errors
37423805
let mut err = self.struct_span_err(self.span,
37433806
"expected field pattern, found `...`");
37443807
err.span_suggestion_with_applicability(
@@ -3749,73 +3812,76 @@ impl<'a> Parser<'a> {
37493812
);
37503813
err.emit();
37513814
}
3815+
self.bump(); // `..` || `...`:w
37523816

3753-
self.bump();
3754-
if self.token != token::CloseDelim(token::Brace) {
3755-
let token_str = self.this_token_to_string();
3756-
let mut err = self.fatal(&format!("expected `{}`, found `{}`", "}", token_str));
3757-
if self.token == token::Comma { // Issue #49257
3758-
err.span_label(self.span,
3759-
"`..` must be in the last position, \
3760-
and cannot have a trailing comma");
3817+
if self.token == token::CloseDelim(token::Brace) {
3818+
etc_span = Some(etc_sp);
3819+
break;
3820+
}
3821+
let token_str = self.this_token_to_string();
3822+
let mut err = self.fatal(&format!("expected `}}`, found `{}`", token_str));
3823+
3824+
err.span_label(self.span, "expected `}`");
3825+
let mut comma_sp = None;
3826+
if self.token == token::Comma { // Issue #49257
3827+
etc_sp = etc_sp.to(self.sess.codemap().span_until_non_whitespace(self.span));
3828+
err.span_label(etc_sp,
3829+
"`..` must be at the end and cannot have a trailing comma");
3830+
comma_sp = Some(self.span);
3831+
self.bump();
3832+
ate_comma = true;
3833+
}
3834+
3835+
etc_span = Some(etc_sp);
3836+
if self.token == token::CloseDelim(token::Brace) {
3837+
// If the struct looks otherwise well formed, recover and continue.
3838+
if let Some(sp) = comma_sp {
3839+
err.span_suggestion_short(sp, "remove this comma", "".into());
3840+
}
3841+
err.emit();
3842+
break;
3843+
} else if self.token.is_ident() && ate_comma {
3844+
// Accept fields coming after `..,`.
3845+
// This way we avoid "pattern missing fields" errors afterwards.
3846+
// We delay this error until the end in order to have a span for a
3847+
// suggested fix.
3848+
if let Some(mut delayed_err) = delayed_err {
3849+
delayed_err.emit();
3850+
return Err(err);
37613851
} else {
3762-
err.span_label(self.span, "expected `}`");
3852+
delayed_err = Some(err);
3853+
}
3854+
} else {
3855+
if let Some(mut err) = delayed_err {
3856+
err.emit();
37633857
}
37643858
return Err(err);
37653859
}
3766-
etc = true;
3767-
break;
37683860
}
37693861

3770-
// Check if a colon exists one ahead. This means we're parsing a fieldname.
3771-
let (subpat, fieldname, is_shorthand) = if self.look_ahead(1, |t| t == &token::Colon) {
3772-
// Parsing a pattern of the form "fieldname: pat"
3773-
let fieldname = self.parse_field_name()?;
3774-
self.bump();
3775-
let pat = self.parse_pat()?;
3776-
hi = pat.span;
3777-
(pat, fieldname, false)
3778-
} else {
3779-
// Parsing a pattern of the form "(box) (ref) (mut) fieldname"
3780-
let is_box = self.eat_keyword(keywords::Box);
3781-
let boxed_span = self.span;
3782-
let is_ref = self.eat_keyword(keywords::Ref);
3783-
let is_mut = self.eat_keyword(keywords::Mut);
3784-
let fieldname = self.parse_ident()?;
3785-
hi = self.prev_span;
3786-
3787-
let bind_type = match (is_ref, is_mut) {
3788-
(true, true) => BindingMode::ByRef(Mutability::Mutable),
3789-
(true, false) => BindingMode::ByRef(Mutability::Immutable),
3790-
(false, true) => BindingMode::ByValue(Mutability::Mutable),
3791-
(false, false) => BindingMode::ByValue(Mutability::Immutable),
3792-
};
3793-
let fieldpat = P(Pat {
3794-
id: ast::DUMMY_NODE_ID,
3795-
node: PatKind::Ident(bind_type, fieldname, None),
3796-
span: boxed_span.to(hi),
3797-
});
3798-
3799-
let subpat = if is_box {
3800-
P(Pat {
3801-
id: ast::DUMMY_NODE_ID,
3802-
node: PatKind::Box(fieldpat),
3803-
span: lo.to(hi),
3804-
})
3805-
} else {
3806-
fieldpat
3807-
};
3808-
(subpat, fieldname, true)
3809-
};
3810-
3811-
fields.push(codemap::Spanned { span: lo.to(hi),
3812-
node: ast::FieldPat {
3813-
ident: fieldname,
3814-
pat: subpat,
3815-
is_shorthand,
3816-
attrs: attrs.into(),
3817-
}
3862+
fields.push(match self.parse_pat_field(lo, attrs) {
3863+
Ok(field) => field,
3864+
Err(err) => {
3865+
if let Some(mut delayed_err) = delayed_err {
3866+
delayed_err.emit();
3867+
}
3868+
return Err(err);
3869+
}
38183870
});
3871+
ate_comma = self.eat(&token::Comma);
3872+
}
3873+
3874+
if let Some(mut err) = delayed_err {
3875+
if let Some(etc_span) = etc_span {
3876+
err.multipart_suggestion(
3877+
"move the `..` to the end of the field list",
3878+
vec![
3879+
(etc_span, "".into()),
3880+
(self.span, format!("{}.. }}", if ate_comma { "" } else { ", " })),
3881+
],
3882+
);
3883+
}
3884+
err.emit();
38193885
}
38203886
return Ok((fields, etc));
38213887
}

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)