Skip to main content

masterror_derive/
lib.rs

1// SPDX-FileCopyrightText: 2025-2026 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
17#![allow(
18    clippy::cast_possible_truncation,
19    reason = "syn::Index stores u32; derive inputs never approach u32::MAX"
20)]
21
22mod app_error_impl;
23mod display;
24mod error_trait;
25mod from_impl;
26mod input;
27mod lint;
28mod masterror_impl;
29mod span;
30mod template_support;
31
32use proc_macro::TokenStream;
33use quote::quote;
34use syn::{Attribute, Data, DeriveInput, Error, parse_macro_input};
35
36#[proc_macro_derive(Error, attributes(error, source, from, backtrace, app_error, provide))]
37pub fn derive_error(tokens: TokenStream) -> TokenStream {
38    let input = parse_macro_input!(tokens as DeriveInput);
39    match expand(input) {
40        Ok(stream) => stream.into(),
41        Err(err) => err.to_compile_error().into()
42    }
43}
44
45#[proc_macro_derive(
46    Masterror,
47    attributes(error, source, from, backtrace, masterror, provide)
48)]
49pub fn derive_masterror(tokens: TokenStream) -> TokenStream {
50    let input = parse_macro_input!(tokens as DeriveInput);
51    match expand_masterror(input) {
52        Ok(stream) => stream.into(),
53        Err(err) => err.to_compile_error().into()
54    }
55}
56
57fn expand(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> {
58    let deprecated = references_deprecated(&input);
59    let parsed = input::parse_input(input)?;
60    let display_impl = display::expand(&parsed)?;
61    let error_impl = error_trait::expand(&parsed);
62    let from_impls = from_impl::expand(&parsed)?;
63    let app_error_impls = app_error_impl::expand(&parsed)?;
64    Ok(allow_deprecated(
65        deprecated,
66        quote! {
67            #display_impl
68            #error_impl
69            #(#from_impls)*
70            #(#app_error_impls)*
71        }
72    ))
73}
74
75fn expand_masterror(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> {
76    let deprecated = references_deprecated(&input);
77    let parsed = input::parse_input(input)?;
78    let display_impl = display::expand(&parsed)?;
79    let error_impl = error_trait::expand(&parsed);
80    let from_impls = from_impl::expand(&parsed)?;
81    let masterror_impl = masterror_impl::expand(&parsed)?;
82    Ok(allow_deprecated(
83        deprecated,
84        quote! {
85            #display_impl
86            #error_impl
87            #(#from_impls)*
88            #masterror_impl
89        }
90    ))
91}
92
93/// Checks whether generated code will reference a deprecated item.
94///
95/// Returns `true` when the type itself or any enum variant carries a
96/// `#[deprecated]` attribute, in which case the expansion must be shielded
97/// from the `deprecated` lint.
98fn references_deprecated(input: &DeriveInput) -> bool {
99    fn has_deprecated(attrs: &[Attribute]) -> bool {
100        attrs.iter().any(|attr| attr.path().is_ident("deprecated"))
101    }
102    if has_deprecated(&input.attrs) {
103        return true;
104    }
105    match &input.data {
106        Data::Enum(data) => data
107            .variants
108            .iter()
109            .any(|variant| has_deprecated(&variant.attrs)),
110        _ => false
111    }
112}
113
114/// Wraps generated implementations in `#[allow(deprecated)]` when needed.
115///
116/// Deriving on a `#[deprecated]` type (or an enum with deprecated variants)
117/// must not trigger the `deprecated` lint in the expansion.
118fn allow_deprecated(deprecated: bool, body: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
119    if deprecated {
120        quote! {
121            #[allow(deprecated)]
122            const _: () = {
123                #body
124            };
125        }
126    } else {
127        body
128    }
129}