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} }