Change:
Unmatched delimiters are hard to deal with by taking consideration of surrounding code because they actually occur during lexing. Still, it'd be nice if we detected some common sources of issues like this one. No response No response <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
claim

Discussion:
use rustc_ast::token::{self, Delimiter, Token}; use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_ast_pretty::pprust::token_to_string; <del> use rustc_errors::PErr; </del> <ins> use rustc_errors::{Applicability, PErr}; use rustc_span::symbol::kw; </ins> pub(super) struct TokenTreesReader<'a> { string_reader: StringReader<'a>,

// We stop at any delimiter so we can try to recover if the user // uses an incorrect delimiter. let (tts, res) = self.parse_token_trees(/* is_delimited */ true); <del> if let Err(mut errs) = res { // If there are unclosed delims, see if there are diff markers and if so, point them // 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![]; while parser.token != token::Eof { if let Err(diff_err) = parser.err_diff_marker() { diff_errs.push(diff_err); } parser.bump(); } if !diff_errs.is_empty() { errs.iter_mut().for_each(|err| { err.delay_as_bug(); }); return Err(diff_errs); } return Err(errs); </del> <ins> if let Err(errs) = res { return Err(self.unclosed_delim_err(tts, errs)); </ins> } // Expand to cover the entire delimited token tree

Ok(TokenTree::Delimited(delim_span, open_delim, tts)) } <ins> fn unclosed_delim_err(&mut self, tts: TokenStream, mut errs: Vec<PErr<'a>>) -> Vec<PErr<'a>> { // If there are unclosed delims, see if there are diff markers and if so, point them // 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.is_keyword_ahead(0, &[kw::If, kw::While]) { in_cond = true; } else if matches!( parser.token.kind, token::CloseDelim(Delimiter::Brace) | token::FatArrow ) { // end of the `if`/`while` body, or the end of a `match` guard 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_label( maybe_andand.span.to(maybe_let.span), "you might have meant to continue the let-chain here", ); errs.push(err); } } parser.bump(); } if !diff_errs.is_empty() { errs.iter_mut().for_each(|err| { err.delay_as_bug(); }); return diff_errs; } return errs; } </ins> fn close_delim_err(&mut self, delim: Delimiter) -> PErr<'a> { // An unexpected closing delimiter (i.e., there is no // matching opening delimiter).

} /// Returns whether any of the given keywords are `dist` tokens ahead of the current one. <del> fn is_keyword_ahead(&self, dist: usize, kws: &[Symbol]) -> bool { </del> <ins> pub(crate) fn is_keyword_ahead(&self, dist: usize, kws: &[Symbol]) -> bool { </ins> self.look_ahead(dist, |t| kws.iter().any(|&kw| t.is_keyword(kw))) }

<ins> // issue #117766 #![feature(let_chains)] fn main() { if let () = () && let () = () { //~ERROR: found a `{` in the middle of a let-chain && let () = () { } } fn quux() { while let () = () && let () = () { //~ERROR: found a `{` in the middle of a let-chain && let () = () { } } fn foobar() { while false {} { && let () = () } fn fubar() { while false { { && let () = () } } fn qux() { let foo = false; match foo { _ if foo => { && let () = () _ => {} } } fn foo() { { && let () = () } fn bar() { if false {} { && let () = () } fn baz() { if false { { && let () = () } } //~ERROR: this file contains an unclosed delimiter </ins>

<ins> error: this file contains an unclosed delimiter --> $DIR/brace-in-let-chain.rs:58:54 | LL | fn main() { |           - unclosed delimiter ... LL | fn quux() { |           - unclosed delimiter ... LL | fn foobar() { |             - unclosed delimiter ... LL | fn fubar() { |            - unclosed delimiter ... LL | fn qux() { |          - 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:14:24 | LL |         && let () = () { |                        ^ LL |         && let () = () |         ------ you might have meant to continue the let-chain here | help: consider removing this brace to parse the `let` as part of the same chain | LL -         && let () = () { LL +         && let () = () | error: found a `{` in the middle of a let-chain --> $DIR/brace-in-let-chain.rs:6:24 | LL |         && let () = () { |                        ^ LL |         && let () = () |         ------ you might have meant to continue the let-chain here | help: consider removing this brace to parse the `let` as part of the same chain | LL -         && let () = () { LL +         && let () = () | error: aborting due to 3 previous errors </ins>