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//! [](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}