Skip to content

Commit

Permalink
Rollup merge of rust-lang#54415 - petrochenkov:norollback, r=estebank
Browse files Browse the repository at this point in the history
parser: Tweak function parameter parsing to avoid rollback on succesfull path

Since rollback is not perfect and may e.g. leave non-fatal errors after it, we need to make sure compilation fails if it happens.
So in particular case of `fn parse_arg_general` we need to parse the "good" `TYPE` first and only then rollback and recover erroneous `PAT: TYPE` if necessary.
Found when working on rust-lang/rfcs#2544 (comment).

r? @ghost
  • Loading branch information
pietroalbini authored Sep 22, 2018
2 parents 0bf52b3 + 9784d35 commit 7c34cf7
Showing 1 changed file with 26 additions and 36 deletions.
62 changes: 26 additions & 36 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1781,27 +1781,32 @@ impl<'a> Parser<'a> {
(pat, self.parse_ty()?)
} else {
debug!("parse_arg_general ident_to_pat");
let parser_snapshot_before_ty = self.clone();
let mut ty = self.parse_ty();
if ty.is_ok() && self.token == token::Colon {
// This wasn't actually a type, but a pattern looking like a type,
// so we are going to rollback and re-parse for recovery.
ty = self.unexpected();
}
match ty {
Ok(ty) => {
let ident = Ident::new(keywords::Invalid.name(), self.prev_span);
let pat = P(Pat {
id: ast::DUMMY_NODE_ID,
node: PatKind::Ident(
BindingMode::ByValue(Mutability::Immutable), ident, None),
span: ty.span,
});
(pat, ty)
}
Err(mut err) => {
// Recover from attempting to parse the argument as a type without pattern.
err.cancel();
mem::replace(self, parser_snapshot_before_ty);
let pat = self.parse_pat()?;
self.expect(&token::Colon)?;
let ty = self.parse_ty()?;

let parser_snapshot_before_pat = self.clone();

// Once we can use edition 2018 in the compiler,
// replace this with real try blocks.
macro_rules! try_block {
($($inside:tt)*) => (
(||{ ::std::ops::Try::from_ok({ $($inside)* }) })()
)
}

// We're going to try parsing the argument as a pattern (even though it's not
// allowed). This way we can provide better errors to the user.
let pat_arg: PResult<'a, _> = try_block! {
let pat = self.parse_pat()?;
self.expect(&token::Colon)?;
(pat, self.parse_ty()?)
};

match pat_arg {
Ok((pat, ty)) => {
let mut err = self.diagnostic().struct_span_err_with_code(
pat.span,
"patterns aren't allowed in methods without bodies",
Expand All @@ -1814,6 +1819,7 @@ impl<'a> Parser<'a> {
Applicability::MachineApplicable,
);
err.emit();

// Pretend the pattern is `_`, to avoid duplicate errors from AST validation.
let pat = P(Pat {
node: PatKind::Wild,
Expand All @@ -1822,22 +1828,6 @@ impl<'a> Parser<'a> {
});
(pat, ty)
}
Err(mut err) => {
err.cancel();
// Recover from attempting to parse the argument as a pattern. This means
// the type is alone, with no name, e.g. `fn foo(u32)`.
mem::replace(self, parser_snapshot_before_pat);
debug!("parse_arg_general ident_to_pat");
let ident = Ident::new(keywords::Invalid.name(), self.prev_span);
let ty = self.parse_ty()?;
let pat = P(Pat {
id: ast::DUMMY_NODE_ID,
node: PatKind::Ident(
BindingMode::ByValue(Mutability::Immutable), ident, None),
span: ty.span,
});
(pat, ty)
}
}
};

Expand Down

0 comments on commit 7c34cf7

Please sign in to comment.