derive_deftly_macros/
adviseable.rs

1//! errors with compatibility advice
2//!
3//! Suitable for local glob import.
4//!
5//! ### Non-local errors from syn
6//!
7//! `syn` automatically produces "unexpected token" errors,
8//! if not all of the input is consumed, somewhere.
9//!
10//! Empirically:
11//! these errors are squirreled away somewhere, and surface
12//! on return from one of the top-level syn `parse` functions
13//! (the ones that provide a `ParseStream`).
14//!
15//! If the top-level function would return `Ok`,
16//! the unexpected tokens error appears instead.
17//! But if there's going to be an error anyway,
18//! the unexpected tokens error is discarded.
19
20use super::prelude::*;
21
22/// A `Result` whose error might, or might not, need compat advice
23///
24///  * `Err(raw)`: unexpected error, probably mismatched
25///    derive-deftly versions.
26///    [`adviseable_parse2`] will return the error
27///    but with compat advice for the user added.
28///  * `Ok(ErrNeedsNoAdvice(cooked))`: "expected" error, fully reported for
29///    the user's benefit.  Returned as-is by `parse_advised`.
30///  * `Ok(Ok(T))`.  All went well.
31///
32/// This odd structure is to add a note to most of the errors that
33/// come out of syn parsing.  The `braced!` etc. macros insist that the
34/// calling scope throws precisely `syn::Error`; `Into<syn::Error>`
35/// isn't enough.
36///
37/// This is the return type of `ParseAdviseable::parse_adviseable`.
38//
39// This is all rather unsatisfactory.  For example, it implies
40// the AOk and ErrNNA type aliases and consequent ad-hoc glob imports
41// of this module.  We'd prefer a custom error type, convertible from
42// syn::Error, but syn won't allow that.
43pub type AdviseableResult<T> = syn::Result<AdviseableInnerResult<T>>;
44
45/// Typically found as `syn::Result<AdvisedResult<T>>`
46#[derive(Debug)]
47#[must_use]
48pub enum AdviseableInnerResult<T> {
49    /// Success
50    Ok(T),
51
52    /// Failure, but doesn't need advice
53    ///
54    /// Typically found as
55    /// `sync::Result::Ok(AdvisedInnerResult::NeedsNoAdvice(..))`.
56    ErrNeedsNoAdvice(syn::Error),
57}
58
59/// Types that can be parsed, but might need compat advice
60pub trait ParseAdviseable {
61    fn parse_adviseable(input: ParseStream) -> AdviseableResult<Self>
62    where
63        Self: Sized;
64}
65
66pub use AdviseableInnerResult::ErrNeedsNoAdvice as ErrNNA;
67pub use AdviseableInnerResult::Ok as AOk;
68
69/// Parses with a callback, and produces advice if necessary
70///
71/// Version of `adviseable_parse2` that takes a callback function,
72/// rather than a trait impl.
73pub fn adviseable_parse2_call<T>(
74    input: TokenStream,
75    call: impl FnOnce(ParseStream) -> AdviseableResult<T>,
76) -> syn::Result<T> {
77    // If somehow our closure doesn't get called, we want to give
78    // advice, so make that the default.
79    let mut needs_advice = true;
80    let ar = Parser::parse2(
81        |input: ParseStream<'_>| {
82            // When we're returning an error that needs no advice, we must
83            // return *Err* from here, not Ok(NNA), because if we return Ok,
84            // syn might surface an unexpected tokens error instead of the
85            // non-advice-needing error we actually want.
86            //
87            // Encoding the advice-needed status in the error would
88            // be difficult, given how opaque syn::Error is.  So
89            // we smuggle a &mut bool into the closure.
90            match call(input) {
91                Err(needs) => Err(needs),
92                Ok(ErrNNA(unadv)) => {
93                    needs_advice = false;
94                    Err(unadv)
95                }
96                Ok(AOk(y)) => Ok(y),
97            }
98        },
99        input,
100    );
101    ar.map_err(|e| {
102        if needs_advice {
103            advise_incompatibility(e)
104        } else {
105            e
106        }
107    })
108}
109
110/// **Parses `T`, and produces advice if necessary** (principal entrypoint)
111///
112/// All top-level proc_macro entrypoints that want to give advice,
113/// should ideally call this.
114/// (See the note in `advise_incompatibility`.)
115pub fn adviseable_parse2<T: ParseAdviseable>(
116    input: TokenStream,
117) -> syn::Result<T> {
118    adviseable_parse2_call(input, T::parse_adviseable)
119}
120
121impl<T> AdviseableInnerResult<T> {
122    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> AdviseableInnerResult<U> {
123        match self {
124            AOk(y) => AOk(f(y)),
125            ErrNNA(e) => ErrNNA(e),
126        }
127    }
128}
129
130/// Add a warning about derive-deftly version incompatibility
131///
132/// ### Lost advice hazard
133///
134/// Take care!
135/// `syn` generates errors from unprocessed tokens in [`syn::parse2`] etc.
136/// Calling this function *within* `syn::parse2`
137/// (ie, somewhere you have a `ParseStream`,
138/// will result in those errors not receiving advice.
139///
140/// Ideally, call this from functions that have a `TokenStream`.
141/// If you do that, then functions *isnide* that can
142/// use this method, avoiding the problem:
143/// any errors stored up by `syn` will emerge at that call site,
144/// and be properly dealt with.
145pub fn advise_incompatibility(err_needing_advice: syn::Error) -> syn::Error {
146    let mut advice = Span::call_site().error(
147        "bad input to derive_deftly_engine inner template expansion proc macro; might be due to incompatible derive-deftly versions(s)"
148    );
149    advice.combine(err_needing_advice);
150    advice
151}
152
153/// Within `parse_adviseable`, handle errors *without* giving advice
154///
155/// `parse_unadvised! { CONTENT_IDENT => || CLOSURE_BODY }`
156/// expects `CONTENT_IDENT` to be the contents from
157/// [`braced!`], [`bracketed!]` or [`parenthesized!`].
158/// Calls the closure.
159/// Errors within the closure won't get advice.
160///
161/// `parse_unadvised! { CONTENT_IDENT }`
162/// shorthand for calling `.parse()` on the content.
163///
164/// # Sketch
165///
166/// ```rust,ignore
167/// let something;
168/// let _ = bracketed!(something in input);
169/// parse_unadvised! {
170///     something => || {
171///         // do something with something, eg something.parse()
172///         Ok(...)
173///     }
174/// }
175/// ```
176macro_rules! parse_unadvised { {
177    $content:ident
178} => {
179    parse_unadvised! { $content => || $content.parse() }
180}; {
181    $content:ident => || $( $call:tt )*
182} => {
183    match syn::parse::Parser::parse2(
184        // We convert the input to the `TokenStream2` and back,
185        // so that we surface "unexpected token errors" here
186        // rather than at the toplevel parsing call.
187        |$content: ParseStream<'_>| -> syn::Result<_> {
188            $($call)*
189        },
190        $content.parse::<TokenStream>()?
191    ) {
192        Ok(y) => y,
193        Err::<_, syn::Error>(e) => return Ok(ErrNNA(e)),
194    }
195} }