Coverage Report

Created: 2025-02-07 03:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/runner/work/bare_err_tree/bare_err_tree/bare_err_tree_proc/src/lib.rs
Line
Count
Source
1
/*
2
 * This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
 */
6
7
//! Derive macros for `bare_err_tree`.
8
9
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
10
#![cfg_attr(coverage, feature(coverage_attribute))]
11
12
extern crate proc_macro;
13
use core::panic;
14
15
use proc_macro::{Span, TokenStream};
16
use quote::{quote, ToTokens};
17
use syn::{
18
    parse::Parser, parse_macro_input, punctuated::Punctuated, token::Brace, Attribute, Data,
19
    DataStruct, DeriveInput, Error, Field, Fields, FieldsNamed, Generics, Ident, Meta, Visibility,
20
};
21
22
mod errtype;
23
use errtype::*;
24
mod boiler;
25
use boiler::*;
26
mod fields;
27
use fields::*;
28
29
/// Implements a type as an error tree.
30
///
31
/// The struct must define [`Error`](`core::error::Error`) and be annotated with `#[err_tree]` above
32
/// any attributes relying on a full field definition. The type must then be
33
/// internally constructed with `Self::_tree` to capture extra error
34
/// information in a hidden field.
35
///
36
/// Any derive such as [`Clone`] that relies on all fields being present must
37
/// occur after the `#[err_tree]` macro. The `_err_tree_pkg` field will
38
/// otherwise be added late and break the derivation.
39
///
40
/// # `Self::_tree`
41
/// This is an internal-use constructor that takes all struct fields in order.
42
/// Use `#[track_caller]` on any functions calling `Self::_tree` to store the
43
/// callsite correctly.
44
/// [Open an issue or PR](<https://github.com/Bennett-Petzold/bare_err_tree>)
45
/// if this hidden field degrades a struct's API (aside from requiring a
46
/// constructor method).
47
///
48
/// #### Example
49
/// ```
50
1
/// # #![cfg_attr(coverage, feature(coverage_attribute))]
51
/// # use std::{error::Error, fmt::{self, Debug, Display, Formatter}};
52
/// use bare_err_tree::{err_tree, tree_unwrap};
53
///
54
/// #[err_tree]
55
/// #[derive(Debug)]
56
/// struct Foo {
57
///     num: i32,
58
/// }
59
///
60
/// impl Foo {
61
/// #   #[cfg_attr(coverage, coverage(off))]
62
///     #[track_caller]
63
///     pub fn new(num: i32) -> Self {
64
///         Foo::_tree(num)
65
///     }
66
/// }
67
///
68
/// impl Error for Foo {
69
/// #   #[cfg_attr(coverage, coverage(off))]
70
///     fn source(&self) -> Option<&(dyn Error + 'static)> {
71
///         # /*
72
///         ...
73
///         # */
74
///         # unimplemented!()
75
///     }
76
/// }
77
/// impl Display for Foo {
78
/// #   #[cfg_attr(coverage, coverage(off))]
79
///     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
80
///         # /*
81
///         ...
82
///         # */
83
///         # unimplemented!()
84
///     }
85
/// }
86
1
/// ```
87
///
88
/// # Field Annotations
89
/// The macro needs annotations for underlying source fields.
90
///
91
/// #### Single Item
92
/// * `tree_err`: Mark a field as a `ErrTree` implementing [`Error`](`core::error::Error`).
93
/// * `dyn_err`: Mark a field as a generic [`Error`](`core::error::Error`).
94
///
95
/// #### Collection
96
/// `*_iter_err` works on any type with a `.iter()` method returning its items.
97
///
98
/// * `tree_iter_err`: Mark a field as a collection of `ErrTree` implementing [`Error`](`core::error::Error`)s.
99
/// * `dyn_iter_err`: Mark a field as a collection of generic [`Error`](`core::error::Error`)s.
100
///
101
/// `*_iter_err` does not allocate for arrays with a known length.
102
/// The `derive_alloc` feature enables generation of allocating code to support
103
/// dynamically sized collections.
104
///
105
/// #### Example
106
/// ```
107
/// # #![cfg_attr(coverage, feature(coverage_attribute))]
108
/// # use std::{any::Any, error::Error, fmt::{self, Debug, Display, Formatter}};
109
/// use bare_err_tree::{err_tree, tree_unwrap, AsErrTree, ErrTree};
110
///
111
/// #[err_tree]
112
/// #[derive(Debug)]
113
/// struct Foo {
114
///     #[dyn_err]
115
///     io_err: std::io::Error,
116
///     #[dyn_iter_err]
117
///     extra_io_errs: [std::io::Error; 5],
118
/// }
119
///
120
/// impl Foo {
121
/// #   #[cfg_attr(coverage, coverage(off))]
122
///     #[track_caller]
123
///     pub fn new(io_err: std::io::Error, extra_io_errs: [std::io::Error; 5]) -> Self {
124
///         Foo::_tree(io_err, extra_io_errs)
125
///     }
126
/// }
127
///
128
/// impl Error for Foo {
129
/// #   #[cfg_attr(coverage, coverage(off))]
130
///     fn source(&self) -> Option<&(dyn Error + 'static)> {
131
///         # /*
132
///         ...
133
///         # */
134
///         # unimplemented!()
135
///     }
136
/// }
137
/// impl Display for Foo {
138
/// #   #[cfg_attr(coverage, coverage(off))]
139
///     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
140
///         # /*
141
///         ...
142
///         # */
143
///         # unimplemented!()
144
///     }
145
/// }
146
///
147
1
/// fn main() {
148
1
///     // Make a Foo of all EOF errors
149
6
///     let eof_gen = || std::io::Error::from(std::io::ErrorKind::UnexpectedEof);
150
5
///     
let err = Foo::new(eof_gen(), std::array::from1
_fn(|_| eof_gen()));
151
1
///
152
1
///     // Confirm exactly six sources from annotation
153
1
///     err.as_err_tree(&mut |tree| {
154
1
///         let sources = tree.sources();
155
1
///         assert_eq!(sources.count(), 6);
156
1
///     });
157
1
/// }
158
/// ```
159
///
160
/// # Generating a Wrapper
161
/// `#[err_tree(WRAPPER)]` will generate a wrapper struct for storing metadata.
162
/// Enums need this form, as a hidden field cannot be added to the enum.
163
/// `WRAPPER` provides [`From`](`core::convert::From`) both ways and
164
/// [`Deref`](`core::ops::Deref`)/[`DerefMut`](`core::ops::DerefMut`) to be
165
/// maximally transparent.
166
/// Some derives are automatically re-derived for the wrapper; any other traits
167
/// that need to be implemented for the wrapper can be written manually.
168
///
169
/// #### Wrapper automatic re-derives
170
// https://doc.rust-lang.org/rust-by-example/trait/derive.html
171
/// [`Eq`](`core::cmp::Eq`), [`PartialEq`](`core::cmp::PartialEq`),
172
/// [`Ord`](`core::cmp::Ord`), [`PartialOrd`](`core::cmp::PartialOrd`),
173
/// [`Clone`](`core::clone::Clone`), [`Hash`](`core::hash::Hash`),
174
/// [`Default`](`core::default::Default).
175
///
176
/// #### Enum Example
177
/// ```
178
/// # #![cfg_attr(coverage, feature(coverage_attribute))]
179
/// # use std::{error::Error, fmt::{self, Debug, Display, Formatter}};
180
/// use bare_err_tree::{err_tree, tree_unwrap};
181
///
182
/// // Generates `FooWrap<T: Debug>`
183
/// #[err_tree(FooWrap)]
184
/// #[derive(Debug)]
185
/// enum Foo<T: Debug> {
186
///     Val(T),
187
///     #[dyn_err]
188
///     Single(std::io::Error),
189
///     #[dyn_iter_err]
190
///     Many([std::io::Error; 5]),
191
/// }
192
///
193
/// impl<T: Debug> Error for Foo<T> {
194
/// #   #[cfg_attr(coverage, coverage(off))]
195
///     fn source(&self) -> Option<&(dyn Error + 'static)> {
196
///         # /*
197
///         ...
198
///         # */
199
///         # unimplemented!()
200
///     }
201
/// }
202
/// impl<T: Debug> Display for Foo<T> {
203
/// #   #[cfg_attr(coverage, coverage(off))]
204
///     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
205
///         # /*
206
///         ...
207
///         # */
208
///         # unimplemented!()
209
///     }
210
/// }
211
///
212
/// fn main() {
213
///     let wrapped = FooWrap::from(Foo::Val(8_i32));
214
///     assert!(matches!(*wrapped, Foo::Val(8_i32)));
215
/// }
216
///
217
/// ```
218
///
219
/// # Full Usage Example:
220
/// ```
221
/// # use std::{error::Error, fmt::{self, Debug, Display, Formatter}};
222
/// use bare_err_tree::{err_tree, tree_unwrap};
223
///
224
/// #[err_tree]
225
/// #[derive(Debug)]
226
/// struct Foo {
227
///     #[dyn_err]
228
///     io_err: std::io::Error,
229
///     #[dyn_iter_err]
230
///     extra_io_errs: [std::io::Error; 5],
231
/// }
232
///
233
/// impl Foo {
234
1
///     #[track_caller]
235
1
///     pub fn new(io_err: std::io::Error, extra_io_errs: [std::io::Error; 5]) -> Self {
236
1
///         Foo::_tree(io_err, extra_io_errs)
237
///     }
238
/// }
239
///
240
0
/// impl Error for Foo {
241
0
///     fn source(&self) -> Option<&(dyn Error + 'static)> {
242
0
///         Some(&self.io_err)
243
///     }
244
/// }
245
1
/// impl Display for Foo {
246
1
///     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
247
1
///         # /*
248
1
///         ...
249
1
///         # */
250
1
///         # Display::fmt(&self.io_err, f)
251
///     }
252
/// }
253
///
254
1
/// /// Always return the error with tree formatting support
255
6
/// pub fn always_fail() -> Result<(), Foo> {
256
1
///     # let get_err = || std::io::Error::from(std::io::ErrorKind::UnexpectedEof);
257
1
///     Err(Foo::new(
258
1
///     # /*
259
1
///         ...
260
5
///     # */
261
1
///     # get_err(), std::array::from_fn(|_| get_err()),
262
1
///     ))
263
/// }
264
///
265
/// const MAX_DEPTH: usize = 10;
266
/// const MAX_CHARS: usize = MAX_DEPTH * 6;
267
///
268
/// pub fn main() {
269
///     # let _ = std::panic::catch_unwind(|| {
270
///     let result = always_fail();
271
///
272
///     /// Fancy display panic with a maximum tree depth of 10 errors
273
///     tree_unwrap::<MAX_CHARS, _, _>(result);
274
///     # });
275
/// }
276
/// ```
277
#[proc_macro_attribute]
278
4
pub fn err_tree(args: TokenStream, input: TokenStream) -> TokenStream {
279
4
    let args = parse_macro_input!(args with Punctuated::<Meta, syn::Token![,]>::parse_terminated);
280
281
4
    let name_attribute = name_attribute(&args);
282
283
    let DeriveInput {
284
4
        attrs,
285
4
        vis,
286
4
        ident,
287
4
        generics,
288
4
        mut data,
289
4
    } = parse_macro_input!(input as DeriveInput);
290
291
4
    let generated = match data {
292
        // Only structs are directly valid for injecting the hidden field
293
3
        Data::Struct(ref mut data) => {
294
3
            let errs: Vec<_> = get_struct_macros(data).collect();
295
296
3
            if let Some(
name_attribute0
) = name_attribute {
297
0
                foreign_err_tree(
298
0
                    &ident,
299
0
                    &vis,
300
0
                    &attrs,
301
0
                    name_attribute,
302
0
                    &generics,
303
0
                    &errs,
304
0
                    Foreign::Struct,
305
0
                )
306
            } else {
307
3
                clean_struct_macros(data);
308
3
                err_tree_struct(&ident, &vis, &generics, data, &errs, Foreign::Not)
309
            }
310
        }
311
        // Enums can be handled by a generated wrapping struct
312
1
        Data::Enum(ref mut data) => {
313
1
            let errs: Vec<_> = get_enum_macros(data).collect();
314
1
            clean_enum_macros(data);
315
316
1
            if let Some(name_attribute) = name_attribute {
317
1
                foreign_err_tree(
318
1
                    &ident,
319
1
                    &vis,
320
1
                    &attrs,
321
1
                    name_attribute,
322
1
                    &generics,
323
1
                    &errs,
324
1
                    Foreign::Enum(&ident),
325
1
                )
326
            } else {
327
0
                TokenStream::from(
328
0
                    Error::new(
329
0
                        Span::call_site().into(),
330
0
                        "err_tree cannot implement directly on an enum type. Use '#[err_tree(WRAPPER)]'",
331
0
                    )
332
0
                    .into_compile_error(),
333
0
                )
334
            }
335
        }
336
        // This datatype is barely used -- mostly C interop -- so the lack of
337
        // functionality doesn't really matter. I've never seen a Union Error.
338
0
        Data::Union(_) => TokenStream::from(
339
0
            Error::new(
340
0
                Span::call_site().into(),
341
0
                "err_tree cannot be annotated on union types",
342
0
            )
343
0
            .into_compile_error(),
344
0
        ),
345
    };
346
347
4
    TokenStream::from_iter([
348
4
        DeriveInput {
349
4
            attrs,
350
4
            vis,
351
4
            ident,
352
4
            generics,
353
4
            data,
354
4
        }
355
4
        .into_token_stream()
356
4
        .into(),
357
4
        generated,
358
4
    ])
359
4
}
360
361
#[derive(Debug)]
362
enum Foreign<'a> {
363
    /// Direct struct generation
364
    Not,
365
    /// Wrapper around a struct, doesn't need a defined ident
366
    Struct,
367
    /// Wrapper around an enum, needs an enum ident for pattern matching
368
    Enum(&'a Ident),
369
}
370
371
/// Generate a foreign wrapper.
372
///
373
/// Boilerplates a wrapper notice into docs, copies all struct docs, creates
374
/// automatic Deref and From impls, and re-derives known trivial methods.
375
///
376
/// Concludes with a call to [`err_tree_struct`].
377
1
fn foreign_err_tree(
378
1
    ident: &Ident,
379
1
    vis: &Visibility,
380
1
    attrs: &[Attribute],
381
1
    name_attribute: &Ident,
382
1
    generics: &Generics,
383
1
    errs: &[TreeErr],
384
1
    foreign_type: Foreign,
385
1
) -> TokenStream {
386
1
    let (_, ty_generics, _) = generics.split_for_impl();
387
1
388
1
    let doc_attrs = attrs.iter().filter(|x| {
389
1
        if let Ok(
x0
) = x.meta.require_name_value() {
390
0
            if let Some(x) = x.path.get_ident() {
391
0
                x == "doc"
392
            } else {
393
0
                false
394
            }
395
        } else {
396
1
            false
397
        }
398
1
    });
399
1
400
1
    let ident_link = format!("Wrapper for [`{ident}`] generated by [`bare_err_tree`].");
401
1
    let wrapper_struct: TokenStream = quote! {
402
1
        #[doc = #ident_link]
403
1
        ///
404
1
        #(#doc_attrs)*
405
1
        #vis struct #name_attribute #generics {
406
1
            inner: #ident #ty_generics,
407
1
        }
408
1
    }
409
1
    .into();
410
411
1
    let mut wrapper_struct = parse_macro_input!(wrapper_struct as DeriveInput);
412
413
1
    if let Data::Struct(ref mut wrapper_struct_data) = &mut wrapper_struct.data {
414
1
        let boilerplate = wrapper_boilerplate(ident, generics, attrs, name_attribute);
415
1
        let generated_impl = err_tree_struct(
416
1
            name_attribute,
417
1
            vis,
418
1
            &wrapper_struct.generics,
419
1
            wrapper_struct_data,
420
1
            errs,
421
1
            foreign_type,
422
1
        );
423
1
        TokenStream::from_iter([
424
1
            wrapper_struct.to_token_stream().into(),
425
1
            boilerplate,
426
1
            generated_impl,
427
1
        ])
428
    } else {
429
0
        panic!("The wrapper is always a struct!")
430
    }
431
1
}
432
433
/// Injects `_err_tree_pkg`, the `_tree` constructor, and the `_as_err_tree`
434
/// impl.
435
4
fn err_tree_struct(
436
4
    ident: &Ident,
437
4
    vis: &Visibility,
438
4
    generics: &Generics,
439
4
    data: &mut DataStruct,
440
4
    errs: &[TreeErr],
441
4
    foreign: Foreign<'_>,
442
4
) -> TokenStream {
443
4
    let FieldsStrip {
444
4
        bounds: field_bounds,
445
4
        idents: field_names,
446
4
    } = strip_fields(&data.fields);
447
448
    // Generate the with_pkg call on all notated sources
449
4
    let sources = match foreign {
450
3
        Foreign::Not => gen_sources_struct(errs, false),
451
0
        Foreign::Struct => gen_sources_struct(errs, true),
452
1
        Foreign::Enum(ident) => gen_sources_enum(errs, ident),
453
    };
454
4
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
455
4
456
4
    match &mut data.fields {
457
        // Struct with fields like { a: usize, b: usize }
458
4
        Fields::Named(fields) => {
459
4
            // Insert the pkg field
460
4
            let field_ident = proc_macro2::Ident::new("_err_tree_pkg", Span::call_site().into());
461
4
            fields.named.push(
462
4
                Field::parse_named
463
4
                    .parse2(quote! { #field_ident: ::bare_err_tree::ErrTreePkg })
464
4
                    .unwrap(),
465
4
            );
466
4
            let field_ident = field_ident.into_token_stream();
467
4
468
4
            quote! {
469
4
                #[automatically_derived]
470
4
                impl #impl_generics ::bare_err_tree::AsErrTree for #ident #ty_generics #where_clause {
471
4
                    #[track_caller]
472
4
                    fn as_err_tree(&self, func: &mut dyn FnMut(::bare_err_tree::ErrTree<'_>)) {
473
4
                        let _err_tree_pkg = &self.#field_ident;
474
4
                        #sources
475
4
                    }
476
4
                }
477
4
478
4
                #[automatically_derived]
479
4
                impl #impl_generics #ident #ty_generics #where_clause {
480
4
                    #[track_caller]
481
4
                    #[allow(clippy::too_many_arguments)]
482
4
                    fn _tree(#field_bounds) -> Self {
483
4
                        let #field_ident = ::bare_err_tree::ErrTreePkg::new();
484
4
                        Self {
485
4
                            #(#field_names,)*
486
4
                            #field_ident
487
4
                        }
488
4
                    }
489
4
                }
490
4
            }
491
4
            .into()
492
        }
493
        // Struct with fields like ( usize, usize )
494
0
        Fields::Unnamed(fields) => {
495
0
            // Insert the pkg field
496
0
            let prev_len = syn::Index::from(fields.unnamed.len());
497
0
            fields.unnamed.push(
498
0
                Field::parse_unnamed
499
0
                    .parse2(quote! { ::bare_err_tree::ErrTreePkg })
500
0
                    .unwrap(),
501
0
            );
502
0
503
0
            quote! {
504
0
                #[automatically_derived]
505
0
                impl #impl_generics ::bare_err_tree::AsErrTree for #ident #ty_generics #where_clause {
506
0
                    #[track_caller]
507
0
                    fn as_err_tree(&self, func: &mut dyn FnMut(::bare_err_tree::ErrTree<'_>)) {
508
0
                        let _err_tree_pkg = &self.#prev_len;
509
0
                        #sources
510
0
                    }
511
0
                }
512
0
513
0
                #[automatically_derived]
514
0
                impl #impl_generics #ident #ty_generics #where_clause {
515
0
                    #[track_caller]
516
0
                    #[allow(clippy::too_many_arguments)]
517
0
                    fn _tree(#field_bounds) -> Self {
518
0
                        let _err_tree_pkg = ::bare_err_tree::ErrTreePkg::new();
519
0
                        Self (
520
0
                            #(#field_names,)*
521
0
                            _err_tree_pkg
522
0
                        )
523
0
                    }
524
0
                }
525
0
            }
526
0
            .into()
527
        }
528
        // Transmutes a unit struct into a named struct for pkg injection
529
        // Adds new and default methods for easy construction
530
        Fields::Unit => {
531
            // Insert the pkg field
532
0
            let field_ident = proc_macro2::Ident::new("_err_tree_pkg", Span::call_site().into());
533
0
            let mut named = Punctuated::default();
534
0
            named.push(
535
0
                Field::parse_named
536
0
                    .parse2(quote! { #field_ident: ::bare_err_tree::ErrTreePkg })
537
0
                    .unwrap(),
538
0
            );
539
0
            let field_ident = field_ident.into_token_stream();
540
0
            data.fields = Fields::Named(FieldsNamed {
541
0
                brace_token: Brace::default(),
542
0
                named,
543
0
            });
544
0
545
0
            quote! {
546
0
                #[automatically_derived]
547
0
                impl #impl_generics ::bare_err_tree::AsErrTree for #ident #ty_generics #where_clause {
548
0
                    #[track_caller]
549
0
                    fn as_err_tree(&self, func: &mut dyn FnMut(::bare_err_tree::ErrTree<'_>)) {
550
0
                        let _err_tree_pkg = &self.#field_ident;
551
0
                        #sources
552
0
                    }
553
0
                }
554
0
555
0
                #[automatically_derived]
556
0
                impl #impl_generics #ident #ty_generics #where_clause {
557
0
                    #[track_caller]
558
0
                    fn _tree() -> Self {
559
0
                        let #field_ident = ::bare_err_tree::ErrTreePkg::new();
560
0
                        Self {
561
0
                            #field_ident
562
0
                        }
563
0
                    }
564
0
                }
565
0
566
0
                #[automatically_derived]
567
0
                impl #impl_generics ::core::default::Default for #ident #ty_generics #where_clause {
568
0
                    #[track_caller]
569
0
                    fn default() -> Self {
570
0
                        Self::_tree()
571
0
                    }
572
0
                }
573
0
574
0
                #[automatically_derived]
575
0
                impl #impl_generics #ident #ty_generics #where_clause {
576
0
                    #[track_caller]
577
0
                    #vis fn new() -> Self {
578
0
                        Self::_tree()
579
0
                    }
580
0
                }
581
0
            }
582
0
            .into()
583
        }
584
    }
585
4
}