traceback_derive/
lib.rs

1//! # `traceback-derive`
2//!
3//! `traceback-derive` is a procedural macro crate designed to enhance the functionality of the
4//! `traceback-error` crate by providing custom macros for streamlined error handling and tracebacks in Rust.
5//!
6//! ## Usage
7//!
8//! To use `traceback-derive` in your Rust project, follow these steps:
9//!
10//! 1. Add `traceback-derive` and `traceback-error` as dependencies in your `Cargo.toml`:
11//!
12//! ```toml
13//! [dependencies]
14//! traceback-derive = "0.1.1"
15//! traceback-error = "0.1.5"
16//! ```
17//!
18//! The `#[traceback]` attribute enhances the function with traceback capabilities, making it easier to handle errors
19//! and capture detailed trace information.
20//!
21//! 2. Apply the `traceback` macro to your function to create and handle errors with tracebacks:
22//!
23//! ```rust
24//! #[traceback_derive::traceback]
25//! fn my_function() -> Result<(), traceback_error::TracebackError> {
26//!     // Your code here
27//!     risky_function()?;
28//!     // ...
29//! }
30//! ```
31//!
32//! The `traceback!` macro simplifies error creation and captures relevant context information.
33//!
34//! ## Examples
35//!
36//! Here's an example of how `traceback-derive` simplifies error handling compared to using `traceback-error` directly:
37//!
38//! **Without `traceback-derive` (using `traceback-error` directly):**
39//!
40//! ```rust
41//! use traceback_error::{traceback, TracebackError};
42//!
43//! fn main() {
44//!     match caller_of_tasks() {
45//!         Ok(_) => {}
46//!         Err(e) => {
47//!             traceback!(e, "One of the tasks failed");
48//!         }
49//!     }
50//! }
51//!
52//! fn task_that_may_fail() -> Result<(), TracebackError> {
53//!     return Err(traceback!("task_that_may_fail failed"));
54//! }
55//!
56//! fn other_task_that_may_fail() -> Result<(), TracebackError> {
57//!     return Err(traceback!("other_task_that_may_fail failed"));
58//! }
59//!
60//! fn caller_of_tasks() -> Result<(), TracebackError> {
61//!     match task_that_may_fail() {
62//!         Ok(_) => {}
63//!         Err(e) => {
64//!             return Err(traceback!(err e));
65//!         }
66//!     };
67//!     match other_task_that_may_fail() {
68//!         Ok(_) => {}
69//!         Err(e) => {
70//!             return Err(traceback!(err e));
71//!         }
72//!     };
73//!     Ok(())
74//! }
75//! ```
76//!
77//! **With `traceback-derive`:**
78//!
79//! ```rust
80//! use traceback_error::{traceback, TracebackError};
81//!
82//! fn main() {
83//!     match caller_of_tasks() {
84//!         Ok(_) => {}
85//!         Err(e) => {
86//!             traceback!(e, "One of the tasks failed");
87//!         }
88//!     }
89//! }
90//!
91//! fn task_that_may_fail() -> Result<(), TracebackError> {
92//!     return Err(traceback!("task_that_may_fail failed"));
93//! }
94//!
95//! fn other_task_that_may_fail() -> Result<(), TracebackError> {
96//!     return Err(traceback!("other_task_that_may_fail failed"));
97//! }
98//!
99//! #[traceback_derive::traceback]
100//! fn caller_of_tasks() -> Result<(), TracebackError> {
101//!     task_that_may_fail()?;
102//!     other_task_that_may_fail()?;
103//!     Ok(())
104//! }
105//! ```
106//!
107//! The two code snippets are equivalent when expanded, but `traceback-derive` simplifies error handling and capture.
108//!
109//! ## Contribution
110//!
111//! Contributions are welcome! Feel free to open issues or pull requests on the GitHub repository.
112//! This project is still in very early development, and proper contribution guidelines have not yet been established.
113//!
114//! ## License
115//!
116//! This crate is dual-licensed under the [MIT License](LICENSE-MIT) and the [Apache License, Version 2.0](LICENSE-APACHE-2.0).
117//! You may choose either of these licenses when using this crate.
118//! See the [LICENSE-MIT](LICENSE-MIT) and [LICENSE-APACHE-2.0](LICENSE-APACHE-2.0) files for the full text of the licenses.
119//!
120//! ## GitHub Repository
121//!
122//! For more information and to contribute to the development of `traceback-derive`, visit the
123//! [GitHub repository](https://github.com/Tommy-ASD/traceback-derive).
124//!
125
126use proc_macro::TokenStream;
127use quote::{quote, quote_spanned};
128use syn::spanned::Spanned;
129use syn::ExprIndex;
130use syn::{parse_macro_input, visit_mut::VisitMut, Expr};
131
132#[proc_macro_attribute]
133pub fn traceback(_attrs: TokenStream, input: TokenStream) -> TokenStream {
134    let mut function = parse_macro_input!(input as syn::ItemFn);
135
136    let mut visitor = TracingVisitor;
137    visitor.visit_item_fn_mut(&mut function);
138
139    TokenStream::from(quote! { #function })
140}
141
142struct TracingVisitor;
143
144impl VisitMut for TracingVisitor {
145    fn visit_expr_mut(&mut self, expr: &mut Expr) {
146        match expr {
147            Expr::Try(expr_try) => {
148                let span = expr_try.question_token.span();
149                let inner_expr = &expr_try.expr;
150                let new_expr = syn::parse2(quote_spanned! { span=>{
151                    match #inner_expr {
152                        Ok(val) => Ok(val),
153                        Err(e) => Err(traceback!(err e))
154                    }
155                }?
156                })
157                .expect("Failed to create traceback match expression");
158
159                *expr = new_expr;
160            }
161            Expr::Index(index) => {
162                // Extract the parts of the index expression
163                let ExprIndex {
164                    attrs: _,
165                    expr: inner_expr,
166                    bracket_token: _,
167                    index,
168                } = index.clone();
169
170                // Create a new expression for safe indexing
171                let safe_indexing_expr = quote_spanned!(expr.span() =>
172                    match #inner_expr.get(#index) {
173                        Some(value) => value,
174                        None => {
175                            return Err(traceback!(format!("Error while indexing into {} in variable {:?}", #index, #inner_expr)));
176                        },
177                    }
178                );
179
180                // Replace the current expression with the safe indexing expression
181                *expr = syn::parse2(safe_indexing_expr).unwrap();
182            }
183            _ => {
184                syn::visit_mut::visit_expr_mut(self, expr);
185            }
186        }
187    }
188}