Auto merge of #117770 - sjwang05:issue-117766, r=estebank,TaKO8Ki
Catch stray `{` in let-chains Fixes #117766
This commit is contained in:
commit
ea1e5cc91f
@ -5,7 +5,8 @@ use super::{StringReader, UnmatchedDelim};
|
|||||||
use rustc_ast::token::{self, Delimiter, Token};
|
use rustc_ast::token::{self, Delimiter, Token};
|
||||||
use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree};
|
use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree};
|
||||||
use rustc_ast_pretty::pprust::token_to_string;
|
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> {
|
pub(super) struct TokenTreesReader<'a> {
|
||||||
string_reader: StringReader<'a>,
|
string_reader: StringReader<'a>,
|
||||||
@ -116,24 +117,8 @@ impl<'a> TokenTreesReader<'a> {
|
|||||||
// We stop at any delimiter so we can try to recover if the user
|
// We stop at any delimiter so we can try to recover if the user
|
||||||
// uses an incorrect delimiter.
|
// uses an incorrect delimiter.
|
||||||
let (tts, res) = self.parse_token_trees(/* is_delimited */ true);
|
let (tts, res) = self.parse_token_trees(/* is_delimited */ true);
|
||||||
if let Err(mut errs) = res {
|
if let Err(errs) = res {
|
||||||
// If there are unclosed delims, see if there are diff markers and if so, point them
|
return Err(self.unclosed_delim_err(tts, errs));
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand to cover the entire delimited token tree
|
// Expand to cover the entire delimited token tree
|
||||||
@ -220,6 +205,62 @@ impl<'a> TokenTreesReader<'a> {
|
|||||||
Ok(TokenTree::Delimited(delim_span, open_delim, tts))
|
Ok(TokenTree::Delimited(delim_span, open_delim, tts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
fn close_delim_err(&mut self, delim: Delimiter) -> PErr<'a> {
|
fn close_delim_err(&mut self, delim: Delimiter) -> PErr<'a> {
|
||||||
// An unexpected closing delimiter (i.e., there is no
|
// An unexpected closing delimiter (i.e., there is no
|
||||||
// matching opening delimiter).
|
// matching opening delimiter).
|
||||||
|
@ -1115,7 +1115,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether any of the given keywords are `dist` tokens ahead of the current one.
|
/// Returns whether any of the given keywords are `dist` tokens ahead of the current one.
|
||||||
fn is_keyword_ahead(&self, dist: usize, kws: &[Symbol]) -> bool {
|
pub(crate) fn is_keyword_ahead(&self, dist: usize, kws: &[Symbol]) -> bool {
|
||||||
self.look_ahead(dist, |t| kws.iter().any(|&kw| t.is_keyword(kw)))
|
self.look_ahead(dist, |t| kws.iter().any(|&kw| t.is_keyword(kw)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
58
tests/ui/parser/brace-in-let-chain.rs
Normal file
58
tests/ui/parser/brace-in-let-chain.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// 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
|
65
tests/ui/parser/brace-in-let-chain.stderr
Normal file
65
tests/ui/parser/brace-in-let-chain.stderr
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
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
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user