Rollup merge of #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 #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 #120329 this could result in some sub-optimal suggestions in error messages, but nothing else. But #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 #120397. r? `@compiler-errors`
This commit is contained in:
commit
c00192ae2a
4 changed files with 120 additions and 31 deletions
|
@ -28,6 +28,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
|
|||
use rustc_errors::{
|
||||
AddToDiagnostic, Applicability, Diagnostic, DiagnosticBuilder, PResult, StashKey,
|
||||
};
|
||||
use rustc_lexer::unescape::unescape_char;
|
||||
use rustc_macros::Subdiagnostic;
|
||||
use rustc_session::errors::{report_lit_error, ExprParenthesesNeeded};
|
||||
use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP;
|
||||
|
@ -1665,6 +1666,7 @@ impl<'a> Parser<'a> {
|
|||
&& self.may_recover()
|
||||
&& (matches!(self.token.kind, token::CloseDelim(_) | token::Comma)
|
||||
|| self.token.is_punct())
|
||||
&& could_be_unclosed_char_literal(label_.ident)
|
||||
{
|
||||
let (lit, _) =
|
||||
self.recover_unclosed_char(label_.ident, Parser::mk_token_lit_char, |self_| {
|
||||
|
@ -1750,16 +1752,17 @@ impl<'a> Parser<'a> {
|
|||
Ok(expr)
|
||||
}
|
||||
|
||||
/// Emit an error when a char is parsed as a lifetime because of a missing quote.
|
||||
/// Emit an error when a char is parsed as a lifetime or label because of a missing quote.
|
||||
pub(super) fn recover_unclosed_char<L>(
|
||||
&self,
|
||||
lifetime: Ident,
|
||||
ident: Ident,
|
||||
mk_lit_char: impl FnOnce(Symbol, Span) -> L,
|
||||
err: impl FnOnce(&Self) -> DiagnosticBuilder<'a>,
|
||||
) -> L {
|
||||
if let Some(diag) = self.dcx().steal_diagnostic(lifetime.span, StashKey::LifetimeIsChar) {
|
||||
assert!(could_be_unclosed_char_literal(ident));
|
||||
if let Some(diag) = self.dcx().steal_diagnostic(ident.span, StashKey::LifetimeIsChar) {
|
||||
diag.with_span_suggestion_verbose(
|
||||
lifetime.span.shrink_to_hi(),
|
||||
ident.span.shrink_to_hi(),
|
||||
"add `'` to close the char literal",
|
||||
"'",
|
||||
Applicability::MaybeIncorrect,
|
||||
|
@ -1768,15 +1771,15 @@ impl<'a> Parser<'a> {
|
|||
} else {
|
||||
err(self)
|
||||
.with_span_suggestion_verbose(
|
||||
lifetime.span.shrink_to_hi(),
|
||||
ident.span.shrink_to_hi(),
|
||||
"add `'` to close the char literal",
|
||||
"'",
|
||||
Applicability::MaybeIncorrect,
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
let name = lifetime.without_first_quote().name;
|
||||
mk_lit_char(name, lifetime.span)
|
||||
let name = ident.without_first_quote().name;
|
||||
mk_lit_char(name, ident.span)
|
||||
}
|
||||
|
||||
/// Recover on the syntax `do catch { ... }` suggesting `try { ... }` instead.
|
||||
|
@ -2047,8 +2050,11 @@ impl<'a> Parser<'a> {
|
|||
let msg = format!("unexpected token: {}", super::token_descr(&token));
|
||||
self_.dcx().struct_span_err(token.span, msg)
|
||||
};
|
||||
// On an error path, eagerly consider a lifetime to be an unclosed character lit
|
||||
if self.token.is_lifetime() {
|
||||
// On an error path, eagerly consider a lifetime to be an unclosed character lit, if that
|
||||
// makes sense.
|
||||
if let Some(ident) = self.token.lifetime()
|
||||
&& could_be_unclosed_char_literal(ident)
|
||||
{
|
||||
let lt = self.expect_lifetime();
|
||||
Ok(self.recover_unclosed_char(lt.ident, mk_lit_char, err))
|
||||
} else {
|
||||
|
@ -3776,6 +3782,13 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Could this lifetime/label be an unclosed char literal? For example, `'a`
|
||||
/// could be, but `'abc` could not.
|
||||
pub(crate) fn could_be_unclosed_char_literal(ident: Ident) -> bool {
|
||||
ident.name.as_str().starts_with('\'')
|
||||
&& unescape_char(ident.without_first_quote().name.as_str()).is_ok()
|
||||
}
|
||||
|
||||
/// Used to forbid `let` expressions in certain syntactic locations.
|
||||
#[derive(Clone, Copy, Subdiagnostic)]
|
||||
pub(crate) enum ForbiddenLetReason {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue