1 | //! This library provides a convenient derive macro for the standard library's |
2 | //! [`core::fmt::Display`] trait. |
3 | //! |
4 | //! [`core::fmt::Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html |
5 | //! |
6 | //! ```toml |
7 | //! [dependencies] |
8 | //! displaydoc = "0.2" |
9 | //! ``` |
10 | //! |
11 | //! *Compiler support: requires rustc 1.56+* |
12 | //! |
13 | //! <br> |
14 | //! |
15 | //! ## Example |
16 | //! |
17 | //! *Demonstration alongside the [`Error`][std::error::Error] derive macro from [`thiserror`](https://docs.rs/thiserror/1.0.25/thiserror/index.html), |
18 | //! to propagate source locations from [`io::Error`][std::io::Error] with the `#[source]` attribute:* |
19 | //! ```rust |
20 | //! use std::io; |
21 | //! use displaydoc::Display; |
22 | //! use thiserror::Error; |
23 | //! |
24 | //! #[derive(Display, Error, Debug)] |
25 | //! pub enum DataStoreError { |
26 | //! /// data store disconnected |
27 | //! Disconnect(#[source] io::Error), |
28 | //! /// the data for key `{0}` is not available |
29 | //! Redaction(String), |
30 | //! /// invalid header (expected {expected:?}, found {found:?}) |
31 | //! InvalidHeader { |
32 | //! expected: String, |
33 | //! found: String, |
34 | //! }, |
35 | //! /// unknown data store error |
36 | //! Unknown, |
37 | //! } |
38 | //! |
39 | //! let error = DataStoreError::Redaction("CLASSIFIED CONTENT" .to_string()); |
40 | //! assert!("the data for key `CLASSIFIED CONTENT` is not available" == &format!("{}" , error)); |
41 | //! ``` |
42 | //! *Note that although [`io::Error`][std::io::Error] implements `Display`, we do not add it to the |
43 | //! generated message for `DataStoreError::Disconnect`, since it is already made available via |
44 | //! `#[source]`. See further context on avoiding duplication in error reports at the rust blog |
45 | //! [here](https://github.com/yaahc/blog.rust-lang.org/blob/master/posts/inside-rust/2021-05-15-What-the-error-handling-project-group-is-working-towards.md#duplicate-information-issue).* |
46 | //! |
47 | //! <br> |
48 | //! |
49 | //! ## Details |
50 | //! |
51 | //! - A `fmt::Display` impl is generated for your enum if you provide |
52 | //! a docstring comment on each variant as shown above in the example. The |
53 | //! `Display` derive macro supports a shorthand for interpolating fields from |
54 | //! the error: |
55 | //! - `/// {var}` ⟶ `write!("{}", self.var)` |
56 | //! - `/// {0}` ⟶ `write!("{}", self.0)` |
57 | //! - `/// {var:?}` ⟶ `write!("{:?}", self.var)` |
58 | //! - `/// {0:?}` ⟶ `write!("{:?}", self.0)` |
59 | //! - This also works with structs and [generic types][crate::Display#generic-type-parameters]: |
60 | //! ```rust |
61 | //! # use displaydoc::Display; |
62 | //! /// oh no, an error: {0} |
63 | //! #[derive(Display)] |
64 | //! pub struct Error<E>(pub E); |
65 | //! |
66 | //! let error: Error<&str> = Error("muahaha i am an error" ); |
67 | //! assert!("oh no, an error: muahaha i am an error" == &format!("{}" , error)); |
68 | //! ``` |
69 | //! |
70 | //! - Two optional attributes can be added to your types next to the derive: |
71 | //! |
72 | //! - `#[ignore_extra_doc_attributes]` makes the macro ignore any doc |
73 | //! comment attributes (or `///` lines) after the first. Multi-line |
74 | //! comments using `///` are otherwise treated as an error, so use this |
75 | //! attribute or consider switching to block doc comments (`/** */`). |
76 | //! |
77 | //! - `#[prefix_enum_doc_attributes]` combines the doc comment message on |
78 | //! your enum itself with the messages for each variant, in the format |
79 | //! “enum: variant”. When added to an enum, the doc comment on the enum |
80 | //! becomes mandatory. When added to any other type, it has no effect. |
81 | //! |
82 | //! - In case you want to have an independent doc comment, the |
83 | //! `#[displaydoc("...")` atrribute may be used on the variant or struct to |
84 | //! override it. |
85 | //! |
86 | //! <br> |
87 | //! |
88 | //! ## FAQ |
89 | //! |
90 | //! 1. **Is this crate `no_std` compatible?** |
91 | //! * Yes! This crate implements the [`core::fmt::Display`] trait, not the [`std::fmt::Display`] trait, so it should work in `std` and `no_std` environments. Just add `default-features = false`. |
92 | //! |
93 | //! 2. **Does this crate work with `Path` and `PathBuf` via the `Display` trait?** |
94 | //! * Yuuup. This crate uses @dtolnay's [autoref specialization technique](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md) to add a special trait for types to get the display impl. It then specializes for `Path` and `PathBuf`, and when either of these types are found, it calls `self.display()` to get a `std::path::Display<'_>` type which can be used with the `Display` format specifier! |
95 | #![doc (html_root_url = "https://docs.rs/displaydoc/0.2.3" )] |
96 | #![cfg_attr (docsrs, feature(doc_cfg))] |
97 | #![warn ( |
98 | rust_2018_idioms, |
99 | unreachable_pub, |
100 | bad_style, |
101 | dead_code, |
102 | improper_ctypes, |
103 | non_shorthand_field_patterns, |
104 | no_mangle_generic_items, |
105 | overflowing_literals, |
106 | path_statements, |
107 | patterns_in_fns_without_body, |
108 | unconditional_recursion, |
109 | unused, |
110 | unused_allocation, |
111 | unused_comparisons, |
112 | unused_parens, |
113 | while_true |
114 | )] |
115 | #![allow (clippy::try_err)] |
116 | |
117 | #[allow (unused_extern_crates)] |
118 | extern crate proc_macro; |
119 | |
120 | mod attr; |
121 | mod expand; |
122 | mod fmt; |
123 | |
124 | use proc_macro::TokenStream; |
125 | use syn::{parse_macro_input, DeriveInput}; |
126 | |
127 | /// [Custom `#[derive(...)]` macro](https://doc.rust-lang.org/edition-guide/rust-2018/macros/custom-derive.html) |
128 | /// for implementing [`fmt::Display`][core::fmt::Display] via doc comment attributes. |
129 | /// |
130 | /// ### Generic Type Parameters |
131 | /// |
132 | /// Type parameters to an enum or struct using this macro should *not* need to |
133 | /// have an explicit `Display` constraint at the struct or enum definition |
134 | /// site. A `Display` implementation for the `derive`d struct or enum is |
135 | /// generated assuming each type parameter implements `Display`, but that should |
136 | /// be possible without adding the constraint to the struct definition itself: |
137 | /// ```rust |
138 | /// use displaydoc::Display; |
139 | /// |
140 | /// /// oh no, an error: {0} |
141 | /// #[derive(Display)] |
142 | /// pub struct Error<E>(pub E); |
143 | /// |
144 | /// // No need to require `E: Display`, since `displaydoc::Display` adds that implicitly. |
145 | /// fn generate_error<E>(e: E) -> Error<E> { Error(e) } |
146 | /// |
147 | /// assert!("oh no, an error: muahaha" == &format!("{}" , generate_error("muahaha" ))); |
148 | /// ``` |
149 | /// |
150 | /// ### Using [`Debug`][core::fmt::Debug] Implementations with Type Parameters |
151 | /// However, if a type parameter must instead be constrained with the |
152 | /// [`Debug`][core::fmt::Debug] trait so that some field may be printed with |
153 | /// `{:?}`, that constraint must currently still also be specified redundantly |
154 | /// at the struct or enum definition site. If a struct or enum field is being |
155 | /// formatted with `{:?}` via [`displaydoc`][crate], and a generic type |
156 | /// parameter must implement `Debug` to do that, then that struct or enum |
157 | /// definition will need to propagate the `Debug` constraint to every type |
158 | /// parameter it's instantiated with: |
159 | /// ```rust |
160 | /// use core::fmt::Debug; |
161 | /// use displaydoc::Display; |
162 | /// |
163 | /// /// oh no, an error: {0:?} |
164 | /// #[derive(Display)] |
165 | /// pub struct Error<E: Debug>(pub E); |
166 | /// |
167 | /// // `E: Debug` now has to propagate to callers. |
168 | /// fn generate_error<E: Debug>(e: E) -> Error<E> { Error(e) } |
169 | /// |
170 | /// assert!("oh no, an error: \"cool \"" == &format!("{}" , generate_error("cool" ))); |
171 | /// |
172 | /// // Try this with a struct that doesn't impl `Display` at all, unlike `str`. |
173 | /// #[derive(Debug)] |
174 | /// pub struct Oh; |
175 | /// assert!("oh no, an error: Oh" == &format!("{}" , generate_error(Oh))); |
176 | /// ``` |
177 | #[proc_macro_derive ( |
178 | Display, |
179 | attributes(ignore_extra_doc_attributes, prefix_enum_doc_attributes, displaydoc) |
180 | )] |
181 | pub fn derive_error(input: TokenStream) -> TokenStream { |
182 | let input: DeriveInput = parse_macro_input!(input as DeriveInput); |
183 | expandTokenStream::derive(&input) |
184 | .unwrap_or_else(|err: Error| err.to_compile_error()) |
185 | .into() |
186 | } |
187 | |