Skip to content

Commit d02a140

Browse files
authored
Unrolled build for rust-lang#120460
Rollup merge of rust-lang#120460 - nnethercote:fix-120397, r=compiler-errors Be more careful about interpreting a label/lifetime as a mistyped char literal. Currently the parser interprets any label/lifetime in certain positions as a mistyped char literal, on the assumption that the trailing single quote was accidentally omitted. In such cases it gives an error with a suggestion to add the trailing single quote, and then puts the appropriate char literal into the AST. This behaviour was introduced in rust-lang#101293. This is reasonable for a case like this: ``` let c = 'a; ``` because `'a'` is a valid char literal. It's less reasonable for a case like this: ``` let c = 'abc; ``` because `'abc'` is not a valid char literal. Prior to rust-lang#120329 this could result in some sub-optimal suggestions in error messages, but nothing else. But rust-lang#120329 changed `LitKind::from_token_lit` to assume that the char/byte/string literals it receives are valid, and to assert if not. This is reasonable because the lexer does not produce invalid char/byte/string literals in general. But in this "interpret label/lifetime as unclosed char literal" case the parser can produce an invalid char literal with contents such as `abc`, which triggers an assertion failure. This PR changes the parser so it's more cautious about interpreting labels/lifetimes as unclosed char literals. Fixes rust-lang#120397. r? `@compiler-errors`
2 parents c401f09 + 306612e commit d02a140

File tree

4 files changed

+119
-30
lines changed

4 files changed

+119
-30
lines changed

compiler/rustc_parse/src/parser/expr.rs

+22-9
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;
@@ -1665,6 +1666,7 @@ impl<'a> Parser<'a> {
16651666
&& self.may_recover()
16661667
&& (matches!(self.token.kind, token::CloseDelim(_) | token::Comma)
16671668
|| self.token.is_punct())
1669+
&& could_be_unclosed_char_literal(label_.ident)
16681670
{
16691671
let (lit, _) =
16701672
self.recover_unclosed_char(label_.ident, Parser::mk_token_lit_char, |self_| {
@@ -1750,16 +1752,17 @@ impl<'a> Parser<'a> {
17501752
Ok(expr)
17511753
}
17521754

1753-
/// Emit an error when a char is parsed as a lifetime because of a missing quote.
1755+
/// Emit an error when a char is parsed as a lifetime or label because of a missing quote.
17541756
pub(super) fn recover_unclosed_char<L>(
17551757
&self,
1756-
lifetime: Ident,
1758+
ident: Ident,
17571759
mk_lit_char: impl FnOnce(Symbol, Span) -> L,
17581760
err: impl FnOnce(&Self) -> DiagnosticBuilder<'a>,
17591761
) -> L {
1760-
if let Some(diag) = self.dcx().steal_diagnostic(lifetime.span, StashKey::LifetimeIsChar) {
1762+
assert!(could_be_unclosed_char_literal(ident));
1763+
if let Some(diag) = self.dcx().steal_diagnostic(ident.span, StashKey::LifetimeIsChar) {
17611764
diag.with_span_suggestion_verbose(
1762-
lifetime.span.shrink_to_hi(),
1765+
ident.span.shrink_to_hi(),
17631766
"add `'` to close the char literal",
17641767
"'",
17651768
Applicability::MaybeIncorrect,
@@ -1768,15 +1771,15 @@ impl<'a> Parser<'a> {
17681771
} else {
17691772
err(self)
17701773
.with_span_suggestion_verbose(
1771-
lifetime.span.shrink_to_hi(),
1774+
ident.span.shrink_to_hi(),
17721775
"add `'` to close the char literal",
17731776
"'",
17741777
Applicability::MaybeIncorrect,
17751778
)
17761779
.emit();
17771780
}
1778-
let name = lifetime.without_first_quote().name;
1779-
mk_lit_char(name, lifetime.span)
1781+
let name = ident.without_first_quote().name;
1782+
mk_lit_char(name, ident.span)
17801783
}
17811784

17821785
/// Recover on the syntax `do catch { ... }` suggesting `try { ... }` instead.
@@ -2047,8 +2050,11 @@ impl<'a> Parser<'a> {
20472050
let msg = format!("unexpected token: {}", super::token_descr(&token));
20482051
self_.dcx().struct_span_err(token.span, msg)
20492052
};
2050-
// On an error path, eagerly consider a lifetime to be an unclosed character lit
2051-
if self.token.is_lifetime() {
2053+
// On an error path, eagerly consider a lifetime to be an unclosed character lit, if that
2054+
// makes sense.
2055+
if let Some(ident) = self.token.lifetime()
2056+
&& could_be_unclosed_char_literal(ident)
2057+
{
20522058
let lt = self.expect_lifetime();
20532059
Ok(self.recover_unclosed_char(lt.ident, mk_lit_char, err))
20542060
} else {
@@ -3776,6 +3782,13 @@ impl<'a> Parser<'a> {
37763782
}
37773783
}
37783784

3785+
/// Could this lifetime/label be an unclosed char literal? For example, `'a`
3786+
/// could be, but `'abc` could not.
3787+
pub(crate) fn could_be_unclosed_char_literal(ident: Ident) -> bool {
3788+
ident.name.as_str().starts_with('\'')
3789+
&& unescape_char(ident.without_first_quote().name.as_str()).is_ok()
3790+
}
3791+
37793792
/// Used to forbid `let` expressions in certain syntactic locations.
37803793
#[derive(Clone, Copy, Subdiagnostic)]
37813794
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
UnexpectedParenInRangePat, UnexpectedParenInRangePatSugg,
1111
UnexpectedVertVertBeforeFunctionParam, 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;
@@ -535,11 +536,12 @@ impl<'a> Parser<'a> {
535536
None => PatKind::Path(qself, path),
536537
}
537538
}
538-
} else if matches!(self.token.kind, token::Lifetime(_))
539+
} else if let token::Lifetime(lt) = self.token.kind
539540
// In pattern position, we're totally fine with using "next token isn't colon"
540541
// as a heuristic. We could probably just always try to recover if it's a lifetime,
541542
// because we never have `'a: label {}` in a pattern position anyways, but it does
542543
// keep us from suggesting something like `let 'a: Ty = ..` => `let 'a': Ty = ..`
544+
&& could_be_unclosed_char_literal(Ident::with_dummy_span(lt))
543545
&& !self.look_ahead(1, |token| matches!(token.kind, token::Colon))
544546
{
545547
// 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)