Skip to content

Commit ea1e5cc

Browse files
committed
Auto merge of #117770 - sjwang05:issue-117766, r=estebank,TaKO8Ki
Catch stray `{` in let-chains Fixes #117766
2 parents 0828c15 + 274824b commit ea1e5cc

File tree

4 files changed

+184
-20
lines changed

4 files changed

+184
-20
lines changed

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

+60-19
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>,
@@ -116,24 +117,8 @@ impl<'a> TokenTreesReader<'a> {
116117
// We stop at any delimiter so we can try to recover if the user
117118
// uses an incorrect delimiter.
118119
let (tts, res) = self.parse_token_trees(/* is_delimited */ true);
119-
if let Err(mut errs) = res {
120-
// If there are unclosed delims, see if there are diff markers and if so, point them
121-
// out instead of complaining about the unclosed delims.
122-
let mut parser = crate::stream_to_parser(self.string_reader.sess, tts, None);
123-
let mut diff_errs = vec![];
124-
while parser.token != token::Eof {
125-
if let Err(diff_err) = parser.err_diff_marker() {
126-
diff_errs.push(diff_err);
127-
}
128-
parser.bump();
129-
}
130-
if !diff_errs.is_empty() {
131-
errs.iter_mut().for_each(|err| {
132-
err.delay_as_bug();
133-
});
134-
return Err(diff_errs);
135-
}
136-
return Err(errs);
120+
if let Err(errs) = res {
121+
return Err(self.unclosed_delim_err(tts, errs));
137122
}
138123

139124
// Expand to cover the entire delimited token tree
@@ -220,6 +205,62 @@ impl<'a> TokenTreesReader<'a> {
220205
Ok(TokenTree::Delimited(delim_span, open_delim, tts))
221206
}
222207

208+
fn unclosed_delim_err(&mut self, tts: TokenStream, mut errs: Vec<PErr<'a>>) -> Vec<PErr<'a>> {
209+
// If there are unclosed delims, see if there are diff markers and if so, point them
210+
// out instead of complaining about the unclosed delims.
211+
let mut parser = crate::stream_to_parser(self.string_reader.sess, tts, None);
212+
let mut diff_errs = vec![];
213+
// Suggest removing a `{` we think appears in an `if`/`while` condition
214+
// We want to suggest removing a `{` only if we think we're in an `if`/`while` condition, but
215+
// we have no way of tracking this in the lexer itself, so we piggyback on the parser
216+
let mut in_cond = false;
217+
while parser.token != token::Eof {
218+
if let Err(diff_err) = parser.err_diff_marker() {
219+
diff_errs.push(diff_err);
220+
} else if parser.is_keyword_ahead(0, &[kw::If, kw::While]) {
221+
in_cond = true;
222+
} else if matches!(
223+
parser.token.kind,
224+
token::CloseDelim(Delimiter::Brace) | token::FatArrow
225+
) {
226+
// end of the `if`/`while` body, or the end of a `match` guard
227+
in_cond = false;
228+
} else if in_cond && parser.token == token::OpenDelim(Delimiter::Brace) {
229+
// Store the `&&` and `let` to use their spans later when creating the diagnostic
230+
let maybe_andand = parser.look_ahead(1, |t| t.clone());
231+
let maybe_let = parser.look_ahead(2, |t| t.clone());
232+
if maybe_andand == token::OpenDelim(Delimiter::Brace) {
233+
// This might be the beginning of the `if`/`while` body (i.e., the end of the condition)
234+
in_cond = false;
235+
} else if maybe_andand == token::AndAnd && maybe_let.is_keyword(kw::Let) {
236+
let mut err = parser.struct_span_err(
237+
parser.token.span,
238+
"found a `{` in the middle of a let-chain",
239+
);
240+
err.span_suggestion(
241+
parser.token.span,
242+
"consider removing this brace to parse the `let` as part of the same chain",
243+
"",
244+
Applicability::MachineApplicable,
245+
);
246+
err.span_label(
247+
maybe_andand.span.to(maybe_let.span),
248+
"you might have meant to continue the let-chain here",
249+
);
250+
errs.push(err);
251+
}
252+
}
253+
parser.bump();
254+
}
255+
if !diff_errs.is_empty() {
256+
errs.iter_mut().for_each(|err| {
257+
err.delay_as_bug();
258+
});
259+
return diff_errs;
260+
}
261+
return errs;
262+
}
263+
223264
fn close_delim_err(&mut self, delim: Delimiter) -> PErr<'a> {
224265
// An unexpected closing delimiter (i.e., there is no
225266
// matching opening delimiter).

Diff for: compiler/rustc_parse/src/parser/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,7 @@ impl<'a> Parser<'a> {
11151115
}
11161116

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

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

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 quux() {
13+
while let () = ()
14+
&& let () = () { //~ERROR: found a `{` in the middle of a let-chain
15+
&& let () = ()
16+
{
17+
}
18+
}
19+
20+
fn foobar() {
21+
while false {}
22+
{
23+
&& let () = ()
24+
}
25+
26+
fn fubar() {
27+
while false {
28+
{
29+
&& let () = ()
30+
}
31+
}
32+
33+
fn qux() {
34+
let foo = false;
35+
match foo {
36+
_ if foo => {
37+
&& let () = ()
38+
_ => {}
39+
}
40+
}
41+
42+
fn foo() {
43+
{
44+
&& let () = ()
45+
}
46+
47+
fn bar() {
48+
if false {}
49+
{
50+
&& let () = ()
51+
}
52+
53+
fn baz() {
54+
if false {
55+
{
56+
&& let () = ()
57+
}
58+
} //~ERROR: this file contains an unclosed delimiter

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

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

0 commit comments

Comments
 (0)