rust-lang/rust-analyzer · error
{punct:#?} is not a valid punct
Error message
{punct:#?} is not a valid punct What it means
This panic fires inside `to_parser_input` in syntax-bridge when converting macro-expansion `tt` token trees into parser `Input`. For every `tt::Leaf::Punct` it calls `SyntaxKind::from_char(punct.char)`; if the punctuation character has no corresponding Rust `SyntaxKind`, the code panics with a debug dump of the punct. It indicates a punct token outside the Rust punctuation set reached the converter.
Source
Thrown at crates/syntax-bridge/src/to_parser_input.rs:81
let edition = ctx_edition(ident.span.ctx);
match ident.sym.as_str() {
"_" => res.push(T![_], edition),
i if i.starts_with('\'') => res.push(LIFETIME_IDENT, edition),
_ if ident.is_raw.yes() => res.push(IDENT, edition),
text => match SyntaxKind::from_keyword(text, edition) {
Some(kind) => res.push(kind, edition),
None => {
let contextual_keyword =
SyntaxKind::from_contextual_keyword(text, edition)
.unwrap_or(SyntaxKind::IDENT);
res.push_ident(contextual_keyword, edition);
}
},
}
}
tt::Leaf::Punct(punct) => {
let kind = SyntaxKind::from_char(punct.char)
.unwrap_or_else(|| panic!("{punct:#?} is not a valid punct"));
res.push(kind, ctx_edition(punct.span.ctx));
if punct.spacing == tt::Spacing::Joint {
res.was_joint();
}
}
}
current.bump();
}
Some(tt::TokenTree::Subtree(subtree)) => {
if let Some(kind) = match subtree.delimiter.kind {
tt::DelimiterKind::Parenthesis => Some(T!['(']),
tt::DelimiterKind::Brace => Some(T!['{']),
tt::DelimiterKind::Bracket => Some(T!['[']),
tt::DelimiterKind::Invisible => None,
} {
res.push(kind, ctx_edition(subtree.delimiter.open.ctx));
}
current.bump();View on GitHub (pinned to e8f7e90aa3)
Solutions
- Inspect the `{punct:#?}` dump in the panic message to identify the offending `char` and trace the proc-macro or code that produced the token tree.
- Fix the producing proc-macro so it only emits valid Rust punctuation characters.
- If the char is a legitimate new Rust punct, add a mapping for it in `SyntaxKind::from_char`.
- When processing untrusted token trees, pre-filter puncts via `SyntaxKind::from_char(punct.char).is_some()` and skip/error on invalid ones instead of panicking.
Example fix
// before (panics)
let kind = SyntaxKind::from_char(punct.char)
.unwrap_or_else(|| panic!("{punct:#?} is not a valid punct"));
// after (caller-side guard)
if SyntaxKind::from_char(punct.char).is_none() {
return Err(format!("invalid punct {:?}", punct.char));
} Defensive patterns
Strategy: validation
Validate before calling
fn is_valid_punct(p: &tt::Punct) -> bool {
syntax::SyntaxKind::from_char(p.char).is_some()
}
// check every Punct leaf in the TokenTreesView before calling to_parser_input Type guard
fn valid_punct_kind(p: &tt::Punct) -> Option<syntax::SyntaxKind> {
syntax::SyntaxKind::from_char(p.char)
} Prevention
- Check `SyntaxKind::from_char` before feeding puncts into `to_parser_input`.
- Fuzz proc-macro outputs with arbitrary punct characters.
- Keep the `tt` crate and `syntax` SyntaxKind punctuation mappings in sync when adding new puncts.
- Never hand-craft token trees with non-Rust punctuation in tests.
When it happens
Trigger: Calling `to_parser_input(buffer, span_to_edition)` with a `tt::TokenTreesView` containing a `Punct` leaf whose `char` is not a recognized Rust punctuation character (e.g. a non-ASCII or invented punct produced by a buggy proc-macro or corrupted token tree), so `SyntaxKind::from_char` returns None.
Common situations: Buggy or malicious procedural macros emitting synthesized punct characters; version mismatch between the `tt` crate representation and `SyntaxKind::from_char` (a new punct added on one side only); hand-crafted token trees in tests using non-Rust punctuation characters.
Related errors
- Next token must be ident
- bad kind {other}
- bad spacing {other}
- bad tag: {other}
- unmatched closing delimiter from syntax fixup
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/a35c45eec125a8aa.
Report an issue: GitHub.