From 9dc7abe06d2b2771b09b6dac6eef5ac83b58573f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 22 Oct 2017 09:19:30 -0700 Subject: [PATCH] Detect `=` -> `:` typo in let bindings When encountering a let binding type error, attempt to parse as initializer instead. If successful, it is likely just a typo: ```rust fn main() { let x: Vec::with_capacity(10); } ``` ``` error: expected type, found `10` --> file.rs:3:31 | 3 | let x: Vec::with_capacity(10, 20); | -- ^^ | || | |help: did you mean assign here?: `=` | while parsing the type for `x` ``` --- src/librustc_resolve/lib.rs | 2 +- src/libsyntax/parse/parser.rs | 132 ++++++++++++------ .../compile-fail/self-vs-path-ambiguity.rs | 1 - src/test/parse-fail/issue-33413.rs | 1 - src/test/ui/issue-44406.stderr | 11 +- .../ui/resolve/token-error-correct.stderr | 20 +-- .../type-ascription-instead-of-initializer.rs | 13 ++ ...e-ascription-instead-of-initializer.stderr | 17 +++ 8 files changed, 121 insertions(+), 76 deletions(-) create mode 100644 src/test/ui/suggestions/type-ascription-instead-of-initializer.rs create mode 100644 src/test/ui/suggestions/type-ascription-instead-of-initializer.stderr diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs index 3b27890013a22..4b7f1cbe0ad7f 100644 --- a/src/librustc_resolve/lib.rs +++ b/src/librustc_resolve/lib.rs @@ -2597,7 +2597,7 @@ impl<'a> Resolver<'a> { } } err.span_label(span, format!("did you mean `{} {{ /* fields */ }}`?", - path_str)); + path_str)); return (err, candidates); } _ => {} diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index a3a265450ab0e..52593ada71ec3 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -974,11 +974,12 @@ impl<'a> Parser<'a> { pub fn eat_to_tokens(&mut self, kets: &[&token::Token]) { let handler = self.diagnostic(); - self.parse_seq_to_before_tokens(kets, - SeqSep::none(), - TokenExpectType::Expect, - |p| Ok(p.parse_token_tree()), - |mut e| handler.cancel(&mut e)); + if let Err(ref mut err) = self.parse_seq_to_before_tokens(kets, + SeqSep::none(), + TokenExpectType::Expect, + |p| Ok(p.parse_token_tree())) { + handler.cancel(err); + } } /// Parse a sequence, including the closing delimiter. The function @@ -991,7 +992,7 @@ impl<'a> Parser<'a> { -> PResult<'a, Vec> where F: FnMut(&mut Parser<'a>) -> PResult<'a, T>, { - let val = self.parse_seq_to_before_end(ket, sep, f); + let val = self.parse_seq_to_before_end(ket, sep, f)?; self.bump(); Ok(val) } @@ -1003,22 +1004,19 @@ impl<'a> Parser<'a> { ket: &token::Token, sep: SeqSep, f: F) - -> Vec - where F: FnMut(&mut Parser<'a>) -> PResult<'a, T> + -> PResult<'a, Vec> + where F: FnMut(&mut Parser<'a>) -> PResult<'a, T> { - self.parse_seq_to_before_tokens(&[ket], sep, TokenExpectType::Expect, f, |mut e| e.emit()) + self.parse_seq_to_before_tokens(&[ket], sep, TokenExpectType::Expect, f) } - // `fe` is an error handler. - fn parse_seq_to_before_tokens(&mut self, + fn parse_seq_to_before_tokens(&mut self, kets: &[&token::Token], sep: SeqSep, expect: TokenExpectType, - mut f: F, - mut fe: Fe) - -> Vec - where F: FnMut(&mut Parser<'a>) -> PResult<'a, T>, - Fe: FnMut(DiagnosticBuilder) + mut f: F) + -> PResult<'a, Vec> + where F: FnMut(&mut Parser<'a>) -> PResult<'a, T> { let mut first: bool = true; let mut v = vec![]; @@ -1031,14 +1029,14 @@ impl<'a> Parser<'a> { if first { first = false; } else { - if let Err(e) = self.expect(t) { - fe(e); + if let Err(mut e) = self.expect(t) { // Attempt to keep parsing if it was a similar separator if let Some(ref tokens) = t.similar_tokens() { if tokens.contains(&self.token) { self.bump(); } } + e.emit(); // Attempt to keep parsing if it was an omitted separator match f(self) { Ok(t) => { @@ -1062,16 +1060,11 @@ impl<'a> Parser<'a> { break; } - match f(self) { - Ok(t) => v.push(t), - Err(e) => { - fe(e); - break; - } - } + let t = f(self)?; + v.push(t); } - v + Ok(v) } /// Parse a sequence, including the closing delimiter. The function @@ -1086,7 +1079,7 @@ impl<'a> Parser<'a> { F: FnMut(&mut Parser<'a>) -> PResult<'a, T>, { self.expect(bra)?; - let result = self.parse_seq_to_before_end(ket, sep, f); + let result = self.parse_seq_to_before_end(ket, sep, f)?; if self.token == *ket { self.bump(); } @@ -1105,7 +1098,7 @@ impl<'a> Parser<'a> { { let lo = self.span; self.expect(bra)?; - let result = self.parse_seq_to_before_end(ket, sep, f); + let result = self.parse_seq_to_before_end(ket, sep, f)?; let hi = self.span; self.bump(); Ok(respan(lo.to(hi), result)) @@ -1551,7 +1544,7 @@ impl<'a> Parser<'a> { }; let span = lo.to(self.prev_span); - let ty = Ty { node: node, span: span, id: ast::DUMMY_NODE_ID }; + let ty = Ty { node, span, id: ast::DUMMY_NODE_ID }; // Try to recover from use of `+` with incorrect priority. self.maybe_recover_from_bad_type_plus(allow_plus, &ty)?; @@ -1868,8 +1861,11 @@ impl<'a> Parser<'a> { self.parse_path(style) } - fn parse_path_segments(&mut self, segments: &mut Vec, style: PathStyle, - enable_warning: bool) -> PResult<'a, ()> { + fn parse_path_segments(&mut self, + segments: &mut Vec, + style: PathStyle, + enable_warning: bool) + -> PResult<'a, ()> { loop { segments.push(self.parse_path_segment(style, enable_warning)?); @@ -1914,9 +1910,12 @@ impl<'a> Parser<'a> { } else { // `(T, U) -> R` self.bump(); // `(` - let inputs = self.parse_seq_to_end(&token::CloseDelim(token::Paren), - SeqSep::trailing_allowed(token::Comma), - |p| p.parse_ty())?; + let inputs = self.parse_seq_to_before_tokens( + &[&token::CloseDelim(token::Paren)], + SeqSep::trailing_allowed(token::Comma), + TokenExpectType::Expect, + |p| p.parse_ty())?; + self.bump(); // `)` let output = if self.eat(&token::RArrow) { Some(self.parse_ty_no_plus()?) } else { @@ -3309,10 +3308,12 @@ impl<'a> Parser<'a> { } /// Parse the RHS of a local variable declaration (e.g. '= 14;') - fn parse_initializer(&mut self) -> PResult<'a, Option>> { + fn parse_initializer(&mut self, skip_eq: bool) -> PResult<'a, Option>> { if self.check(&token::Eq) { self.bump(); Ok(Some(self.parse_expr()?)) + } else if skip_eq { + Ok(Some(self.parse_expr()?)) } else { Ok(None) } @@ -3719,12 +3720,56 @@ impl<'a> Parser<'a> { let lo = self.prev_span; let pat = self.parse_pat()?; - let ty = if self.eat(&token::Colon) { - Some(self.parse_ty()?) + let (err, ty) = if self.eat(&token::Colon) { + // Save the state of the parser before parsing type normally, in case there is a `:` + // instead of an `=` typo. + let parser_snapshot_before_type = self.clone(); + let colon_sp = self.prev_span; + match self.parse_ty() { + Ok(ty) => (None, Some(ty)), + Err(mut err) => { + // Rewind to before attempting to parse the type and continue parsing + let parser_snapshot_after_type = self.clone(); + mem::replace(self, parser_snapshot_before_type); + + let snippet = self.sess.codemap().span_to_snippet(pat.span).unwrap(); + err.span_label(pat.span, format!("while parsing the type for `{}`", snippet)); + (Some((parser_snapshot_after_type, colon_sp, err)), None) + } + } } else { - None + (None, None) + }; + let init = match (self.parse_initializer(err.is_some()), err) { + (Ok(init), None) => { // init parsed, ty parsed + init + } + (Ok(init), Some((_, colon_sp, mut err))) => { // init parsed, ty error + // Could parse the type as if it were the initializer, it is likely there was a + // typo in the code: `:` instead of `=`. Add suggestion and emit the error. + err.span_suggestion_short(colon_sp, + "use `=` if you meant to assign", + "=".to_string()); + err.emit(); + // As this was parsed successfuly, continue as if the code has been fixed for the + // rest of the file. It will still fail due to the emitted error, but we avoid + // extra noise. + init + } + (Err(mut init_err), Some((snapshot, _, ty_err))) => { // init error, ty error + init_err.cancel(); + // Couldn't parse the type nor the initializer, only raise the type error and + // return to the parser state before parsing the type as the initializer. + // let x: ; + mem::replace(self, snapshot); + return Err(ty_err); + } + (Err(err), None) => { // init error, ty parsed + // Couldn't parse the initializer and we're not attempting to recover a failed + // parse of the type, return the error. + return Err(err); + } }; - let init = self.parse_initializer()?; let hi = if self.token == token::Semi { self.span } else { @@ -4781,14 +4826,14 @@ impl<'a> Parser<'a> { } else if self.eat(&token::Comma) { let mut fn_inputs = vec![self_arg]; fn_inputs.append(&mut self.parse_seq_to_before_end( - &token::CloseDelim(token::Paren), sep, parse_arg_fn) + &token::CloseDelim(token::Paren), sep, parse_arg_fn)? ); fn_inputs } else { return self.unexpected(); } } else { - self.parse_seq_to_before_end(&token::CloseDelim(token::Paren), sep, parse_arg_fn) + self.parse_seq_to_before_end(&token::CloseDelim(token::Paren), sep, parse_arg_fn)? }; // Parse closing paren and return type. @@ -4811,9 +4856,8 @@ impl<'a> Parser<'a> { &[&token::BinOp(token::Or), &token::OrOr], SeqSep::trailing_allowed(token::Comma), TokenExpectType::NoExpect, - |p| p.parse_fn_block_arg(), - |mut e| e.emit() - ); + |p| p.parse_fn_block_arg() + )?; self.expect_or()?; args } diff --git a/src/test/compile-fail/self-vs-path-ambiguity.rs b/src/test/compile-fail/self-vs-path-ambiguity.rs index 9753014e7810a..b2dba3bd61b36 100644 --- a/src/test/compile-fail/self-vs-path-ambiguity.rs +++ b/src/test/compile-fail/self-vs-path-ambiguity.rs @@ -17,7 +17,6 @@ impl S { fn g(&self::S: &S) {} fn h(&mut self::S: &mut S) {} fn i(&'a self::S: &S) {} //~ ERROR unexpected lifetime `'a` in pattern - //~^ ERROR expected one of `)` or `mut`, found `'a` } fn main() {} diff --git a/src/test/parse-fail/issue-33413.rs b/src/test/parse-fail/issue-33413.rs index 699af8ca7ab4f..25ae7b4c55a2b 100644 --- a/src/test/parse-fail/issue-33413.rs +++ b/src/test/parse-fail/issue-33413.rs @@ -12,5 +12,4 @@ impl S { fn f(*, a: u8) -> u8 {} //~ ERROR expected pattern, found `*` - //~^ ERROR expected one of `)`, `-`, `box`, `false`, `mut`, `ref`, or `true`, found `*` } diff --git a/src/test/ui/issue-44406.stderr b/src/test/ui/issue-44406.stderr index 9beae91540abf..e7afbb574efd7 100644 --- a/src/test/ui/issue-44406.stderr +++ b/src/test/ui/issue-44406.stderr @@ -13,14 +13,5 @@ error: expected type, found keyword `true` 18 | foo!(true); | ^^^^ expecting a type here because of type ascription -error: expected one of `!`, `&&`, `&`, `(`, `*`, `.`, `;`, `<`, `?`, `[`, `_`, `dyn`, `extern`, `fn`, `for`, `impl`, `unsafe`, `}`, an operator, or lifetime, found `true` - --> $DIR/issue-44406.rs:18:10 - | -13 | bar(baz: $rest) - | - expected one of 20 possible tokens here -... -18 | foo!(true); - | ^^^^ unexpected token - -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors diff --git a/src/test/ui/resolve/token-error-correct.stderr b/src/test/ui/resolve/token-error-correct.stderr index 281c21f6f85ee..6bd63f4fbbb89 100644 --- a/src/test/ui/resolve/token-error-correct.stderr +++ b/src/test/ui/resolve/token-error-correct.stderr @@ -28,29 +28,11 @@ error: expected expression, found `;` 14 | foo(bar(; | ^ -error: expected one of `)`, `,`, `.`, `<`, `?`, `break`, `continue`, `false`, `for`, `if`, `loop`, `match`, `move`, `return`, `true`, `unsafe`, `while`, `yield`, or an operator, found `;` - --> $DIR/token-error-correct.rs:14:13 - | -14 | foo(bar(; - | ^ expected one of 19 possible tokens here - error: expected expression, found `)` --> $DIR/token-error-correct.rs:23:1 | 23 | } | ^ -error[E0425]: cannot find function `foo` in this scope - --> $DIR/token-error-correct.rs:14:5 - | -14 | foo(bar(; - | ^^^ not found in this scope - -error[E0425]: cannot find function `bar` in this scope - --> $DIR/token-error-correct.rs:14:9 - | -14 | foo(bar(; - | ^^^ not found in this scope - -error: aborting due to 7 previous errors +error: aborting due to 4 previous errors diff --git a/src/test/ui/suggestions/type-ascription-instead-of-initializer.rs b/src/test/ui/suggestions/type-ascription-instead-of-initializer.rs new file mode 100644 index 0000000000000..bcd965f10fabb --- /dev/null +++ b/src/test/ui/suggestions/type-ascription-instead-of-initializer.rs @@ -0,0 +1,13 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + let x: Vec::with_capacity(10, 20); +} diff --git a/src/test/ui/suggestions/type-ascription-instead-of-initializer.stderr b/src/test/ui/suggestions/type-ascription-instead-of-initializer.stderr new file mode 100644 index 0000000000000..647e3f84685f1 --- /dev/null +++ b/src/test/ui/suggestions/type-ascription-instead-of-initializer.stderr @@ -0,0 +1,17 @@ +error: expected type, found `10` + --> $DIR/type-ascription-instead-of-initializer.rs:12:31 + | +12 | let x: Vec::with_capacity(10, 20); + | -- ^^ + | || + | |help: use `=` if you meant to assign + | while parsing the type for `x` + +error[E0061]: this function takes 1 parameter but 2 parameters were supplied + --> $DIR/type-ascription-instead-of-initializer.rs:12:31 + | +12 | let x: Vec::with_capacity(10, 20); + | ^^^^^^ expected 1 parameter + +error: aborting due to 2 previous errors +