{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreig6fxoxdmylf2sfmsu72v435s74lleimulrzaewo7ibwn2sqswl7u",
"uri": "at://did:plc:ivbknywyskln22er3nkssdhl/app.bsky.feed.post/3mofvr2rxw6x2"
},
"path": "/t/parser-error-recovery-in-syn-for-better-ide-support-with-proc-macros/24362?page=2#post_23",
"publishedAt": "2026-06-16T10:30:29.000Z",
"site": "https://internals.rust-lang.org",
"tags": [
"84277",
"[1]",
"↩︎"
],
"textContent": "This is tangential, but I just found this _really_ interesting parser API that I thought I'd share.\n\nThe core idea is that **diagnostics, parser-to-input alignment and output are all independently optional** when a recovering parser returns:\n\n\n // example: tuple.rs\n\n impl<T0, T1, T2, Last> PopParsedFrom for (T0, T1, T2, Last)\n where\n T0: PopParsedFrom,\n T1: PopParsedFrom,\n T2: PopParsedFrom,\n Last: PopParsedFrom,\n {\n // (Enables parser combinators.)\n type Parsed = (T1::Parsed, T2::Parsed, T3::Parsed, Last::Parsed);\n\n fn pop_parsed_from(\n input: &mut crate::Input,\n errors: &mut crate::Errors, // not used for control flow\n ) -> Parsed<Self::Parsed> {\n // This could also be written with a `Parsed::zip`,\n // so imagine a more complicated parser here.\n let t = T0::pop_parsed_from(input, errors)?\n .zip(T1::pop_parsed_from(input, errors)?)\n .zip(T2::pop_parsed_from(input, errors)?);\n );\n Last::pop_parsed_from(input, errors).map_some_and(|last| {\n t.map(|((t0, t1), t2)| (t0, t1, t2, last))\n })\n }\n }\n\n // parsed.rs\n\n #![try_trait_v2]\n\n struct Parsed<T> {\n /// Determines branching.\n pub aligned: bool,\n /// Unwrapped by `?` iff `aligned`.\n pub output: Option<T>,\n }\n\n impl<T> FromResidual for Parsed<T> {\n fn from_residual(_: <Self as Try>::Residual) -> Self {\n Self { aligned: false, output: None }\n }\n }\n\n impl<T> Try for Parsed<T> {\n type Output = Option<T>;\n type Residual = Option<T>; // Might be useful for something.\n\n fn from_output(output: Self::Output) -> Self {\n Self { aligned: true, output }\n }\n\n fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {\n if self.aligned {\n ControlFlow::Continue(self.output);\n } else {\n ControlFlow::Break(self.output);\n }\n }\n }\n\n\n(At least I think I can refer to non-associated `Parsed` unambiguously in the return type.)\n\nFor example, delimited groups, separated repeats and \"end of input\" can realign the parser.\nOptionals, repeats and most punctuation (via `Default`) can always have `Some` `output`.\n\nThe nested tuple there is most likely the most concise way to write this without macros.\nI think muncher-less `macro_rules!` implementations will also be possible with some chaff.\n\n* * *\n\nSince `try_trait_v2` (#84277) stabilisation seems far off, I'm currently emulating this with\n\nwith `try_trait_v2` | stable\n---|---\n`Parsed<Self::Parsed>` | `ControlFlow<Option<Self::Parsed>, Option<Self::Parsed>>`\n`?` | `.map_break(|_| None)?`\n`….map_some_and(…)`[1] | `match … { ControlFlow::… => …, ControlFlow::… => … }`\n\nwhich makes it somewhat clunky.\n\n* * *\n\n 1. Name pending. ↩︎\n\n\n",
"title": "Parser error recovery in `syn` for better IDE support with proc-macros"
}