Skip to main content

masterror_derive/
lib.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2//
3// SPDX-License-Identifier: MIT
4
5//! Derive macro for `masterror::Error`.
6//!
7//! This crate is not intended to be used directly. Re-exported as
8//! `masterror::Error`.
9//!
10//! The `From<T> for AppError` conversion generated by `#[app_error(...)]`
11//! attaches the domain error as the source of the produced `AppError`,
12//! keeping it downcastable and part of the error chain. Attaching requires
13//! `T: Send + Sync + 'static`; use the `no_source` flag in
14//! `#[app_error(...)]` to opt out and drop the domain error during
15//! conversion instead.
16
17mod app_error_impl;
18mod display;
19mod error_trait;
20mod from_impl;
21mod input;
22mod lint;
23mod masterror_impl;
24mod span;
25mod template_support;
26
27use proc_macro::TokenStream;
28use quote::quote;
29use syn::{Attribute, Data, DeriveInput, Error, parse_macro_input};
30
31#[proc_macro_derive(Error, attributes(error, source, from, backtrace, app_error, provide))]
32pub fn derive_error(tokens: TokenStream) -> TokenStream {
33    let input = parse_macro_input!(tokens as DeriveInput);
34    match expand(input) {
35        Ok(stream) => stream.into(),
36        Err(err) => err.to_compile_error().into()
37    }
38}
39
40#[proc_macro_derive(
41    Masterror,
42    attributes(error, source, from, backtrace, masterror, provide)
43)]
44pub fn derive_masterror(tokens: TokenStream) -> TokenStream {
45    let input = parse_macro_input!(tokens as DeriveInput);
46    match expand_masterror(input) {
47        Ok(stream) => stream.into(),
48        Err(err) => err.to_compile_error().into()
49    }
50}
51
52fn expand(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> {
53    let deprecated = references_deprecated(&input);
54    let parsed = input::parse_input(input)?;
55    let display_impl = display::expand(&parsed)?;
56    let error_impl = error_trait::expand(&parsed)?;
57    let from_impls = from_impl::expand(&parsed)?;
58    let app_error_impls = app_error_impl::expand(&parsed)?;
59    Ok(allow_deprecated(
60        deprecated,
61        quote! {
62            #display_impl
63            #error_impl
64            #(#from_impls)*
65            #(#app_error_impls)*
66        }
67    ))
68}
69
70fn expand_masterror(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> {
71    let deprecated = references_deprecated(&input);
72    let parsed = input::parse_input(input)?;
73    let display_impl = display::expand(&parsed)?;
74    let error_impl = error_trait::expand(&parsed)?;
75    let from_impls = from_impl::expand(&parsed)?;
76    let masterror_impl = masterror_impl::expand(&parsed)?;
77    Ok(allow_deprecated(
78        deprecated,
79        quote! {
80            #display_impl
81            #error_impl
82            #(#from_impls)*
83            #masterror_impl
84        }
85    ))
86}
87
88/// Checks whether generated code will reference a deprecated item.
89///
90/// Returns `true` when the type itself or any enum variant carries a
91/// `#[deprecated]` attribute, in which case the expansion must be shielded
92/// from the `deprecated` lint.
93fn references_deprecated(input: &DeriveInput) -> bool {
94    fn has_deprecated(attrs: &[Attribute]) -> bool {
95        attrs.iter().any(|attr| attr.path().is_ident("deprecated"))
96    }
97    if has_deprecated(&input.attrs) {
98        return true;
99    }
100    match &input.data {
101        Data::Enum(data) => data
102            .variants
103            .iter()
104            .any(|variant| has_deprecated(&variant.attrs)),
105        _ => false
106    }
107}
108
109/// Wraps generated implementations in `#[allow(deprecated)]` when needed.
110///
111/// Deriving on a `#[deprecated]` type (or an enum with deprecated variants)
112/// must not trigger the `deprecated` lint in the expansion.
113fn allow_deprecated(deprecated: bool, body: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
114    if deprecated {
115        quote! {
116            #[allow(deprecated)]
117            const _: () = {
118                #body
119            };
120        }
121    } else {
122        body
123    }
124}