From 4d30b92e3e57cf606a25c807a9e4ab2b7a4d1064 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Sat, 21 Mar 2020 08:32:55 +0100 Subject: [PATCH] recover on `for<'a> |...| body` closures. --- src/librustc_parse/parser/expr.rs | 33 +++++++++++++++++-- src/librustc_parse/parser/generics.rs | 19 +++++------ src/librustc_parse/parser/item.rs | 2 +- .../ui/parser/recover-quantified-closure.rs | 10 ++++++ .../parser/recover-quantified-closure.stderr | 16 +++++++++ 5 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 src/test/ui/parser/recover-quantified-closure.rs create mode 100644 src/test/ui/parser/recover-quantified-closure.stderr diff --git a/src/librustc_parse/parser/expr.rs b/src/librustc_parse/parser/expr.rs index c65e99842c5dd..957bdd082bb62 100644 --- a/src/librustc_parse/parser/expr.rs +++ b/src/librustc_parse/parser/expr.rs @@ -925,8 +925,17 @@ impl<'a> Parser<'a> { self.parse_closure_expr(attrs) } else if self.eat_keyword(kw::If) { self.parse_if_expr(attrs) - } else if self.eat_keyword(kw::For) { - self.parse_for_expr(None, self.prev_token.span, attrs) + } else if self.check_keyword(kw::For) { + if self.choose_generics_over_qpath(1) { + // NOTE(Centril, eddyb): DO NOT REMOVE! Beyond providing parser recovery, + // this is an insurance policy in case we allow qpaths in (tuple-)struct patterns. + // When `for ::Proj in $expr $block` is wanted, + // you can disambiguate in favor of a pattern with `(...)`. + self.recover_quantified_closure_expr(attrs) + } else { + assert!(self.eat_keyword(kw::For)); + self.parse_for_expr(None, self.prev_token.span, attrs) + } } else if self.eat_keyword(kw::While) { self.parse_while_expr(None, self.prev_token.span, attrs) } else if let Some(label) = self.eat_label() { @@ -1416,6 +1425,26 @@ impl<'a> Parser<'a> { Ok(self.mk_expr(blk.span, ExprKind::Block(blk, opt_label), attrs)) } + /// Recover on an explicitly quantified closure expression, e.g., `for<'a> |x: &'a u8| *x + 1`. + fn recover_quantified_closure_expr(&mut self, attrs: AttrVec) -> PResult<'a, P> { + let lo = self.token.span; + let _ = self.parse_late_bound_lifetime_defs()?; + let span_for = lo.to(self.prev_token.span); + let closure = self.parse_closure_expr(attrs)?; + + self.struct_span_err(span_for, "cannot introduce explicit parameters for a closure") + .span_label(closure.span, "the parameters are attached to this closure") + .span_suggestion( + span_for, + "remove the parameters", + String::new(), + Applicability::MachineApplicable, + ) + .emit(); + + Ok(self.mk_expr_err(lo.to(closure.span))) + } + /// Parses a closure expression (e.g., `move |args| expr`). fn parse_closure_expr(&mut self, attrs: AttrVec) -> PResult<'a, P> { let lo = self.token.span; diff --git a/src/librustc_parse/parser/generics.rs b/src/librustc_parse/parser/generics.rs index 59fd5f7c4be1f..3442c5081c18f 100644 --- a/src/librustc_parse/parser/generics.rs +++ b/src/librustc_parse/parser/generics.rs @@ -181,7 +181,7 @@ impl<'a> Parser<'a> { // We are considering adding generics to the `where` keyword as an alternative higher-rank // parameter syntax (as in `where<'a>` or `where`. To avoid that being a breaking // change we parse those generics now, but report an error. - if self.choose_generics_over_qpath() { + if self.choose_generics_over_qpath(0) { let generics = self.parse_generics()?; self.struct_span_err( generics.span, @@ -257,7 +257,7 @@ impl<'a> Parser<'a> { } } - pub(super) fn choose_generics_over_qpath(&self) -> bool { + pub(super) fn choose_generics_over_qpath(&self, start: usize) -> bool { // There's an ambiguity between generic parameters and qualified paths in impls. // If we see `<` it may start both, so we have to inspect some following tokens. // The following combinations can only start generics, @@ -274,15 +274,12 @@ impl<'a> Parser<'a> { // we disambiguate it in favor of generics (`impl ::absolute::Path { ... }`) // because this is what almost always expected in practice, qualified paths in impls // (`impl ::AssocTy { ... }`) aren't even allowed by type checker at the moment. - self.token == token::Lt - && (self.look_ahead(1, |t| t == &token::Pound || t == &token::Gt) - || self.look_ahead(1, |t| t.is_lifetime() || t.is_ident()) - && self.look_ahead(2, |t| { - t == &token::Gt - || t == &token::Comma - || t == &token::Colon - || t == &token::Eq + self.look_ahead(start, |t| t == &token::Lt) + && (self.look_ahead(start + 1, |t| t == &token::Pound || t == &token::Gt) + || self.look_ahead(start + 1, |t| t.is_lifetime() || t.is_ident()) + && self.look_ahead(start + 2, |t| { + matches!(t.kind, token::Gt | token::Comma | token::Colon | token::Eq) }) - || self.is_keyword_ahead(1, &[kw::Const])) + || self.is_keyword_ahead(start + 1, &[kw::Const])) } } diff --git a/src/librustc_parse/parser/item.rs b/src/librustc_parse/parser/item.rs index 9d70f606f3ef4..42c4bac3b8db4 100644 --- a/src/librustc_parse/parser/item.rs +++ b/src/librustc_parse/parser/item.rs @@ -458,7 +458,7 @@ impl<'a> Parser<'a> { self.expect_keyword(kw::Impl)?; // First, parse generic parameters if necessary. - let mut generics = if self.choose_generics_over_qpath() { + let mut generics = if self.choose_generics_over_qpath(0) { self.parse_generics()? } else { let mut generics = Generics::default(); diff --git a/src/test/ui/parser/recover-quantified-closure.rs b/src/test/ui/parser/recover-quantified-closure.rs new file mode 100644 index 0000000000000..381324738f62b --- /dev/null +++ b/src/test/ui/parser/recover-quantified-closure.rs @@ -0,0 +1,10 @@ +fn main() { + for<'a> |x: &'a u8| *x + 1; + //~^ ERROR cannot introduce explicit parameters for a closure +} + +enum Foo { Bar } +fn foo(x: impl Iterator) { + for ::Bar in x {} + //~^ ERROR expected one of `move`, `static`, `|` +} diff --git a/src/test/ui/parser/recover-quantified-closure.stderr b/src/test/ui/parser/recover-quantified-closure.stderr new file mode 100644 index 0000000000000..0f01132651648 --- /dev/null +++ b/src/test/ui/parser/recover-quantified-closure.stderr @@ -0,0 +1,16 @@ +error: cannot introduce explicit parameters for a closure + --> $DIR/recover-quantified-closure.rs:2:5 + | +LL | for<'a> |x: &'a u8| *x + 1; + | ^^^^^^^ ------------------ the parameters are attached to this closure + | | + | help: remove the parameters + +error: expected one of `move`, `static`, `|`, or `||`, found `::` + --> $DIR/recover-quantified-closure.rs:8:14 + | +LL | for ::Bar in x {} + | ^^ expected one of `move`, `static`, `|`, or `||` + +error: aborting due to 2 previous errors +