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:
parent
8f4a5429c2
commit
cbc70a0d68
3 changed files with 170 additions and 82 deletions
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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`.
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue