Skip to content

Commit 0094238

Browse files
committed
Catch stray { in let-chains
1 parent 4c8862b commit 0094238

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

Diff for: compiler/rustc_parse/src/lexer/tokentrees.rs

+33-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use super::{StringReader, UnmatchedDelim};
55
use rustc_ast::token::{self, Delimiter, Token};
66
use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree};
77
use rustc_ast_pretty::pprust::token_to_string;
8-
use rustc_errors::PErr;
8+
use rustc_errors::{Applicability, PErr};
9+
use rustc_span::symbol::kw;
910

1011
pub(super) struct TokenTreesReader<'a> {
1112
string_reader: StringReader<'a>,
@@ -121,9 +122,40 @@ impl<'a> TokenTreesReader<'a> {
121122
// out instead of complaining about the unclosed delims.
122123
let mut parser = crate::stream_to_parser(self.string_reader.sess, tts, None);
123124
let mut diff_errs = vec![];
125+
// Suggest removing a `{` we think appears in an `if`/`while` condition
126+
// We want to suggest removing a `{` only if we think we're in an `if`/`while` condition, but
127+
// we have no way of tracking this in the lexer itself, so we piggyback on the parser
128+
let mut in_cond = false;
124129
while parser.token != token::Eof {
125130
if let Err(diff_err) = parser.err_diff_marker() {
126131
diff_errs.push(diff_err);
132+
} else if parser.token.is_keyword(kw::If) {
133+
in_cond = true;
134+
} else if parser.token == token::CloseDelim(Delimiter::Brace) {
135+
in_cond = false;
136+
} else if in_cond && parser.token == token::OpenDelim(Delimiter::Brace) {
137+
// Store the `&&` and `let` to use their spans later when creating the diagnostic
138+
let maybe_andand = parser.look_ahead(1, |t| t.clone());
139+
let maybe_let = parser.look_ahead(2, |t| t.clone());
140+
if maybe_andand == token::OpenDelim(Delimiter::Brace) {
141+
// This might be the beginning of the `if`/`while` body (i.e., the end of the condition)
142+
in_cond = false;
143+
} else if maybe_andand == token::AndAnd && maybe_let.is_keyword(kw::Let) {
144+
let mut err = parser.struct_span_err(
145+
parser.token.span,
146+
"found a `{` in the middle of a let-chain",
147+
);
148+
err.span_suggestion(
149+
parser.token.span,
150+
"consider removing this brace to parse the `let` as part of the same chain",
151+
"", Applicability::MachineApplicable
152+
);
153+
err.span_note(
154+
maybe_andand.span.to(maybe_let.span),
155+
"you might have meant to continue the let-chain here",
156+
);
157+
errs.push(err);
158+
}
127159
}
128160
parser.bump();
129161
}

Diff for: tests/ui/parser/brace-in-let-chain.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// issue #117766
2+
3+
#![feature(let_chains)]
4+
fn main() {
5+
if let () = ()
6+
&& let () = () { //~ERROR: found a `{` in the middle of a let-chain
7+
&& let () = ()
8+
{
9+
}
10+
}
11+
12+
fn foo() {
13+
{
14+
&& let () = ()
15+
}
16+
17+
fn bar() {
18+
if false {}
19+
{
20+
&& let () = ()
21+
}
22+
23+
fn baz() {
24+
if false {
25+
{
26+
&& let () = ()
27+
}
28+
} //~ERROR: this file contains an unclosed delimiter

Diff for: tests/ui/parser/brace-in-let-chain.stderr

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
error: this file contains an unclosed delimiter
2+
--> $DIR/brace-in-let-chain.rs:28:54
3+
|
4+
LL | fn main() {
5+
| - unclosed delimiter
6+
...
7+
LL | fn foo() {
8+
| - unclosed delimiter
9+
...
10+
LL | fn bar() {
11+
| - unclosed delimiter
12+
...
13+
LL | fn baz() {
14+
| - unclosed delimiter
15+
LL | if false {
16+
LL | {
17+
| - this delimiter might not be properly closed...
18+
LL | && let () = ()
19+
LL | }
20+
| - ...as it matches this but it has different indentation
21+
LL | }
22+
| ^
23+
24+
error: found a `{` in the middle of a let-chain
25+
--> $DIR/brace-in-let-chain.rs:6:24
26+
|
27+
LL | && let () = () {
28+
| ^
29+
|
30+
note: you might have meant to continue the let-chain here
31+
--> $DIR/brace-in-let-chain.rs:7:9
32+
|
33+
LL | && let () = ()
34+
| ^^^^^^
35+
help: consider removing this brace to parse the `let` as part of the same chain
36+
|
37+
LL - && let () = () {
38+
LL + && let () = ()
39+
|
40+
41+
error: aborting due to 2 previous errors
42+

0 commit comments

Comments
 (0)