call2_for_syn/
lib.rs

1//! This library provides a `call2` function that sits somewhere in-between `syn`'s `parse2` and `ParseBuffer::call`:
2//! It lets you conveniently apply a parser function to a `proc-macro2` token stream, for example from a `quote!`.
3//!
4//! [![Zulip Chat](https://img.shields.io/endpoint?label=chat&url=https%3A%2F%2Fiteration-square-automation.schichler.dev%2F.netlify%2Ffunctions%2Fstream_subscribers_shield%3Fstream%3Dproject%252Fcall2-for-syn)](https://iteration-square.schichler.dev/#narrow/stream/project.2Fcall2-for-syn)
5
6#![doc(html_root_url = "https://docs.rs/call2-for-syn/3.0.4")]
7#![warn(clippy::pedantic, missing_docs)]
8#![allow(clippy::semicolon_if_nothing_returned)]
9#![forbid(unsafe_code)]
10
11use proc_macro2::TokenStream;
12use std::{
13	error::Error,
14	fmt::{self, Debug, Display, Formatter},
15	result::Result as stdResult,
16};
17use syn::parse::{ParseStream, Parser};
18
19//FIXME: Reenable this once there's a Syn 2 version of unquote.
20#[cfg(all(never, not(never)))]
21#[cfg(doctest)]
22#[doc = include_str!("../README.md")]
23mod readme {}
24
25/// Analogous to [`syn::parse2`] and [`syn::parse::ParseBuffer::call`].
26///
27/// This function ignores an underlying check whether input was fully parsed.
28///
29/// # Examples
30///
31/// ```rust
32/// use call2_for_syn::call2_allow_incomplete;
33/// use quote::quote;
34/// use syn::{Ident, Token};
35///
36/// # (|| {
37/// let (hello, world) = call2_allow_incomplete(quote!(Hello world!), |input| {
38///     let hello: Ident = input.parse()?;
39///     let world: Ident = input.parse()?;
40///     // input.parse::<Token![!]>()?;
41///     syn::Result::Ok((hello, world))
42/// })?;
43///
44/// assert_eq!(format!("{}", hello), "Hello");
45/// assert_eq!(format!("{}", world), "world");
46/// # syn::Result::Ok(())
47/// # })().unwrap();
48/// ```
49///
50/// ```rust
51/// use call2_for_syn::call2_allow_incomplete;
52/// use quote::quote;
53/// use syn::{Ident, Token};
54///
55/// # (|| {
56/// let (hello, world) = call2_allow_incomplete(quote!(Hello world!), |input| {
57///     let hello: Ident = input.parse()?;
58///     let world: Ident = input.parse()?;
59///     input.parse::<Token![!]>()?;
60///     syn::Result::Ok((hello, world))
61/// })?;
62///
63/// assert_eq!(format!("{}", hello), "Hello");
64/// assert_eq!(format!("{}", world), "world");
65/// # syn::Result::Ok(())
66/// # })().unwrap();
67/// ```
68///
69/// [`syn::parse2`]: https://docs.rs/syn/1.0.14/syn/fn.parse2.html
70/// [`syn::parse::ParseBuffer::call`]: https://docs.rs/syn/1.0.14/syn/parse/struct.ParseBuffer.html#method.call
71pub fn call2_allow_incomplete<T, P: FnOnce(ParseStream) -> T>(input: TokenStream, parser: P) -> T {
72	let mut result = None;
73	Parser::parse2(
74		|input: ParseStream| {
75			result = Some(parser(input));
76			Ok(())
77		},
78		input,
79	)
80	.ok();
81	match result {
82		Some(result) => result,
83		None => {
84			unreachable!()
85		}
86	}
87}
88
89/// Analogous to [`syn::parse2`] and [`syn::parse::ParseBuffer::call`].
90///
91/// # Errors
92///
93/// Iff not all of `input` is consumed by `parser`.  
94/// `parser`'s result is still returned in the error value, in this case.
95///
96/// # Examples
97///
98/// ```rust
99/// use call2_for_syn::call2_strict;
100/// use quote::quote;
101/// use syn::{Ident, Token};
102///
103/// # (|| {
104/// let (hello, world) = call2_strict(quote!(Hello world!), |input| {
105///     let hello: Ident = input.parse()?;
106///     let world: Ident = input.parse()?;
107///     // input.parse::<Token![!]>()?;
108///     syn::Result::Ok((hello, world))
109/// }).unwrap_err().parsed?;
110///
111/// assert_eq!(format!("{}", hello), "Hello");
112/// assert_eq!(format!("{}", world), "world");
113/// # syn::Result::Ok(())
114/// # })().unwrap();
115/// ```
116///
117/// ```rust
118/// use call2_for_syn::call2_strict;
119/// use quote::quote;
120/// use syn::{Ident, Token};
121///
122/// # (|| {
123/// let (hello, world) = call2_strict(quote!(Hello world!), |input| {
124///     let hello: Ident = input.parse()?;
125///     let world: Ident = input.parse()?;
126///     input.parse::<Token![!]>()?;
127///     syn::Result::Ok((hello, world))
128/// })??;
129///
130/// assert_eq!(format!("{}", hello), "Hello");
131/// assert_eq!(format!("{}", world), "world");
132/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
133/// # })().unwrap();
134/// ```
135///
136/// [`syn::parse2`]: https://docs.rs/syn/1.0.14/syn/fn.parse2.html
137/// [`syn::parse::ParseBuffer::call`]: https://docs.rs/syn/1.0.14/syn/parse/struct.ParseBuffer.html#method.call
138#[track_caller]
139#[allow(clippy::missing_panics_doc)]
140pub fn call2_strict<T, P: FnOnce(ParseStream) -> T>(
141	input: TokenStream,
142	parser: P,
143) -> stdResult<T, Incomplete<T>> {
144	let mut parsed = None;
145	match Parser::parse2(
146		|input: ParseStream| {
147			parsed = Some(parser(input));
148			Ok(())
149		},
150		input,
151	) {
152		Ok(()) => Ok(parsed.expect("infallible")),
153		Err(syn_error) => Err(Incomplete {
154			parsed: parsed.expect("infallible"),
155			syn_error,
156		}),
157	}
158}
159
160/// Returned by [`call2_strict`].
161///
162/// [`call2_strict`]: ./fn.call2_strict.html
163pub type Result<T> = stdResult<T, Incomplete<T>>;
164
165/// Signifies that `parser` did not consume all of `input` in a [`call2_strict`] call.
166///
167/// [`call2_strict`]: ./fn.call2_strict.html
168#[derive(Debug, Clone)]
169pub struct Incomplete<T> {
170	/// The parsed instance.
171	pub parsed: T,
172	/// The [`Error`](syn::Error) raised because not all input was parsed.
173	pub syn_error: syn::Error,
174}
175
176impl<T: Debug> Error for Incomplete<T> {
177	fn source(&self) -> Option<&(dyn Error + 'static)> {
178		Some(&self.syn_error)
179	}
180}
181
182impl<T> Display for Incomplete<T> {
183	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
184		write!(f, "TokenStream parsed incompletely")
185	}
186}