fack_macro/lib.rs
1use proc_macro::TokenStream;
2
3use fack_codegen::{Target, expand::Expand};
4
5/// Derive the `Error` trait for a struct.
6///
7/// The `#[derive(Error)]` macro provides a declarative way to implement
8/// both the [`Error`] and [`Display`] traits for your custom error types.
9/// It is controlled by `#[error(...)]` *type-level attributes* that specify
10/// how the error should behave.
11///
12/// # General form
13///
14/// ```ignore
15/// #[derive(Error, Debug)]
16/// #[error("a formatted message {arg}", arg)]
17/// // #[error(source(...))]
18/// // #[error(transparent(...))]
19/// // #[error(from)]
20/// struct MyError { /* fields */ }
21/// ```
22///
23/// The attribute syntax follows the form:
24///
25/// ```text
26/// #[error(<parameter>)]
27/// ```
28///
29/// Multiple `#[error(...)]` attributes can be applied to the same type,
30/// and are classified according to their *parameter kind*.
31///
32///
33/// # Attribute kinds
34///
35/// The following attribute kinds are supported:
36///
37/// ## `#[error("...")]` - format string
38///
39/// Provides a [`Display`] implementation for the error type, using
40/// Rust formatting syntax. Additional expressions may be listed as
41/// comma-separated arguments.
42///
43/// ```rust
44/// # use fack_macro::Error;
45/// #[derive(Error, Debug)]
46/// #[error("failed to read file: {path}")]
47/// struct ReadError {
48/// path: String,
49/// }
50/// ```
51///
52/// Expands to:
53///
54/// ```ignore
55/// impl std::fmt::Display for ReadError {
56/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57/// write!(f, "failed to read file: {}", self.path)
58/// }
59/// }
60/// ```
61///
62/// ## `#[error(source(<field>))]` - source field
63///
64/// Declares which field represents the underlying cause of the error.
65/// This field will be returned from `Error::source`.
66///
67/// ```rust
68/// # use fack_macro::Error;
69/// #[derive(Error, Debug)]
70/// #[error("network request failed")]
71/// #[error(source(io))]
72/// struct NetworkError {
73/// io: std::io::Error,
74/// }
75/// ```
76///
77/// Produces:
78/// ```ignore
79/// impl std::error::Error for NetworkError {
80/// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
81/// Some(&self.io)
82/// }
83/// }
84/// ```
85///
86/// ## `#[error(transparent(<field>))]` - transparent wrapper
87///
88/// Makes the error type a transparent wrapper around one of its fields.
89/// Both [`Display`] and [`Error`] are forwarded directly to that field.
90/// ```rust
91/// # use fack_macro::Error;
92/// #[derive(Error, Debug)]
93/// #[error(transparent(0))]
94/// struct Wrapper(std::io::Error);
95/// ```
96///
97/// This makes `Wrapper` behave exactly like its inner error for
98/// formatting and chaining purposes.
99///
100///
101/// ## `#[error(from)]` conversion
102///
103/// Requests that a `From<T>` implementation be generated for a specified
104/// field type, allowing automatic conversion into the error type.
105/// ```rust
106/// # use fack_macro::Error;
107/// #[derive(Error, Debug)]
108/// #[error("parse failed")]
109/// #[error(from)]
110/// struct ParseError {
111/// inner: std::num::ParseIntError,
112/// }
113/// ```
114///
115/// Allows:
116/// ```ignore
117/// let err: ParseError = "abc".parse::<u32>().unwrap_err().into();
118/// ```
119///
120///
121/// # Enums
122///
123/// Enums may use the same attribute kinds at the *variant* level.
124/// Each variant can provide its own formatting, source, or transparency.
125/// ```
126/// # use fack_macro::Error;
127/// #[derive(Error, Debug)]
128/// enum MyError {
129/// #[error("invalid input: {_0}")]
130/// InvalidInput(String),
131///
132/// #[error(transparent(inner))]
133/// Io {
134/// inner: std::io::Error,
135/// },
136///
137/// #[error("other error")]
138/// Other,
139/// }
140/// ```
141///
142///
143/// # Inline options
144///
145/// Inline behavior for generated methods can be controlled using:
146/// ```rust
147/// # use fack_macro::Error;
148/// #[derive(Error, Debug)]
149/// #[error(inline(always))]
150/// #[error("{msg}")]
151/// struct FastError {
152/// msg: &'static str,
153/// }
154/// ```
155///
156/// Valid values are:
157/// - `neutral` (default) → `#[inline]`
158/// - `always` → `#[inline(always)]`
159/// - `never` → `#[inline(never)]`
160///
161/// When omitted, no explicit `#[inline(...)]` attribute is emitted.
162///
163///
164/// # Root import
165///
166/// All generated code defaults to using `::core`. This can be overridden:
167/// ```rust
168/// # use fack_macro::Error;
169/// #[derive(Error, Debug)]
170/// #[error("{msg}")]
171/// #[error(import(::std))]
172/// struct StdError {
173/// msg: &'static str,
174/// }
175/// ```
176///
177/// This allows compatibility with `std::error::Error` where desired.
178///
179/// [`Error`]: error
180/// [`Display`]: core::fmt::Display
181#[proc_macro_derive(Error, attributes(error))]
182pub fn error(input: TokenStream) -> TokenStream {
183 let input = &syn::parse_macro_input!(input as syn::DeriveInput);
184
185 match Target::input(input).map(Expand::target) {
186 Ok(Ok(target_stream)) => TokenStream::from(target_stream),
187 Err(target_error) | Ok(Err(target_error)) => target_error.to_compile_error().into(),
188 }
189}