diff --git a/compiler/rustc_parse/src/lexer/tokentrees.rs b/compiler/rustc_parse/src/lexer/tokentrees.rs index 31d91fe80bd..7aa4ac7c4cb 100644 --- a/compiler/rustc_parse/src/lexer/tokentrees.rs +++ b/compiler/rustc_parse/src/lexer/tokentrees.rs @@ -5,7 +5,8 @@ use super::{StringReader, UnmatchedDelim}; use rustc_ast::token::{self, Delimiter, Token}; use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_ast_pretty::pprust::token_to_string; -use rustc_errors::PErr; +use rustc_errors::{Applicability, PErr}; +use rustc_span::symbol::kw; pub(super) struct TokenTreesReader<'a> { string_reader: StringReader<'a>, @@ -121,9 +122,40 @@ impl<'a> TokenTreesReader<'a> { // out instead of complaining about the unclosed delims. let mut parser = crate::stream_to_parser(self.string_reader.sess, tts, None); let mut diff_errs = vec![]; + // Suggest removing a `{` we think appears in an `if`/`while` condition + // We want to suggest removing a `{` only if we think we're in an `if`/`while` condition, but + // we have no way of tracking this in the lexer itself, so we piggyback on the parser + let mut in_cond = false; while parser.token != token::Eof { if let Err(diff_err) = parser.err_diff_marker() { diff_errs.push(diff_err); + } else if parser.token.is_keyword(kw::If) { + in_cond = true; + } else if parser.token == token::CloseDelim(Delimiter::Brace) { + in_cond = false; + } else if in_cond && parser.token == token::OpenDelim(Delimiter::Brace) { + // Store the `&&` and `let` to use their spans later when creating the diagnostic + let maybe_andand = parser.look_ahead(1, |t| t.clone()); + let maybe_let = parser.look_ahead(2, |t| t.clone()); + if maybe_andand == token::OpenDelim(Delimiter::Brace) { + // This might be the beginning of the `if`/`while` body (i.e., the end of the condition) + in_cond = false; + } else if maybe_andand == token::AndAnd && maybe_let.is_keyword(kw::Let) { + let mut err = parser.struct_span_err( + parser.token.span, + "found a `{` in the middle of a let-chain", + ); + err.span_suggestion( + parser.token.span, + "consider removing this brace to parse the `let` as part of the same chain", + "", Applicability::MachineApplicable + ); + err.span_note( + maybe_andand.span.to(maybe_let.span), + "you might have meant to continue the let-chain here", + ); + errs.push(err); + } } parser.bump(); } diff --git a/tests/ui/parser/brace-in-let-chain.rs b/tests/ui/parser/brace-in-let-chain.rs new file mode 100644 index 00000000000..4dc13fb3847 --- /dev/null +++ b/tests/ui/parser/brace-in-let-chain.rs @@ -0,0 +1,28 @@ +// issue #117766 + +#![feature(let_chains)] +fn main() { + if let () = () + && let () = () { //~ERROR: found a `{` in the middle of a let-chain + && let () = () + { + } +} + +fn foo() { + { + && let () = () +} + +fn bar() { + if false {} + { + && let () = () +} + +fn baz() { + if false { + { + && let () = () + } +} //~ERROR: this file contains an unclosed delimiter diff --git a/tests/ui/parser/brace-in-let-chain.stderr b/tests/ui/parser/brace-in-let-chain.stderr new file mode 100644 index 00000000000..7550d5c43cf --- /dev/null +++ b/tests/ui/parser/brace-in-let-chain.stderr @@ -0,0 +1,42 @@ +error: this file contains an unclosed delimiter + --> $DIR/brace-in-let-chain.rs:28:54 + | +LL | fn main() { + | - unclosed delimiter +... +LL | fn foo() { + | - unclosed delimiter +... +LL | fn bar() { + | - unclosed delimiter +... +LL | fn baz() { + | - unclosed delimiter +LL | if false { +LL | { + | - this delimiter might not be properly closed... +LL | && let () = () +LL | } + | - ...as it matches this but it has different indentation +LL | } + | ^ + +error: found a `{` in the middle of a let-chain + --> $DIR/brace-in-let-chain.rs:6:24 + | +LL | && let () = () { + | ^ + | +note: you might have meant to continue the let-chain here + --> $DIR/brace-in-let-chain.rs:7:9 + | +LL | && let () = () + | ^^^^^^ +help: consider removing this brace to parse the `let` as part of the same chain + | +LL - && let () = () { +LL + && let () = () + | + +error: aborting due to 2 previous errors +