rust-lang/rust · critical
expected error
Error message
expected error
What it means
Internal assertion in the lexer's raw-lifetime handling on Edition 2021+. When a raw lifetime token (e.g. `r#'a`) is immediately followed by another `'`, the lexer reinterprets the sequence as a malformed character literal and calls `emit_unescape_error` for `MoreThanOneChar`, which must return an `Err`. The `.expect("expected error")` asserts an error was actually emitted; if the unescape machinery returns `Ok`, the lexer's internal model is inconsistent and it panics rather than silently miscompile.
Source
Thrown at compiler/rustc_parse/src/lexer/mod.rs:356
if prefix_span.at_least_rust_2021() {
// If the raw lifetime is followed by \' then treat it a normal
// lifetime followed by a \', which is to interpret it as a character
// literal. In this case, it's always an invalid character literal
// since the literal must necessarily have >3 characters (r#...) inside
// of it, which is invalid.
if self.cursor.as_str().starts_with('\'') {
let lit_span = self.mk_sp(start, self.pos + BytePos(1));
let contents = self.str_from_to(start + BytePos(1), self.pos);
emit_unescape_error(
self.dcx(),
contents,
lit_span,
lit_span,
Mode::Char,
0..contents.len(),
EscapeError::MoreThanOneChar,
)
.expect("expected error");
}
let span = self.mk_sp(start, self.pos);
let lifetime_name_without_tick =
Symbol::intern(&self.str_from(ident_start));
if !lifetime_name_without_tick.can_be_raw() {
self.dcx().emit_err(
crate::diagnostics::CannotBeRawLifetime {
span,
ident: lifetime_name_without_tick
}
);
}
// Put the `'` back onto the lifetime name.
let mut lifetime_name =
String::with_capacity(lifetime_name_without_tick.as_str().len() + 1);View on GitHub (pinned to 22057b88b0)
Solutions
- Determine whether the offending token is generator output; if so, fix the generator to avoid emitting `r#'<ident>'` sequences
- Reduce the triggering source to a minimal file and report it as a rustc lexer ICE with the backtrace
- Run on stable to confirm whether it is a nightly regression in the raw-identifier/lifetime lexer
Defensive patterns
Strategy: validation
Validate before calling
use rustc_span::BytePos;
// Verify the source window is actually malformed the way the lexer expects it to be
// before invoking the error-expecting path in rustc_parse::lexer.
fn source_window_is_errored(src: &str, start: BytePos, end: BytePos) -> bool {
let s = &src[start.to_usize()..end.to_usize()];
// crude check: contains a known-bad token sequence
s.contains("\\") || !rustc_lexer::is_terminal(s)
}
if !source_window_is_errored(src, lo, hi) { return Ok(()); } Try / catch
use std::panic;
match panic::catch_unwind(panic::AssertUnwindSafe(|| lexer.expect_error(token))) {
Ok(()) => {},
Err(_) => /* lexer disagreed about erroneousness; skip the diagnostic */ {},
} Prevention
- Only call the error-expecting lexer path for source spans you have already classified as erroneous via a pre-scan.
- Keep your source classification in sync with rustc_lexer's token table so 'expected error' assertions never mismatch.
- Avoid hand-feeding token windows to the lexer; prefer full-source tokenization so the lexer's own state machine stays consistent.
- If you mutate source text between lexing passes, re-tokenize from scratch rather than reusing spans.
When it happens
Trigger: Lexing a raw lifetime immediately followed by a quote character in an Edition 2021+ crate — the `MoreThanOneChar` escape-error path of `emit_unescape_error` returns `Ok` instead of `Err`.
Common situations: Typo'd or machine-generated/proc-macro-emitted tokens of the shape `r#'<ident>'` in a 2021+ crate; exceedingly rare in hand-written source. Almost always indicates a lexer regression on nightly.
Related errors
- no error found for supposedly invalid raw string literal
- non-is_byte literal paired with NonAsciiCharInByte
- layout decided on a larger discriminant type ({min_ity:?}) t
- encountered a non-arbitrary layout during enum layout
- obj_size_bound: unknown pointer bit size {bits}
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/aec9ac8f4d52c20e.json.
Report an issue: GitHub.