├── .github └── workflows │ └── check-test.yaml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── derive ├── Cargo.toml └── src │ ├── expand.rs │ ├── lib.rs │ └── thiserror │ ├── LICENSE │ ├── ast.rs │ ├── attr.rs │ ├── fmt.rs │ ├── generics.rs │ ├── mod.rs │ ├── props.rs │ ├── scan_expr.rs │ └── unraw.rs ├── examples └── context.rs ├── rust-toolchain.toml ├── src ├── as_dyn.rs ├── backtrace.rs ├── lib.rs ├── ptr.rs └── report.rs └── tests ├── arc_new_type.rs ├── backtrace.rs ├── basic.rs ├── context_into.rs ├── macro.rs ├── macro_anyhow.rs ├── report.rs ├── report_debug.rs └── v2.rs /.github/workflows/check-test.yaml: -------------------------------------------------------------------------------- 1 | name: Check and Test 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | name: Build (${{ matrix.channel }}) 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | include: 19 | - toolchain: "1.86" 20 | features: "" 21 | channel: "stable" 22 | - toolchain: "nightly-2025-03-05" 23 | features: "--features backtrace" 24 | channel: "nightly" 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Override Rust toolchain 29 | run: rustup override set ${{ matrix.toolchain }} 30 | - name: Add Rust components 31 | run: rustup component add rustfmt clippy 32 | - name: Format 33 | run: cargo fmt --check 34 | - name: Build 35 | run: cargo build --all-targets ${{ matrix.features }} 36 | - name: Clippy 37 | run: cargo clippy ${{ matrix.features }} -- -D warnings 38 | - name: Run tests 39 | run: cargo test --workspace ${{ matrix.features }} 40 | - name: Generate docs 41 | run: RUSTDOCFLAGS="-Dwarnings --cfg docsrs" cargo doc --no-deps 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": "all" 3 | } 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thiserror-ext" 3 | description = "Useful extension utilities for `thiserror`." 4 | version = { workspace = true } 5 | edition = { workspace = true } 6 | authors = { workspace = true } 7 | repository = { workspace = true } 8 | keywords = { workspace = true } 9 | categories = { workspace = true } 10 | license = { workspace = true } 11 | 12 | [dependencies] 13 | thiserror = "2" 14 | thiserror-ext-derive = { version = "=0.3.0", path = "derive" } 15 | 16 | [dev-dependencies] 17 | anyhow = "1" 18 | expect-test = "1" 19 | sealed_test = "1" 20 | 21 | [features] 22 | backtrace = ["thiserror-ext-derive/backtrace"] 23 | 24 | [workspace] 25 | members = ["derive"] 26 | package.version = "0.3.0" 27 | package.edition = "2021" 28 | package.authors = ["Bugen Zhao "] 29 | package.repository = "https://github.com/risingwavelabs/thiserror-ext" 30 | package.keywords = ["error", "error-handling", "derive"] 31 | package.categories = ["rust-patterns"] 32 | package.license = "Apache-2.0" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 RisingWave Labs 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `thiserror-ext` 2 | 3 | [![Crate](https://img.shields.io/crates/v/thiserror-ext.svg)](https://crates.io/crates/thiserror-ext) 4 | [![Docs](https://docs.rs/thiserror-ext/badge.svg)](https://docs.rs/thiserror-ext) 5 | 6 | Useful extension utilities for `thiserror`. See the [documentation](https://docs.rs/thiserror-ext) for more details. 7 | 8 | ```rust 9 | #[derive( 10 | Debug, 11 | thiserror::Error, 12 | thiserror_ext::Box, 13 | thiserror_ext::Construct, 14 | thiserror_ext::ContextInto, 15 | thiserror_ext::Macro, 16 | )] 17 | #[thiserror_ext( 18 | newtype(name = Error, backtrace), 19 | macro(path = "crate::foo"), 20 | )] 21 | enum ErrorKind { 22 | #[error("cannot parse int from `{from}`")] 23 | Parse { 24 | source: std::num::ParseIntError, 25 | from: String, 26 | }, 27 | 28 | #[error("not yet implemented: {msg}")] 29 | NotImplemented { 30 | issue: Option, 31 | #[message] msg: String, 32 | }, 33 | 34 | #[error("internal error: {0}")] 35 | Internal(String), 36 | } 37 | 38 | // `thiserror_ext::Construct` 39 | let error: Error = Error::internal("oops"); 40 | 41 | // `thiserror_ext::Box` 42 | assert_eq!(std::mem::size_of::(), std::mem::size_of::()); 43 | let bt: &Backtrace = std::error::request_ref(&error).unwrap(); 44 | 45 | // `thiserror_ext::ContextInto` 46 | let result: Result = "foo".parse().into_parse("foo"); 47 | 48 | // `thiserror_ext::AsReport` 49 | // 50 | // "cannot parse int from `foo`: invalid digit found in string" 51 | println!("{}", result.unwrap_err().as_report()); 52 | 53 | // `thiserror_ext::Macro` 54 | bail_not_implemented!(issue = 42, "an {} feature", "awesome"); 55 | ``` 56 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thiserror-ext-derive" 3 | description = "Procedural macros for `thiserror_ext`." 4 | version = { workspace = true } 5 | edition = { workspace = true } 6 | authors = { workspace = true } 7 | repository = { workspace = true } 8 | keywords = { workspace = true } 9 | categories = { workspace = true } 10 | license = { workspace = true } 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [features] 16 | backtrace = [] 17 | 18 | [dependencies] 19 | either = "1" 20 | proc-macro2 = "1" 21 | quote = "1" 22 | syn = "2" 23 | -------------------------------------------------------------------------------- /derive/src/expand.rs: -------------------------------------------------------------------------------- 1 | use either::{for_both, Either}; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote, ToTokens}; 4 | use syn::{ 5 | DeriveInput, Error, GenericArgument, Ident, LitStr, PathArguments, Result, Type, Visibility, 6 | }; 7 | 8 | use crate::thiserror::ast::{Field, Input, Variant}; 9 | use crate::thiserror::unraw::MemberUnraw; 10 | 11 | struct Args { 12 | other_args: Vec, 13 | other_names: Vec, 14 | other_tys: Vec, 15 | source_arg: Option, 16 | ctor_args: Vec, 17 | } 18 | 19 | enum SourceInto { 20 | Yes, 21 | No, 22 | } 23 | 24 | fn resolve_variant_args(variant: &Variant<'_>, source_into: SourceInto) -> Args { 25 | let mut other_args = Vec::new(); 26 | let mut other_names = Vec::new(); 27 | let mut other_tys = Vec::new(); 28 | let mut source_arg = None; 29 | let mut ctor_args = Vec::new(); 30 | 31 | for (i, field) in variant.fields.iter().enumerate() { 32 | let ty = &field.ty; 33 | let member = &field.member; 34 | 35 | let name = match &field.member { 36 | MemberUnraw::Named(named) => named.to_local(), 37 | MemberUnraw::Unnamed(_) => { 38 | if field.is_non_from_source() { 39 | format_ident!("source") 40 | } else { 41 | format_ident!("arg_{}", i) 42 | } 43 | } 44 | }; 45 | 46 | if field.is_backtrace() { 47 | let expr = if type_is_option(ty) { 48 | quote!(std::option::Option::Some( 49 | std::backtrace::Backtrace::capture() 50 | )) 51 | } else { 52 | quote!(std::convert::From::from( 53 | std::backtrace::Backtrace::capture() 54 | )) 55 | }; 56 | ctor_args.push(quote!(#member: #expr,)) 57 | } else if field.is_non_from_source() { 58 | match source_into { 59 | SourceInto::Yes => { 60 | source_arg = Some(quote!(#name: impl Into<#ty>,)); 61 | ctor_args.push(quote!(#member: #name.into(),)); 62 | } 63 | SourceInto::No => { 64 | source_arg = Some(quote!(#name: #ty,)); 65 | ctor_args.push(quote!(#member: #name,)); 66 | } 67 | } 68 | } else { 69 | other_args.push(quote!(#name: impl Into<#ty>,)); 70 | other_names.push(name.clone()); 71 | other_tys.push((**ty).clone()); 72 | ctor_args.push(quote!(#member: #name.into(),)); 73 | } 74 | } 75 | 76 | Args { 77 | other_args, 78 | other_names, 79 | other_tys, 80 | source_arg, 81 | ctor_args, 82 | } 83 | } 84 | 85 | struct MacroArgs { 86 | other_args: Vec, 87 | other_call_args: Vec, 88 | ctor_args: Vec, 89 | } 90 | 91 | fn resolve_args_for_macro(fields: &[Field<'_>]) -> MacroArgs { 92 | let mut other_args = Vec::new(); 93 | let mut other_call_args = Vec::new(); 94 | let mut ctor_args = Vec::new(); 95 | 96 | for (i, field) in fields.iter().enumerate() { 97 | let ty = &field.ty; 98 | let member = &field.member; 99 | 100 | let name = match &field.member { 101 | MemberUnraw::Named(named) => named.to_local(), 102 | MemberUnraw::Unnamed(_) => format_ident!("arg_{}", i), 103 | }; 104 | 105 | if field.is_backtrace() { 106 | let expr = if type_is_option(ty) { 107 | quote!(std::option::Option::Some( 108 | std::backtrace::Backtrace::capture() 109 | )) 110 | } else { 111 | quote!(std::convert::From::from( 112 | std::backtrace::Backtrace::capture() 113 | )) 114 | }; 115 | ctor_args.push(quote!(#member: #expr,)) 116 | } else if field.is_message() { 117 | ctor_args.push(quote!(#member: ::std::format!($($fmt_arg)*).into(),)); 118 | } else { 119 | other_args.push(quote!(#name = $#name:expr,)); 120 | other_call_args.push(quote!(#name)); 121 | ctor_args.push(quote!(#member: $#name,)); 122 | } 123 | } 124 | 125 | MacroArgs { 126 | other_args, 127 | other_call_args, 128 | ctor_args, 129 | } 130 | } 131 | 132 | struct DeriveMeta { 133 | impl_type: Ident, 134 | nt_backtrace: bool, 135 | macro_mangle: bool, 136 | macro_path: Option, 137 | macro_vis: Option, 138 | } 139 | 140 | fn resolve_meta(input: &DeriveInput) -> Result { 141 | let mut new_type = None; 142 | let mut nt_backtrace = false; 143 | let mut macro_mangle = false; 144 | let mut macro_path = None; 145 | let mut macro_vis = None; 146 | 147 | for attr in &input.attrs { 148 | if attr.path().is_ident("thiserror_ext") { 149 | attr.parse_nested_meta(|meta| { 150 | if meta.path.is_ident("newtype") { 151 | meta.parse_nested_meta(|meta| { 152 | if meta.path.is_ident("name") { 153 | let value = meta.value()?; 154 | new_type = Some(value.parse()?); 155 | } else if meta.path.is_ident("backtrace") { 156 | if cfg!(feature = "backtrace") { 157 | nt_backtrace = true; 158 | } else { 159 | return Err(Error::new_spanned( 160 | meta.path, 161 | "enable the `backtrace` feature to use `backtrace` attribute", 162 | )); 163 | } 164 | } else { 165 | return Err(Error::new_spanned(meta.path, "unknown attribute")); 166 | } 167 | Ok(()) 168 | })?; 169 | } else if meta.path.is_ident("macro") { 170 | meta.parse_nested_meta(|meta| { 171 | if meta.path.is_ident("mangle") { 172 | macro_mangle = true; 173 | } else if meta.path.is_ident("path") { 174 | let value = meta.value()?; 175 | let path: LitStr = value.parse()?; 176 | let mut path = path.value(); 177 | 178 | if path.starts_with("crate") { 179 | path.insert(0, '$'); 180 | if !path.ends_with("::") { 181 | path.push_str("::"); 182 | } 183 | macro_path = Some(path.parse()?); 184 | } else { 185 | return Err(Error::new_spanned( 186 | meta.path, 187 | "macro path should start with `crate`", 188 | )); 189 | } 190 | } else if meta.path.is_ident("vis") { 191 | let value = meta.value()?; 192 | macro_vis = Some(if let Ok(lit_str) = value.parse::() { 193 | lit_str.parse()? 194 | } else { 195 | value.parse()? 196 | }) 197 | } else { 198 | return Err(Error::new_spanned(meta.path, "unknown attribute")); 199 | } 200 | Ok(()) 201 | })?; 202 | } else { 203 | return Err(Error::new_spanned(meta.path, "unknown attribute")); 204 | } 205 | Ok(()) 206 | })?; 207 | } 208 | } 209 | let impl_type = new_type.unwrap_or_else(|| input.ident.clone()); 210 | 211 | Ok(DeriveMeta { 212 | impl_type, 213 | nt_backtrace, 214 | macro_mangle, 215 | macro_path, 216 | macro_vis, 217 | }) 218 | } 219 | 220 | pub enum DeriveCtorType { 221 | Construct, 222 | ContextInto, 223 | } 224 | 225 | pub enum DeriveNewType { 226 | Box, 227 | Arc, 228 | } 229 | 230 | impl DeriveNewType { 231 | fn name(&self) -> &'static str { 232 | match self { 233 | DeriveNewType::Box => "Box", 234 | DeriveNewType::Arc => "Arc", 235 | } 236 | } 237 | 238 | fn ty_ident(&self) -> Ident { 239 | match self { 240 | DeriveNewType::Box => format_ident!("ErrorBox"), 241 | DeriveNewType::Arc => format_ident!("ErrorArc"), 242 | } 243 | } 244 | } 245 | 246 | pub fn derive_new_type(input: &DeriveInput, ty: DeriveNewType) -> Result { 247 | let input_type = input.ident.clone(); 248 | let vis = &input.vis; 249 | 250 | let DeriveMeta { 251 | impl_type, 252 | nt_backtrace: backtrace, 253 | .. 254 | } = resolve_meta(input)?; 255 | 256 | if impl_type == input_type { 257 | return Err(Error::new_spanned( 258 | input, 259 | format!("should specify a different type for `{}` derive with `#[thiserror_ext(newtype(name = ))]`", ty.name()), 260 | )); 261 | } 262 | 263 | let backtrace_type_param = if backtrace { 264 | quote!(thiserror_ext::__private::MaybeBacktrace) 265 | } else { 266 | quote!(thiserror_ext::__private::NoExtraBacktrace) 267 | }; 268 | 269 | let doc = format!( 270 | "The `{}`-wrapped type of [`{}`].{}", 271 | ty.name(), 272 | input_type, 273 | if backtrace { 274 | "\n\nA backtrace is captured when the inner error doesn't provide one." 275 | } else { 276 | "" 277 | } 278 | ); 279 | let new_type = ty.ty_ident(); 280 | let extra_derive = match ty { 281 | DeriveNewType::Box => quote!(), 282 | DeriveNewType::Arc => quote!(Clone), 283 | }; 284 | let backtrace_attr = if cfg!(feature = "backtrace") { 285 | quote!(#[backtrace]) 286 | } else { 287 | quote!() 288 | }; 289 | 290 | let into_inner = match ty { 291 | DeriveNewType::Box => quote!( 292 | #[doc = "Consumes `self` and returns the inner error."] 293 | #vis fn into_inner(self) -> #input_type { 294 | self.0.into_inner() 295 | } 296 | ), 297 | DeriveNewType::Arc => quote!(), 298 | }; 299 | 300 | let generated = quote!( 301 | #[doc = #doc] 302 | #[derive(thiserror_ext::__private::thiserror::Error, #extra_derive)] 303 | #[error(transparent)] 304 | #vis struct #impl_type( 305 | #[from] 306 | #backtrace_attr 307 | thiserror_ext::__private::#new_type< 308 | #input_type, 309 | #backtrace_type_param, 310 | >, 311 | ); 312 | 313 | // For `?` to work. 314 | impl From for #impl_type 315 | where 316 | E: Into<#input_type>, 317 | { 318 | fn from(error: E) -> Self { 319 | Self(thiserror_ext::__private::#new_type::new(error.into())) 320 | } 321 | } 322 | 323 | impl std::fmt::Debug for #impl_type { 324 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 325 | std::fmt::Debug::fmt(&self.0, f) 326 | } 327 | } 328 | 329 | impl #impl_type { 330 | #[doc = "Returns the reference to the inner error."] 331 | #vis fn inner(&self) -> &#input_type { 332 | self.0.inner() 333 | } 334 | 335 | #into_inner 336 | } 337 | ); 338 | 339 | Ok(generated) 340 | } 341 | 342 | pub fn derive_ctor(input: &DeriveInput, t: DeriveCtorType) -> Result { 343 | let input_type = input.ident.clone(); 344 | let vis = &input.vis; 345 | 346 | let DeriveMeta { impl_type, .. } = resolve_meta(input)?; 347 | 348 | let input = Input::from_syn(input)?; 349 | 350 | let input = match input { 351 | Input::Struct(input) => { 352 | return Err(Error::new_spanned( 353 | input.original, 354 | "only `enum` is supported for `Construct` and `ContextInto`", 355 | )) 356 | } 357 | Input::Enum(input) => input, 358 | }; 359 | 360 | let mut items = Vec::new(); 361 | 362 | for variant in input.variants { 363 | // Why not directly use `From`? 364 | if variant.from_field().is_some() { 365 | continue; 366 | } 367 | 368 | let skipped = match t { 369 | DeriveCtorType::Construct => variant.attrs.extra.construct_skip.is_some(), 370 | DeriveCtorType::ContextInto => variant.attrs.extra.context_into_skip.is_some(), 371 | }; 372 | if skipped { 373 | continue; 374 | } 375 | 376 | let variant_name = &variant.ident; 377 | 378 | let Args { 379 | other_args, 380 | other_names, 381 | other_tys, 382 | source_arg, 383 | ctor_args, 384 | } = resolve_variant_args( 385 | &variant, 386 | match t { 387 | DeriveCtorType::Construct => SourceInto::Yes, 388 | DeriveCtorType::ContextInto => SourceInto::No, 389 | }, 390 | ); 391 | 392 | let ctor_expr = quote!(#input_type::#variant_name { 393 | #(#ctor_args)* 394 | }); 395 | 396 | let item = match t { 397 | DeriveCtorType::Construct => { 398 | let ctor_name = format_ident!( 399 | "{}", 400 | big_camel_case_to_snake_case(&variant_name.to_string()), 401 | span = variant_name.span() 402 | ); 403 | let doc = format!("Constructs a [`{input_type}::{variant_name}`] variant."); 404 | 405 | quote!( 406 | #[doc = #doc] 407 | #vis fn #ctor_name(#source_arg #(#other_args)*) -> Self { 408 | #ctor_expr.into() 409 | } 410 | ) 411 | } 412 | DeriveCtorType::ContextInto => { 413 | // It's implemented on `Result`, so there's must be the `source` field, 414 | // and we expect there's at least one argument. 415 | if source_arg.is_none() || other_args.is_empty() { 416 | continue; 417 | } 418 | let source_ty = variant.source_field().unwrap().ty; 419 | let source_ty_name = get_type_string(source_ty); 420 | 421 | let ext_name = format_ident!("Into{}", variant_name, span = variant_name.span()); 422 | 423 | let doc_trait = format!( 424 | "Extension trait for converting [`{source_ty_name}`] \ 425 | into [`{input_type}::{variant_name}`] with the given context.", 426 | ); 427 | 428 | let method_sig = { 429 | let name = format_ident!( 430 | "into_{}", 431 | big_camel_case_to_snake_case(&variant_name.to_string()), 432 | span = variant_name.span() 433 | ); 434 | let doc = format!( 435 | "Converts [`{source_ty_name}`] \ 436 | into [`{input_type}::{variant_name}`] with the given context.", 437 | ); 438 | 439 | quote!( 440 | #[doc = #doc] 441 | fn #name(self, #(#other_args)*) -> Self::Ret 442 | ) 443 | }; 444 | 445 | let method_with_sig = { 446 | let name = format_ident!( 447 | "into_{}_with", 448 | big_camel_case_to_snake_case(&variant_name.to_string()), 449 | span = variant_name.span() 450 | ); 451 | let doc = format!( 452 | "Converts [`{source_ty_name}`] \ 453 | into [`{input_type}::{variant_name}`] with the context returned by the given function.", 454 | ); 455 | 456 | let ret_tys: Vec<_> = other_names 457 | .iter() 458 | .map(|name| format_ident!("__{}", name.to_string().to_uppercase())) 459 | .collect(); 460 | let ret_ty_bounds: Vec<_> = ret_tys 461 | .iter() 462 | .zip(other_tys.iter()) 463 | .map(|(ret_ty, ty)| quote!(#ret_ty: Into<#ty>)) 464 | .collect(); 465 | 466 | quote!( 467 | #[doc = #doc] 468 | fn #name<__F, #( #ret_tys, )*>( 469 | self, 470 | f: __F, 471 | ) -> Self::Ret 472 | where 473 | __F: FnOnce() -> (#( #ret_tys ),*), 474 | #( #ret_ty_bounds, )* 475 | ) 476 | }; 477 | 478 | quote!( 479 | #[doc = #doc_trait] 480 | #vis trait #ext_name { 481 | type Ret; 482 | #method_sig; 483 | #method_with_sig; 484 | } 485 | impl #ext_name for #source_ty { 486 | type Ret = #impl_type; 487 | #method_sig { 488 | (move |#source_arg| #ctor_expr.into())(self) 489 | } 490 | #method_with_sig { 491 | let (#( #other_names ),*) = f(); 492 | (move |#source_arg| #ctor_expr.into())(self) 493 | } 494 | } 495 | impl<__T> #ext_name for std::result::Result<__T, #source_ty> { 496 | type Ret = std::result::Result<__T, #impl_type>; 497 | #method_sig { 498 | self.map_err(move |#source_arg| #ctor_expr.into()) 499 | } 500 | #method_with_sig { 501 | self.map_err(move |#source_arg| { 502 | let (#( #other_names ),*) = f(); 503 | #ctor_expr.into() 504 | }) 505 | } 506 | } 507 | ) 508 | } 509 | }; 510 | 511 | items.push(item); 512 | } 513 | 514 | let generated = match t { 515 | DeriveCtorType::Construct => { 516 | quote!( 517 | #[automatically_derived] 518 | impl #impl_type { 519 | #(#items)* 520 | } 521 | ) 522 | } 523 | DeriveCtorType::ContextInto => { 524 | quote!(#(#items)*) 525 | } 526 | }; 527 | 528 | Ok(generated) 529 | } 530 | 531 | pub fn derive_macro_inner(input: &DeriveInput, bail: bool) -> Result { 532 | let DeriveMeta { 533 | impl_type, 534 | macro_mangle, 535 | macro_path, 536 | macro_vis, 537 | .. 538 | } = resolve_meta(input)?; 539 | 540 | let input_type = input.ident.clone(); 541 | let vis = macro_vis.unwrap_or_else(|| input.vis.clone()); 542 | let input = Input::from_syn(input)?; 543 | 544 | let variants = match input { 545 | Input::Struct(input) => vec![Either::Left(input)], 546 | Input::Enum(input) => input.variants.into_iter().map(Either::Right).collect(), 547 | }; 548 | 549 | let mut items = Vec::new(); 550 | 551 | for variant in variants { 552 | // We only care about variants with `message` field. 553 | if for_both!(&variant, v => v.message_field()).is_none() { 554 | continue; 555 | } 556 | 557 | let variant_name = match &variant { 558 | Either::Left(_s) => quote!(#impl_type), // newtype name 559 | Either::Right(v) => v.ident.to_token_stream(), 560 | }; 561 | let ctor_path = match &variant { 562 | Either::Left(_s) => quote!(#macro_path #input_type), 563 | Either::Right(_v) => quote!(#macro_path #input_type::#variant_name), 564 | }; 565 | 566 | let fields = for_both!(&variant, v => &v.fields); 567 | 568 | let MacroArgs { 569 | other_args, 570 | other_call_args, 571 | ctor_args, 572 | } = resolve_args_for_macro(fields); 573 | 574 | let ctor_expr = quote!(#ctor_path { 575 | #(#ctor_args)* 576 | }); 577 | 578 | let bail_prefix = if bail { "bail_" } else { "" }; 579 | let bail_suffix = if bail { "__bail" } else { "" }; 580 | 581 | let ctor_span = for_both!(&variant, v => v.ident.span()); 582 | 583 | let export_name = format_ident!( 584 | "{}{}", 585 | bail_prefix, 586 | big_camel_case_to_snake_case(&variant_name.to_string()), 587 | span = ctor_span, 588 | ); 589 | let mangled_name = if macro_mangle { 590 | format_ident!( 591 | "__thiserror_ext_macro__{}__{}{}", 592 | big_camel_case_to_snake_case(&input_type.to_string()), 593 | big_camel_case_to_snake_case(&variant_name.to_string()), 594 | bail_suffix, 595 | span = ctor_span, 596 | ) 597 | } else { 598 | export_name.clone() 599 | }; 600 | 601 | let bail_doc = if bail { " and bails out" } else { "" }; 602 | let doc = match &variant { 603 | Either::Left(_s) => { 604 | format!("Constructs a [`{input_type}`]{bail_doc}.") 605 | } 606 | Either::Right(_v) => { 607 | format!("Constructs a [`{input_type}::{variant_name}`] variant{bail_doc}.") 608 | } 609 | }; 610 | 611 | let mut arms = Vec::new(); 612 | 613 | let len = other_args.len(); 614 | 615 | let message_arg = quote!($($fmt_arg:tt)*); 616 | let message_call_arg = quote!($($fmt_arg)*); 617 | 618 | for bitset in (0..(1 << len)).rev() { 619 | let mut args = Vec::new(); 620 | let mut call_args = Vec::new(); 621 | for (i, (arg, call_arg)) in (other_args.iter()).zip(other_call_args.iter()).enumerate() 622 | { 623 | if bitset & (1 << i) != 0 { 624 | args.push(arg); 625 | call_args.push(quote!(#call_arg = $#call_arg.into(),)); 626 | } else { 627 | call_args.push(quote!(#call_arg = ::std::default::Default::default(),)); 628 | } 629 | } 630 | 631 | let arm = quote!( 632 | (#(#args)* #message_arg) => { 633 | #export_name!(@ #(#call_args)* #message_call_arg) 634 | }; 635 | ); 636 | arms.push(arm); 637 | } 638 | 639 | let full_inner = if bail { 640 | quote!({ 641 | let res: #macro_path #impl_type = (#ctor_expr).into(); 642 | return ::std::result::Result::Err(res.into()); 643 | }) 644 | } else { 645 | quote!({ 646 | let res: #macro_path #impl_type = (#ctor_expr).into(); 647 | res 648 | }) 649 | }; 650 | 651 | let full = quote!( 652 | () => { // empty macro call 653 | #export_name!("") 654 | }; 655 | (@ #(#other_args)* #message_arg) => { 656 | #full_inner 657 | }; 658 | ); 659 | 660 | let macro_export = if let Visibility::Public(_) = &vis { 661 | quote!(#[macro_export]) 662 | } else { 663 | quote!() 664 | }; 665 | 666 | let item = quote!( 667 | #[doc = #doc] 668 | #[allow(unused_macros)] 669 | #macro_export 670 | macro_rules! #mangled_name { 671 | #full 672 | #(#arms)* 673 | } 674 | 675 | #[allow(unused_imports)] 676 | #vis use #mangled_name as #export_name; 677 | ); 678 | 679 | items.push(item); 680 | } 681 | 682 | let generated = quote!( 683 | #( #items )* 684 | ); 685 | 686 | Ok(generated) 687 | } 688 | 689 | pub fn derive_macro(input: &DeriveInput) -> Result { 690 | let ctor = derive_macro_inner(input, false)?; 691 | let bail = derive_macro_inner(input, true)?; 692 | 693 | let generated = quote!( 694 | #ctor 695 | #bail 696 | ); 697 | 698 | Ok(generated) 699 | } 700 | 701 | pub fn derive_report_debug(input: &DeriveInput) -> Result { 702 | let input_type = input.ident.clone(); 703 | 704 | // 1. Delegate to `Debug` impl as the backtrace provided by the error 705 | // could be different than where panic happens. 706 | // 2. Passthrough the `alternate` flag. 707 | let generated = quote!( 708 | impl ::std::fmt::Debug for #input_type { 709 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 710 | use ::thiserror_ext::AsReport; 711 | ::std::fmt::Debug::fmt(&self.as_report(), f) 712 | } 713 | } 714 | ); 715 | 716 | Ok(generated) 717 | } 718 | 719 | fn big_camel_case_to_snake_case(input: &str) -> String { 720 | let mut output = String::new(); 721 | 722 | for (i, c) in input.char_indices() { 723 | if i == 0 { 724 | output.push(c.to_ascii_lowercase()); 725 | } else if c.is_uppercase() { 726 | output.push('_'); 727 | output.push(c.to_ascii_lowercase()); 728 | } else { 729 | output.push(c); 730 | } 731 | } 732 | 733 | output 734 | } 735 | 736 | fn type_is_option(ty: &Type) -> bool { 737 | type_parameter_of_option(ty).is_some() 738 | } 739 | 740 | fn type_parameter_of_option(ty: &Type) -> Option<&Type> { 741 | let path = match ty { 742 | Type::Path(ty) => &ty.path, 743 | _ => return None, 744 | }; 745 | 746 | let last = path.segments.last().unwrap(); 747 | if last.ident != "Option" { 748 | return None; 749 | } 750 | 751 | let bracketed = match &last.arguments { 752 | PathArguments::AngleBracketed(bracketed) => bracketed, 753 | _ => return None, 754 | }; 755 | 756 | if bracketed.args.len() != 1 { 757 | return None; 758 | } 759 | 760 | match &bracketed.args[0] { 761 | GenericArgument::Type(arg) => Some(arg), 762 | _ => None, 763 | } 764 | } 765 | 766 | fn get_type_string(type_: &Type) -> String { 767 | let tokens = type_.to_token_stream(); 768 | let mut type_string = String::new(); 769 | 770 | for token in tokens { 771 | let stringified = token.to_string(); 772 | type_string.push_str(&stringified); 773 | } 774 | 775 | type_string 776 | } 777 | -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(rustdoc::broken_intra_doc_links)] 2 | 3 | //! Procedural macros for `thiserror_ext`. 4 | 5 | use expand::{DeriveCtorType, DeriveNewType}; 6 | use proc_macro::TokenStream; 7 | use syn::{parse_macro_input, DeriveInput}; 8 | 9 | mod expand; 10 | mod thiserror; 11 | 12 | /// Generates constructor functions for different variants of the error type. 13 | /// 14 | /// The arguments of the constructor functions can be any types that implement 15 | /// [`Into`] for the corresponding fields of the variant, enabling convenient 16 | /// construction of the error type. 17 | /// 18 | /// # Example 19 | /// 20 | /// ```ignore 21 | /// #[derive(Debug, thiserror::Error, thiserror_ext::Construct)] 22 | /// enum Error { 23 | /// #[error("unsupported feature: {0}")] 24 | /// UnsupportedFeature { name: String }, 25 | /// 26 | /// #[error("internal error: {0}")] 27 | /// #[construct(skip)] // to skip generating the constructor 28 | /// InternalError(String), 29 | /// } 30 | /// 31 | /// // Any type that implements `Into` is accepted as the argument. 32 | /// let _: Error = Error::unsupported_feature("foo"); 33 | /// ``` 34 | /// 35 | /// # New type 36 | /// 37 | /// If a new type is specified with `#[thiserror_ext(newtype(..))]`, the 38 | /// constructors will be implemented on the new type instead. 39 | /// 40 | /// See the documentation of [`thiserror_ext::Box`] or [`thiserror_ext::Arc`] 41 | /// for more details. 42 | /// 43 | /// [`thiserror_ext::Box`]: derive@Box 44 | /// [`thiserror_ext::Arc`]: derive@Arc 45 | #[proc_macro_derive(Construct, attributes(thiserror_ext, construct))] 46 | pub fn derive_construct(input: TokenStream) -> TokenStream { 47 | let input = parse_macro_input!(input as DeriveInput); 48 | 49 | expand::derive_ctor(&input, DeriveCtorType::Construct) 50 | .unwrap_or_else(|err| err.to_compile_error()) 51 | .into() 52 | } 53 | 54 | /// Generates extension traits for converting the external error type into the 55 | /// the provided one, with extra context. 56 | /// 57 | /// This can be helpful when the external error type does not provide enough 58 | /// context for the application. `thiserror` does not allow specifying `#[from]` 59 | /// on the error field if there're extra fields in the variant. 60 | /// 61 | /// The extension trait is only generated when there's a field named `source` 62 | /// or marked with `#[source]` but not `#[from]`. The rest of the fields (except 63 | /// the backtrace) are treated as the context. Both single and multiple context 64 | /// fields are supported. 65 | /// 66 | /// # Example 67 | /// 68 | /// ```ignore 69 | /// #[derive(Debug, thiserror::Error, thiserror_ext::ContextInto)] 70 | /// enum Error { 71 | /// #[error("cannot parse int from `{from}`")] 72 | /// ParseInt { 73 | /// source: std::num::ParseIntError, 74 | /// from: String, 75 | /// }, 76 | /// 77 | /// #[error("cannot parse float from `{from}`")] 78 | /// #[context_into(skip)] // to skip generating the extension 79 | /// ParseFloat { 80 | /// source: std::num::ParseIntError, 81 | /// from: String, 82 | /// }, 83 | /// } 84 | /// 85 | /// // Specify the `from` as "foo" and convert it into `Error::ParseInt`. 86 | /// let _: Error = "foo".parse::().unwrap_err().into_parse_int("foo"); 87 | /// 88 | /// // Can also be called on `Result` 89 | /// let _: Result = "foo".parse().into_parse_int("foo"); 90 | /// 91 | /// // Call `into_*_with` with a closure to lazily evaluate the context. 92 | /// let _: Result = "foo".parse().into_parse_int_with(|| format!("{}", 1 + 1)); 93 | /// ``` 94 | /// 95 | /// # New type 96 | /// 97 | /// If a new type is specified with `#[thiserror_ext(newtype(..))]`, the 98 | /// extensions will convert the errors into the new type instead. 99 | /// 100 | /// See the documentation of [`thiserror_ext::Box`] or [`thiserror_ext::Arc`] 101 | /// for more details. 102 | /// 103 | /// [`thiserror_ext::Box`]: derive@Box 104 | /// [`thiserror_ext::Arc`]: derive@Arc 105 | #[proc_macro_derive(ContextInto, attributes(thiserror_ext, context_into))] 106 | pub fn derive_context_into(input: TokenStream) -> TokenStream { 107 | let input = parse_macro_input!(input as DeriveInput); 108 | 109 | expand::derive_ctor(&input, DeriveCtorType::ContextInto) 110 | .unwrap_or_else(|err| err.to_compile_error()) 111 | .into() 112 | } 113 | 114 | /// Generates macros for different variants of the error type to construct 115 | /// it or directly bail out. 116 | /// 117 | /// # Inline formatting 118 | /// 119 | /// It's common to put a message string in the error variant. With this macro, 120 | /// one can directly format the message in the macro call, instead of calling 121 | /// [`format!`]. 122 | /// 123 | /// To mark a field as the message to be formatted, name it `message` or mark 124 | /// it with `#[message]`. The message field can be any type that implements 125 | /// `From`. 126 | /// 127 | /// ## Example 128 | /// 129 | /// ```ignore 130 | /// #[derive(Debug, thiserror::Error, thiserror_ext::Macro)] 131 | /// enum Error { 132 | /// #[error("internal error: {msg}")] 133 | /// Internal { #[message] msg: Box }, 134 | /// } 135 | /// 136 | /// // Equivalent to `Error::Internal { msg: format!(..).into() }`. 137 | /// let _: Error = internal!("{} is a bad number", 42); 138 | /// 139 | /// // Equivalent to `return Err(Error::Internal { msg: format!(..).into() }.into())`. 140 | /// bail_internal!("{} is a bad number", 42); 141 | /// ``` 142 | /// 143 | /// # Extra fields 144 | /// 145 | /// If there're extra fields along with the message field, one can specify 146 | /// the values of them with `field = value` syntax before the message. The 147 | /// values can be any types that implement [`Into`] for the corresponding 148 | /// fields. 149 | /// 150 | /// Fields can be omitted, in which case [`Default::default()`] will be used. 151 | /// 152 | /// ## Example 153 | /// 154 | /// ```ignore 155 | /// #[derive(Debug, thiserror::Error, thiserror_ext::Macro)] 156 | /// #[error("not yet implemented: {message}")] 157 | /// struct NotYetImplemented { 158 | /// issue: Option, 159 | /// pr: Option, 160 | /// message: String, 161 | /// } 162 | /// 163 | /// let _: Error = not_yet_implemented!(issue = 42, pr = 88, "foo"); 164 | /// let _: Error = not_yet_implemented!(issue = 42, "foo"); // pr = None 165 | /// let _: Error = not_yet_implemented!(pr = 88, "foo"); // issue = None 166 | /// let _: Error = not_yet_implemented!("foo"); // issue = None, pr = None 167 | /// ``` 168 | /// 169 | /// # Visibility 170 | /// 171 | /// There's a different rule set for the visibility of the macros. The macros 172 | /// generated by this proc-macro are marked with `#[macro_export]` only if the 173 | /// visibility of the error type is `pub`, otherwise they're just re-exported 174 | /// with the same visibility as the error type and only work in the same crate. 175 | /// 176 | /// There're some extra configurations to help to better handle the visibility, 177 | /// specified in `#[thiserror_ext(macro(..))]`: 178 | /// 179 | /// - `vis = ..`: use a different visibility for the macro re-export. 180 | /// - `mangle`: mangle the macro names so that they don't conflict with other 181 | /// macros with the same name in the crate root. 182 | /// - `path = "crate::.."`: the path to the current module. When specified, 183 | /// types in the generated macros will use the qualified path like 184 | /// `$crate::foo::bar::Error`, enabling the callers to use the macros without 185 | /// importing the error type. 186 | /// 187 | /// # New type 188 | /// 189 | /// If a new type is specified with `#[thiserror_ext(newtype(..))]`, the macros 190 | /// will generate the new type instead. 191 | /// 192 | /// See the documentation of [`thiserror_ext::Box`] or [`thiserror_ext::Arc`] 193 | /// for more details. 194 | /// 195 | /// [`thiserror_ext::Box`]: derive@Box 196 | /// [`thiserror_ext::Arc`]: derive@Arc 197 | #[proc_macro_derive(Macro, attributes(thiserror_ext, message))] 198 | pub fn derive_macro(input: TokenStream) -> TokenStream { 199 | let input = parse_macro_input!(input as DeriveInput); 200 | 201 | expand::derive_macro(&input) 202 | .unwrap_or_else(|err| err.to_compile_error()) 203 | .into() 204 | } 205 | 206 | /// Generates a new type that wraps the original error type in a [`struct@Box`]. 207 | /// 208 | /// Specify the name of the new type with `#[thiserror_ext(newtype(name = ..))]`. 209 | /// 210 | /// # Reduce size 211 | /// 212 | /// The most common motivation for using this macro is to reduce the size of 213 | /// the original error type. As a sum-type, a [`Result`] is at least as large 214 | /// as its largest variant. Large error type may hurt the performance of a 215 | /// function call returning a [`Result`]. With this macro, the new type always 216 | /// has the same size as a [`struct@Box`]. 217 | /// 218 | /// On the other hand, returning an error should be an exceptional case in most 219 | /// cases. Therefore, even though boxing the error type may lead to extra 220 | /// allocation, it's usually acceptable. 221 | /// 222 | /// ## Example 223 | /// 224 | /// ```ignore 225 | /// #[derive(Debug, thiserror::Error, thiserror_ext::Box)] 226 | /// #[thiserror_ext(newtype(name = Error))] 227 | /// enum ErrorKind { 228 | /// #[error("foo")] 229 | /// Foo, 230 | /// #[error("io")] 231 | /// Io(#[from] std::io::Error), 232 | /// } 233 | /// 234 | /// // The size of `Error` is one pointer. 235 | /// assert_eq!(std::mem::size_of::(), std::mem::size_of::()); 236 | /// 237 | /// // Convert to `Error`, from `ErrorKind` or other types that can be converted 238 | /// // to `ErrorKind`. 239 | /// let error: Error = ErrorKind::Foo.into(); 240 | /// let error: Error = io_error().into(); 241 | /// 242 | /// // Get the reference or the value of the inner error. 243 | /// let _: &ErrorKind = error.inner(); 244 | /// let _: ErrorKind = error.into_inner(); 245 | /// ``` 246 | /// 247 | /// # Backtrace 248 | /// 249 | /// Another use case is to capture backtrace when the error is created. Without 250 | /// a new type, one has to manually add a [`Backtrace`] field to each variant 251 | /// of the error type. The new type allows one to capture backtrace in a single 252 | /// place. 253 | /// 254 | /// Specify `#[thiserror_ext(newtype(.., backtrace))]` to enable capturing 255 | /// backtrace. The extra backtrace is captured **only if** the original error 256 | /// type does not [`provide`] one. Typically, this should be maintained by the 257 | /// `#[backtrace]` attribute from `thiserror`. 258 | /// 259 | /// ## Example 260 | /// 261 | /// ```ignore 262 | /// # use std::backtrace::Backtrace; 263 | /// #[derive(Debug, thiserror::Error, thiserror_ext::Box)] 264 | /// #[thiserror_ext(newtype(name = Error, backtrace))] 265 | /// enum ErrorKind { 266 | /// #[error("foo")] 267 | /// Foo, 268 | /// } 269 | /// 270 | /// let error: Error = ErrorKind::Foo.into(); 271 | /// let backtrace: &Backtrace = std::error::request_ref(&error).unwrap(); 272 | /// ``` 273 | /// 274 | /// [`Backtrace`]: std::backtrace::Backtrace 275 | /// [`provide`]: std::error::Error::provide 276 | #[proc_macro_derive(Box, attributes(thiserror_ext))] 277 | pub fn derive_box(input: TokenStream) -> TokenStream { 278 | let input = parse_macro_input!(input as DeriveInput); 279 | 280 | expand::derive_new_type(&input, DeriveNewType::Box) 281 | .unwrap_or_else(|err| err.to_compile_error()) 282 | .into() 283 | } 284 | 285 | /// Generates a new type that wraps the original error type in an [`Arc`]. 286 | /// 287 | /// Specify the name of the new type with `#[thiserror_ext(newtype(name = ..))]`. 288 | /// 289 | /// This is similar to [`thiserror_ext::Box`] but wraps the original error type 290 | /// in an [`Arc`], so that it can always be cloned and shared across threads. 291 | /// See [`thiserror_ext::Box`] for the explanation and examples. 292 | /// 293 | /// [`Arc`]: std::sync::Arc 294 | /// [`thiserror_ext::Box`]: derive@Box 295 | #[proc_macro_derive(Arc, attributes(thiserror_ext))] 296 | pub fn derive_arc(input: TokenStream) -> TokenStream { 297 | let input = parse_macro_input!(input as DeriveInput); 298 | 299 | expand::derive_new_type(&input, DeriveNewType::Arc) 300 | .unwrap_or_else(|err| err.to_compile_error()) 301 | .into() 302 | } 303 | 304 | /// Generates the [`Debug`] implementation that delegates to the [`Report`] of 305 | /// an error. 306 | /// 307 | /// Generally, the [`Debug`] representation of an error should not be used in 308 | /// user-facing scenarios. However, if [`Result::unwrap`] or [`Result::expect`] 309 | /// is called, or an error is used as [`Termination`], the standard library 310 | /// will format the error with [`Debug`]. By delegating to [`Report`], we ensure 311 | /// that the error is still formatted in a user-friendly way and the source 312 | /// chain can be kept in these cases. 313 | /// 314 | /// # Example 315 | /// ```ignore 316 | /// #[derive(thiserror::Error, thiserror_ext::ReportDebug)] 317 | /// #[error("inner")] 318 | /// struct Inner; 319 | /// 320 | /// #[derive(thiserror::Error, thiserror_ext::ReportDebug)] 321 | /// #[error("outer")] 322 | /// struct Outer { 323 | /// #[source] 324 | /// inner: Inner, 325 | /// } 326 | /// 327 | /// let error = Outer { inner: Inner }; 328 | /// println!("{:?}", error); 329 | /// ``` 330 | /// 331 | /// [`Report`]: thiserror_ext::Report 332 | /// [`Termination`]: std::process::Termination 333 | /// 334 | /// # New type 335 | /// 336 | /// Since the new type delegates its [`Debug`] implementation to the original 337 | /// error type, if the original error type derives [`ReportDebug`], the new type 338 | /// will also behave the same. 339 | #[proc_macro_derive(ReportDebug)] 340 | pub fn derive_report_debug(input: TokenStream) -> TokenStream { 341 | let input = parse_macro_input!(input as DeriveInput); 342 | 343 | expand::derive_report_debug(&input) 344 | .unwrap_or_else(|err| err.to_compile_error()) 345 | .into() 346 | } 347 | -------------------------------------------------------------------------------- /derive/src/thiserror/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /derive/src/thiserror/ast.rs: -------------------------------------------------------------------------------- 1 | use super::attr::{self, Attrs}; 2 | use super::generics::ParamsInScope; 3 | use super::unraw::{IdentUnraw, MemberUnraw}; 4 | use proc_macro2::Span; 5 | use std::fmt::{self, Display}; 6 | use syn::{ 7 | Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Generics, Ident, Index, Result, Type, 8 | }; 9 | 10 | pub enum Input<'a> { 11 | Struct(Struct<'a>), 12 | Enum(Enum<'a>), 13 | } 14 | 15 | pub struct Struct<'a> { 16 | pub original: &'a DeriveInput, 17 | pub attrs: Attrs<'a>, 18 | pub ident: Ident, 19 | pub generics: &'a Generics, 20 | pub fields: Vec>, 21 | } 22 | 23 | pub struct Enum<'a> { 24 | pub original: &'a DeriveInput, 25 | pub attrs: Attrs<'a>, 26 | pub ident: Ident, 27 | pub generics: &'a Generics, 28 | pub variants: Vec>, 29 | } 30 | 31 | pub struct Variant<'a> { 32 | pub original: &'a syn::Variant, 33 | pub attrs: Attrs<'a>, 34 | pub ident: Ident, 35 | pub fields: Vec>, 36 | } 37 | 38 | pub struct Field<'a> { 39 | pub original: &'a syn::Field, 40 | pub attrs: Attrs<'a>, 41 | pub member: MemberUnraw, 42 | pub ty: &'a Type, 43 | pub contains_generic: bool, 44 | } 45 | 46 | #[derive(Copy, Clone)] 47 | pub enum ContainerKind { 48 | Struct, 49 | TupleStruct, 50 | UnitStruct, 51 | StructVariant, 52 | TupleVariant, 53 | UnitVariant, 54 | } 55 | 56 | impl<'a> Input<'a> { 57 | pub fn from_syn(node: &'a DeriveInput) -> Result { 58 | match &node.data { 59 | Data::Struct(data) => Struct::from_syn(node, data).map(Input::Struct), 60 | Data::Enum(data) => Enum::from_syn(node, data).map(Input::Enum), 61 | Data::Union(_) => Err(Error::new_spanned( 62 | node, 63 | "union as errors are not supported", 64 | )), 65 | } 66 | } 67 | } 68 | 69 | impl<'a> Struct<'a> { 70 | fn from_syn(node: &'a DeriveInput, data: &'a DataStruct) -> Result { 71 | let mut attrs = attr::get(&node.attrs)?; 72 | let scope = ParamsInScope::new(&node.generics); 73 | let fields = Field::multiple_from_syn(&data.fields, &scope)?; 74 | if let Some(display) = &mut attrs.display { 75 | let container = ContainerKind::from_struct(data); 76 | display.expand_shorthand(&fields, container)?; 77 | } 78 | Ok(Struct { 79 | original: node, 80 | attrs, 81 | ident: node.ident.clone(), 82 | generics: &node.generics, 83 | fields, 84 | }) 85 | } 86 | } 87 | 88 | impl<'a> Enum<'a> { 89 | fn from_syn(node: &'a DeriveInput, data: &'a DataEnum) -> Result { 90 | let attrs = attr::get(&node.attrs)?; 91 | let scope = ParamsInScope::new(&node.generics); 92 | let variants = data 93 | .variants 94 | .iter() 95 | .map(|node| { 96 | let mut variant = Variant::from_syn(node, &scope)?; 97 | if variant.attrs.display.is_none() 98 | && variant.attrs.transparent.is_none() 99 | && variant.attrs.fmt.is_none() 100 | { 101 | variant.attrs.display.clone_from(&attrs.display); 102 | variant.attrs.transparent = attrs.transparent; 103 | variant.attrs.fmt.clone_from(&attrs.fmt); 104 | } 105 | if let Some(display) = &mut variant.attrs.display { 106 | let container = ContainerKind::from_variant(node); 107 | display.expand_shorthand(&variant.fields, container)?; 108 | } 109 | Ok(variant) 110 | }) 111 | .collect::>()?; 112 | Ok(Enum { 113 | original: node, 114 | attrs, 115 | ident: node.ident.clone(), 116 | generics: &node.generics, 117 | variants, 118 | }) 119 | } 120 | } 121 | 122 | impl<'a> Variant<'a> { 123 | fn from_syn(node: &'a syn::Variant, scope: &ParamsInScope<'a>) -> Result { 124 | let attrs = attr::get(&node.attrs)?; 125 | Ok(Variant { 126 | original: node, 127 | attrs, 128 | ident: node.ident.clone(), 129 | fields: Field::multiple_from_syn(&node.fields, scope)?, 130 | }) 131 | } 132 | } 133 | 134 | impl<'a> Field<'a> { 135 | fn multiple_from_syn(fields: &'a Fields, scope: &ParamsInScope<'a>) -> Result> { 136 | fields 137 | .iter() 138 | .enumerate() 139 | .map(|(i, field)| Field::from_syn(i, field, scope)) 140 | .collect() 141 | } 142 | 143 | fn from_syn(i: usize, node: &'a syn::Field, scope: &ParamsInScope<'a>) -> Result { 144 | Ok(Field { 145 | original: node, 146 | attrs: attr::get(&node.attrs)?, 147 | member: match &node.ident { 148 | Some(name) => MemberUnraw::Named(IdentUnraw::new(name.clone())), 149 | None => MemberUnraw::Unnamed(Index { 150 | index: i as u32, 151 | span: Span::call_site(), 152 | }), 153 | }, 154 | ty: &node.ty, 155 | contains_generic: scope.intersects(&node.ty), 156 | }) 157 | } 158 | } 159 | 160 | impl ContainerKind { 161 | fn from_struct(node: &DataStruct) -> Self { 162 | match node.fields { 163 | Fields::Named(_) => ContainerKind::Struct, 164 | Fields::Unnamed(_) => ContainerKind::TupleStruct, 165 | Fields::Unit => ContainerKind::UnitStruct, 166 | } 167 | } 168 | 169 | fn from_variant(node: &syn::Variant) -> Self { 170 | match node.fields { 171 | Fields::Named(_) => ContainerKind::StructVariant, 172 | Fields::Unnamed(_) => ContainerKind::TupleVariant, 173 | Fields::Unit => ContainerKind::UnitVariant, 174 | } 175 | } 176 | } 177 | 178 | impl Display for ContainerKind { 179 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 180 | formatter.write_str(match self { 181 | ContainerKind::Struct => "struct", 182 | ContainerKind::TupleStruct => "tuple struct", 183 | ContainerKind::UnitStruct => "unit struct", 184 | ContainerKind::StructVariant => "struct variant", 185 | ContainerKind::TupleVariant => "tuple variant", 186 | ContainerKind::UnitVariant => "unit variant", 187 | }) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /derive/src/thiserror/attr.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; 2 | use quote::{format_ident, quote, quote_spanned, ToTokens}; 3 | use std::collections::BTreeSet as Set; 4 | use syn::parse::discouraged::Speculative; 5 | use syn::parse::{End, ParseStream}; 6 | use syn::{ 7 | braced, bracketed, parenthesized, token, Attribute, Error, ExprPath, Ident, Index, LitFloat, 8 | LitInt, LitStr, Meta, Result, Token, 9 | }; 10 | 11 | pub struct Extra<'a> { 12 | pub message: Option<&'a Attribute>, 13 | pub construct_skip: Option<&'a Attribute>, 14 | pub context_into_skip: Option<&'a Attribute>, 15 | } 16 | 17 | pub struct Attrs<'a> { 18 | pub display: Option>, 19 | pub source: Option>, 20 | pub backtrace: Option<&'a Attribute>, 21 | pub from: Option>, 22 | pub transparent: Option>, 23 | pub fmt: Option>, 24 | pub extra: Extra<'a>, 25 | } 26 | 27 | #[derive(Clone)] 28 | pub struct Display<'a> { 29 | pub original: &'a Attribute, 30 | pub fmt: LitStr, 31 | pub args: TokenStream, 32 | pub requires_fmt_machinery: bool, 33 | pub has_bonus_display: bool, 34 | pub infinite_recursive: bool, 35 | pub implied_bounds: Set<(usize, Trait)>, 36 | pub bindings: Vec<(Ident, TokenStream)>, 37 | } 38 | 39 | #[derive(Copy, Clone)] 40 | pub struct Source<'a> { 41 | pub original: &'a Attribute, 42 | pub span: Span, 43 | } 44 | 45 | #[derive(Copy, Clone)] 46 | pub struct From<'a> { 47 | pub original: &'a Attribute, 48 | pub span: Span, 49 | } 50 | 51 | #[derive(Copy, Clone)] 52 | pub struct Transparent<'a> { 53 | pub original: &'a Attribute, 54 | pub span: Span, 55 | } 56 | 57 | #[derive(Clone)] 58 | pub struct Fmt<'a> { 59 | pub original: &'a Attribute, 60 | pub path: ExprPath, 61 | } 62 | 63 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] 64 | pub enum Trait { 65 | Debug, 66 | Display, 67 | Octal, 68 | LowerHex, 69 | UpperHex, 70 | Pointer, 71 | Binary, 72 | LowerExp, 73 | UpperExp, 74 | } 75 | 76 | pub fn get(input: &[Attribute]) -> Result { 77 | let mut attrs = Attrs { 78 | display: None, 79 | source: None, 80 | backtrace: None, 81 | from: None, 82 | transparent: None, 83 | fmt: None, 84 | extra: Extra { 85 | message: None, 86 | construct_skip: None, 87 | context_into_skip: None, 88 | }, 89 | }; 90 | 91 | for attr in input { 92 | if attr.path().is_ident("error") { 93 | parse_error_attribute(&mut attrs, attr)?; 94 | } else if attr.path().is_ident("source") { 95 | attr.meta.require_path_only()?; 96 | if attrs.source.is_some() { 97 | return Err(Error::new_spanned(attr, "duplicate #[source] attribute")); 98 | } 99 | let span = (attr.pound_token.span) 100 | .join(attr.bracket_token.span.join()) 101 | .unwrap_or(attr.path().get_ident().unwrap().span()); 102 | attrs.source = Some(Source { 103 | original: attr, 104 | span, 105 | }); 106 | } else if attr.path().is_ident("backtrace") { 107 | attr.meta.require_path_only()?; 108 | if attrs.backtrace.is_some() { 109 | return Err(Error::new_spanned(attr, "duplicate #[backtrace] attribute")); 110 | } 111 | attrs.backtrace = Some(attr); 112 | } else if attr.path().is_ident("from") { 113 | match attr.meta { 114 | Meta::Path(_) => {} 115 | Meta::List(_) | Meta::NameValue(_) => { 116 | // Assume this is meant for derive_more crate or something. 117 | continue; 118 | } 119 | } 120 | if attrs.from.is_some() { 121 | return Err(Error::new_spanned(attr, "duplicate #[from] attribute")); 122 | } 123 | let span = (attr.pound_token.span) 124 | .join(attr.bracket_token.span.join()) 125 | .unwrap_or(attr.path().get_ident().unwrap().span()); 126 | attrs.from = Some(From { 127 | original: attr, 128 | span, 129 | }); 130 | } else if attr.path().is_ident("message") { 131 | attr.meta.require_path_only()?; 132 | if attrs.extra.message.is_some() { 133 | return Err(Error::new_spanned(attr, "duplicate #[message] attribute")); 134 | } 135 | attrs.extra.message = Some(attr); 136 | } else if attr.path().is_ident("construct") { 137 | attr.parse_nested_meta(|meta| { 138 | if meta.path.is_ident("skip") { 139 | attrs.extra.construct_skip = Some(attr); 140 | Ok(()) 141 | } else { 142 | Err(Error::new_spanned(attr, "expected `skip`")) 143 | } 144 | })?; 145 | } else if attr.path().is_ident("context_into") { 146 | attr.parse_nested_meta(|meta| { 147 | if meta.path.is_ident("skip") { 148 | attrs.extra.context_into_skip = Some(attr); 149 | Ok(()) 150 | } else { 151 | Err(Error::new_spanned(attr, "expected `skip`")) 152 | } 153 | })?; 154 | } 155 | } 156 | 157 | Ok(attrs) 158 | } 159 | 160 | fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Result<()> { 161 | mod kw { 162 | syn::custom_keyword!(transparent); 163 | syn::custom_keyword!(fmt); 164 | } 165 | 166 | attr.parse_args_with(|input: ParseStream| { 167 | let lookahead = input.lookahead1(); 168 | let fmt = if lookahead.peek(LitStr) { 169 | input.parse::()? 170 | } else if lookahead.peek(kw::transparent) { 171 | let kw: kw::transparent = input.parse()?; 172 | if attrs.transparent.is_some() { 173 | return Err(Error::new_spanned( 174 | attr, 175 | "duplicate #[error(transparent)] attribute", 176 | )); 177 | } 178 | attrs.transparent = Some(Transparent { 179 | original: attr, 180 | span: kw.span, 181 | }); 182 | return Ok(()); 183 | } else if lookahead.peek(kw::fmt) { 184 | input.parse::()?; 185 | input.parse::()?; 186 | let path: ExprPath = input.parse()?; 187 | if attrs.fmt.is_some() { 188 | return Err(Error::new_spanned( 189 | attr, 190 | "duplicate #[error(fmt = ...)] attribute", 191 | )); 192 | } 193 | attrs.fmt = Some(Fmt { 194 | original: attr, 195 | path, 196 | }); 197 | return Ok(()); 198 | } else { 199 | return Err(lookahead.error()); 200 | }; 201 | 202 | let args = if input.is_empty() || input.peek(Token![,]) && input.peek2(End) { 203 | input.parse::>()?; 204 | TokenStream::new() 205 | } else { 206 | parse_token_expr(input, false)? 207 | }; 208 | 209 | let requires_fmt_machinery = !args.is_empty(); 210 | 211 | let display = Display { 212 | original: attr, 213 | fmt, 214 | args, 215 | requires_fmt_machinery, 216 | has_bonus_display: false, 217 | infinite_recursive: false, 218 | implied_bounds: Set::new(), 219 | bindings: Vec::new(), 220 | }; 221 | if attrs.display.is_some() { 222 | return Err(Error::new_spanned( 223 | attr, 224 | "only one #[error(...)] attribute is allowed", 225 | )); 226 | } 227 | attrs.display = Some(display); 228 | Ok(()) 229 | }) 230 | } 231 | 232 | fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result { 233 | let mut tokens = Vec::new(); 234 | while !input.is_empty() { 235 | if input.peek(token::Group) { 236 | let group: TokenTree = input.parse()?; 237 | tokens.push(group); 238 | begin_expr = false; 239 | continue; 240 | } 241 | 242 | if begin_expr && input.peek(Token![.]) { 243 | if input.peek2(Ident) { 244 | input.parse::()?; 245 | begin_expr = false; 246 | continue; 247 | } else if input.peek2(LitInt) { 248 | input.parse::()?; 249 | let int: Index = input.parse()?; 250 | tokens.push({ 251 | let ident = format_ident!("_{}", int.index, span = int.span); 252 | TokenTree::Ident(ident) 253 | }); 254 | begin_expr = false; 255 | continue; 256 | } else if input.peek2(LitFloat) { 257 | let ahead = input.fork(); 258 | ahead.parse::()?; 259 | let float: LitFloat = ahead.parse()?; 260 | let repr = float.to_string(); 261 | let mut indices = repr.split('.').map(syn::parse_str::); 262 | if let (Some(Ok(first)), Some(Ok(second)), None) = 263 | (indices.next(), indices.next(), indices.next()) 264 | { 265 | input.advance_to(&ahead); 266 | tokens.push({ 267 | let ident = format_ident!("_{}", first, span = float.span()); 268 | TokenTree::Ident(ident) 269 | }); 270 | tokens.push({ 271 | let mut punct = Punct::new('.', Spacing::Alone); 272 | punct.set_span(float.span()); 273 | TokenTree::Punct(punct) 274 | }); 275 | tokens.push({ 276 | let mut literal = Literal::u32_unsuffixed(second.index); 277 | literal.set_span(float.span()); 278 | TokenTree::Literal(literal) 279 | }); 280 | begin_expr = false; 281 | continue; 282 | } 283 | } 284 | } 285 | 286 | begin_expr = input.peek(Token![break]) 287 | || input.peek(Token![continue]) 288 | || input.peek(Token![if]) 289 | || input.peek(Token![in]) 290 | || input.peek(Token![match]) 291 | || input.peek(Token![mut]) 292 | || input.peek(Token![return]) 293 | || input.peek(Token![while]) 294 | || input.peek(Token![+]) 295 | || input.peek(Token![&]) 296 | || input.peek(Token![!]) 297 | || input.peek(Token![^]) 298 | || input.peek(Token![,]) 299 | || input.peek(Token![/]) 300 | || input.peek(Token![=]) 301 | || input.peek(Token![>]) 302 | || input.peek(Token![<]) 303 | || input.peek(Token![|]) 304 | || input.peek(Token![%]) 305 | || input.peek(Token![;]) 306 | || input.peek(Token![*]) 307 | || input.peek(Token![-]); 308 | 309 | let token: TokenTree = if input.peek(token::Paren) { 310 | let content; 311 | let delimiter = parenthesized!(content in input); 312 | let nested = parse_token_expr(&content, true)?; 313 | let mut group = Group::new(Delimiter::Parenthesis, nested); 314 | group.set_span(delimiter.span.join()); 315 | TokenTree::Group(group) 316 | } else if input.peek(token::Brace) { 317 | let content; 318 | let delimiter = braced!(content in input); 319 | let nested = parse_token_expr(&content, true)?; 320 | let mut group = Group::new(Delimiter::Brace, nested); 321 | group.set_span(delimiter.span.join()); 322 | TokenTree::Group(group) 323 | } else if input.peek(token::Bracket) { 324 | let content; 325 | let delimiter = bracketed!(content in input); 326 | let nested = parse_token_expr(&content, true)?; 327 | let mut group = Group::new(Delimiter::Bracket, nested); 328 | group.set_span(delimiter.span.join()); 329 | TokenTree::Group(group) 330 | } else { 331 | input.parse()? 332 | }; 333 | tokens.push(token); 334 | } 335 | Ok(TokenStream::from_iter(tokens)) 336 | } 337 | 338 | impl ToTokens for Display<'_> { 339 | fn to_tokens(&self, tokens: &mut TokenStream) { 340 | if self.infinite_recursive { 341 | let span = self.fmt.span(); 342 | tokens.extend(quote_spanned! {span=> 343 | #[warn(unconditional_recursion)] 344 | fn _fmt() { _fmt() } 345 | }); 346 | } 347 | 348 | let fmt = &self.fmt; 349 | let args = &self.args; 350 | 351 | // Currently `write!(f, "text")` produces less efficient code than 352 | // `f.write_str("text")`. We recognize the case when the format string 353 | // has no braces and no interpolated values, and generate simpler code. 354 | let write = if self.requires_fmt_machinery { 355 | quote! { 356 | ::core::write!(__formatter, #fmt #args) 357 | } 358 | } else { 359 | quote! { 360 | __formatter.write_str(#fmt) 361 | } 362 | }; 363 | 364 | tokens.extend(if self.bindings.is_empty() { 365 | write 366 | } else { 367 | let locals = self.bindings.iter().map(|(local, _value)| local); 368 | let values = self.bindings.iter().map(|(_local, value)| value); 369 | quote! { 370 | match (#(#values,)*) { 371 | (#(#locals,)*) => #write 372 | } 373 | } 374 | }); 375 | } 376 | } 377 | 378 | impl ToTokens for Trait { 379 | fn to_tokens(&self, tokens: &mut TokenStream) { 380 | let trait_name = match self { 381 | Trait::Debug => "Debug", 382 | Trait::Display => "Display", 383 | Trait::Octal => "Octal", 384 | Trait::LowerHex => "LowerHex", 385 | Trait::UpperHex => "UpperHex", 386 | Trait::Pointer => "Pointer", 387 | Trait::Binary => "Binary", 388 | Trait::LowerExp => "LowerExp", 389 | Trait::UpperExp => "UpperExp", 390 | }; 391 | let ident = Ident::new(trait_name, Span::call_site()); 392 | tokens.extend(quote!(::core::fmt::#ident)); 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /derive/src/thiserror/fmt.rs: -------------------------------------------------------------------------------- 1 | use super::ast::{ContainerKind, Field}; 2 | use super::attr::{Display, Trait}; 3 | use super::scan_expr::scan_expr; 4 | use super::unraw::{IdentUnraw, MemberUnraw}; 5 | use proc_macro2::{Delimiter, TokenStream, TokenTree}; 6 | use quote::{format_ident, quote, quote_spanned, ToTokens as _}; 7 | use std::collections::{BTreeSet, HashMap}; 8 | use std::iter; 9 | use syn::ext::IdentExt; 10 | use syn::parse::discouraged::Speculative; 11 | use syn::parse::{Error, ParseStream, Parser, Result}; 12 | use syn::{Expr, Ident, Index, LitStr, Token}; 13 | 14 | impl Display<'_> { 15 | pub fn expand_shorthand(&mut self, fields: &[Field], container: ContainerKind) -> Result<()> { 16 | let raw_args = self.args.clone(); 17 | let FmtArguments { 18 | named: user_named_args, 19 | first_unnamed, 20 | } = explicit_named_args.parse2(raw_args).unwrap(); 21 | 22 | let mut member_index = HashMap::new(); 23 | let mut extra_positional_arguments_allowed = true; 24 | for (i, field) in fields.iter().enumerate() { 25 | member_index.insert(&field.member, i); 26 | extra_positional_arguments_allowed &= matches!(&field.member, MemberUnraw::Named(_)); 27 | } 28 | 29 | let span = self.fmt.span(); 30 | let fmt = self.fmt.value(); 31 | let mut read = fmt.as_str(); 32 | let mut out = String::new(); 33 | let mut has_bonus_display = false; 34 | let mut infinite_recursive = false; 35 | let mut implied_bounds = BTreeSet::new(); 36 | let mut bindings = Vec::new(); 37 | let mut macro_named_args = BTreeSet::new(); 38 | 39 | self.requires_fmt_machinery = self.requires_fmt_machinery || fmt.contains('}'); 40 | 41 | while let Some(brace) = read.find('{') { 42 | self.requires_fmt_machinery = true; 43 | out += &read[..brace + 1]; 44 | read = &read[brace + 1..]; 45 | if read.starts_with('{') { 46 | out.push('{'); 47 | read = &read[1..]; 48 | continue; 49 | } 50 | let next = match read.chars().next() { 51 | Some(next) => next, 52 | None => return Ok(()), 53 | }; 54 | let member = match next { 55 | '0'..='9' => { 56 | let int = take_int(&mut read); 57 | if !extra_positional_arguments_allowed { 58 | if let Some(first_unnamed) = &first_unnamed { 59 | let msg = format!("ambiguous reference to positional arguments by number in a {container}; change this to a named argument"); 60 | return Err(Error::new_spanned(first_unnamed, msg)); 61 | } 62 | } 63 | match int.parse::() { 64 | Ok(index) => MemberUnraw::Unnamed(Index { index, span }), 65 | Err(_) => return Ok(()), 66 | } 67 | } 68 | 'a'..='z' | 'A'..='Z' | '_' => { 69 | if read.starts_with("r#") { 70 | continue; 71 | } 72 | let repr = take_ident(&mut read); 73 | if repr == "_" { 74 | // Invalid. Let rustc produce the diagnostic. 75 | out += repr; 76 | continue; 77 | } 78 | let ident = IdentUnraw::new(Ident::new(repr, span)); 79 | if user_named_args.contains(&ident) { 80 | // Refers to a named argument written by the user, not to field. 81 | out += repr; 82 | continue; 83 | } 84 | MemberUnraw::Named(ident) 85 | } 86 | _ => continue, 87 | }; 88 | let end_spec = match read.find('}') { 89 | Some(end_spec) => end_spec, 90 | None => return Ok(()), 91 | }; 92 | let mut bonus_display = false; 93 | let bound = match read[..end_spec].chars().next_back() { 94 | Some('?') => Trait::Debug, 95 | Some('o') => Trait::Octal, 96 | Some('x') => Trait::LowerHex, 97 | Some('X') => Trait::UpperHex, 98 | Some('p') => Trait::Pointer, 99 | Some('b') => Trait::Binary, 100 | Some('e') => Trait::LowerExp, 101 | Some('E') => Trait::UpperExp, 102 | Some(_) => Trait::Display, 103 | None => { 104 | bonus_display = true; 105 | has_bonus_display = true; 106 | Trait::Display 107 | } 108 | }; 109 | infinite_recursive |= member == *"self" && bound == Trait::Display; 110 | let field = match member_index.get(&member) { 111 | Some(&field) => field, 112 | None => { 113 | out += &member.to_string(); 114 | continue; 115 | } 116 | }; 117 | implied_bounds.insert((field, bound)); 118 | let formatvar_prefix = if bonus_display { 119 | "__display" 120 | } else if bound == Trait::Pointer { 121 | "__pointer" 122 | } else { 123 | "__field" 124 | }; 125 | let mut formatvar = IdentUnraw::new(match &member { 126 | MemberUnraw::Unnamed(index) => format_ident!("{}{}", formatvar_prefix, index), 127 | MemberUnraw::Named(ident) => { 128 | format_ident!("{}_{}", formatvar_prefix, ident.to_string()) 129 | } 130 | }); 131 | while user_named_args.contains(&formatvar) { 132 | formatvar = IdentUnraw::new(format_ident!("_{}", formatvar.to_string())); 133 | } 134 | formatvar.set_span(span); 135 | out += &formatvar.to_string(); 136 | if !macro_named_args.insert(formatvar.clone()) { 137 | // Already added to bindings by a previous use. 138 | continue; 139 | } 140 | let mut binding_value = match &member { 141 | MemberUnraw::Unnamed(index) => format_ident!("_{}", index), 142 | MemberUnraw::Named(ident) => ident.to_local(), 143 | }; 144 | binding_value.set_span(span.resolved_at(fields[field].member.span())); 145 | let wrapped_binding_value = if bonus_display { 146 | quote_spanned!(span=> #binding_value.as_display()) 147 | } else if bound == Trait::Pointer { 148 | quote!(::thiserror::__private::Var(#binding_value)) 149 | } else { 150 | binding_value.into_token_stream() 151 | }; 152 | bindings.push((formatvar.to_local(), wrapped_binding_value)); 153 | } 154 | 155 | out += read; 156 | self.fmt = LitStr::new(&out, self.fmt.span()); 157 | self.has_bonus_display = has_bonus_display; 158 | self.infinite_recursive = infinite_recursive; 159 | self.implied_bounds = implied_bounds; 160 | self.bindings = bindings; 161 | Ok(()) 162 | } 163 | } 164 | 165 | struct FmtArguments { 166 | named: BTreeSet, 167 | first_unnamed: Option, 168 | } 169 | 170 | #[allow(clippy::unnecessary_wraps)] 171 | fn explicit_named_args(input: ParseStream) -> Result { 172 | let ahead = input.fork(); 173 | if let Ok(set) = try_explicit_named_args(&ahead) { 174 | input.advance_to(&ahead); 175 | return Ok(set); 176 | } 177 | 178 | let ahead = input.fork(); 179 | if let Ok(set) = fallback_explicit_named_args(&ahead) { 180 | input.advance_to(&ahead); 181 | return Ok(set); 182 | } 183 | 184 | input.parse::().unwrap(); 185 | Ok(FmtArguments { 186 | named: BTreeSet::new(), 187 | first_unnamed: None, 188 | }) 189 | } 190 | 191 | fn try_explicit_named_args(input: ParseStream) -> Result { 192 | let mut syn_full = None; 193 | let mut args = FmtArguments { 194 | named: BTreeSet::new(), 195 | first_unnamed: None, 196 | }; 197 | 198 | while !input.is_empty() { 199 | input.parse::()?; 200 | if input.is_empty() { 201 | break; 202 | } 203 | 204 | let mut begin_unnamed = None; 205 | if input.peek(Ident::peek_any) && input.peek2(Token![=]) && !input.peek2(Token![==]) { 206 | let ident: IdentUnraw = input.parse()?; 207 | input.parse::()?; 208 | args.named.insert(ident); 209 | } else { 210 | begin_unnamed = Some(input.fork()); 211 | } 212 | 213 | let ahead = input.fork(); 214 | if *syn_full.get_or_insert_with(is_syn_full) && ahead.parse::().is_ok() { 215 | input.advance_to(&ahead); 216 | } else { 217 | scan_expr(input)?; 218 | } 219 | 220 | if let Some(begin_unnamed) = begin_unnamed { 221 | if args.first_unnamed.is_none() { 222 | args.first_unnamed = Some(between(&begin_unnamed, input)); 223 | } 224 | } 225 | } 226 | 227 | Ok(args) 228 | } 229 | 230 | fn fallback_explicit_named_args(input: ParseStream) -> Result { 231 | let mut args = FmtArguments { 232 | named: BTreeSet::new(), 233 | first_unnamed: None, 234 | }; 235 | 236 | while !input.is_empty() { 237 | if input.peek(Token![,]) 238 | && input.peek2(Ident::peek_any) 239 | && input.peek3(Token![=]) 240 | && !input.peek3(Token![==]) 241 | { 242 | input.parse::()?; 243 | let ident: IdentUnraw = input.parse()?; 244 | input.parse::()?; 245 | args.named.insert(ident); 246 | } else { 247 | input.parse::()?; 248 | } 249 | } 250 | 251 | Ok(args) 252 | } 253 | 254 | fn is_syn_full() -> bool { 255 | // Expr::Block contains syn::Block which contains Vec. In the 256 | // current version of Syn, syn::Stmt is exhaustive and could only plausibly 257 | // represent `trait Trait {}` in Stmt::Item which contains syn::Item. Most 258 | // of the point of syn's non-"full" mode is to avoid compiling Item and the 259 | // entire expansive syntax tree it comprises. So the following expression 260 | // being parsed to Expr::Block is a reliable indication that "full" is 261 | // enabled. 262 | let test = quote!({ 263 | trait Trait {} 264 | }); 265 | match syn::parse2(test) { 266 | Ok(Expr::Verbatim(_)) | Err(_) => false, 267 | Ok(Expr::Block(_)) => true, 268 | Ok(_) => unreachable!(), 269 | } 270 | } 271 | 272 | fn take_int<'a>(read: &mut &'a str) -> &'a str { 273 | let mut int_len = 0; 274 | for ch in read.chars() { 275 | match ch { 276 | '0'..='9' => int_len += 1, 277 | _ => break, 278 | } 279 | } 280 | let (int, rest) = read.split_at(int_len); 281 | *read = rest; 282 | int 283 | } 284 | 285 | fn take_ident<'a>(read: &mut &'a str) -> &'a str { 286 | let mut ident_len = 0; 287 | for ch in read.chars() { 288 | match ch { 289 | 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident_len += 1, 290 | _ => break, 291 | } 292 | } 293 | let (ident, rest) = read.split_at(ident_len); 294 | *read = rest; 295 | ident 296 | } 297 | 298 | fn between<'a>(begin: ParseStream<'a>, end: ParseStream<'a>) -> TokenStream { 299 | let end = end.cursor(); 300 | let mut cursor = begin.cursor(); 301 | let mut tokens = TokenStream::new(); 302 | 303 | while cursor < end { 304 | let (tt, next) = cursor.token_tree().unwrap(); 305 | 306 | if end < next { 307 | if let Some((inside, _span, _after)) = cursor.group(Delimiter::None) { 308 | cursor = inside; 309 | continue; 310 | } 311 | if tokens.is_empty() { 312 | tokens.extend(iter::once(tt)); 313 | } 314 | break; 315 | } 316 | 317 | tokens.extend(iter::once(tt)); 318 | cursor = next; 319 | } 320 | 321 | tokens 322 | } 323 | -------------------------------------------------------------------------------- /derive/src/thiserror/generics.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::ToTokens; 3 | use std::collections::btree_map::Entry; 4 | use std::collections::{BTreeMap as Map, BTreeSet as Set}; 5 | use syn::punctuated::Punctuated; 6 | use syn::{parse_quote, GenericArgument, Generics, Ident, PathArguments, Token, Type, WhereClause}; 7 | 8 | pub struct ParamsInScope<'a> { 9 | names: Set<&'a Ident>, 10 | } 11 | 12 | impl<'a> ParamsInScope<'a> { 13 | pub fn new(generics: &'a Generics) -> Self { 14 | ParamsInScope { 15 | names: generics.type_params().map(|param| ¶m.ident).collect(), 16 | } 17 | } 18 | 19 | pub fn intersects(&self, ty: &Type) -> bool { 20 | let mut found = false; 21 | crawl(self, ty, &mut found); 22 | found 23 | } 24 | } 25 | 26 | fn crawl(in_scope: &ParamsInScope, ty: &Type, found: &mut bool) { 27 | if let Type::Path(ty) = ty { 28 | if let Some(qself) = &ty.qself { 29 | crawl(in_scope, &qself.ty, found); 30 | } else { 31 | let front = ty.path.segments.first().unwrap(); 32 | if front.arguments.is_none() && in_scope.names.contains(&front.ident) { 33 | *found = true; 34 | } 35 | } 36 | for segment in &ty.path.segments { 37 | if let PathArguments::AngleBracketed(arguments) = &segment.arguments { 38 | for arg in &arguments.args { 39 | if let GenericArgument::Type(ty) = arg { 40 | crawl(in_scope, ty, found); 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | pub struct InferredBounds { 49 | bounds: Map, Punctuated)>, 50 | order: Vec, 51 | } 52 | 53 | impl InferredBounds { 54 | pub fn new() -> Self { 55 | InferredBounds { 56 | bounds: Map::new(), 57 | order: Vec::new(), 58 | } 59 | } 60 | 61 | pub fn insert(&mut self, ty: impl ToTokens, bound: impl ToTokens) { 62 | let ty = ty.to_token_stream(); 63 | let bound = bound.to_token_stream(); 64 | let entry = self.bounds.entry(ty.to_string()); 65 | if let Entry::Vacant(_) = entry { 66 | self.order.push(ty); 67 | } 68 | let (set, tokens) = entry.or_default(); 69 | if set.insert(bound.to_string()) { 70 | tokens.push(bound); 71 | } 72 | } 73 | 74 | pub fn augment_where_clause(&self, generics: &Generics) -> WhereClause { 75 | let mut generics = generics.clone(); 76 | let where_clause = generics.make_where_clause(); 77 | for ty in &self.order { 78 | let (_set, bounds) = &self.bounds[&ty.to_string()]; 79 | where_clause.predicates.push(parse_quote!(#ty: #bounds)); 80 | } 81 | generics.where_clause.unwrap() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /derive/src/thiserror/mod.rs: -------------------------------------------------------------------------------- 1 | // The code in this module is copied from 2 | // https://github.com/dtolnay/thiserror/blob/89fb343a23b356845fce5098a095b419b94d1c88/impl/src/. 3 | // 4 | // See `LICENSE` for the original license. 5 | 6 | #![allow(dead_code)] 7 | #![allow(clippy::all)] 8 | 9 | pub mod ast; 10 | pub mod attr; 11 | pub mod fmt; 12 | pub mod generics; 13 | pub mod props; 14 | pub mod scan_expr; 15 | pub mod unraw; 16 | -------------------------------------------------------------------------------- /derive/src/thiserror/props.rs: -------------------------------------------------------------------------------- 1 | use super::ast::{Enum, Field, Struct, Variant}; 2 | use super::unraw::MemberUnraw; 3 | use proc_macro2::Span; 4 | use syn::Type; 5 | 6 | impl Struct<'_> { 7 | pub(crate) fn from_field(&self) -> Option<&Field> { 8 | from_field(&self.fields) 9 | } 10 | 11 | pub(crate) fn source_field(&self) -> Option<&Field> { 12 | source_field(&self.fields) 13 | } 14 | 15 | pub(crate) fn backtrace_field(&self) -> Option<&Field> { 16 | backtrace_field(&self.fields) 17 | } 18 | 19 | pub(crate) fn message_field(&self) -> Option<&Field> { 20 | message_field(&self.fields) 21 | } 22 | 23 | pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> { 24 | let backtrace_field = self.backtrace_field()?; 25 | distinct_backtrace_field(backtrace_field, self.from_field()) 26 | } 27 | } 28 | 29 | impl Enum<'_> { 30 | pub(crate) fn has_source(&self) -> bool { 31 | self.variants 32 | .iter() 33 | .any(|variant| variant.source_field().is_some() || variant.attrs.transparent.is_some()) 34 | } 35 | 36 | pub(crate) fn has_backtrace(&self) -> bool { 37 | self.variants 38 | .iter() 39 | .any(|variant| variant.backtrace_field().is_some()) 40 | } 41 | 42 | pub(crate) fn has_display(&self) -> bool { 43 | self.attrs.display.is_some() 44 | || self.attrs.transparent.is_some() 45 | || self.attrs.fmt.is_some() 46 | || self 47 | .variants 48 | .iter() 49 | .any(|variant| variant.attrs.display.is_some() || variant.attrs.fmt.is_some()) 50 | || self 51 | .variants 52 | .iter() 53 | .all(|variant| variant.attrs.transparent.is_some()) 54 | } 55 | } 56 | 57 | impl Variant<'_> { 58 | pub(crate) fn from_field(&self) -> Option<&Field> { 59 | from_field(&self.fields) 60 | } 61 | 62 | pub(crate) fn source_field(&self) -> Option<&Field> { 63 | source_field(&self.fields) 64 | } 65 | 66 | pub(crate) fn backtrace_field(&self) -> Option<&Field> { 67 | backtrace_field(&self.fields) 68 | } 69 | 70 | pub(crate) fn message_field(&self) -> Option<&Field> { 71 | message_field(&self.fields) 72 | } 73 | 74 | pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> { 75 | let backtrace_field = self.backtrace_field()?; 76 | distinct_backtrace_field(backtrace_field, self.from_field()) 77 | } 78 | } 79 | 80 | impl Field<'_> { 81 | pub(crate) fn is_backtrace(&self) -> bool { 82 | type_is_backtrace(self.ty) 83 | } 84 | 85 | /// Whether this field is the `source` field but not the `from` field. 86 | /// 87 | /// See [`Variant::source_field`]. 88 | pub(crate) fn is_non_from_source(&self) -> bool { 89 | if self.attrs.from.is_some() { 90 | false 91 | } else if self.attrs.source.is_some() { 92 | true 93 | } else if matches!(&self.member, MemberUnraw::Named(ident) if ident == "source") { 94 | true 95 | } else { 96 | false 97 | } 98 | } 99 | 100 | /// Whether this field is the `message` field. 101 | pub(crate) fn is_message(&self) -> bool { 102 | if self.attrs.extra.message.is_some() { 103 | true 104 | } else if matches!( 105 | &self.member, 106 | MemberUnraw::Named(ident) if ident == "message" 107 | ) { 108 | true 109 | } else { 110 | false 111 | } 112 | } 113 | 114 | pub(crate) fn source_span(&self) -> Span { 115 | if let Some(source_attr) = &self.attrs.source { 116 | source_attr.span 117 | } else if let Some(from_attr) = &self.attrs.from { 118 | from_attr.span 119 | } else { 120 | self.member.span() 121 | } 122 | } 123 | } 124 | 125 | fn from_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { 126 | for field in fields { 127 | if field.attrs.from.is_some() { 128 | return Some(field); 129 | } 130 | } 131 | None 132 | } 133 | 134 | fn source_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { 135 | for field in fields { 136 | if field.attrs.from.is_some() || field.attrs.source.is_some() { 137 | return Some(field); 138 | } 139 | } 140 | for field in fields { 141 | match &field.member { 142 | MemberUnraw::Named(ident) if ident == "source" => return Some(field), 143 | _ => {} 144 | } 145 | } 146 | None 147 | } 148 | 149 | fn backtrace_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { 150 | for field in fields { 151 | if field.attrs.backtrace.is_some() { 152 | return Some(field); 153 | } 154 | } 155 | for field in fields { 156 | if field.is_backtrace() { 157 | return Some(field); 158 | } 159 | } 160 | None 161 | } 162 | 163 | // The #[backtrace] field, if it is not the same as the #[from] field. 164 | fn distinct_backtrace_field<'a, 'b>( 165 | backtrace_field: &'a Field<'b>, 166 | from_field: Option<&Field>, 167 | ) -> Option<&'a Field<'b>> { 168 | if from_field.map_or(false, |from_field| { 169 | from_field.member == backtrace_field.member 170 | }) { 171 | None 172 | } else { 173 | Some(backtrace_field) 174 | } 175 | } 176 | 177 | fn message_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { 178 | for field in fields { 179 | if field.is_message() { 180 | return Some(field); 181 | } 182 | } 183 | None 184 | } 185 | 186 | fn type_is_backtrace(ty: &Type) -> bool { 187 | let path = match ty { 188 | Type::Path(ty) => &ty.path, 189 | _ => return false, 190 | }; 191 | 192 | let last = path.segments.last().unwrap(); 193 | last.ident == "Backtrace" && last.arguments.is_empty() 194 | } 195 | -------------------------------------------------------------------------------- /derive/src/thiserror/scan_expr.rs: -------------------------------------------------------------------------------- 1 | use self::{Action::*, Input::*}; 2 | use proc_macro2::{Delimiter, Ident, Spacing, TokenTree}; 3 | use syn::parse::{ParseStream, Result}; 4 | use syn::{AngleBracketedGenericArguments, BinOp, Expr, ExprPath, Lifetime, Lit, Token, Type}; 5 | 6 | enum Input { 7 | Keyword(&'static str), 8 | Punct(&'static str), 9 | ConsumeAny, 10 | ConsumeBinOp, 11 | ConsumeBrace, 12 | ConsumeDelimiter, 13 | ConsumeIdent, 14 | ConsumeLifetime, 15 | ConsumeLiteral, 16 | ConsumeNestedBrace, 17 | ExpectPath, 18 | ExpectTurbofish, 19 | ExpectType, 20 | CanBeginExpr, 21 | Otherwise, 22 | Empty, 23 | } 24 | 25 | enum Action { 26 | SetState(&'static [(Input, Action)]), 27 | IncDepth, 28 | DecDepth, 29 | Finish, 30 | } 31 | 32 | static INIT: [(Input, Action); 28] = [ 33 | (ConsumeDelimiter, SetState(&POSTFIX)), 34 | (Keyword("async"), SetState(&ASYNC)), 35 | (Keyword("break"), SetState(&BREAK_LABEL)), 36 | (Keyword("const"), SetState(&CONST)), 37 | (Keyword("continue"), SetState(&CONTINUE)), 38 | (Keyword("for"), SetState(&FOR)), 39 | (Keyword("if"), IncDepth), 40 | (Keyword("let"), SetState(&PATTERN)), 41 | (Keyword("loop"), SetState(&BLOCK)), 42 | (Keyword("match"), IncDepth), 43 | (Keyword("move"), SetState(&CLOSURE)), 44 | (Keyword("return"), SetState(&RETURN)), 45 | (Keyword("static"), SetState(&CLOSURE)), 46 | (Keyword("unsafe"), SetState(&BLOCK)), 47 | (Keyword("while"), IncDepth), 48 | (Keyword("yield"), SetState(&RETURN)), 49 | (Keyword("_"), SetState(&POSTFIX)), 50 | (Punct("!"), SetState(&INIT)), 51 | (Punct("#"), SetState(&[(ConsumeDelimiter, SetState(&INIT))])), 52 | (Punct("&"), SetState(&REFERENCE)), 53 | (Punct("*"), SetState(&INIT)), 54 | (Punct("-"), SetState(&INIT)), 55 | (Punct("..="), SetState(&INIT)), 56 | (Punct(".."), SetState(&RANGE)), 57 | (Punct("|"), SetState(&CLOSURE_ARGS)), 58 | (ConsumeLifetime, SetState(&[(Punct(":"), SetState(&INIT))])), 59 | (ConsumeLiteral, SetState(&POSTFIX)), 60 | (ExpectPath, SetState(&PATH)), 61 | ]; 62 | 63 | static POSTFIX: [(Input, Action); 10] = [ 64 | (Keyword("as"), SetState(&[(ExpectType, SetState(&POSTFIX))])), 65 | (Punct("..="), SetState(&INIT)), 66 | (Punct(".."), SetState(&RANGE)), 67 | (Punct("."), SetState(&DOT)), 68 | (Punct("?"), SetState(&POSTFIX)), 69 | (ConsumeBinOp, SetState(&INIT)), 70 | (Punct("="), SetState(&INIT)), 71 | (ConsumeNestedBrace, SetState(&IF_THEN)), 72 | (ConsumeDelimiter, SetState(&POSTFIX)), 73 | (Empty, Finish), 74 | ]; 75 | 76 | static ASYNC: [(Input, Action); 3] = [ 77 | (Keyword("move"), SetState(&ASYNC)), 78 | (Punct("|"), SetState(&CLOSURE_ARGS)), 79 | (ConsumeBrace, SetState(&POSTFIX)), 80 | ]; 81 | 82 | static BLOCK: [(Input, Action); 1] = [(ConsumeBrace, SetState(&POSTFIX))]; 83 | 84 | static BREAK_LABEL: [(Input, Action); 2] = [ 85 | (ConsumeLifetime, SetState(&BREAK_VALUE)), 86 | (Otherwise, SetState(&BREAK_VALUE)), 87 | ]; 88 | 89 | static BREAK_VALUE: [(Input, Action); 3] = [ 90 | (ConsumeNestedBrace, SetState(&IF_THEN)), 91 | (CanBeginExpr, SetState(&INIT)), 92 | (Otherwise, SetState(&POSTFIX)), 93 | ]; 94 | 95 | static CLOSURE: [(Input, Action); 6] = [ 96 | (Keyword("async"), SetState(&CLOSURE)), 97 | (Keyword("move"), SetState(&CLOSURE)), 98 | (Punct(","), SetState(&CLOSURE)), 99 | (Punct(">"), SetState(&CLOSURE)), 100 | (Punct("|"), SetState(&CLOSURE_ARGS)), 101 | (ConsumeLifetime, SetState(&CLOSURE)), 102 | ]; 103 | 104 | static CLOSURE_ARGS: [(Input, Action); 2] = [ 105 | (Punct("|"), SetState(&CLOSURE_RET)), 106 | (ConsumeAny, SetState(&CLOSURE_ARGS)), 107 | ]; 108 | 109 | static CLOSURE_RET: [(Input, Action); 2] = [ 110 | (Punct("->"), SetState(&[(ExpectType, SetState(&BLOCK))])), 111 | (Otherwise, SetState(&INIT)), 112 | ]; 113 | 114 | static CONST: [(Input, Action); 2] = [ 115 | (Punct("|"), SetState(&CLOSURE_ARGS)), 116 | (ConsumeBrace, SetState(&POSTFIX)), 117 | ]; 118 | 119 | static CONTINUE: [(Input, Action); 2] = [ 120 | (ConsumeLifetime, SetState(&POSTFIX)), 121 | (Otherwise, SetState(&POSTFIX)), 122 | ]; 123 | 124 | static DOT: [(Input, Action); 3] = [ 125 | (Keyword("await"), SetState(&POSTFIX)), 126 | (ConsumeIdent, SetState(&METHOD)), 127 | (ConsumeLiteral, SetState(&POSTFIX)), 128 | ]; 129 | 130 | static FOR: [(Input, Action); 2] = [ 131 | (Punct("<"), SetState(&CLOSURE)), 132 | (Otherwise, SetState(&PATTERN)), 133 | ]; 134 | 135 | static IF_ELSE: [(Input, Action); 2] = [(Keyword("if"), SetState(&INIT)), (ConsumeBrace, DecDepth)]; 136 | static IF_THEN: [(Input, Action); 2] = 137 | [(Keyword("else"), SetState(&IF_ELSE)), (Otherwise, DecDepth)]; 138 | 139 | static METHOD: [(Input, Action); 1] = [(ExpectTurbofish, SetState(&POSTFIX))]; 140 | 141 | static PATH: [(Input, Action); 4] = [ 142 | (Punct("!="), SetState(&INIT)), 143 | (Punct("!"), SetState(&INIT)), 144 | (ConsumeNestedBrace, SetState(&IF_THEN)), 145 | (Otherwise, SetState(&POSTFIX)), 146 | ]; 147 | 148 | static PATTERN: [(Input, Action); 15] = [ 149 | (ConsumeDelimiter, SetState(&PATTERN)), 150 | (Keyword("box"), SetState(&PATTERN)), 151 | (Keyword("in"), IncDepth), 152 | (Keyword("mut"), SetState(&PATTERN)), 153 | (Keyword("ref"), SetState(&PATTERN)), 154 | (Keyword("_"), SetState(&PATTERN)), 155 | (Punct("!"), SetState(&PATTERN)), 156 | (Punct("&"), SetState(&PATTERN)), 157 | (Punct("..="), SetState(&PATTERN)), 158 | (Punct(".."), SetState(&PATTERN)), 159 | (Punct("="), SetState(&INIT)), 160 | (Punct("@"), SetState(&PATTERN)), 161 | (Punct("|"), SetState(&PATTERN)), 162 | (ConsumeLiteral, SetState(&PATTERN)), 163 | (ExpectPath, SetState(&PATTERN)), 164 | ]; 165 | 166 | static RANGE: [(Input, Action); 6] = [ 167 | (Punct("..="), SetState(&INIT)), 168 | (Punct(".."), SetState(&RANGE)), 169 | (Punct("."), SetState(&DOT)), 170 | (ConsumeNestedBrace, SetState(&IF_THEN)), 171 | (Empty, Finish), 172 | (Otherwise, SetState(&INIT)), 173 | ]; 174 | 175 | static RAW: [(Input, Action); 3] = [ 176 | (Keyword("const"), SetState(&INIT)), 177 | (Keyword("mut"), SetState(&INIT)), 178 | (Otherwise, SetState(&POSTFIX)), 179 | ]; 180 | 181 | static REFERENCE: [(Input, Action); 3] = [ 182 | (Keyword("mut"), SetState(&INIT)), 183 | (Keyword("raw"), SetState(&RAW)), 184 | (Otherwise, SetState(&INIT)), 185 | ]; 186 | 187 | static RETURN: [(Input, Action); 2] = [ 188 | (CanBeginExpr, SetState(&INIT)), 189 | (Otherwise, SetState(&POSTFIX)), 190 | ]; 191 | 192 | pub(crate) fn scan_expr(input: ParseStream) -> Result<()> { 193 | let mut state = INIT.as_slice(); 194 | let mut depth = 0usize; 195 | 'table: loop { 196 | for rule in state { 197 | if match rule.0 { 198 | Input::Keyword(expected) => input.step(|cursor| match cursor.ident() { 199 | Some((ident, rest)) if ident == expected => Ok((true, rest)), 200 | _ => Ok((false, *cursor)), 201 | })?, 202 | Input::Punct(expected) => input.step(|cursor| { 203 | let begin = *cursor; 204 | let mut cursor = begin; 205 | for (i, ch) in expected.chars().enumerate() { 206 | match cursor.punct() { 207 | Some((punct, _)) if punct.as_char() != ch => break, 208 | Some((_, rest)) if i == expected.len() - 1 => { 209 | return Ok((true, rest)); 210 | } 211 | Some((punct, rest)) if punct.spacing() == Spacing::Joint => { 212 | cursor = rest; 213 | } 214 | _ => break, 215 | } 216 | } 217 | Ok((false, begin)) 218 | })?, 219 | Input::ConsumeAny => input.parse::>()?.is_some(), 220 | Input::ConsumeBinOp => input.parse::().is_ok(), 221 | Input::ConsumeBrace | Input::ConsumeNestedBrace => { 222 | (matches!(rule.0, Input::ConsumeBrace) || depth > 0) 223 | && input.step(|cursor| match cursor.group(Delimiter::Brace) { 224 | Some((_inside, _span, rest)) => Ok((true, rest)), 225 | None => Ok((false, *cursor)), 226 | })? 227 | } 228 | Input::ConsumeDelimiter => input.step(|cursor| match cursor.any_group() { 229 | Some((_inside, _delimiter, _span, rest)) => Ok((true, rest)), 230 | None => Ok((false, *cursor)), 231 | })?, 232 | Input::ConsumeIdent => input.parse::>()?.is_some(), 233 | Input::ConsumeLifetime => input.parse::>()?.is_some(), 234 | Input::ConsumeLiteral => input.parse::>()?.is_some(), 235 | Input::ExpectPath => { 236 | input.parse::()?; 237 | true 238 | } 239 | Input::ExpectTurbofish => { 240 | if input.peek(Token![::]) { 241 | input.parse::()?; 242 | } 243 | true 244 | } 245 | Input::ExpectType => { 246 | Type::without_plus(input)?; 247 | true 248 | } 249 | Input::CanBeginExpr => Expr::peek(input), 250 | Input::Otherwise => true, 251 | Input::Empty => input.is_empty() || input.peek(Token![,]), 252 | } { 253 | state = match rule.1 { 254 | Action::SetState(next) => next, 255 | Action::IncDepth => (depth += 1, &INIT).1, 256 | Action::DecDepth => (depth -= 1, &POSTFIX).1, 257 | Action::Finish => return if depth == 0 { Ok(()) } else { break }, 258 | }; 259 | continue 'table; 260 | } 261 | } 262 | return Err(input.error("unsupported expression")); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /derive/src/thiserror/unraw.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span, TokenStream}; 2 | use quote::ToTokens; 3 | use std::cmp::Ordering; 4 | use std::fmt::{self, Display}; 5 | use std::hash::{Hash, Hasher}; 6 | use syn::ext::IdentExt as _; 7 | use syn::parse::{Parse, ParseStream, Result}; 8 | use syn::Index; 9 | 10 | #[derive(Clone)] 11 | #[repr(transparent)] 12 | pub struct IdentUnraw(Ident); 13 | 14 | impl IdentUnraw { 15 | pub fn new(ident: Ident) -> Self { 16 | IdentUnraw(ident) 17 | } 18 | 19 | pub fn to_local(&self) -> Ident { 20 | let unraw = self.0.unraw(); 21 | let repr = unraw.to_string(); 22 | if syn::parse_str::(&repr).is_err() { 23 | if let "_" | "super" | "self" | "Self" | "crate" = repr.as_str() { 24 | // Some identifiers are never allowed to appear as raw, like r#self and r#_. 25 | } else { 26 | return Ident::new_raw(&repr, Span::call_site()); 27 | } 28 | } 29 | unraw 30 | } 31 | 32 | pub fn set_span(&mut self, span: Span) { 33 | self.0.set_span(span); 34 | } 35 | } 36 | 37 | impl Display for IdentUnraw { 38 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 39 | Display::fmt(&self.0.unraw(), formatter) 40 | } 41 | } 42 | 43 | impl Eq for IdentUnraw {} 44 | 45 | impl PartialEq for IdentUnraw { 46 | fn eq(&self, other: &Self) -> bool { 47 | PartialEq::eq(&self.0.unraw(), &other.0.unraw()) 48 | } 49 | } 50 | 51 | impl PartialEq for IdentUnraw { 52 | fn eq(&self, other: &str) -> bool { 53 | self.0 == other 54 | } 55 | } 56 | 57 | impl Ord for IdentUnraw { 58 | fn cmp(&self, other: &Self) -> Ordering { 59 | Ord::cmp(&self.0.unraw(), &other.0.unraw()) 60 | } 61 | } 62 | 63 | impl PartialOrd for IdentUnraw { 64 | fn partial_cmp(&self, other: &Self) -> Option { 65 | Some(Self::cmp(self, other)) 66 | } 67 | } 68 | 69 | impl Parse for IdentUnraw { 70 | fn parse(input: ParseStream) -> Result { 71 | input.call(Ident::parse_any).map(IdentUnraw::new) 72 | } 73 | } 74 | 75 | impl ToTokens for IdentUnraw { 76 | fn to_tokens(&self, tokens: &mut TokenStream) { 77 | self.0.unraw().to_tokens(tokens); 78 | } 79 | } 80 | 81 | #[derive(Clone)] 82 | pub enum MemberUnraw { 83 | Named(IdentUnraw), 84 | Unnamed(Index), 85 | } 86 | 87 | impl MemberUnraw { 88 | pub fn span(&self) -> Span { 89 | match self { 90 | MemberUnraw::Named(ident) => ident.0.span(), 91 | MemberUnraw::Unnamed(index) => index.span, 92 | } 93 | } 94 | } 95 | 96 | impl Display for MemberUnraw { 97 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 98 | match self { 99 | MemberUnraw::Named(this) => Display::fmt(this, formatter), 100 | MemberUnraw::Unnamed(this) => Display::fmt(&this.index, formatter), 101 | } 102 | } 103 | } 104 | 105 | impl Eq for MemberUnraw {} 106 | 107 | impl PartialEq for MemberUnraw { 108 | fn eq(&self, other: &Self) -> bool { 109 | match (self, other) { 110 | (MemberUnraw::Named(this), MemberUnraw::Named(other)) => this == other, 111 | (MemberUnraw::Unnamed(this), MemberUnraw::Unnamed(other)) => this == other, 112 | _ => false, 113 | } 114 | } 115 | } 116 | 117 | impl PartialEq for MemberUnraw { 118 | fn eq(&self, other: &str) -> bool { 119 | match self { 120 | MemberUnraw::Named(this) => this == other, 121 | MemberUnraw::Unnamed(_) => false, 122 | } 123 | } 124 | } 125 | 126 | impl Hash for MemberUnraw { 127 | fn hash(&self, hasher: &mut H) { 128 | match self { 129 | MemberUnraw::Named(ident) => ident.0.unraw().hash(hasher), 130 | MemberUnraw::Unnamed(index) => index.hash(hasher), 131 | } 132 | } 133 | } 134 | 135 | impl ToTokens for MemberUnraw { 136 | fn to_tokens(&self, tokens: &mut TokenStream) { 137 | match self { 138 | MemberUnraw::Named(ident) => ident.to_local().to_tokens(tokens), 139 | MemberUnraw::Unnamed(index) => index.to_tokens(tokens), 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /examples/context.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates how to achieve the similar functionality as 2 | //! [`anyhow::Context`] with `thiserror_ext`, in a type-safer manner. 3 | 4 | #![cfg_attr(feature = "backtrace", feature(error_generic_member_access))] 5 | 6 | use thiserror::Error; 7 | use thiserror_ext::{AsReport, Box, ContextInto, Macro}; 8 | 9 | #[derive(Error, Macro, Box, ContextInto, Debug)] 10 | #[thiserror_ext(newtype(name = MyError))] 11 | enum MyErrorKind { 12 | #[error("{0}")] 13 | EvaluationFailed(#[message] String), 14 | 15 | #[error("failed to evaluate expression `{expr}`")] 16 | Context { 17 | #[source] 18 | inner: MyError, 19 | expr: String, 20 | }, 21 | } 22 | 23 | fn eval_add() -> Result<(), MyError> { 24 | bail_evaluation_failed!("not supported") 25 | } 26 | 27 | fn eval() -> Result<(), MyError> { 28 | eval_add().into_context("add") 29 | } 30 | 31 | fn eval_args(args: &str) -> Result<(), MyError> { 32 | eval_add().into_context_with(|| format!("add({args})")) 33 | } 34 | 35 | fn main() { 36 | let err = eval().unwrap_err(); 37 | assert_eq!( 38 | err.to_report_string(), 39 | "failed to evaluate expression `add`: not supported" 40 | ); 41 | 42 | let err = eval_args("int, int").unwrap_err(); 43 | assert_eq!( 44 | err.to_report_string(), 45 | "failed to evaluate expression `add(int, int)`: not supported" 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-03-05" 3 | -------------------------------------------------------------------------------- /src/as_dyn.rs: -------------------------------------------------------------------------------- 1 | /// Extension trait for [`Error`] that casts the error to a trait object. 2 | /// 3 | /// [`Error`]: std::error::Error 4 | pub trait AsDyn: crate::error_sealed::Sealed { 5 | /// Casts the error to a trait object. 6 | fn as_dyn(&self) -> &(dyn std::error::Error + '_); 7 | } 8 | 9 | impl AsDyn for T { 10 | fn as_dyn(&self) -> &(dyn std::error::Error + '_) { 11 | self 12 | } 13 | } 14 | 15 | macro_rules! impl_as_dyn { 16 | ($({$ty:ty},)*) => { 17 | $( 18 | impl AsDyn for $ty { 19 | fn as_dyn(&self) -> &(dyn std::error::Error + '_) { 20 | self 21 | } 22 | } 23 | )* 24 | }; 25 | } 26 | 27 | crate::for_dyn_error_types! { impl_as_dyn } 28 | -------------------------------------------------------------------------------- /src/backtrace.rs: -------------------------------------------------------------------------------- 1 | /// Provides backtrace to the error. 2 | pub trait WithBacktrace { 3 | /// Capture backtrace based on whether the error already has one. 4 | fn capture(inner: &dyn std::error::Error) -> Self; 5 | 6 | #[cfg(feature = "backtrace")] 7 | /// Provide the backtrace, if any. 8 | fn provide<'a>(&'a self, request: &mut std::error::Request<'a>); 9 | } 10 | 11 | /// Do not capture extra backtrace. 12 | #[derive(Clone, Copy)] 13 | pub struct NoExtraBacktrace; 14 | 15 | impl WithBacktrace for NoExtraBacktrace { 16 | fn capture(_inner: &dyn std::error::Error) -> Self { 17 | Self 18 | } 19 | 20 | #[cfg(feature = "backtrace")] 21 | fn provide<'a>(&'a self, _request: &mut std::error::Request<'a>) {} 22 | } 23 | 24 | #[cfg(feature = "backtrace")] 25 | mod maybe { 26 | use super::WithBacktrace; 27 | use std::backtrace::Backtrace; 28 | 29 | /// Capture backtrace if the error does not already have one. 30 | pub struct MaybeBacktrace(Option); 31 | 32 | impl WithBacktrace for MaybeBacktrace { 33 | fn capture(inner: &dyn std::error::Error) -> Self { 34 | let inner = if std::error::request_ref::(inner).is_none() { 35 | Some(Backtrace::capture()) 36 | } else { 37 | None 38 | }; 39 | Self(inner) 40 | } 41 | 42 | fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) { 43 | if let Some(backtrace) = &self.0 { 44 | request.provide_ref(backtrace); 45 | } 46 | } 47 | } 48 | } 49 | 50 | #[cfg(feature = "backtrace")] 51 | pub use maybe::MaybeBacktrace; 52 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Useful extension utilities for [`thiserror`]. 2 | //! 3 | //! ## Painless construction 4 | //! 5 | //! With derive macros of [`Construct`], [`ContextInto`] and [`Macro`], 6 | //! one can construct an error in a much more convenient way, no matter it's 7 | //! from scratch or converted from other errors. 8 | //! 9 | //! ## Better formatting 10 | //! 11 | //! With extension [`AsReport`], one can format an error in a pretty and 12 | //! concise way, without losing any information from the error sources. 13 | //! 14 | //! ## Easier to interact with 15 | //! 16 | //! With derive macros of [`derive@Box`] and [`derive@Arc`], one can easily 17 | //! wrap an `enum` error type into a new type, reducing the size to improve 18 | //! performance, and automatically capturing backtraces if needed. 19 | 20 | #![cfg_attr(feature = "backtrace", feature(error_generic_member_access))] 21 | 22 | mod as_dyn; 23 | mod backtrace; 24 | mod ptr; 25 | mod report; 26 | 27 | pub use as_dyn::AsDyn; 28 | pub use report::{AsReport, Report}; 29 | pub use thiserror_ext_derive::*; 30 | 31 | #[doc(hidden)] 32 | pub mod __private { 33 | #[cfg(feature = "backtrace")] 34 | pub use crate::backtrace::MaybeBacktrace; 35 | pub use crate::backtrace::NoExtraBacktrace; 36 | pub use crate::ptr::{ErrorArc, ErrorBox}; 37 | pub use thiserror; 38 | } 39 | 40 | macro_rules! for_dyn_error_types { 41 | ($macro:ident) => { 42 | $macro! { 43 | { dyn std::error::Error }, 44 | { dyn std::error::Error + Send }, 45 | { dyn std::error::Error + Sync }, 46 | { dyn std::error::Error + Send + Sync }, 47 | { dyn std::error::Error + Send + Sync + std::panic::UnwindSafe }, 48 | } 49 | }; 50 | } 51 | pub(crate) use for_dyn_error_types; 52 | 53 | pub(crate) mod error_sealed { 54 | pub trait Sealed {} 55 | 56 | impl Sealed for T {} 57 | 58 | macro_rules! impl_sealed { 59 | ($({$ty:ty },)*) => { 60 | $( 61 | impl Sealed for $ty {} 62 | )* 63 | }; 64 | } 65 | for_dyn_error_types! { impl_sealed } 66 | } 67 | -------------------------------------------------------------------------------- /src/ptr.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::backtrace::WithBacktrace; 4 | 5 | /// A [`Box`] with optional backtrace. 6 | #[derive(Clone)] 7 | #[repr(transparent)] 8 | pub struct ErrorBox(Box<(T, B)>); 9 | 10 | impl ErrorBox { 11 | pub fn inner_mut(&mut self) -> &mut T { 12 | &mut self.0.as_mut().0 13 | } 14 | 15 | pub fn into_inner(self) -> T { 16 | (*self.0).0 17 | } 18 | } 19 | 20 | impl std::ops::DerefMut for ErrorBox { 21 | fn deref_mut(&mut self) -> &mut Self::Target { 22 | self.inner_mut() 23 | } 24 | } 25 | 26 | /// A [`Arc`] with optional backtrace. 27 | #[repr(transparent)] 28 | pub struct ErrorArc(Arc<(T, B)>); 29 | 30 | impl Clone for ErrorArc { 31 | fn clone(&self) -> Self { 32 | Self(self.0.clone()) 33 | } 34 | } 35 | 36 | macro_rules! impl_methods { 37 | ($ty:ident) => { 38 | impl $ty { 39 | pub fn new(t: T) -> Self { 40 | let backtrace = B::capture(&t); 41 | Self((t, backtrace).into()) 42 | } 43 | } 44 | 45 | impl $ty { 46 | #[cfg_attr(not(feature = "backtrace"), allow(dead_code))] 47 | fn backtrace(&self) -> &B { 48 | &self.0.as_ref().1 49 | } 50 | 51 | pub fn inner(&self) -> &T { 52 | &self.0.as_ref().0 53 | } 54 | } 55 | 56 | impl std::ops::Deref for $ty { 57 | type Target = T; 58 | 59 | fn deref(&self) -> &Self::Target { 60 | self.inner() 61 | } 62 | } 63 | 64 | impl std::fmt::Display for $ty { 65 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 66 | self.inner().fmt(f) 67 | } 68 | } 69 | 70 | impl std::fmt::Debug for $ty { 71 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 72 | self.inner().fmt(f) 73 | } 74 | } 75 | 76 | impl std::error::Error for $ty { 77 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 78 | T::source(self.inner()) 79 | } 80 | 81 | // https://github.com/rust-lang/rust/issues/117432 82 | #[cfg(feature = "backtrace")] 83 | fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) { 84 | self.backtrace().provide(request); 85 | T::provide(self.inner(), request); 86 | } 87 | } 88 | }; 89 | } 90 | 91 | impl_methods!(ErrorBox); 92 | impl_methods!(ErrorArc); 93 | -------------------------------------------------------------------------------- /src/report.rs: -------------------------------------------------------------------------------- 1 | // This module is ported from https://github.com/shepmaster/snafu and then modified. 2 | // Below is the original license. 3 | 4 | // Copyright 2019- Jake Goulding 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use std::fmt; 19 | 20 | /// Extension trait for [`Error`] that provides a [`Report`] which formats 21 | /// the error and its sources in a cleaned-up way. 22 | /// 23 | /// [`Error`]: std::error::Error 24 | pub trait AsReport: crate::error_sealed::Sealed { 25 | /// Returns a [`Report`] that formats the error and its sources in a 26 | /// cleaned-up way. 27 | /// 28 | /// See the documentation for [`Report`] for what the formatting looks 29 | /// like under different options. 30 | /// 31 | /// # Example 32 | /// ```ignore 33 | /// use thiserror_ext::AsReport; 34 | /// 35 | /// let error = fallible_action().unwrap_err(); 36 | /// println!("{}", error.as_report()); 37 | /// ``` 38 | fn as_report(&self) -> Report<'_>; 39 | 40 | /// Converts the error to a [`Report`] and formats it in a compact way. 41 | /// 42 | /// This is equivalent to `format!("{}", self.as_report())`. 43 | /// 44 | /// ## Example 45 | /// ```text 46 | /// outer error: middle error: inner error 47 | /// ``` 48 | fn to_report_string(&self) -> String { 49 | format!("{}", self.as_report()) 50 | } 51 | 52 | /// Converts the error to a [`Report`] and formats it in a compact way, 53 | /// including backtraces if available. 54 | /// 55 | /// This is equivalent to `format!("{:?}", self.as_report())`. 56 | /// 57 | /// ## Example 58 | /// ```text 59 | /// outer error: middle error: inner error 60 | /// 61 | /// Backtrace: 62 | /// ... 63 | /// ``` 64 | fn to_report_string_with_backtrace(&self) -> String { 65 | format!("{:?}", self.as_report()) 66 | } 67 | 68 | /// Converts the error to a [`Report`] and formats it in a pretty way. 69 | /// 70 | /// This is equivalent to `format!("{:#}", self.as_report())`. 71 | /// 72 | /// ## Example 73 | /// ```text 74 | /// outer error 75 | /// 76 | /// Caused by these errors (recent errors listed first): 77 | /// 1: middle error 78 | /// 2: inner error 79 | /// ``` 80 | fn to_report_string_pretty(&self) -> String { 81 | format!("{:#}", self.as_report()) 82 | } 83 | 84 | /// Converts the error to a [`Report`] and formats it in a pretty way, 85 | /// 86 | /// including backtraces if available. 87 | /// 88 | /// ## Example 89 | /// ```text 90 | /// outer error 91 | /// 92 | /// Caused by these errors (recent errors listed first): 93 | /// 1: middle error 94 | /// 2: inner error 95 | /// 96 | /// Backtrace: 97 | /// ... 98 | /// ``` 99 | fn to_report_string_pretty_with_backtrace(&self) -> String { 100 | format!("{:#?}", self.as_report()) 101 | } 102 | } 103 | 104 | impl AsReport for T { 105 | fn as_report(&self) -> Report<'_> { 106 | Report(self) 107 | } 108 | } 109 | 110 | macro_rules! impl_as_report { 111 | ($({$ty:ty },)*) => { 112 | $( 113 | impl AsReport for $ty { 114 | fn as_report(&self) -> Report<'_> { 115 | Report(self) 116 | } 117 | } 118 | )* 119 | }; 120 | } 121 | crate::for_dyn_error_types! { impl_as_report } 122 | 123 | /// A wrapper around an error that provides a cleaned up error trace for 124 | /// display and debug formatting. 125 | /// 126 | /// Constructed using [`AsReport::as_report`]. 127 | /// 128 | /// # Formatting 129 | /// 130 | /// The report can be formatted using [`fmt::Display`] or [`fmt::Debug`], 131 | /// which differs based on the alternate flag (`#`). 132 | /// 133 | /// - Without the alternate flag, the error is formatted in a compact way: 134 | /// ```text 135 | /// Outer error text: Middle error text: Inner error text 136 | /// ``` 137 | /// 138 | /// - With the alternate flag, the error is formatted in a multi-line 139 | /// format, which is more readable: 140 | /// ```text 141 | /// Outer error text 142 | /// 143 | /// Caused by these errors (recent errors listed first): 144 | /// 1. Middle error text 145 | /// 2. Inner error text 146 | /// ``` 147 | /// 148 | /// - Additionally, [`fmt::Debug`] provide backtraces if available. 149 | /// 150 | /// # Error source cleaning 151 | /// 152 | /// It's common for errors with a `source` to have a `Display` 153 | /// implementation that includes their source text as well: 154 | /// 155 | /// ```text 156 | /// Outer error text: Middle error text: Inner error text 157 | /// ``` 158 | /// 159 | /// This works for smaller errors without much detail, but can be 160 | /// annoying when trying to format the error in a more structured way, 161 | /// such as line-by-line: 162 | /// 163 | /// ```text 164 | /// 1. Outer error text: Middle error text: Inner error text 165 | /// 2. Middle error text: Inner error text 166 | /// 3. Inner error text 167 | /// ``` 168 | /// 169 | /// This iterator compares each pair of errors in the source chain, 170 | /// removing the source error's text from the containing error's text: 171 | /// 172 | /// ```text 173 | /// 1. Outer error text 174 | /// 2. Middle error text 175 | /// 3. Inner error text 176 | /// ``` 177 | pub struct Report<'a>(pub &'a dyn std::error::Error); 178 | 179 | impl fmt::Display for Report<'_> { 180 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 181 | self.cleaned_error_trace(f, f.alternate()) 182 | } 183 | } 184 | 185 | impl fmt::Debug for Report<'_> { 186 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 187 | self.cleaned_error_trace(f, f.alternate())?; 188 | 189 | #[cfg(feature = "backtrace")] 190 | { 191 | use std::backtrace::{Backtrace, BacktraceStatus}; 192 | 193 | if let Some(bt) = std::error::request_ref::(self.0) { 194 | // Hack for testing purposes. 195 | // Read the env var could be slow but we short-circuit it in release mode, 196 | // so this should be optimized out in production. 197 | let force_show_backtrace = cfg!(debug_assertions) 198 | && std::env::var("THISERROR_EXT_TEST_SHOW_USELESS_BACKTRACE").is_ok(); 199 | 200 | // If the backtrace is disabled or unsupported, behave as if there's no backtrace. 201 | if bt.status() == BacktraceStatus::Captured || force_show_backtrace { 202 | // The alternate mode contains a trailing newline while non-alternate 203 | // mode does not. So we need to add a newline before the backtrace. 204 | if !f.alternate() { 205 | writeln!(f)?; 206 | } 207 | writeln!(f, "\nBacktrace:\n{}", bt)?; 208 | } 209 | } 210 | } 211 | 212 | Ok(()) 213 | } 214 | } 215 | 216 | impl Report<'_> { 217 | fn cleaned_error_trace(&self, f: &mut fmt::Formatter, pretty: bool) -> Result<(), fmt::Error> { 218 | let cleaned_messages: Vec<_> = CleanedErrorText::new(self.0) 219 | .flat_map(|(_error, msg, _cleaned)| Some(msg).filter(|msg| !msg.is_empty())) 220 | .collect(); 221 | 222 | let mut visible_messages = cleaned_messages.iter(); 223 | 224 | let head = match visible_messages.next() { 225 | Some(v) => v, 226 | None => return Ok(()), 227 | }; 228 | 229 | write!(f, "{}", head)?; 230 | 231 | if pretty { 232 | match cleaned_messages.len() { 233 | 0 | 1 => {} 234 | 2 => { 235 | writeln!(f, "\n\nCaused by:")?; 236 | writeln!(f, " {}", visible_messages.next().unwrap())?; 237 | } 238 | _ => { 239 | writeln!( 240 | f, 241 | "\n\nCaused by these errors (recent errors listed first):" 242 | )?; 243 | for (i, msg) in visible_messages.enumerate() { 244 | // Let's use 1-based indexing for presentation 245 | let i = i + 1; 246 | writeln!(f, "{:3}: {}", i, msg)?; 247 | } 248 | } 249 | } 250 | } else { 251 | // No newline at the end. 252 | for msg in visible_messages { 253 | write!(f, ": {}", msg)?; 254 | } 255 | } 256 | 257 | Ok(()) 258 | } 259 | } 260 | 261 | /// An iterator over an Error and its sources that removes duplicated 262 | /// text from the error display strings. 263 | struct CleanedErrorText<'a>(Option>); 264 | 265 | impl<'a> CleanedErrorText<'a> { 266 | /// Constructs the iterator. 267 | fn new(error: &'a dyn std::error::Error) -> Self { 268 | Self(Some(CleanedErrorTextStep::new(error))) 269 | } 270 | } 271 | 272 | impl<'a> Iterator for CleanedErrorText<'a> { 273 | /// The original error, the display string and if it has been cleaned 274 | type Item = (&'a dyn std::error::Error, String, bool); 275 | 276 | fn next(&mut self) -> Option { 277 | use std::mem; 278 | 279 | let mut step = self.0.take()?; 280 | let mut error_text = mem::take(&mut step.error_text); 281 | 282 | match step.error.source() { 283 | Some(next_error) => { 284 | let next_error_text = next_error.to_string(); 285 | 286 | let cleaned_text = error_text 287 | .trim_end_matches(&next_error_text) 288 | .trim_end() 289 | .trim_end_matches(':'); 290 | let cleaned = cleaned_text.len() != error_text.len(); 291 | let cleaned_len = cleaned_text.len(); 292 | error_text.truncate(cleaned_len); 293 | 294 | self.0 = Some(CleanedErrorTextStep { 295 | error: next_error, 296 | error_text: next_error_text, 297 | }); 298 | 299 | Some((step.error, error_text, cleaned)) 300 | } 301 | None => Some((step.error, error_text, false)), 302 | } 303 | } 304 | } 305 | 306 | struct CleanedErrorTextStep<'a> { 307 | error: &'a dyn std::error::Error, 308 | error_text: String, 309 | } 310 | 311 | impl<'a> CleanedErrorTextStep<'a> { 312 | fn new(error: &'a dyn std::error::Error) -> Self { 313 | let error_text = error.to_string(); 314 | Self { error, error_text } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /tests/arc_new_type.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "backtrace", feature(error_generic_member_access))] 2 | 3 | use std::{error::Error, num::ParseIntError}; 4 | 5 | use thiserror::*; 6 | use thiserror_ext::*; 7 | 8 | #[derive(Error, Debug, Arc, Construct)] 9 | #[thiserror_ext(newtype(name = SharedMyError))] 10 | pub enum MyErrorInner { 11 | #[error("foo: {foo}")] 12 | Foo { source: ParseIntError, foo: String }, 13 | } 14 | 15 | #[test] 16 | fn test() { 17 | let error = SharedMyError::foo("nope".parse::().unwrap_err(), "hello".to_owned()); 18 | let error2 = error.clone(); 19 | 20 | // Test source preserved. 21 | let source = error2.source().unwrap(); 22 | assert_eq!(source.to_string(), "invalid digit found in string"); 23 | } 24 | -------------------------------------------------------------------------------- /tests/backtrace.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "backtrace")] 2 | #![feature(error_generic_member_access)] 3 | 4 | use std::backtrace::Backtrace; 5 | 6 | use sealed_test::prelude::*; 7 | use thiserror::Error; 8 | use thiserror_ext_derive::Box; 9 | 10 | #[derive(Error, Debug)] 11 | #[error("..")] 12 | struct ParseFloatErrorWithBacktrace { 13 | #[from] 14 | source: std::num::ParseFloatError, 15 | #[backtrace] 16 | backtrace: Backtrace, 17 | } 18 | 19 | #[derive(Error, Debug, Box)] 20 | #[thiserror_ext(newtype(name = MyError, backtrace))] 21 | enum MyErrorInner { 22 | #[error("parse int")] 23 | ParseInt { 24 | #[from] 25 | source: std::num::ParseIntError, 26 | }, 27 | 28 | #[error("parse float with backtrace")] 29 | ParseFloatWithBacktrace { 30 | #[from] 31 | #[backtrace] // inner error has backtrace, provide it 32 | source: ParseFloatErrorWithBacktrace, 33 | }, 34 | } 35 | 36 | fn parse_float_with_backtrace(input: &str) -> Result { 37 | fn parse_inner(input: &str) -> Result { 38 | Ok(input.parse()?) // backtrace captured here 39 | } 40 | 41 | Ok(parse_inner(input)?) // already has backtrace, no need to capture 42 | } 43 | 44 | fn parse_int(input: &str) -> Result { 45 | fn parse_inner(input: &str) -> Result { 46 | input.parse() // no backtrace captured here 47 | } 48 | 49 | Ok(parse_inner(input)?) // backtrace captured here by generated `MyError` 50 | } 51 | 52 | #[sealed_test(env = [("RUST_BACKTRACE", "1")])] 53 | fn test_with_source_backtrace() { 54 | let error = parse_float_with_backtrace("not a number").unwrap_err(); 55 | let backtrace = std::error::request_ref::(&error) 56 | .unwrap() 57 | .to_string(); 58 | 59 | assert!(backtrace.contains("parse_inner"), "{backtrace}"); 60 | } 61 | 62 | #[sealed_test(env = [("RUST_BACKTRACE", "1")])] 63 | fn test_without_source_backtrace() { 64 | let error = parse_int("not a number").unwrap_err(); 65 | let backtrace = std::error::request_ref::(&error) 66 | .unwrap() 67 | .to_string(); 68 | 69 | assert!(!backtrace.contains("parse_inner"), "{backtrace}"); 70 | } 71 | -------------------------------------------------------------------------------- /tests/basic.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "backtrace", feature(error_generic_member_access))] 2 | 3 | #[cfg(feature = "backtrace")] 4 | use std::backtrace::Backtrace; 5 | use thiserror::*; 6 | use thiserror_ext::*; 7 | 8 | #[derive(Error, Debug, Construct, ContextInto, Box)] 9 | #[thiserror_ext(newtype(name = MyError))] 10 | pub enum MyErrorInner { 11 | #[error("cannot parse int from `{from}`")] 12 | Parse { 13 | #[source] 14 | error: std::num::ParseIntError, 15 | from: String, 16 | }, 17 | 18 | #[error("cannot parse int from `{from}`")] 19 | ParseImplicitSource { 20 | source: std::num::ParseIntError, 21 | from: String, 22 | }, 23 | 24 | #[error("cannot parse int")] 25 | ParseUnnamed(#[source] std::num::ParseFloatError, String), 26 | 27 | #[error(transparent)] 28 | IoTransparent(std::io::Error), 29 | 30 | #[error("unsupported: {0}")] 31 | UnsupportedSingleField(String), 32 | 33 | #[error("bad id: {0}")] 34 | #[construct(skip)] 35 | BadId(String), 36 | } 37 | 38 | impl MyError { 39 | pub fn bad_id(id: impl ToString) -> Self { 40 | MyErrorInner::BadId(id.to_string()).into() 41 | } 42 | } 43 | 44 | #[cfg(feature = "backtrace")] 45 | #[derive(Error, Debug, Construct, ContextInto, Box)] 46 | #[thiserror_ext(newtype(name = MyErrorBacktrace))] 47 | pub enum MyErrorBacktraceInner { 48 | #[error("cannot parse int")] 49 | ParseWithBacktrace { 50 | #[source] 51 | error: std::num::ParseIntError, 52 | backtrace: Backtrace, 53 | }, 54 | 55 | #[error("cannot parse int from `{from}`")] 56 | ParseWithBacktraceAndContext { 57 | #[source] 58 | error: std::num::ParseIntError, 59 | backtrace: Backtrace, 60 | from: String, 61 | }, 62 | } 63 | 64 | #[test] 65 | fn test() {} 66 | -------------------------------------------------------------------------------- /tests/context_into.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates how to achieve the similar functionality as 2 | //! [`anyhow::Context`] with `thiserror_ext`, in a type-safer manner. 3 | 4 | #![cfg_attr(feature = "backtrace", feature(error_generic_member_access))] 5 | 6 | use expect_test::expect; 7 | use thiserror::Error; 8 | use thiserror_ext::{AsReport, ContextInto}; 9 | 10 | #[derive(Error, Debug)] 11 | #[error("foo")] 12 | struct FooError; 13 | 14 | #[derive(Error, Debug)] 15 | #[error("bar")] 16 | struct BarError; 17 | 18 | #[derive(Error, ContextInto, Debug)] 19 | enum MyError { 20 | #[error("{context}")] 21 | Foo { 22 | #[source] 23 | foo: FooError, 24 | context: String, 25 | }, 26 | 27 | #[error("{context1} && {context2}")] 28 | Bar { 29 | #[source] 30 | bar: BarError, 31 | context1: String, 32 | context2: Box, 33 | }, 34 | } 35 | 36 | fn foo() -> Result<(), FooError> { 37 | Err(FooError) 38 | } 39 | 40 | fn bar() -> Result<(), BarError> { 41 | Err(BarError) 42 | } 43 | 44 | #[test] 45 | fn test_result_into() { 46 | let err: MyError = foo().into_foo("hello").unwrap_err(); 47 | expect!["hello: foo"].assert_eq(&err.to_report_string()); 48 | 49 | let err: MyError = bar().into_bar("hello", "world").unwrap_err(); 50 | expect!["hello && world: bar"].assert_eq(&err.to_report_string()); 51 | } 52 | 53 | #[test] 54 | fn test_result_into_with() { 55 | let err: MyError = foo().into_foo_with(|| "hello").unwrap_err(); 56 | expect!["hello: foo"].assert_eq(&err.to_report_string()); 57 | 58 | let err: MyError = bar() 59 | .into_bar_with(|| ("hello", format!("wo{}", "rld"))) 60 | .unwrap_err(); 61 | expect!["hello && world: bar"].assert_eq(&err.to_report_string()); 62 | } 63 | 64 | #[test] 65 | fn test_error_into() { 66 | let err: MyError = FooError.into_foo("hello"); 67 | expect!["hello: foo"].assert_eq(&err.to_report_string()); 68 | 69 | let err: MyError = BarError.into_bar("hello", "world"); 70 | expect!["hello && world: bar"].assert_eq(&err.to_report_string()); 71 | } 72 | 73 | #[test] 74 | fn test_error_into_with() { 75 | let err: MyError = FooError.into_foo_with(|| "hello"); 76 | expect!["hello: foo"].assert_eq(&err.to_report_string()); 77 | 78 | let err: MyError = BarError.into_bar_with(|| ("hello", format!("wo{}", "rld"))); 79 | expect!["hello && world: bar"].assert_eq(&err.to_report_string()); 80 | } 81 | -------------------------------------------------------------------------------- /tests/macro.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "backtrace", feature(error_generic_member_access))] 2 | 3 | pub mod inner { 4 | use thiserror::Error; 5 | use thiserror_ext_derive::{Box, Macro}; 6 | 7 | #[derive(Error, Debug, Macro, Box)] 8 | #[thiserror_ext(newtype(name = BoxMyError))] 9 | pub(super) enum MyError { 10 | #[error("foo {message}")] 11 | Foo { message: String }, 12 | 13 | #[error("bar {message}")] 14 | Bar { issue: Option, message: String }, 15 | 16 | #[error("baz {msg}")] 17 | Baz { 18 | issue: Option, 19 | pr: Option, 20 | #[message] 21 | msg: String, 22 | }, 23 | 24 | #[error("qux {msg}")] 25 | Qux { 26 | extra: String, 27 | #[message] 28 | msg: Box, 29 | }, 30 | 31 | #[error("quux {message}")] 32 | Quux { message: String }, 33 | } 34 | #[derive(Error, Debug, Macro)] 35 | #[error("not implemented: {message}, issue: {issue:?}")] 36 | #[thiserror_ext(macro(mangle, path = "crate::inner", vis = pub(super)))] 37 | pub struct NotImplemented { 38 | pub issue: Option, 39 | pub message: String, 40 | } 41 | } 42 | 43 | mod tests { 44 | use crate::inner::{BoxMyError, MyError, NotImplemented}; 45 | 46 | #[test] 47 | fn test() { 48 | use crate::inner::{bar, baz, foo, qux}; 49 | 50 | let _: BoxMyError = foo!("hello {}", 42); 51 | 52 | let _ = bar!("hello {}", 42); 53 | let _ = bar!(issue = 42, "hello {}", 42); 54 | let _ = bar!(issue = None, "hello {}", 42); 55 | let a = bar!(issue = Some(42), "hello {}", 42); 56 | assert!( 57 | matches!(a.inner(), MyError::Bar { issue: Some(42), message } if message == "hello 42") 58 | ); 59 | 60 | let _ = baz!("hello {}", 42); 61 | let _ = baz!(issue = 42, pr = Some(88), "hello {}", 42); 62 | let _ = baz!(issue = None, pr = 88, "hello {}", 42); 63 | let _ = baz!(issue = 42, "hello {}", 42); 64 | let a = baz!(pr = 88, "hello {}", 42); 65 | let _ = baz!(pr = Some(88), "hello {}", 42); 66 | assert!(matches!( 67 | a.inner(), 68 | MyError::Baz { 69 | pr: Some(88), 70 | issue: None, 71 | .. 72 | } 73 | )); 74 | 75 | let _ = qux!(extra = "233", "hello {}", 42); 76 | let _ = qux!(extra = "233".to_owned(), "hello {}", 42); 77 | let a = qux!("hello {}", 42); // use default `extra` 78 | assert!(matches!( 79 | a.inner(), 80 | MyError::Qux { 81 | extra, 82 | msg, 83 | .. 84 | } if extra == "" && msg.as_ref() == "hello 42" 85 | )); 86 | } 87 | 88 | #[test] 89 | fn test_bail() { 90 | use crate::inner::bail_quux; 91 | 92 | fn test() -> Result<(), anyhow::Error> { 93 | match 1 + 1 { 94 | 3 => Ok(()), 95 | _ => bail_quux!("1 + 1 != 3"), 96 | } 97 | } 98 | 99 | let error = test().unwrap_err(); 100 | assert!(matches!( 101 | error.downcast_ref::().unwrap().inner(), 102 | MyError::Quux { message } if message == "1 + 1 != 3" 103 | )); 104 | } 105 | 106 | #[test] 107 | fn test_struct() { 108 | use crate::inner::bail_not_implemented; 109 | use crate::inner::not_implemented; 110 | 111 | // As we're mangling the macro name, we can't use the macro directly. 112 | // 113 | // use crate::__thiserror_ext_macro__not_implemented__not_implemented; 114 | // use crate::not_implemented; 115 | 116 | let a: NotImplemented = not_implemented!(issue = 42, "hello {}", 42); 117 | assert!(matches!( 118 | a, 119 | NotImplemented { 120 | issue: Some(42), 121 | message, 122 | } if message == "hello 42" 123 | )); 124 | 125 | fn test() -> Result<(), anyhow::Error> { 126 | match 1 + 1 { 127 | 3 => Ok(()), 128 | _ => bail_not_implemented!(issue = 42, "1 + 1 != 3"), 129 | } 130 | } 131 | 132 | let error = test().unwrap_err(); 133 | assert!(matches!( 134 | error.downcast_ref::().unwrap(), 135 | NotImplemented { 136 | issue: Some(42), 137 | message, 138 | } if message == "1 + 1 != 3" 139 | )); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tests/macro_anyhow.rs: -------------------------------------------------------------------------------- 1 | //! Demonstrate that we can emulate a lightweight version of `anyhow` with `thiserror_ext`. 2 | 3 | #![cfg_attr(feature = "backtrace", feature(error_generic_member_access))] 4 | 5 | use thiserror::Error; 6 | use thiserror_ext::Box; 7 | use thiserror_ext_derive::Macro; 8 | 9 | #[derive(Error, Debug, Box, Macro)] 10 | #[thiserror_ext(newtype(name = Anyhow))] 11 | #[error("{message}")] 12 | struct AnyhowInner { 13 | source: Option, 14 | message: Box, 15 | } 16 | 17 | mod tests { 18 | use expect_test::expect; 19 | use thiserror_ext::AsReport; 20 | 21 | use super::*; 22 | 23 | #[test] 24 | fn test() { 25 | fn test() -> Result<(), Anyhow> { 26 | let a = anyhow!(); // empty input -> empty message -> ignored in report 27 | let b = anyhow!(source = a, "base"); 28 | bail_anyhow!(source = b, "upper {}", 233); 29 | } 30 | 31 | let report = test().unwrap_err().to_report_string(); 32 | expect!["upper 233: base"].assert_eq(&report); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/report.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "backtrace")] 2 | #![feature(error_generic_member_access)] 3 | 4 | use expect_test::expect; 5 | use sealed_test::prelude::*; 6 | use thiserror::Error; 7 | use thiserror_ext::AsReport; 8 | 9 | #[derive(Error, Debug)] 10 | #[error("inner error")] 11 | struct Inner {} 12 | 13 | #[derive(Error, Debug)] 14 | #[error("middle error: {source}")] // some error may include source message in its message 15 | // the suffix should be cleaned up 16 | struct Middle { 17 | #[from] 18 | source: Inner, 19 | #[backtrace] 20 | backtrace: Option, 21 | } 22 | 23 | #[derive(Error, Debug)] 24 | #[error("{source}")] // some may duplicate source message to emulate `#[transparent]` 25 | // the whole message should be cleaned up (as it's empty after removing source message) 26 | struct MiddleTransparent { 27 | #[from] 28 | #[backtrace] 29 | source: Middle, 30 | } 31 | 32 | #[derive(Error, Debug)] 33 | #[error("outer error")] // but the best practice is to not include 34 | struct Outer { 35 | #[from] 36 | #[backtrace] 37 | source: MiddleTransparent, 38 | } 39 | 40 | fn inner() -> Result<(), Inner> { 41 | Err(Inner {}) 42 | } 43 | 44 | fn middle(bt: bool) -> Result<(), Middle> { 45 | inner().map_err(|e| { 46 | if bt { 47 | Middle::from(e) 48 | } else { 49 | Middle { 50 | source: e, 51 | backtrace: None, 52 | } 53 | } 54 | })?; 55 | Ok(()) 56 | } 57 | 58 | fn middle_transparent(bt: bool) -> Result<(), MiddleTransparent> { 59 | middle(bt)?; 60 | Ok(()) 61 | } 62 | 63 | fn outer(bt: bool) -> Result<(), Outer> { 64 | middle_transparent(bt)?; 65 | Ok(()) 66 | } 67 | 68 | #[test] 69 | fn test_report_display() { 70 | let expect = expect!["outer error: middle error: inner error"]; 71 | expect.assert_eq(&format!("{}", outer(true).unwrap_err().as_report())); 72 | } 73 | 74 | #[test] 75 | fn test_report_display_alternate() { 76 | let expect = expect![[r#" 77 | outer error 78 | 79 | Caused by these errors (recent errors listed first): 80 | 1: middle error 81 | 2: inner error 82 | "#]]; 83 | expect.assert_eq(&format!("{:#}", outer(true).unwrap_err().as_report())); 84 | } 85 | 86 | #[test] 87 | fn test_report_display_alternate_single_source() { 88 | let expect = expect![[r#" 89 | middle error 90 | 91 | Caused by: 92 | inner error 93 | "#]]; 94 | expect.assert_eq(&format!("{:#}", middle(true).unwrap_err().as_report())); 95 | } 96 | 97 | // Show that there's extra backtrace information compared to `Display`. 98 | // Backtrace is intentionally disabled to make the test deterministic. 99 | #[sealed_test(env = [("RUST_BACKTRACE", "0"), ("THISERROR_EXT_TEST_SHOW_USELESS_BACKTRACE", "1")])] 100 | fn test_report_debug() { 101 | let expect = expect![[r#" 102 | outer error: middle error: inner error 103 | 104 | Backtrace: 105 | disabled backtrace 106 | "#]]; 107 | expect.assert_eq(&format!("{:?}", outer(true).unwrap_err().as_report())); 108 | } 109 | 110 | // If there's no backtrace, the behavior should be exactly the same as `Display`. 111 | #[test] 112 | fn test_report_debug_no_backtrace() { 113 | let expect = expect!["outer error: middle error: inner error"]; 114 | expect.assert_eq(&format!("{:?}", outer(false).unwrap_err().as_report())); 115 | } 116 | 117 | // Show that there's extra backtrace information compared to `Display`. 118 | // Backtrace is intentionally disabled to make the test deterministic. 119 | #[sealed_test(env = [("RUST_BACKTRACE", "0"), ("THISERROR_EXT_TEST_SHOW_USELESS_BACKTRACE", "1")])] 120 | fn test_report_debug_alternate() { 121 | let expect = expect![[r#" 122 | outer error 123 | 124 | Caused by these errors (recent errors listed first): 125 | 1: middle error 126 | 2: inner error 127 | 128 | Backtrace: 129 | disabled backtrace 130 | "#]]; 131 | expect.assert_eq(&format!("{:#?}", outer(true).unwrap_err().as_report())); 132 | } 133 | 134 | // If there's no backtrace, the behavior should be exactly the same as `Display`. 135 | #[test] 136 | fn test_report_debug_alternate_no_backtrace() { 137 | let expect = expect![[r#" 138 | outer error 139 | 140 | Caused by these errors (recent errors listed first): 141 | 1: middle error 142 | 2: inner error 143 | "#]]; 144 | expect.assert_eq(&format!("{:#?}", outer(false).unwrap_err().as_report())); 145 | } 146 | 147 | // If there's disabled backtrace, the behavior should be exactly the same as `Display` too. 148 | // Note that `THISERROR_EXT_TEST_SHOW_USELESS_BACKTRACE` is not set so this mimics the user's environment. 149 | #[sealed_test(env = [("RUST_BACKTRACE", "0")])] 150 | fn test_report_debug_alternate_disabled_backtrace() { 151 | let expect = expect![[r#" 152 | outer error 153 | 154 | Caused by these errors (recent errors listed first): 155 | 1: middle error 156 | 2: inner error 157 | "#]]; 158 | expect.assert_eq(&format!("{:#?}", outer(true).unwrap_err().as_report())); 159 | } 160 | -------------------------------------------------------------------------------- /tests/report_debug.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "backtrace", feature(error_generic_member_access))] 2 | 3 | use thiserror::Error; 4 | use thiserror_ext::{Box, ReportDebug}; 5 | 6 | #[derive(Error, ReportDebug, Default)] 7 | #[error("inner")] 8 | struct Inner; 9 | 10 | #[derive(Error, ReportDebug, Default, Box)] 11 | #[thiserror_ext(newtype(name = BoxOuter))] 12 | #[error("outer")] 13 | struct Outer { 14 | #[source] 15 | inner: Inner, 16 | } 17 | 18 | #[test] 19 | fn test_report_debug() { 20 | let error = Outer::default(); 21 | 22 | expect_test::expect!["outer: inner"].assert_eq(&format!("{:?}", error)); 23 | 24 | expect_test::expect![[r#" 25 | outer 26 | 27 | Caused by: 28 | inner 29 | "#]] 30 | .assert_eq(&format!("{:#?}", error)); 31 | 32 | let boxed = BoxOuter::from(error); 33 | 34 | expect_test::expect!["outer: inner"].assert_eq(&format!("{:?}", boxed)); 35 | } 36 | 37 | #[test] 38 | #[should_panic] 39 | fn test_unwrap() { 40 | let error = Outer::default(); 41 | let _ = Err::<(), _>(error).unwrap(); 42 | } 43 | 44 | #[test] 45 | #[should_panic] 46 | fn test_expect() { 47 | let error = Outer::default(); 48 | let _ = Err::<(), _>(error).expect("intentional panic"); 49 | } 50 | -------------------------------------------------------------------------------- /tests/v2.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "backtrace", feature(error_generic_member_access))] 2 | #![allow(dead_code)] 3 | 4 | use thiserror::Error; 5 | 6 | // Follow the convention for opting out of the field named `source` by specifying `r#source`. 7 | // https://github.com/dtolnay/thiserror/pull/350 8 | mod opt_out_field_named_message_for_macro { 9 | use super::*; 10 | use std::fmt; 11 | use thiserror_ext::Macro; 12 | 13 | #[derive(Error, Debug, Macro)] 14 | enum Error { 15 | #[error(fmt = demo_fmt)] 16 | Demo { 17 | code: u16, 18 | r#message: Option, 19 | }, 20 | } 21 | 22 | fn demo_fmt( 23 | code: &u16, 24 | message: &Option, 25 | formatter: &mut fmt::Formatter, 26 | ) -> fmt::Result { 27 | write!(formatter, "{code}")?; 28 | if let Some(msg) = message { 29 | write!(formatter, " - {msg}")?; 30 | } 31 | Ok(()) 32 | } 33 | 34 | // This shows that we don't generate a macro named `bail_demo` with `derive(Macro)`. 35 | #[allow(unused_macros)] 36 | #[macro_export] 37 | macro_rules! bail_demo { 38 | () => {}; 39 | } 40 | } 41 | 42 | #[test] 43 | fn test() {} 44 | --------------------------------------------------------------------------------