Summary
type_args_after_open in crates/sploosh-parser/src/lib.rs rejects spec-legal type arguments that do not start with an identifier:
fn f(v: Vec<&str>) {} // Err: "expected identifier" at the `&`
fn f(v: Vec<(i64, i64)>) {} // same
fn f(v: Vec<[u8; 4]>) {} // same
All of these are legal per LANGUAGE_SPEC.md §3 / §16 (type_args = type_arg { "," type_arg } with type_arg = type | IDENT "=" type).
Root cause
The associated-type-binding checkpoint tries self.ident() first and backtracks by resetting self.pos — but ident() pushes an "expected identifier" ParseError into self.errors before the backtrack, and the checkpoint never truncates self.errors. The fallback ty() then parses the type argument correctly, but the stale error survives and parse_program returns Err.
Verified empirically against the real crates (probe test, 2026-07-02): all four shapes above fail today; each parses fine once the spurious error is discarded.
Suggested fix
Record self.errors.len() alongside the position checkpoint and truncate on backtrack, or restructure the Assoc lookahead to peek (Ident followed by =) before committing, which avoids the error-pushing path entirely. The peek option cannot regress error ordering elsewhere and matches how other lookaheads in the parser work (turbofish, send-statement head).
Scope note
Found during the parser enhancement wave (PR #71) while designing the attribute-argument parser; deliberately not fixed there because that wave is scoped to no-behavior-change refactors. The probe cases above should land as regression tests with the fix.
Summary
type_args_after_openincrates/sploosh-parser/src/lib.rsrejects spec-legal type arguments that do not start with an identifier:All of these are legal per LANGUAGE_SPEC.md §3 / §16 (
type_args = type_arg { "," type_arg }withtype_arg = type | IDENT "=" type).Root cause
The associated-type-binding checkpoint tries
self.ident()first and backtracks by resettingself.pos— butident()pushes an "expected identifier"ParseErrorintoself.errorsbefore the backtrack, and the checkpoint never truncatesself.errors. The fallbackty()then parses the type argument correctly, but the stale error survives andparse_programreturnsErr.Verified empirically against the real crates (probe test, 2026-07-02): all four shapes above fail today; each parses fine once the spurious error is discarded.
Suggested fix
Record
self.errors.len()alongside the position checkpoint and truncate on backtrack, or restructure the Assoc lookahead to peek (Identfollowed by=) before committing, which avoids the error-pushing path entirely. The peek option cannot regress error ordering elsewhere and matches how other lookaheads in the parser work (turbofish, send-statement head).Scope note
Found during the parser enhancement wave (PR #71) while designing the attribute-argument parser; deliberately not fixed there because that wave is scoped to no-behavior-change refactors. The probe cases above should land as regression tests with the fix.