From 2305b76f44bc73573cc8b8b44d988d3ef94a717d Mon Sep 17 00:00:00 2001 From: Rain Date: Tue, 1 Sep 2026 19:37:56 -0700 Subject: [PATCH] better message for stray tokens after a ParseWrapper value When a `ParseWrapper

` value is followed by tokens that `P` did not consume (for example `bool_expr = true false`), `syn::parse2` rejected them with a bare "unexpected token" (though the span was fine). Produce a better error message in this case, similar to the message you would get from a non-`ParseWrapper` type. Doing this in `ParseWrapper` rather than in individual `Parse` impls keeps those impls composable. --- src/ibidem.rs | 18 +++++++++++++++++- ui-tests/tests/ui/bad_parse_wrapper_stray.rs | 17 +++++++++++++++++ .../tests/ui/bad_parse_wrapper_stray.stderr | 5 +++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 ui-tests/tests/ui/bad_parse_wrapper_stray.rs create mode 100644 ui-tests/tests/ui/bad_parse_wrapper_stray.stderr diff --git a/src/ibidem.rs b/src/ibidem.rs index ab403a1..d65623d 100644 --- a/src/ibidem.rs +++ b/src/ibidem.rs @@ -5,6 +5,8 @@ use std::cell::RefCell; use proc_macro2::{TokenStream, TokenTree}; use serde::{Deserialize, de::Error, de::Visitor}; +use crate::serde_tokenstream::spanned_error; + /// A wrapper around [`TokenStream`] that implements [`Deserialize`] in the /// context of [`from_tokenstream`]. /// @@ -74,7 +76,21 @@ impl<'de, P: syn::parse::Parse> Deserialize<'de> for ParseWrapper

{ { let token_stream = deserializer.deserialize_bytes(WrapperVisitor)?; - match syn::parse2::

(token_stream) { + let parser = |input: syn::parse::ParseStream<'_>| -> syn::Result

{ + let parsed = P::parse(input)?; + // The deserializer hands over every token up to the next `,`, `=`, + // or EOF, so anything left after `P` is a stray token. Report such + // tokens as errors. + if let Some((tt, _)) = input.cursor().token_tree() { + return Err(spanned_error( + &tt, + format!("expected `,` or nothing, but found `{tt}`"), + )); + } + Ok(parsed) + }; + + match syn::parse::Parser::parse2(parser, token_stream) { Ok(parsed) => Ok(Self(parsed)), Err(err) => { let msg = err.to_string(); diff --git a/ui-tests/tests/ui/bad_parse_wrapper_stray.rs b/ui-tests/tests/ui/bad_parse_wrapper_stray.rs new file mode 100644 index 0000000..0c4d9e3 --- /dev/null +++ b/ui-tests/tests/ui/bad_parse_wrapper_stray.rs @@ -0,0 +1,17 @@ +// Copyright 2026 Oxide Computer Company + +// Ensure that tokens left over after a ParseWrapper value are reported at the +// first stray token. + +use testlib::annotation; + +#[annotation { + string = "test", + options = OptionA, + unit = (), + tup = (1, 2.0), + bool_expr = true false +}] +fn test() {} + +fn main() {} diff --git a/ui-tests/tests/ui/bad_parse_wrapper_stray.stderr b/ui-tests/tests/ui/bad_parse_wrapper_stray.stderr new file mode 100644 index 0000000..cdd315d --- /dev/null +++ b/ui-tests/tests/ui/bad_parse_wrapper_stray.stderr @@ -0,0 +1,5 @@ +error: expected `,` or nothing, but found `false` + --> tests/ui/bad_parse_wrapper_stray.rs:13:22 + | +13 | bool_expr = true false + | ^^^^^