{
  "$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"
}