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