rust-lang/rust · critical
failed to unescape char literal
Error message
failed to unescape char literal
What it means
Panic in LitKind::from_token_lit when unescape_char returns Err for a token::Char literal. As with the byte case, the lexer is contractually expected to have validated char escapes beforehand, so this panic signals a violation of that invariant — a malformed char literal reached semantic conversion. An internal-only path that should be unreachable from user source.
Source
Thrown at compiler/rustc_ast/src/util/literal.rs:84
}
// For byte/char/string literals, chars and escapes have already been
// checked in the lexer (in `cook_lexer_literal`). So we can assume all
// chars and escapes are valid here.
Ok(match kind {
token::Bool => {
assert!(symbol.is_bool_lit());
LitKind::Bool(symbol == kw::True)
}
token::Byte => {
return unescape_byte(symbol.as_str())
.map(LitKind::Byte)
.map_err(|_| panic!("failed to unescape byte literal"));
}
token::Char => {
return unescape_char(symbol.as_str())
.map(LitKind::Char)
.map_err(|_| panic!("failed to unescape char literal"));
}
// There are some valid suffixes for integer and float literals,
// so all the handling is done internally.
token::Integer => return integer_lit(symbol, suffix),
token::Float => return float_lit(symbol, suffix),
token::Str => {
// If there are no characters requiring special treatment we can
// reuse the symbol from the token. Otherwise, we must generate a
// new symbol because the string in the LitKind is different to the
// string in the token.
let s = symbol.as_str();
// Vanilla strings are so common we optimize for the common case where no chars
// requiring special behaviour are present.
let symbol = if s.contains('\\') {
let mut buf = String::with_capacity(s.len());
// Force-inlining here is aggressive but the closure isView on GitHub (pinned to 22057b88b0)
Solutions
- Report the ICE to rust-lang/rust with the literal and rustc version hash.
- If generating char literal tokens programmatically, ensure symbols are valid char literals or route through the lexer.
- Pin to a known-good nightly to confirm the regression window.
- Run the failing input through rustc_lexer directly to see whether validation is skipped.
Defensive patterns
Strategy: validation
Validate before calling
// Same shape as byte literals but for '...' char literals.
fn valid_char_literal(sym: &str) -> bool {
let inner = sym.strip_prefix("'").and_then(|s| s.strip_suffix("'"));
let Some(inner) = inner else { return false; };
rustc_lexer::unescape::unescape_char(inner).is_ok()
}
if !valid_char_literal(symbol.as_str()) {
return Err(format!("invalid char literal: {}", symbol));
} Type guard
fn is_valid_char_lit(kind: token::LitKind, sym: Symbol) -> bool {
matches!(kind, token::Char) && rustc_lexer::unescape::unescape_char(sym.as_str()).is_ok()
} Try / catch
let lit = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| LitKind::from_token_lit(raw_lit)));
match lit {
Ok(Ok(LitKind::Char(c))) => c,
Ok(Ok(other)) => other,
Ok(Err(e)) => return Err(format!("char literal error: {:?}", e)),
Err(_) => return Err(format!("char literal failed to unescape: {}", raw_lit.symbol)),
} Prevention
- Char literals must hold exactly one Unicode scalar value: a single source char or a \u{...} escape in 0..=0x10FFFF excluding surrogates (0xD800..=0xDFFF).
- Avoid bare \x in '...' — use \u{..}; \xHH is only valid in byte literals.
- When generating char literals from arbitrary code points, gate on char::from_u32(cp).is_some() before emitting the literal.
- In proc-macro output, build LitKind::Char(c) directly instead of generating source text that must be re-lexed.
When it happens
Trigger: Reached when from_token_lit is called on a token::Char whose symbol is not a valid char literal (e.g. '\xZZ') without prior lexer validation. Possible via hand-built token streams in proc-macros, fuzzing the parser boundary, or a compiler change that bypasses cook_lexer_literal.
Common situations: Nightly rustc regressions in the lexer; proc-macro tooling that fabricates char literal tokens; fuzz targets. Normal user code with a bad char literal emits a diagnostic at parse time.
Related errors
- failed to unescape byte literal
- unsupported integer: {self:?}
- unsupported float: {self:?}
- `homogeneous_aggregate` should not be called for scalable ve
- aggregates can't have `FieldsShape::Primitive`
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/f6dca02c96aa023d.json.
Report an issue: GitHub.