Improve diagnostics for incorrect .. usage

When using `..` somewhere other than the end, parse the rest of the
pattern correctly while still emitting an error.

Add suggestions to either remove trailing `,` or moving the `..` to the
end.
This commit is contained in:
Esteban Küber 2018-05-29 22:31:00 -07:00
parent 8f4a5429c2
commit cbc70a0d68
3 changed files with 170 additions and 82 deletions

View file

@ -3710,26 +3710,89 @@ impl<'a> Parser<'a> {
Ok((before, slice, after)) Ok((before, slice, after))
} }
fn parse_pat_field(
&mut self,
lo: Span,
attrs: Vec<Attribute>
) -> PResult<'a, codemap::Spanned<ast::FieldPat>> {
// Check if a colon exists one ahead. This means we're parsing a fieldname.
let hi;
let (subpat, fieldname, is_shorthand) = if self.look_ahead(1, |t| t == &token::Colon) {
// Parsing a pattern of the form "fieldname: pat"
let fieldname = self.parse_field_name()?;
self.bump();
let pat = self.parse_pat()?;
hi = pat.span;
(pat, fieldname, false)
} else {
// Parsing a pattern of the form "(box) (ref) (mut) fieldname"
let is_box = self.eat_keyword(keywords::Box);
let boxed_span = self.span;
let is_ref = self.eat_keyword(keywords::Ref);
let is_mut = self.eat_keyword(keywords::Mut);
let fieldname = self.parse_ident()?;
hi = self.prev_span;
let bind_type = match (is_ref, is_mut) {
(true, true) => BindingMode::ByRef(Mutability::Mutable),
(true, false) => BindingMode::ByRef(Mutability::Immutable),
(false, true) => BindingMode::ByValue(Mutability::Mutable),
(false, false) => BindingMode::ByValue(Mutability::Immutable),
};
let fieldpat = P(Pat {
id: ast::DUMMY_NODE_ID,
node: PatKind::Ident(bind_type, fieldname, None),
span: boxed_span.to(hi),
});
let subpat = if is_box {
P(Pat {
id: ast::DUMMY_NODE_ID,
node: PatKind::Box(fieldpat),
span: lo.to(hi),
})
} else {
fieldpat
};
(subpat, fieldname, true)
};
Ok(codemap::Spanned {
span: lo.to(hi),
node: ast::FieldPat {
ident: fieldname,
pat: subpat,
is_shorthand,
attrs: attrs.into(),
}
})
}
/// Parse the fields of a struct-like pattern /// Parse the fields of a struct-like pattern
fn parse_pat_fields(&mut self) -> PResult<'a, (Vec<codemap::Spanned<ast::FieldPat>>, bool)> { fn parse_pat_fields(&mut self) -> PResult<'a, (Vec<codemap::Spanned<ast::FieldPat>>, bool)> {
let mut fields = Vec::new(); let mut fields = Vec::new();
let mut etc = false; let mut etc = false;
let mut first = true; let mut ate_comma = true;
while self.token != token::CloseDelim(token::Brace) { let mut delayed_err: Option<DiagnosticBuilder<'a>> = None;
if first { let mut etc_span = None;
first = false;
} else {
self.expect(&token::Comma)?;
// accept trailing commas
if self.check(&token::CloseDelim(token::Brace)) { break }
}
while self.token != token::CloseDelim(token::Brace) {
let attrs = self.parse_outer_attributes()?; let attrs = self.parse_outer_attributes()?;
let lo = self.span; let lo = self.span;
let hi;
// check that a comma comes after every field
if !ate_comma {
let err = self.struct_span_err(self.prev_span, "expected `,`");
return Err(err);
}
ate_comma = false;
if self.check(&token::DotDot) || self.token == token::DotDotDot { if self.check(&token::DotDot) || self.token == token::DotDotDot {
etc = true;
let mut etc_sp = self.span;
if self.token == token::DotDotDot { // Issue #46718 if self.token == token::DotDotDot { // Issue #46718
// Accept `...` as if it were `..` to avoid further errors
let mut err = self.struct_span_err(self.span, let mut err = self.struct_span_err(self.span,
"expected field pattern, found `...`"); "expected field pattern, found `...`");
err.span_suggestion_with_applicability( err.span_suggestion_with_applicability(
@ -3740,83 +3803,76 @@ impl<'a> Parser<'a> {
); );
err.emit(); err.emit();
} }
self.bump(); // `..` || `...`:w
self.bump(); if self.token == token::CloseDelim(token::Brace) {
if self.token != token::CloseDelim(token::Brace) { etc_span = Some(etc_sp);
let token_str = self.this_token_to_string(); break;
let mut err = self.fatal(&format!("expected `{}`, found `{}`", "}", token_str)); }
if self.token == token::Comma { // Issue #49257 let token_str = self.this_token_to_string();
err.span_label(self.span, let mut err = self.fatal(&format!("expected `}}`, found `{}`", token_str));
"`..` must be in the last position, \
and cannot have a trailing comma"); err.span_label(self.span, "expected `}`");
if self.look_ahead(1, |t| { let mut comma_sp = None;
t == &token::CloseDelim(token::Brace) || t.is_ident() if self.token == token::Comma { // Issue #49257
}) { etc_sp = etc_sp.to(self.sess.codemap().span_until_non_whitespace(self.span));
// If the struct looks otherwise well formed, recover and continue. err.span_label(etc_sp,
// This way we avoid "pattern missing fields" errors afterwards. "`..` must be at the end and cannot have a trailing comma");
err.emit(); comma_sp = Some(self.span);
self.bump(); self.bump();
etc = true; ate_comma = true;
break; }
}
etc_span = Some(etc_sp);
if self.token == token::CloseDelim(token::Brace) {
// If the struct looks otherwise well formed, recover and continue.
if let Some(sp) = comma_sp {
err.span_suggestion_short(sp, "remove this comma", "".into());
}
err.emit();
break;
} else if self.token.is_ident() && ate_comma {
// Accept fields coming after `..,`.
// This way we avoid "pattern missing fields" errors afterwards.
// We delay this error until the end in order to have a span for a
// suggested fix.
if let Some(mut delayed_err) = delayed_err {
delayed_err.emit();
return Err(err);
} else { } else {
err.span_label(self.span, "expected `}`"); delayed_err = Some(err);
}
} else {
if let Some(mut err) = delayed_err {
err.emit();
} }
return Err(err); return Err(err);
} }
etc = true;
break;
} }
// Check if a colon exists one ahead. This means we're parsing a fieldname. fields.push(match self.parse_pat_field(lo, attrs) {
let (subpat, fieldname, is_shorthand) = if self.look_ahead(1, |t| t == &token::Colon) { Ok(field) => field,
// Parsing a pattern of the form "fieldname: pat" Err(err) => {
let fieldname = self.parse_field_name()?; if let Some(mut delayed_err) = delayed_err {
self.bump(); delayed_err.emit();
let pat = self.parse_pat()?; }
hi = pat.span; return Err(err);
(pat, fieldname, false) }
} else {
// Parsing a pattern of the form "(box) (ref) (mut) fieldname"
let is_box = self.eat_keyword(keywords::Box);
let boxed_span = self.span;
let is_ref = self.eat_keyword(keywords::Ref);
let is_mut = self.eat_keyword(keywords::Mut);
let fieldname = self.parse_ident()?;
hi = self.prev_span;
let bind_type = match (is_ref, is_mut) {
(true, true) => BindingMode::ByRef(Mutability::Mutable),
(true, false) => BindingMode::ByRef(Mutability::Immutable),
(false, true) => BindingMode::ByValue(Mutability::Mutable),
(false, false) => BindingMode::ByValue(Mutability::Immutable),
};
let fieldpat = P(Pat {
id: ast::DUMMY_NODE_ID,
node: PatKind::Ident(bind_type, fieldname, None),
span: boxed_span.to(hi),
});
let subpat = if is_box {
P(Pat {
id: ast::DUMMY_NODE_ID,
node: PatKind::Box(fieldpat),
span: lo.to(hi),
})
} else {
fieldpat
};
(subpat, fieldname, true)
};
fields.push(codemap::Spanned { span: lo.to(hi),
node: ast::FieldPat {
ident: fieldname,
pat: subpat,
is_shorthand,
attrs: attrs.into(),
}
}); });
ate_comma = self.eat(&token::Comma);
}
if let Some(mut err) = delayed_err {
if let Some(etc_span) = etc_span {
err.multipart_suggestion(
"move the `..` to the end of the field list",
vec![
(etc_span, "".into()),
(self.span, ", .. }".into()),
],
);
}
err.emit();
} }
return Ok((fields, etc)); return Ok((fields, etc));
} }

View file

@ -17,5 +17,8 @@ struct Point { x: u8, y: u8 }
fn main() { fn main() {
let p = Point { x: 0, y: 0 }; let p = Point { x: 0, y: 0 };
let Point { .., y, } = p; //~ ERROR expected `}`, found `,`
let Point { .., y } = p; //~ ERROR expected `}`, found `,` let Point { .., y } = p; //~ ERROR expected `}`, found `,`
let Point { .., } = p; //~ ERROR expected `}`, found `,`
let Point { .. } = p;
} }

View file

@ -1,9 +1,38 @@
error: expected `}`, found `,` error: expected `}`, found `,`
--> $DIR/issue-49257.rs:20:19 --> $DIR/issue-49257.rs:20:19
| |
LL | let Point { .., y, } = p; //~ ERROR expected `}`, found `,`
| --^
| | |
| | expected `}`
| `..` must be at the end and cannot have a trailing comma
help: move the `..` to the end of the field list
|
LL | let Point { y, , .. } = p; //~ ERROR expected `}`, found `,`
| -- ^^^^^^
error: expected `}`, found `,`
--> $DIR/issue-49257.rs:21:19
|
LL | let Point { .., y } = p; //~ ERROR expected `}`, found `,` LL | let Point { .., y } = p; //~ ERROR expected `}`, found `,`
| ^ `..` must be in the last position, and cannot have a trailing comma | --^
| | |
| | expected `}`
| `..` must be at the end and cannot have a trailing comma
help: move the `..` to the end of the field list
|
LL | let Point { y , .. } = p; //~ ERROR expected `}`, found `,`
| -- ^^^^^^
error: aborting due to previous error error: expected `}`, found `,`
--> $DIR/issue-49257.rs:22:19
|
LL | let Point { .., } = p; //~ ERROR expected `}`, found `,`
| --^
| | |
| | expected `}`
| | help: remove this comma
| `..` must be at the end and cannot have a trailing comma
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0027`.