Skip to content

Commit 306612e

Browse files
committed
Be more careful about interpreting a label/lifetime as a mistyped char literal.
Currently the parser will interpret any label/lifetime in certain positions as a mistyped char literal, on the assumption that the trailing single quote was accidentally omitted. This is reasonable for a something like 'a (because 'a' would be valid) but not reasonable for a something like 'abc (because 'abc' is not valid). This commit restricts this behaviour only to labels/lifetimes that would be valid char literals, via the new `could_be_unclosed_char_literal` function. The commit also augments the `label-is-actually-char.rs` test in a couple of ways: - Adds testing of labels/lifetimes with identifiers longer than one char, e.g. 'abc. - Adds a new match with simpler patterns, because the `recover_unclosed_char` call in `parse_pat_with_range_pat` was not being exercised (in this test or any other ui tests). Fixes rust-lang#120397, an assertion failure, which was caused by this behaviour in the parser interacting with some new stricter char literal checking added in rust-lang#120329.
1 parent 5bda589 commit 306612e

File tree

4 files changed

+112
-23
lines changed

4 files changed

+112
-23
lines changed

compiler/rustc_parse/src/parser/expr.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
2828
use rustc_errors::{
2929
AddToDiagnostic, Applicability, Diagnostic, DiagnosticBuilder, PResult, StashKey,
3030
};
31+
use rustc_lexer::unescape::unescape_char;
3132
use rustc_macros::Subdiagnostic;
3233
use rustc_session::errors::{report_lit_error, ExprParenthesesNeeded};
3334
use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP;
@@ -1652,6 +1653,7 @@ impl<'a> Parser<'a> {
16521653
&& self.may_recover()
16531654
&& (matches!(self.token.kind, token::CloseDelim(_) | token::Comma)
16541655
|| self.token.is_punct())
1656+
&& could_be_unclosed_char_literal(label_.ident)
16551657
{
16561658
let (lit, _) =
16571659
self.recover_unclosed_char(label_.ident, Parser::mk_token_lit_char, |self_| {
@@ -1744,6 +1746,7 @@ impl<'a> Parser<'a> {
17441746
mk_lit_char: impl FnOnce(Symbol, Span) -> L,
17451747
err: impl FnOnce(&Self) -> DiagnosticBuilder<'a>,
17461748
) -> L {
1749+
assert!(could_be_unclosed_char_literal(ident));
17471750
if let Some(diag) = self.dcx().steal_diagnostic(ident.span, StashKey::LifetimeIsChar) {
17481751
diag.with_span_suggestion_verbose(
17491752
ident.span.shrink_to_hi(),
@@ -2034,8 +2037,11 @@ impl<'a> Parser<'a> {
20342037
let msg = format!("unexpected token: {}", super::token_descr(&token));
20352038
self_.dcx().struct_span_err(token.span, msg)
20362039
};
2037-
// On an error path, eagerly consider a lifetime to be an unclosed character lit
2038-
if self.token.is_lifetime() {
2040+
// On an error path, eagerly consider a lifetime to be an unclosed character lit, if that
2041+
// makes sense.
2042+
if let Some(ident) = self.token.lifetime()
2043+
&& could_be_unclosed_char_literal(ident)
2044+
{
20392045
let lt = self.expect_lifetime();
20402046
Ok(self.recover_unclosed_char(lt.ident, mk_lit_char, err))
20412047
} else {
@@ -3763,6 +3769,13 @@ impl<'a> Parser<'a> {
37633769
}
37643770
}
37653771

3772+
/// Could this lifetime/label be an unclosed char literal? For example, `'a`
3773+
/// could be, but `'abc` could not.
3774+
pub(crate) fn could_be_unclosed_char_literal(ident: Ident) -> bool {
3775+
ident.name.as_str().starts_with('\'')
3776+
&& unescape_char(ident.without_first_quote().name.as_str()).is_ok()
3777+
}
3778+
37663779
/// Used to forbid `let` expressions in certain syntactic locations.
37673780
#[derive(Clone, Copy, Subdiagnostic)]
37683781
pub(crate) enum ForbiddenLetReason {

compiler/rustc_parse/src/parser/pat.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::errors::{
1010
UnexpectedParenInRangePatSugg, UnexpectedVertVertBeforeFunctionParam,
1111
UnexpectedVertVertInPattern,
1212
};
13+
use crate::parser::expr::could_be_unclosed_char_literal;
1314
use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
1415
use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor};
1516
use rustc_ast::ptr::P;
@@ -443,11 +444,12 @@ impl<'a> Parser<'a> {
443444
} else {
444445
PatKind::Path(qself, path)
445446
}
446-
} else if matches!(self.token.kind, token::Lifetime(_))
447+
} else if let token::Lifetime(lt) = self.token.kind
447448
// In pattern position, we're totally fine with using "next token isn't colon"
448449
// as a heuristic. We could probably just always try to recover if it's a lifetime,
449450
// because we never have `'a: label {}` in a pattern position anyways, but it does
450451
// keep us from suggesting something like `let 'a: Ty = ..` => `let 'a': Ty = ..`
452+
&& could_be_unclosed_char_literal(Ident::with_dummy_span(lt))
451453
&& !self.look_ahead(1, |token| matches!(token.kind, token::Colon))
452454
{
453455
// Recover a `'a` as a `'a'` literal
+34-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,43 @@
1+
// Note: it's ok to interpret 'a as 'a', but but not ok to interpret 'abc as
2+
// 'abc' because 'abc' is not a valid char literal.
3+
14
fn main() {
25
let c = 'a;
36
//~^ ERROR expected `while`, `for`, `loop` or `{` after a label
47
//~| HELP add `'` to close the char literal
5-
match c {
8+
9+
let c = 'abc;
10+
//~^ ERROR expected `while`, `for`, `loop` or `{` after a label
11+
//~| ERROR expected expression, found `;`
12+
}
13+
14+
fn f() {
15+
match 'a' {
616
'a'..='b => {}
717
//~^ ERROR unexpected token: `'b`
818
//~| HELP add `'` to close the char literal
9-
_ => {}
19+
'c'..='def => {}
20+
//~^ ERROR unexpected token: `'def`
1021
}
11-
let x = ['a, 'b];
12-
//~^ ERROR expected `while`, `for`, `loop` or `{` after a label
13-
//~| ERROR expected `while`, `for`, `loop` or `{` after a label
14-
//~| HELP add `'` to close the char literal
15-
//~| HELP add `'` to close the char literal
22+
}
23+
24+
fn g() {
25+
match 'g' {
26+
'g => {}
27+
//~^ ERROR expected pattern, found `=>`
28+
//~| HELP add `'` to close the char literal
29+
'hij => {}
30+
//~^ ERROR expected pattern, found `'hij`
31+
_ => {}
32+
}
33+
}
34+
35+
fn h() {
36+
let x = ['a, 'b, 'cde];
37+
//~^ ERROR expected `while`, `for`, `loop` or `{` after a label
38+
//~| HELP add `'` to close the char literal
39+
//~| ERROR expected `while`, `for`, `loop` or `{` after a label
40+
//~| HELP add `'` to close the char literal
41+
//~| ERROR expected `while`, `for`, `loop` or `{` after a label
42+
//~| ERROR expected expression, found `]`
1643
}
+60-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: expected `while`, `for`, `loop` or `{` after a label
2-
--> $DIR/label-is-actually-char.rs:2:15
2+
--> $DIR/label-is-actually-char.rs:5:15
33
|
44
LL | let c = 'a;
55
| ^ expected `while`, `for`, `loop` or `{` after a label
@@ -9,8 +9,20 @@ help: add `'` to close the char literal
99
LL | let c = 'a';
1010
| +
1111

12+
error: expected `while`, `for`, `loop` or `{` after a label
13+
--> $DIR/label-is-actually-char.rs:9:17
14+
|
15+
LL | let c = 'abc;
16+
| ^ expected `while`, `for`, `loop` or `{` after a label
17+
18+
error: expected expression, found `;`
19+
--> $DIR/label-is-actually-char.rs:9:17
20+
|
21+
LL | let c = 'abc;
22+
| ^ expected expression
23+
1224
error: unexpected token: `'b`
13-
--> $DIR/label-is-actually-char.rs:6:15
25+
--> $DIR/label-is-actually-char.rs:16:15
1426
|
1527
LL | 'a'..='b => {}
1628
| ^^
@@ -20,27 +32,62 @@ help: add `'` to close the char literal
2032
LL | 'a'..='b' => {}
2133
| +
2234

35+
error: unexpected token: `'def`
36+
--> $DIR/label-is-actually-char.rs:19:15
37+
|
38+
LL | 'c'..='def => {}
39+
| ^^^^
40+
41+
error: expected pattern, found `=>`
42+
--> $DIR/label-is-actually-char.rs:26:11
43+
|
44+
LL | 'g => {}
45+
| ^^ expected pattern
46+
|
47+
help: add `'` to close the char literal
48+
|
49+
LL | 'g' => {}
50+
| +
51+
52+
error: expected pattern, found `'hij`
53+
--> $DIR/label-is-actually-char.rs:29:8
54+
|
55+
LL | 'hij => {}
56+
| ^^^^ expected pattern
57+
2358
error: expected `while`, `for`, `loop` or `{` after a label
24-
--> $DIR/label-is-actually-char.rs:11:16
59+
--> $DIR/label-is-actually-char.rs:36:15
2560
|
26-
LL | let x = ['a, 'b];
27-
| ^ expected `while`, `for`, `loop` or `{` after a label
61+
LL | let x = ['a, 'b, 'cde];
62+
| ^ expected `while`, `for`, `loop` or `{` after a label
2863
|
2964
help: add `'` to close the char literal
3065
|
31-
LL | let x = ['a', 'b];
32-
| +
66+
LL | let x = ['a', 'b, 'cde];
67+
| +
3368

3469
error: expected `while`, `for`, `loop` or `{` after a label
35-
--> $DIR/label-is-actually-char.rs:11:20
70+
--> $DIR/label-is-actually-char.rs:36:19
3671
|
37-
LL | let x = ['a, 'b];
38-
| ^ expected `while`, `for`, `loop` or `{` after a label
72+
LL | let x = ['a, 'b, 'cde];
73+
| ^ expected `while`, `for`, `loop` or `{` after a label
3974
|
4075
help: add `'` to close the char literal
4176
|
42-
LL | let x = ['a, 'b'];
43-
| +
77+
LL | let x = ['a, 'b', 'cde];
78+
| +
79+
80+
error: expected `while`, `for`, `loop` or `{` after a label
81+
--> $DIR/label-is-actually-char.rs:36:25
82+
|
83+
LL | let x = ['a, 'b, 'cde];
84+
| ^ expected `while`, `for`, `loop` or `{` after a label
85+
86+
error: expected expression, found `]`
87+
--> $DIR/label-is-actually-char.rs:36:25
88+
|
89+
LL | let x = ['a, 'b, 'cde];
90+
| ^ expected expression
4491

45-
error: aborting due to 4 previous errors
92+
error: aborting due to 11 previous errors
4693

0 commit comments

Comments
 (0)