├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── build └── probe.rs ├── impl ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── ast.rs │ ├── attr.rs │ ├── expand.rs │ ├── fallback.rs │ ├── fmt.rs │ ├── generics.rs │ ├── lib.rs │ ├── prop.rs │ ├── scan_expr.rs │ ├── unraw.rs │ └── valid.rs ├── rust-toolchain.toml ├── src ├── aserror.rs ├── display.rs ├── lib.rs ├── provide.rs └── var.rs └── tests ├── compiletest.rs ├── no-std ├── Cargo.toml └── test.rs ├── test_backtrace.rs ├── test_display.rs ├── test_error.rs ├── test_expr.rs ├── test_from.rs ├── test_generics.rs ├── test_lints.rs ├── test_option.rs ├── test_path.rs ├── test_source.rs ├── test_transparent.rs └── ui ├── bad-field-attr.rs ├── bad-field-attr.stderr ├── concat-display.rs ├── concat-display.stderr ├── display-underscore.rs ├── display-underscore.stderr ├── duplicate-enum-source.rs ├── duplicate-enum-source.stderr ├── duplicate-fmt.rs ├── duplicate-fmt.stderr ├── duplicate-struct-source.rs ├── duplicate-struct-source.stderr ├── duplicate-transparent.rs ├── duplicate-transparent.stderr ├── expression-fallback.rs ├── expression-fallback.stderr ├── fallback-impl-with-display.rs ├── fallback-impl-with-display.stderr ├── from-backtrace-backtrace.rs ├── from-backtrace-backtrace.stderr ├── from-not-source.rs ├── from-not-source.stderr ├── invalid-input-impl-anyway.rs ├── invalid-input-impl-anyway.stderr ├── lifetime.rs ├── lifetime.stderr ├── missing-display.rs ├── missing-display.stderr ├── missing-fmt.rs ├── missing-fmt.stderr ├── no-display.rs ├── no-display.stderr ├── numbered-positional-tuple.rs ├── numbered-positional-tuple.stderr ├── raw-identifier.rs ├── raw-identifier.stderr ├── same-from-type.rs ├── same-from-type.stderr ├── source-enum-not-error.rs ├── source-enum-not-error.stderr ├── source-enum-unnamed-field-not-error.rs ├── source-enum-unnamed-field-not-error.stderr ├── source-struct-not-error.rs ├── source-struct-not-error.stderr ├── source-struct-unnamed-field-not-error.rs ├── source-struct-unnamed-field-not-error.stderr ├── struct-with-fmt.rs ├── struct-with-fmt.stderr ├── transparent-display.rs ├── transparent-display.stderr ├── transparent-enum-many.rs ├── transparent-enum-many.stderr ├── transparent-enum-not-error.rs ├── transparent-enum-not-error.stderr ├── transparent-enum-source.rs ├── transparent-enum-source.stderr ├── transparent-enum-unnamed-field-not-error.rs ├── transparent-enum-unnamed-field-not-error.stderr ├── transparent-struct-many.rs ├── transparent-struct-many.stderr ├── transparent-struct-not-error.rs ├── transparent-struct-not-error.stderr ├── transparent-struct-source.rs ├── transparent-struct-source.stderr ├── transparent-struct-unnamed-field-not-error.rs ├── transparent-struct-unnamed-field-not-error.stderr ├── unconditional-recursion.rs ├── unconditional-recursion.stderr ├── unexpected-field-fmt.rs ├── unexpected-field-fmt.stderr ├── unexpected-struct-source.rs ├── unexpected-struct-source.stderr ├── union.rs └── union.stderr /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dtolnay 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: [cron: "40 1 * * *"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | pre_ci: 17 | uses: dtolnay/.github/.github/workflows/pre_ci.yml@master 18 | 19 | test: 20 | name: Rust ${{matrix.rust}} 21 | needs: pre_ci 22 | if: needs.pre_ci.outputs.continue 23 | runs-on: ubuntu-latest 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | rust: [nightly, beta, stable, 1.81.0, 1.70.0] 28 | timeout-minutes: 45 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: dtolnay/rust-toolchain@master 32 | with: 33 | toolchain: ${{matrix.rust}} 34 | components: rust-src 35 | - name: Enable type layout randomization 36 | run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV 37 | if: matrix.rust == 'nightly' 38 | - name: Enable nightly-only tests 39 | run: echo RUSTFLAGS=${RUSTFLAGS}\ --cfg=thiserror_nightly_testing >> $GITHUB_ENV 40 | if: matrix.rust == 'nightly' 41 | - run: cargo test --workspace --exclude thiserror_no_std_test 42 | - run: cargo test --manifest-path tests/no-std/Cargo.toml 43 | if: matrix.rust != '1.70.0' 44 | - run: cargo test --no-default-features 45 | - uses: actions/upload-artifact@v4 46 | if: matrix.rust == 'nightly' && always() 47 | with: 48 | name: Cargo.lock 49 | path: Cargo.lock 50 | continue-on-error: true 51 | 52 | msrv: 53 | name: Rust 1.61.0 54 | needs: pre_ci 55 | if: needs.pre_ci.outputs.continue 56 | runs-on: ubuntu-latest 57 | timeout-minutes: 45 58 | steps: 59 | - uses: actions/checkout@v4 60 | - uses: dtolnay/rust-toolchain@1.61.0 61 | with: 62 | components: rust-src 63 | - run: cargo check 64 | 65 | minimal: 66 | name: Minimal versions 67 | needs: pre_ci 68 | if: needs.pre_ci.outputs.continue 69 | runs-on: ubuntu-latest 70 | timeout-minutes: 45 71 | steps: 72 | - uses: actions/checkout@v4 73 | - uses: dtolnay/rust-toolchain@nightly 74 | - run: cargo generate-lockfile -Z minimal-versions 75 | - run: cargo check --locked 76 | 77 | doc: 78 | name: Documentation 79 | needs: pre_ci 80 | if: needs.pre_ci.outputs.continue 81 | runs-on: ubuntu-latest 82 | timeout-minutes: 45 83 | env: 84 | RUSTDOCFLAGS: -Dwarnings 85 | steps: 86 | - uses: actions/checkout@v4 87 | - uses: dtolnay/rust-toolchain@nightly 88 | with: 89 | components: rust-src 90 | - uses: dtolnay/install@cargo-docs-rs 91 | - run: cargo docs-rs 92 | 93 | clippy: 94 | name: Clippy 95 | runs-on: ubuntu-latest 96 | if: github.event_name != 'pull_request' 97 | timeout-minutes: 45 98 | steps: 99 | - uses: actions/checkout@v4 100 | - uses: dtolnay/rust-toolchain@nightly 101 | with: 102 | components: clippy, rust-src 103 | - run: cargo clippy --tests --workspace -- -Dclippy::all -Dclippy::pedantic 104 | 105 | miri: 106 | name: Miri 107 | needs: pre_ci 108 | if: needs.pre_ci.outputs.continue 109 | runs-on: ubuntu-latest 110 | timeout-minutes: 45 111 | steps: 112 | - uses: actions/checkout@v4 113 | - uses: dtolnay/rust-toolchain@miri 114 | with: 115 | toolchain: nightly-2025-05-16 # https://github.com/rust-lang/miri/issues/4323 116 | - run: cargo miri setup 117 | - run: cargo miri test 118 | env: 119 | MIRIFLAGS: -Zmiri-strict-provenance 120 | 121 | outdated: 122 | name: Outdated 123 | runs-on: ubuntu-latest 124 | if: github.event_name != 'pull_request' 125 | timeout-minutes: 45 126 | steps: 127 | - uses: actions/checkout@v4 128 | - uses: dtolnay/rust-toolchain@stable 129 | - uses: dtolnay/install@cargo-outdated 130 | - run: cargo outdated --workspace --exit-code 1 131 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thiserror" 3 | version = "2.0.12" 4 | authors = ["David Tolnay "] 5 | categories = ["rust-patterns"] 6 | description = "derive(Error)" 7 | documentation = "https://docs.rs/thiserror" 8 | edition = "2021" 9 | keywords = ["error", "error-handling", "derive"] 10 | license = "MIT OR Apache-2.0" 11 | repository = "https://github.com/dtolnay/thiserror" 12 | rust-version = "1.61" 13 | 14 | [features] 15 | default = ["std"] 16 | 17 | # Std feature enables support for formatting std::path::{Path, PathBuf} 18 | # conveniently in an error message. 19 | # 20 | # #[derive(Error, Debug)] 21 | # #[error("failed to create configuration file {path}")] 22 | # pub struct MyError { 23 | # pub path: PathBuf, 24 | # pub source: std::io::Error, 25 | # } 26 | # 27 | # Without std, this would need to be written #[error("... {}", path.display())]. 28 | std = [] 29 | 30 | [dependencies] 31 | thiserror-impl = { version = "=2.0.12", path = "impl" } 32 | 33 | [dev-dependencies] 34 | anyhow = "1.0.73" 35 | ref-cast = "1.0.18" 36 | rustversion = "1.0.13" 37 | trybuild = { version = "1.0.81", features = ["diff"] } 38 | 39 | [workspace] 40 | members = ["impl", "tests/no-std"] 41 | 42 | [package.metadata.docs.rs] 43 | targets = ["x86_64-unknown-linux-gnu"] 44 | rustdoc-args = [ 45 | "--generate-link-to-definition", 46 | "--extern-html-root-url=core=https://doc.rust-lang.org", 47 | "--extern-html-root-url=alloc=https://doc.rust-lang.org", 48 | "--extern-html-root-url=std=https://doc.rust-lang.org", 49 | ] 50 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | derive(Error) 2 | ============= 3 | 4 | [github](https://github.com/dtolnay/thiserror) 5 | [crates.io](https://crates.io/crates/thiserror) 6 | [docs.rs](https://docs.rs/thiserror) 7 | [build status](https://github.com/dtolnay/thiserror/actions?query=branch%3Amaster) 8 | 9 | This library provides a convenient derive macro for the standard library's 10 | [`std::error::Error`] trait. 11 | 12 | [`std::error::Error`]: https://doc.rust-lang.org/std/error/trait.Error.html 13 | 14 | ```toml 15 | [dependencies] 16 | thiserror = "2" 17 | ``` 18 | 19 | *Compiler support: requires rustc 1.61+* 20 | 21 |
22 | 23 | ## Example 24 | 25 | ```rust 26 | use thiserror::Error; 27 | 28 | #[derive(Error, Debug)] 29 | pub enum DataStoreError { 30 | #[error("data store disconnected")] 31 | Disconnect(#[from] io::Error), 32 | #[error("the data for key `{0}` is not available")] 33 | Redaction(String), 34 | #[error("invalid header (expected {expected:?}, found {found:?})")] 35 | InvalidHeader { 36 | expected: String, 37 | found: String, 38 | }, 39 | #[error("unknown data store error")] 40 | Unknown, 41 | } 42 | ``` 43 | 44 |
45 | 46 | ## Details 47 | 48 | - Thiserror deliberately does not appear in your public API. You get the same 49 | thing as if you had written an implementation of `std::error::Error` by hand, 50 | and switching from handwritten impls to thiserror or vice versa is not a 51 | breaking change. 52 | 53 | - Errors may be enums, structs with named fields, tuple structs, or unit 54 | structs. 55 | 56 | - A `Display` impl is generated for your error if you provide `#[error("...")]` 57 | messages on the struct or each variant of your enum, as shown above in the 58 | example. 59 | 60 | The messages support a shorthand for interpolating fields from the error. 61 | 62 | - `#[error("{var}")]` ⟶ `write!("{}", self.var)` 63 | - `#[error("{0}")]` ⟶ `write!("{}", self.0)` 64 | - `#[error("{var:?}")]` ⟶ `write!("{:?}", self.var)` 65 | - `#[error("{0:?}")]` ⟶ `write!("{:?}", self.0)` 66 | 67 | These shorthands can be used together with any additional format args, which 68 | may be arbitrary expressions. For example: 69 | 70 | ```rust 71 | #[derive(Error, Debug)] 72 | pub enum Error { 73 | #[error("invalid rdo_lookahead_frames {0} (expected < {max})", max = i32::MAX)] 74 | InvalidLookahead(u32), 75 | } 76 | ``` 77 | 78 | If one of the additional expression arguments needs to refer to a field of the 79 | struct or enum, then refer to named fields as `.var` and tuple fields as `.0`. 80 | 81 | ```rust 82 | #[derive(Error, Debug)] 83 | pub enum Error { 84 | #[error("first letter must be lowercase but was {:?}", first_char(.0))] 85 | WrongCase(String), 86 | #[error("invalid index {idx}, expected at least {} and at most {}", .limits.lo, .limits.hi)] 87 | OutOfBounds { idx: usize, limits: Limits }, 88 | } 89 | ``` 90 | 91 | - A `From` impl is generated for each variant that contains a `#[from]` 92 | attribute. 93 | 94 | The variant using `#[from]` must not contain any other fields beyond the 95 | source error (and possibly a backtrace — see below). Usually `#[from]` 96 | fields are unnamed, but `#[from]` is allowed on a named field too. 97 | 98 | ```rust 99 | #[derive(Error, Debug)] 100 | pub enum MyError { 101 | Io(#[from] io::Error), 102 | Glob(#[from] globset::Error), 103 | } 104 | ``` 105 | 106 | - The Error trait's `source()` method is implemented to return whichever field 107 | has a `#[source]` attribute or is named `source`, if any. This is for 108 | identifying the underlying lower level error that caused your error. 109 | 110 | The `#[from]` attribute always implies that the same field is `#[source]`, so 111 | you don't ever need to specify both attributes. 112 | 113 | Any error type that implements `std::error::Error` or dereferences to `dyn 114 | std::error::Error` will work as a source. 115 | 116 | ```rust 117 | #[derive(Error, Debug)] 118 | pub struct MyError { 119 | msg: String, 120 | #[source] // optional if field name is `source` 121 | source: anyhow::Error, 122 | } 123 | ``` 124 | 125 | - The Error trait's `provide()` method is implemented to provide whichever field 126 | has a type named `Backtrace`, if any, as a `std::backtrace::Backtrace`. Using 127 | `Backtrace` in errors requires a nightly compiler with Rust version 1.73 or 128 | newer. 129 | 130 | ```rust 131 | use std::backtrace::Backtrace; 132 | 133 | #[derive(Error, Debug)] 134 | pub struct MyError { 135 | msg: String, 136 | backtrace: Backtrace, // automatically detected 137 | } 138 | ``` 139 | 140 | - If a field is both a source (named `source`, or has `#[source]` or `#[from]` 141 | attribute) *and* is marked `#[backtrace]`, then the Error trait's `provide()` 142 | method is forwarded to the source's `provide` so that both layers of the error 143 | share the same backtrace. The `#[backtrace]` attribute requires a nightly 144 | compiler with Rust version 1.73 or newer. 145 | 146 | 147 | ```rust 148 | #[derive(Error, Debug)] 149 | pub enum MyError { 150 | Io { 151 | #[backtrace] 152 | source: io::Error, 153 | }, 154 | } 155 | ``` 156 | 157 | - For variants that use `#[from]` and also contain a `Backtrace` field, a 158 | backtrace is captured from within the `From` impl. 159 | 160 | ```rust 161 | #[derive(Error, Debug)] 162 | pub enum MyError { 163 | Io { 164 | #[from] 165 | source: io::Error, 166 | backtrace: Backtrace, 167 | }, 168 | } 169 | ``` 170 | 171 | - Errors may use `error(transparent)` to forward the source and Display methods 172 | straight through to an underlying error without adding an additional message. 173 | This would be appropriate for enums that need an "anything else" variant. 174 | 175 | ```rust 176 | #[derive(Error, Debug)] 177 | pub enum MyError { 178 | ... 179 | 180 | #[error(transparent)] 181 | Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error 182 | } 183 | ``` 184 | 185 | Another use case is hiding implementation details of an error representation 186 | behind an opaque error type, so that the representation is able to evolve 187 | without breaking the crate's public API. 188 | 189 | ```rust 190 | // PublicError is public, but opaque and easy to keep compatible. 191 | #[derive(Error, Debug)] 192 | #[error(transparent)] 193 | pub struct PublicError(#[from] ErrorRepr); 194 | 195 | impl PublicError { 196 | // Accessors for anything we do want to expose publicly. 197 | } 198 | 199 | // Private and free to change across minor version of the crate. 200 | #[derive(Error, Debug)] 201 | enum ErrorRepr { 202 | ... 203 | } 204 | ``` 205 | 206 | - See also the [`anyhow`] library for a convenient single error type to use in 207 | application code. 208 | 209 | [`anyhow`]: https://github.com/dtolnay/anyhow 210 | 211 |
212 | 213 | ## Comparison to anyhow 214 | 215 | Use thiserror if you care about designing your own dedicated error type(s) so 216 | that the caller receives exactly the information that you choose in the event of 217 | failure. This most often applies to library-like code. Use [Anyhow] if you don't 218 | care what error type your functions return, you just want it to be easy. This is 219 | common in application-like code. 220 | 221 | [Anyhow]: https://github.com/dtolnay/anyhow 222 | 223 |
224 | 225 | #### License 226 | 227 | 228 | Licensed under either of Apache License, Version 229 | 2.0 or MIT license at your option. 230 | 231 | 232 |
233 | 234 | 235 | Unless you explicitly state otherwise, any contribution intentionally submitted 236 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 237 | be dual licensed as above, without any additional terms or conditions. 238 | 239 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::OsString; 3 | use std::fs; 4 | use std::io::ErrorKind; 5 | use std::iter; 6 | use std::path::Path; 7 | use std::process::{self, Command, Stdio}; 8 | use std::str; 9 | 10 | fn main() { 11 | println!("cargo:rerun-if-changed=build/probe.rs"); 12 | 13 | println!("cargo:rustc-check-cfg=cfg(error_generic_member_access)"); 14 | println!("cargo:rustc-check-cfg=cfg(thiserror_nightly_testing)"); 15 | println!("cargo:rustc-check-cfg=cfg(thiserror_no_backtrace_type)"); 16 | 17 | let error_generic_member_access; 18 | let consider_rustc_bootstrap; 19 | if compile_probe(false) { 20 | // This is a nightly or dev compiler, so it supports unstable features 21 | // regardless of RUSTC_BOOTSTRAP. No need to rerun build script if 22 | // RUSTC_BOOTSTRAP is changed. 23 | error_generic_member_access = true; 24 | consider_rustc_bootstrap = false; 25 | } else if let Some(rustc_bootstrap) = env::var_os("RUSTC_BOOTSTRAP") { 26 | if compile_probe(true) { 27 | // This is a stable or beta compiler for which the user has set 28 | // RUSTC_BOOTSTRAP to turn on unstable features. Rerun build script 29 | // if they change it. 30 | error_generic_member_access = true; 31 | consider_rustc_bootstrap = true; 32 | } else if rustc_bootstrap == "1" { 33 | // This compiler does not support the generic member access API in 34 | // the form that thiserror expects. No need to pay attention to 35 | // RUSTC_BOOTSTRAP. 36 | error_generic_member_access = false; 37 | consider_rustc_bootstrap = false; 38 | } else { 39 | // This is a stable or beta compiler for which RUSTC_BOOTSTRAP is 40 | // set to restrict the use of unstable features by this crate. 41 | error_generic_member_access = false; 42 | consider_rustc_bootstrap = true; 43 | } 44 | } else { 45 | // Without RUSTC_BOOTSTRAP, this compiler does not support the generic 46 | // member access API in the form that thiserror expects, but try again 47 | // if the user turns on unstable features. 48 | error_generic_member_access = false; 49 | consider_rustc_bootstrap = true; 50 | } 51 | 52 | if error_generic_member_access { 53 | println!("cargo:rustc-cfg=error_generic_member_access"); 54 | } 55 | 56 | if consider_rustc_bootstrap { 57 | println!("cargo:rerun-if-env-changed=RUSTC_BOOTSTRAP"); 58 | } 59 | 60 | // core::error::Error stabilized in Rust 1.81 61 | // https://blog.rust-lang.org/2024/09/05/Rust-1.81.0.html#coreerrorerror 62 | let rustc = rustc_minor_version(); 63 | if cfg!(not(feature = "std")) && rustc.map_or(false, |rustc| rustc < 81) { 64 | println!("cargo:rustc-cfg=feature=\"std\""); 65 | } 66 | 67 | let rustc = match rustc { 68 | Some(rustc) => rustc, 69 | None => return, 70 | }; 71 | 72 | // std::backtrace::Backtrace stabilized in Rust 1.65 73 | // https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#stabilized-apis 74 | if rustc < 65 { 75 | println!("cargo:rustc-cfg=thiserror_no_backtrace_type"); 76 | } 77 | } 78 | 79 | fn compile_probe(rustc_bootstrap: bool) -> bool { 80 | if env::var_os("RUSTC_STAGE").is_some() { 81 | // We are running inside rustc bootstrap. This is a highly non-standard 82 | // environment with issues such as: 83 | // 84 | // https://github.com/rust-lang/cargo/issues/11138 85 | // https://github.com/rust-lang/rust/issues/114839 86 | // 87 | // Let's just not use nightly features here. 88 | return false; 89 | } 90 | 91 | let rustc = cargo_env_var("RUSTC"); 92 | let out_dir = cargo_env_var("OUT_DIR"); 93 | let out_subdir = Path::new(&out_dir).join("probe"); 94 | let probefile = Path::new("build").join("probe.rs"); 95 | 96 | if let Err(err) = fs::create_dir(&out_subdir) { 97 | if err.kind() != ErrorKind::AlreadyExists { 98 | eprintln!("Failed to create {}: {}", out_subdir.display(), err); 99 | process::exit(1); 100 | } 101 | } 102 | 103 | let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|wrapper| !wrapper.is_empty()); 104 | let rustc_workspace_wrapper = 105 | env::var_os("RUSTC_WORKSPACE_WRAPPER").filter(|wrapper| !wrapper.is_empty()); 106 | let mut rustc = rustc_wrapper 107 | .into_iter() 108 | .chain(rustc_workspace_wrapper) 109 | .chain(iter::once(rustc)); 110 | let mut cmd = Command::new(rustc.next().unwrap()); 111 | cmd.args(rustc); 112 | 113 | if !rustc_bootstrap { 114 | cmd.env_remove("RUSTC_BOOTSTRAP"); 115 | } 116 | 117 | cmd.stderr(Stdio::null()) 118 | .arg("--edition=2018") 119 | .arg("--crate-name=thiserror") 120 | .arg("--crate-type=lib") 121 | .arg("--cap-lints=allow") 122 | .arg("--emit=dep-info,metadata") 123 | .arg("--out-dir") 124 | .arg(&out_subdir) 125 | .arg(probefile); 126 | 127 | if let Some(target) = env::var_os("TARGET") { 128 | cmd.arg("--target").arg(target); 129 | } 130 | 131 | // If Cargo wants to set RUSTFLAGS, use that. 132 | if let Ok(rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") { 133 | if !rustflags.is_empty() { 134 | for arg in rustflags.split('\x1f') { 135 | cmd.arg(arg); 136 | } 137 | } 138 | } 139 | 140 | let success = match cmd.status() { 141 | Ok(status) => status.success(), 142 | Err(_) => false, 143 | }; 144 | 145 | // Clean up to avoid leaving nondeterministic absolute paths in the dep-info 146 | // file in OUT_DIR, which causes nonreproducible builds in build systems 147 | // that treat the entire OUT_DIR as an artifact. 148 | if let Err(err) = fs::remove_dir_all(&out_subdir) { 149 | if err.kind() != ErrorKind::NotFound { 150 | eprintln!("Failed to clean up {}: {}", out_subdir.display(), err); 151 | process::exit(1); 152 | } 153 | } 154 | 155 | success 156 | } 157 | 158 | fn rustc_minor_version() -> Option { 159 | let rustc = cargo_env_var("RUSTC"); 160 | let output = Command::new(rustc).arg("--version").output().ok()?; 161 | let version = str::from_utf8(&output.stdout).ok()?; 162 | let mut pieces = version.split('.'); 163 | if pieces.next() != Some("rustc 1") { 164 | return None; 165 | } 166 | pieces.next()?.parse().ok() 167 | } 168 | 169 | fn cargo_env_var(key: &str) -> OsString { 170 | env::var_os(key).unwrap_or_else(|| { 171 | eprintln!("Environment variable ${key} is not set during execution of build script"); 172 | process::exit(1); 173 | }) 174 | } 175 | -------------------------------------------------------------------------------- /build/probe.rs: -------------------------------------------------------------------------------- 1 | // This code exercises the surface area that we expect of the Error generic 2 | // member access API. If the current toolchain is able to compile it, then 3 | // thiserror is able to provide backtrace support. 4 | 5 | #![no_std] 6 | #![feature(error_generic_member_access)] 7 | 8 | use core::error::{Error, Request}; 9 | use core::fmt::{self, Debug, Display}; 10 | 11 | struct MyError(Thing); 12 | struct Thing; 13 | 14 | impl Debug for MyError { 15 | fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { 16 | unimplemented!() 17 | } 18 | } 19 | 20 | impl Display for MyError { 21 | fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { 22 | unimplemented!() 23 | } 24 | } 25 | 26 | impl Error for MyError { 27 | fn provide<'a>(&'a self, request: &mut Request<'a>) { 28 | request.provide_ref(&self.0); 29 | } 30 | } 31 | 32 | // Include in sccache cache key. 33 | const _: Option<&str> = option_env!("RUSTC_BOOTSTRAP"); 34 | -------------------------------------------------------------------------------- /impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thiserror-impl" 3 | version = "2.0.12" 4 | authors = ["David Tolnay "] 5 | description = "Implementation detail of the `thiserror` crate" 6 | edition = "2021" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/dtolnay/thiserror" 9 | rust-version = "1.61" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | proc-macro2 = "1.0.74" 16 | quote = "1.0.35" 17 | syn = "2.0.87" 18 | 19 | [package.metadata.docs.rs] 20 | targets = ["x86_64-unknown-linux-gnu"] 21 | rustdoc-args = [ 22 | "--generate-link-to-definition", 23 | "--extern-html-root-url=core=https://doc.rust-lang.org", 24 | "--extern-html-root-url=alloc=https://doc.rust-lang.org", 25 | "--extern-html-root-url=std=https://doc.rust-lang.org", 26 | "--extern-html-root-url=proc_macro=https://doc.rust-lang.org", 27 | ] 28 | -------------------------------------------------------------------------------- /impl/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /impl/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /impl/src/ast.rs: -------------------------------------------------------------------------------- 1 | use crate::attr::{self, Attrs}; 2 | use crate::generics::ParamsInScope; 3 | use crate::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 attrs: Attrs<'a>, 17 | pub ident: Ident, 18 | pub generics: &'a Generics, 19 | pub fields: Vec>, 20 | } 21 | 22 | pub struct Enum<'a> { 23 | pub attrs: Attrs<'a>, 24 | pub ident: Ident, 25 | pub generics: &'a Generics, 26 | pub variants: Vec>, 27 | } 28 | 29 | pub struct Variant<'a> { 30 | pub original: &'a syn::Variant, 31 | pub attrs: Attrs<'a>, 32 | pub ident: Ident, 33 | pub fields: Vec>, 34 | } 35 | 36 | pub struct Field<'a> { 37 | pub original: &'a syn::Field, 38 | pub attrs: Attrs<'a>, 39 | pub member: MemberUnraw, 40 | pub ty: &'a Type, 41 | pub contains_generic: bool, 42 | } 43 | 44 | #[derive(Copy, Clone)] 45 | pub enum ContainerKind { 46 | Struct, 47 | TupleStruct, 48 | UnitStruct, 49 | StructVariant, 50 | TupleVariant, 51 | UnitVariant, 52 | } 53 | 54 | impl<'a> Input<'a> { 55 | pub fn from_syn(node: &'a DeriveInput) -> Result { 56 | match &node.data { 57 | Data::Struct(data) => Struct::from_syn(node, data).map(Input::Struct), 58 | Data::Enum(data) => Enum::from_syn(node, data).map(Input::Enum), 59 | Data::Union(_) => Err(Error::new_spanned( 60 | node, 61 | "union as errors are not supported", 62 | )), 63 | } 64 | } 65 | } 66 | 67 | impl<'a> Struct<'a> { 68 | fn from_syn(node: &'a DeriveInput, data: &'a DataStruct) -> Result { 69 | let mut attrs = attr::get(&node.attrs)?; 70 | let scope = ParamsInScope::new(&node.generics); 71 | let fields = Field::multiple_from_syn(&data.fields, &scope)?; 72 | if let Some(display) = &mut attrs.display { 73 | let container = ContainerKind::from_struct(data); 74 | display.expand_shorthand(&fields, container)?; 75 | } 76 | Ok(Struct { 77 | attrs, 78 | ident: node.ident.clone(), 79 | generics: &node.generics, 80 | fields, 81 | }) 82 | } 83 | } 84 | 85 | impl<'a> Enum<'a> { 86 | fn from_syn(node: &'a DeriveInput, data: &'a DataEnum) -> Result { 87 | let attrs = attr::get(&node.attrs)?; 88 | let scope = ParamsInScope::new(&node.generics); 89 | let variants = data 90 | .variants 91 | .iter() 92 | .map(|node| { 93 | let mut variant = Variant::from_syn(node, &scope)?; 94 | if variant.attrs.display.is_none() 95 | && variant.attrs.transparent.is_none() 96 | && variant.attrs.fmt.is_none() 97 | { 98 | variant.attrs.display.clone_from(&attrs.display); 99 | variant.attrs.transparent = attrs.transparent; 100 | variant.attrs.fmt.clone_from(&attrs.fmt); 101 | } 102 | if let Some(display) = &mut variant.attrs.display { 103 | let container = ContainerKind::from_variant(node); 104 | display.expand_shorthand(&variant.fields, container)?; 105 | } 106 | Ok(variant) 107 | }) 108 | .collect::>()?; 109 | Ok(Enum { 110 | attrs, 111 | ident: node.ident.clone(), 112 | generics: &node.generics, 113 | variants, 114 | }) 115 | } 116 | } 117 | 118 | impl<'a> Variant<'a> { 119 | fn from_syn(node: &'a syn::Variant, scope: &ParamsInScope<'a>) -> Result { 120 | let attrs = attr::get(&node.attrs)?; 121 | Ok(Variant { 122 | original: node, 123 | attrs, 124 | ident: node.ident.clone(), 125 | fields: Field::multiple_from_syn(&node.fields, scope)?, 126 | }) 127 | } 128 | } 129 | 130 | impl<'a> Field<'a> { 131 | fn multiple_from_syn(fields: &'a Fields, scope: &ParamsInScope<'a>) -> Result> { 132 | fields 133 | .iter() 134 | .enumerate() 135 | .map(|(i, field)| Field::from_syn(i, field, scope)) 136 | .collect() 137 | } 138 | 139 | fn from_syn(i: usize, node: &'a syn::Field, scope: &ParamsInScope<'a>) -> Result { 140 | Ok(Field { 141 | original: node, 142 | attrs: attr::get(&node.attrs)?, 143 | member: match &node.ident { 144 | Some(name) => MemberUnraw::Named(IdentUnraw::new(name.clone())), 145 | None => MemberUnraw::Unnamed(Index { 146 | index: i as u32, 147 | span: Span::call_site(), 148 | }), 149 | }, 150 | ty: &node.ty, 151 | contains_generic: scope.intersects(&node.ty), 152 | }) 153 | } 154 | } 155 | 156 | impl ContainerKind { 157 | fn from_struct(node: &DataStruct) -> Self { 158 | match node.fields { 159 | Fields::Named(_) => ContainerKind::Struct, 160 | Fields::Unnamed(_) => ContainerKind::TupleStruct, 161 | Fields::Unit => ContainerKind::UnitStruct, 162 | } 163 | } 164 | 165 | fn from_variant(node: &syn::Variant) -> Self { 166 | match node.fields { 167 | Fields::Named(_) => ContainerKind::StructVariant, 168 | Fields::Unnamed(_) => ContainerKind::TupleVariant, 169 | Fields::Unit => ContainerKind::UnitVariant, 170 | } 171 | } 172 | } 173 | 174 | impl Display for ContainerKind { 175 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 176 | formatter.write_str(match self { 177 | ContainerKind::Struct => "struct", 178 | ContainerKind::TupleStruct => "tuple struct", 179 | ContainerKind::UnitStruct => "unit struct", 180 | ContainerKind::StructVariant => "struct variant", 181 | ContainerKind::TupleVariant => "tuple variant", 182 | ContainerKind::UnitVariant => "unit variant", 183 | }) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /impl/src/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 Attrs<'a> { 12 | pub display: Option>, 13 | pub source: Option>, 14 | pub backtrace: Option<&'a Attribute>, 15 | pub from: Option>, 16 | pub transparent: Option>, 17 | pub fmt: Option>, 18 | } 19 | 20 | #[derive(Clone)] 21 | pub struct Display<'a> { 22 | pub original: &'a Attribute, 23 | pub fmt: LitStr, 24 | pub args: TokenStream, 25 | pub requires_fmt_machinery: bool, 26 | pub has_bonus_display: bool, 27 | pub infinite_recursive: bool, 28 | pub implied_bounds: Set<(usize, Trait)>, 29 | pub bindings: Vec<(Ident, TokenStream)>, 30 | } 31 | 32 | #[derive(Copy, Clone)] 33 | pub struct Source<'a> { 34 | pub original: &'a Attribute, 35 | pub span: Span, 36 | } 37 | 38 | #[derive(Copy, Clone)] 39 | pub struct From<'a> { 40 | pub original: &'a Attribute, 41 | pub span: Span, 42 | } 43 | 44 | #[derive(Copy, Clone)] 45 | pub struct Transparent<'a> { 46 | pub original: &'a Attribute, 47 | pub span: Span, 48 | } 49 | 50 | #[derive(Clone)] 51 | pub struct Fmt<'a> { 52 | pub original: &'a Attribute, 53 | pub path: ExprPath, 54 | } 55 | 56 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] 57 | pub enum Trait { 58 | Debug, 59 | Display, 60 | Octal, 61 | LowerHex, 62 | UpperHex, 63 | Pointer, 64 | Binary, 65 | LowerExp, 66 | UpperExp, 67 | } 68 | 69 | pub fn get(input: &[Attribute]) -> Result { 70 | let mut attrs = Attrs { 71 | display: None, 72 | source: None, 73 | backtrace: None, 74 | from: None, 75 | transparent: None, 76 | fmt: None, 77 | }; 78 | 79 | for attr in input { 80 | if attr.path().is_ident("error") { 81 | parse_error_attribute(&mut attrs, attr)?; 82 | } else if attr.path().is_ident("source") { 83 | attr.meta.require_path_only()?; 84 | if attrs.source.is_some() { 85 | return Err(Error::new_spanned(attr, "duplicate #[source] attribute")); 86 | } 87 | let span = (attr.pound_token.span) 88 | .join(attr.bracket_token.span.join()) 89 | .unwrap_or(attr.path().get_ident().unwrap().span()); 90 | attrs.source = Some(Source { 91 | original: attr, 92 | span, 93 | }); 94 | } else if attr.path().is_ident("backtrace") { 95 | attr.meta.require_path_only()?; 96 | if attrs.backtrace.is_some() { 97 | return Err(Error::new_spanned(attr, "duplicate #[backtrace] attribute")); 98 | } 99 | attrs.backtrace = Some(attr); 100 | } else if attr.path().is_ident("from") { 101 | match attr.meta { 102 | Meta::Path(_) => {} 103 | Meta::List(_) | Meta::NameValue(_) => { 104 | // Assume this is meant for derive_more crate or something. 105 | continue; 106 | } 107 | } 108 | if attrs.from.is_some() { 109 | return Err(Error::new_spanned(attr, "duplicate #[from] attribute")); 110 | } 111 | let span = (attr.pound_token.span) 112 | .join(attr.bracket_token.span.join()) 113 | .unwrap_or(attr.path().get_ident().unwrap().span()); 114 | attrs.from = Some(From { 115 | original: attr, 116 | span, 117 | }); 118 | } 119 | } 120 | 121 | Ok(attrs) 122 | } 123 | 124 | fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Result<()> { 125 | mod kw { 126 | syn::custom_keyword!(transparent); 127 | syn::custom_keyword!(fmt); 128 | } 129 | 130 | attr.parse_args_with(|input: ParseStream| { 131 | let lookahead = input.lookahead1(); 132 | let fmt = if lookahead.peek(LitStr) { 133 | input.parse::()? 134 | } else if lookahead.peek(kw::transparent) { 135 | let kw: kw::transparent = input.parse()?; 136 | if attrs.transparent.is_some() { 137 | return Err(Error::new_spanned( 138 | attr, 139 | "duplicate #[error(transparent)] attribute", 140 | )); 141 | } 142 | attrs.transparent = Some(Transparent { 143 | original: attr, 144 | span: kw.span, 145 | }); 146 | return Ok(()); 147 | } else if lookahead.peek(kw::fmt) { 148 | input.parse::()?; 149 | input.parse::()?; 150 | let path: ExprPath = input.parse()?; 151 | if attrs.fmt.is_some() { 152 | return Err(Error::new_spanned( 153 | attr, 154 | "duplicate #[error(fmt = ...)] attribute", 155 | )); 156 | } 157 | attrs.fmt = Some(Fmt { 158 | original: attr, 159 | path, 160 | }); 161 | return Ok(()); 162 | } else { 163 | return Err(lookahead.error()); 164 | }; 165 | 166 | let args = if input.is_empty() || input.peek(Token![,]) && input.peek2(End) { 167 | input.parse::>()?; 168 | TokenStream::new() 169 | } else { 170 | parse_token_expr(input, false)? 171 | }; 172 | 173 | let requires_fmt_machinery = !args.is_empty(); 174 | 175 | let display = Display { 176 | original: attr, 177 | fmt, 178 | args, 179 | requires_fmt_machinery, 180 | has_bonus_display: false, 181 | infinite_recursive: false, 182 | implied_bounds: Set::new(), 183 | bindings: Vec::new(), 184 | }; 185 | if attrs.display.is_some() { 186 | return Err(Error::new_spanned( 187 | attr, 188 | "only one #[error(...)] attribute is allowed", 189 | )); 190 | } 191 | attrs.display = Some(display); 192 | Ok(()) 193 | }) 194 | } 195 | 196 | fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result { 197 | let mut tokens = Vec::new(); 198 | while !input.is_empty() { 199 | if input.peek(token::Group) { 200 | let group: TokenTree = input.parse()?; 201 | tokens.push(group); 202 | begin_expr = false; 203 | continue; 204 | } 205 | 206 | if begin_expr && input.peek(Token![.]) { 207 | if input.peek2(Ident) { 208 | input.parse::()?; 209 | begin_expr = false; 210 | continue; 211 | } else if input.peek2(LitInt) { 212 | input.parse::()?; 213 | let int: Index = input.parse()?; 214 | tokens.push({ 215 | let ident = format_ident!("_{}", int.index, span = int.span); 216 | TokenTree::Ident(ident) 217 | }); 218 | begin_expr = false; 219 | continue; 220 | } else if input.peek2(LitFloat) { 221 | let ahead = input.fork(); 222 | ahead.parse::()?; 223 | let float: LitFloat = ahead.parse()?; 224 | let repr = float.to_string(); 225 | let mut indices = repr.split('.').map(syn::parse_str::); 226 | if let (Some(Ok(first)), Some(Ok(second)), None) = 227 | (indices.next(), indices.next(), indices.next()) 228 | { 229 | input.advance_to(&ahead); 230 | tokens.push({ 231 | let ident = format_ident!("_{}", first, span = float.span()); 232 | TokenTree::Ident(ident) 233 | }); 234 | tokens.push({ 235 | let mut punct = Punct::new('.', Spacing::Alone); 236 | punct.set_span(float.span()); 237 | TokenTree::Punct(punct) 238 | }); 239 | tokens.push({ 240 | let mut literal = Literal::u32_unsuffixed(second.index); 241 | literal.set_span(float.span()); 242 | TokenTree::Literal(literal) 243 | }); 244 | begin_expr = false; 245 | continue; 246 | } 247 | } 248 | } 249 | 250 | begin_expr = input.peek(Token![break]) 251 | || input.peek(Token![continue]) 252 | || input.peek(Token![if]) 253 | || input.peek(Token![in]) 254 | || input.peek(Token![match]) 255 | || input.peek(Token![mut]) 256 | || input.peek(Token![return]) 257 | || input.peek(Token![while]) 258 | || input.peek(Token![+]) 259 | || input.peek(Token![&]) 260 | || input.peek(Token![!]) 261 | || input.peek(Token![^]) 262 | || input.peek(Token![,]) 263 | || input.peek(Token![/]) 264 | || input.peek(Token![=]) 265 | || input.peek(Token![>]) 266 | || input.peek(Token![<]) 267 | || input.peek(Token![|]) 268 | || input.peek(Token![%]) 269 | || input.peek(Token![;]) 270 | || input.peek(Token![*]) 271 | || input.peek(Token![-]); 272 | 273 | let token: TokenTree = if input.peek(token::Paren) { 274 | let content; 275 | let delimiter = parenthesized!(content in input); 276 | let nested = parse_token_expr(&content, true)?; 277 | let mut group = Group::new(Delimiter::Parenthesis, nested); 278 | group.set_span(delimiter.span.join()); 279 | TokenTree::Group(group) 280 | } else if input.peek(token::Brace) { 281 | let content; 282 | let delimiter = braced!(content in input); 283 | let nested = parse_token_expr(&content, true)?; 284 | let mut group = Group::new(Delimiter::Brace, nested); 285 | group.set_span(delimiter.span.join()); 286 | TokenTree::Group(group) 287 | } else if input.peek(token::Bracket) { 288 | let content; 289 | let delimiter = bracketed!(content in input); 290 | let nested = parse_token_expr(&content, true)?; 291 | let mut group = Group::new(Delimiter::Bracket, nested); 292 | group.set_span(delimiter.span.join()); 293 | TokenTree::Group(group) 294 | } else { 295 | input.parse()? 296 | }; 297 | tokens.push(token); 298 | } 299 | Ok(TokenStream::from_iter(tokens)) 300 | } 301 | 302 | impl ToTokens for Display<'_> { 303 | fn to_tokens(&self, tokens: &mut TokenStream) { 304 | if self.infinite_recursive { 305 | let span = self.fmt.span(); 306 | tokens.extend(quote_spanned! {span=> 307 | #[warn(unconditional_recursion)] 308 | fn _fmt() { _fmt() } 309 | }); 310 | } 311 | 312 | let fmt = &self.fmt; 313 | let args = &self.args; 314 | 315 | // Currently `write!(f, "text")` produces less efficient code than 316 | // `f.write_str("text")`. We recognize the case when the format string 317 | // has no braces and no interpolated values, and generate simpler code. 318 | let write = if self.requires_fmt_machinery { 319 | quote! { 320 | ::core::write!(__formatter, #fmt #args) 321 | } 322 | } else { 323 | quote! { 324 | __formatter.write_str(#fmt) 325 | } 326 | }; 327 | 328 | tokens.extend(if self.bindings.is_empty() { 329 | write 330 | } else { 331 | let locals = self.bindings.iter().map(|(local, _value)| local); 332 | let values = self.bindings.iter().map(|(_local, value)| value); 333 | quote! { 334 | match (#(#values,)*) { 335 | (#(#locals,)*) => #write 336 | } 337 | } 338 | }); 339 | } 340 | } 341 | 342 | impl ToTokens for Trait { 343 | fn to_tokens(&self, tokens: &mut TokenStream) { 344 | let trait_name = match self { 345 | Trait::Debug => "Debug", 346 | Trait::Display => "Display", 347 | Trait::Octal => "Octal", 348 | Trait::LowerHex => "LowerHex", 349 | Trait::UpperHex => "UpperHex", 350 | Trait::Pointer => "Pointer", 351 | Trait::Binary => "Binary", 352 | Trait::LowerExp => "LowerExp", 353 | Trait::UpperExp => "UpperExp", 354 | }; 355 | let ident = Ident::new(trait_name, Span::call_site()); 356 | tokens.extend(quote!(::core::fmt::#ident)); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /impl/src/expand.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{Enum, Field, Input, Struct}; 2 | use crate::attr::Trait; 3 | use crate::fallback; 4 | use crate::generics::InferredBounds; 5 | use crate::unraw::MemberUnraw; 6 | use proc_macro2::{Ident, Span, TokenStream}; 7 | use quote::{format_ident, quote, quote_spanned, ToTokens}; 8 | use std::collections::BTreeSet as Set; 9 | use syn::{DeriveInput, GenericArgument, PathArguments, Result, Token, Type}; 10 | 11 | pub fn derive(input: &DeriveInput) -> TokenStream { 12 | match try_expand(input) { 13 | Ok(expanded) => expanded, 14 | // If there are invalid attributes in the input, expand to an Error impl 15 | // anyway to minimize spurious secondary errors in other code that uses 16 | // this type as an Error. 17 | Err(error) => fallback::expand(input, error), 18 | } 19 | } 20 | 21 | fn try_expand(input: &DeriveInput) -> Result { 22 | let input = Input::from_syn(input)?; 23 | input.validate()?; 24 | Ok(match input { 25 | Input::Struct(input) => impl_struct(input), 26 | Input::Enum(input) => impl_enum(input), 27 | }) 28 | } 29 | 30 | fn impl_struct(input: Struct) -> TokenStream { 31 | let ty = call_site_ident(&input.ident); 32 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 33 | let mut error_inferred_bounds = InferredBounds::new(); 34 | 35 | let source_body = if let Some(transparent_attr) = &input.attrs.transparent { 36 | let only_field = &input.fields[0]; 37 | if only_field.contains_generic { 38 | error_inferred_bounds.insert(only_field.ty, quote!(::thiserror::__private::Error)); 39 | } 40 | let member = &only_field.member; 41 | Some(quote_spanned! {transparent_attr.span=> 42 | ::thiserror::__private::Error::source(self.#member.as_dyn_error()) 43 | }) 44 | } else if let Some(source_field) = input.source_field() { 45 | let source = &source_field.member; 46 | if source_field.contains_generic { 47 | let ty = unoptional_type(source_field.ty); 48 | error_inferred_bounds.insert(ty, quote!(::thiserror::__private::Error + 'static)); 49 | } 50 | let asref = if type_is_option(source_field.ty) { 51 | Some(quote_spanned!(source.span()=> .as_ref()?)) 52 | } else { 53 | None 54 | }; 55 | let dyn_error = quote_spanned! {source_field.source_span()=> 56 | self.#source #asref.as_dyn_error() 57 | }; 58 | Some(quote! { 59 | ::core::option::Option::Some(#dyn_error) 60 | }) 61 | } else { 62 | None 63 | }; 64 | let source_method = source_body.map(|body| { 65 | quote! { 66 | fn source(&self) -> ::core::option::Option<&(dyn ::thiserror::__private::Error + 'static)> { 67 | use ::thiserror::__private::AsDynError as _; 68 | #body 69 | } 70 | } 71 | }); 72 | 73 | let provide_method = input.backtrace_field().map(|backtrace_field| { 74 | let request = quote!(request); 75 | let backtrace = &backtrace_field.member; 76 | let body = if let Some(source_field) = input.source_field() { 77 | let source = &source_field.member; 78 | let source_provide = if type_is_option(source_field.ty) { 79 | quote_spanned! {source.span()=> 80 | if let ::core::option::Option::Some(source) = &self.#source { 81 | source.thiserror_provide(#request); 82 | } 83 | } 84 | } else { 85 | quote_spanned! {source.span()=> 86 | self.#source.thiserror_provide(#request); 87 | } 88 | }; 89 | let self_provide = if source == backtrace { 90 | None 91 | } else if type_is_option(backtrace_field.ty) { 92 | Some(quote! { 93 | if let ::core::option::Option::Some(backtrace) = &self.#backtrace { 94 | #request.provide_ref::<::thiserror::__private::Backtrace>(backtrace); 95 | } 96 | }) 97 | } else { 98 | Some(quote! { 99 | #request.provide_ref::<::thiserror::__private::Backtrace>(&self.#backtrace); 100 | }) 101 | }; 102 | quote! { 103 | use ::thiserror::__private::ThiserrorProvide as _; 104 | #source_provide 105 | #self_provide 106 | } 107 | } else if type_is_option(backtrace_field.ty) { 108 | quote! { 109 | if let ::core::option::Option::Some(backtrace) = &self.#backtrace { 110 | #request.provide_ref::<::thiserror::__private::Backtrace>(backtrace); 111 | } 112 | } 113 | } else { 114 | quote! { 115 | #request.provide_ref::<::thiserror::__private::Backtrace>(&self.#backtrace); 116 | } 117 | }; 118 | quote! { 119 | fn provide<'_request>(&'_request self, #request: &mut ::core::error::Request<'_request>) { 120 | #body 121 | } 122 | } 123 | }); 124 | 125 | let mut display_implied_bounds = Set::new(); 126 | let display_body = if input.attrs.transparent.is_some() { 127 | let only_field = &input.fields[0].member; 128 | display_implied_bounds.insert((0, Trait::Display)); 129 | Some(quote! { 130 | ::core::fmt::Display::fmt(&self.#only_field, __formatter) 131 | }) 132 | } else if let Some(display) = &input.attrs.display { 133 | display_implied_bounds.clone_from(&display.implied_bounds); 134 | let use_as_display = use_as_display(display.has_bonus_display); 135 | let pat = fields_pat(&input.fields); 136 | Some(quote! { 137 | #use_as_display 138 | #[allow(unused_variables, deprecated)] 139 | let Self #pat = self; 140 | #display 141 | }) 142 | } else { 143 | None 144 | }; 145 | let display_impl = display_body.map(|body| { 146 | let mut display_inferred_bounds = InferredBounds::new(); 147 | for (field, bound) in display_implied_bounds { 148 | let field = &input.fields[field]; 149 | if field.contains_generic { 150 | display_inferred_bounds.insert(field.ty, bound); 151 | } 152 | } 153 | let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics); 154 | quote! { 155 | #[allow(unused_qualifications)] 156 | #[automatically_derived] 157 | impl #impl_generics ::core::fmt::Display for #ty #ty_generics #display_where_clause { 158 | #[allow(clippy::used_underscore_binding)] 159 | fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 160 | #body 161 | } 162 | } 163 | } 164 | }); 165 | 166 | let from_impl = input.from_field().map(|from_field| { 167 | let span = from_field.attrs.from.unwrap().span; 168 | let backtrace_field = input.distinct_backtrace_field(); 169 | let from = unoptional_type(from_field.ty); 170 | let source_var = Ident::new("source", span); 171 | let body = from_initializer(from_field, backtrace_field, &source_var); 172 | let from_function = quote! { 173 | fn from(#source_var: #from) -> Self { 174 | #ty #body 175 | } 176 | }; 177 | let from_impl = quote_spanned! {span=> 178 | #[automatically_derived] 179 | impl #impl_generics ::core::convert::From<#from> for #ty #ty_generics #where_clause { 180 | #from_function 181 | } 182 | }; 183 | Some(quote! { 184 | #[allow( 185 | deprecated, 186 | unused_qualifications, 187 | clippy::elidable_lifetime_names, 188 | clippy::needless_lifetimes, 189 | )] 190 | #from_impl 191 | }) 192 | }); 193 | 194 | if input.generics.type_params().next().is_some() { 195 | let self_token = ::default(); 196 | error_inferred_bounds.insert(self_token, Trait::Debug); 197 | error_inferred_bounds.insert(self_token, Trait::Display); 198 | } 199 | let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics); 200 | 201 | quote! { 202 | #[allow(unused_qualifications)] 203 | #[automatically_derived] 204 | impl #impl_generics ::thiserror::__private::Error for #ty #ty_generics #error_where_clause { 205 | #source_method 206 | #provide_method 207 | } 208 | #display_impl 209 | #from_impl 210 | } 211 | } 212 | 213 | fn impl_enum(input: Enum) -> TokenStream { 214 | let ty = call_site_ident(&input.ident); 215 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 216 | let mut error_inferred_bounds = InferredBounds::new(); 217 | 218 | let source_method = if input.has_source() { 219 | let arms = input.variants.iter().map(|variant| { 220 | let ident = &variant.ident; 221 | if let Some(transparent_attr) = &variant.attrs.transparent { 222 | let only_field = &variant.fields[0]; 223 | if only_field.contains_generic { 224 | error_inferred_bounds.insert(only_field.ty, quote!(::thiserror::__private::Error)); 225 | } 226 | let member = &only_field.member; 227 | let source = quote_spanned! {transparent_attr.span=> 228 | ::thiserror::__private::Error::source(transparent.as_dyn_error()) 229 | }; 230 | quote! { 231 | #ty::#ident {#member: transparent} => #source, 232 | } 233 | } else if let Some(source_field) = variant.source_field() { 234 | let source = &source_field.member; 235 | if source_field.contains_generic { 236 | let ty = unoptional_type(source_field.ty); 237 | error_inferred_bounds.insert(ty, quote!(::thiserror::__private::Error + 'static)); 238 | } 239 | let asref = if type_is_option(source_field.ty) { 240 | Some(quote_spanned!(source.span()=> .as_ref()?)) 241 | } else { 242 | None 243 | }; 244 | let varsource = quote!(source); 245 | let dyn_error = quote_spanned! {source_field.source_span()=> 246 | #varsource #asref.as_dyn_error() 247 | }; 248 | quote! { 249 | #ty::#ident {#source: #varsource, ..} => ::core::option::Option::Some(#dyn_error), 250 | } 251 | } else { 252 | quote! { 253 | #ty::#ident {..} => ::core::option::Option::None, 254 | } 255 | } 256 | }); 257 | Some(quote! { 258 | fn source(&self) -> ::core::option::Option<&(dyn ::thiserror::__private::Error + 'static)> { 259 | use ::thiserror::__private::AsDynError as _; 260 | #[allow(deprecated)] 261 | match self { 262 | #(#arms)* 263 | } 264 | } 265 | }) 266 | } else { 267 | None 268 | }; 269 | 270 | let provide_method = if input.has_backtrace() { 271 | let request = quote!(request); 272 | let arms = input.variants.iter().map(|variant| { 273 | let ident = &variant.ident; 274 | match (variant.backtrace_field(), variant.source_field()) { 275 | (Some(backtrace_field), Some(source_field)) 276 | if backtrace_field.attrs.backtrace.is_none() => 277 | { 278 | let backtrace = &backtrace_field.member; 279 | let source = &source_field.member; 280 | let varsource = quote!(source); 281 | let source_provide = if type_is_option(source_field.ty) { 282 | quote_spanned! {source.span()=> 283 | if let ::core::option::Option::Some(source) = #varsource { 284 | source.thiserror_provide(#request); 285 | } 286 | } 287 | } else { 288 | quote_spanned! {source.span()=> 289 | #varsource.thiserror_provide(#request); 290 | } 291 | }; 292 | let self_provide = if type_is_option(backtrace_field.ty) { 293 | quote! { 294 | if let ::core::option::Option::Some(backtrace) = backtrace { 295 | #request.provide_ref::<::thiserror::__private::Backtrace>(backtrace); 296 | } 297 | } 298 | } else { 299 | quote! { 300 | #request.provide_ref::<::thiserror::__private::Backtrace>(backtrace); 301 | } 302 | }; 303 | quote! { 304 | #ty::#ident { 305 | #backtrace: backtrace, 306 | #source: #varsource, 307 | .. 308 | } => { 309 | use ::thiserror::__private::ThiserrorProvide as _; 310 | #source_provide 311 | #self_provide 312 | } 313 | } 314 | } 315 | (Some(backtrace_field), Some(source_field)) 316 | if backtrace_field.member == source_field.member => 317 | { 318 | let backtrace = &backtrace_field.member; 319 | let varsource = quote!(source); 320 | let source_provide = if type_is_option(source_field.ty) { 321 | quote_spanned! {backtrace.span()=> 322 | if let ::core::option::Option::Some(source) = #varsource { 323 | source.thiserror_provide(#request); 324 | } 325 | } 326 | } else { 327 | quote_spanned! {backtrace.span()=> 328 | #varsource.thiserror_provide(#request); 329 | } 330 | }; 331 | quote! { 332 | #ty::#ident {#backtrace: #varsource, ..} => { 333 | use ::thiserror::__private::ThiserrorProvide as _; 334 | #source_provide 335 | } 336 | } 337 | } 338 | (Some(backtrace_field), _) => { 339 | let backtrace = &backtrace_field.member; 340 | let body = if type_is_option(backtrace_field.ty) { 341 | quote! { 342 | if let ::core::option::Option::Some(backtrace) = backtrace { 343 | #request.provide_ref::<::thiserror::__private::Backtrace>(backtrace); 344 | } 345 | } 346 | } else { 347 | quote! { 348 | #request.provide_ref::<::thiserror::__private::Backtrace>(backtrace); 349 | } 350 | }; 351 | quote! { 352 | #ty::#ident {#backtrace: backtrace, ..} => { 353 | #body 354 | } 355 | } 356 | } 357 | (None, _) => quote! { 358 | #ty::#ident {..} => {} 359 | }, 360 | } 361 | }); 362 | Some(quote! { 363 | fn provide<'_request>(&'_request self, #request: &mut ::core::error::Request<'_request>) { 364 | #[allow(deprecated)] 365 | match self { 366 | #(#arms)* 367 | } 368 | } 369 | }) 370 | } else { 371 | None 372 | }; 373 | 374 | let display_impl = if input.has_display() { 375 | let mut display_inferred_bounds = InferredBounds::new(); 376 | let has_bonus_display = input.variants.iter().any(|v| { 377 | v.attrs 378 | .display 379 | .as_ref() 380 | .map_or(false, |display| display.has_bonus_display) 381 | }); 382 | let use_as_display = use_as_display(has_bonus_display); 383 | let void_deref = if input.variants.is_empty() { 384 | Some(quote!(*)) 385 | } else { 386 | None 387 | }; 388 | let arms = input.variants.iter().map(|variant| { 389 | let mut display_implied_bounds = Set::new(); 390 | let display = if let Some(display) = &variant.attrs.display { 391 | display_implied_bounds.clone_from(&display.implied_bounds); 392 | display.to_token_stream() 393 | } else if let Some(fmt) = &variant.attrs.fmt { 394 | let fmt_path = &fmt.path; 395 | let vars = variant.fields.iter().map(|field| match &field.member { 396 | MemberUnraw::Named(ident) => ident.to_local(), 397 | MemberUnraw::Unnamed(index) => format_ident!("_{}", index), 398 | }); 399 | quote!(#fmt_path(#(#vars,)* __formatter)) 400 | } else { 401 | let only_field = match &variant.fields[0].member { 402 | MemberUnraw::Named(ident) => ident.to_local(), 403 | MemberUnraw::Unnamed(index) => format_ident!("_{}", index), 404 | }; 405 | display_implied_bounds.insert((0, Trait::Display)); 406 | quote!(::core::fmt::Display::fmt(#only_field, __formatter)) 407 | }; 408 | for (field, bound) in display_implied_bounds { 409 | let field = &variant.fields[field]; 410 | if field.contains_generic { 411 | display_inferred_bounds.insert(field.ty, bound); 412 | } 413 | } 414 | let ident = &variant.ident; 415 | let pat = fields_pat(&variant.fields); 416 | quote! { 417 | #ty::#ident #pat => #display 418 | } 419 | }); 420 | let arms = arms.collect::>(); 421 | let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics); 422 | Some(quote! { 423 | #[allow(unused_qualifications)] 424 | #[automatically_derived] 425 | impl #impl_generics ::core::fmt::Display for #ty #ty_generics #display_where_clause { 426 | fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 427 | #use_as_display 428 | #[allow(unused_variables, deprecated, clippy::used_underscore_binding)] 429 | match #void_deref self { 430 | #(#arms,)* 431 | } 432 | } 433 | } 434 | }) 435 | } else { 436 | None 437 | }; 438 | 439 | let from_impls = input.variants.iter().filter_map(|variant| { 440 | let from_field = variant.from_field()?; 441 | let span = from_field.attrs.from.unwrap().span; 442 | let backtrace_field = variant.distinct_backtrace_field(); 443 | let variant = &variant.ident; 444 | let from = unoptional_type(from_field.ty); 445 | let source_var = Ident::new("source", span); 446 | let body = from_initializer(from_field, backtrace_field, &source_var); 447 | let from_function = quote! { 448 | fn from(#source_var: #from) -> Self { 449 | #ty::#variant #body 450 | } 451 | }; 452 | let from_impl = quote_spanned! {span=> 453 | #[automatically_derived] 454 | impl #impl_generics ::core::convert::From<#from> for #ty #ty_generics #where_clause { 455 | #from_function 456 | } 457 | }; 458 | Some(quote! { 459 | #[allow( 460 | deprecated, 461 | unused_qualifications, 462 | clippy::elidable_lifetime_names, 463 | clippy::needless_lifetimes, 464 | )] 465 | #from_impl 466 | }) 467 | }); 468 | 469 | if input.generics.type_params().next().is_some() { 470 | let self_token = ::default(); 471 | error_inferred_bounds.insert(self_token, Trait::Debug); 472 | error_inferred_bounds.insert(self_token, Trait::Display); 473 | } 474 | let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics); 475 | 476 | quote! { 477 | #[allow(unused_qualifications)] 478 | #[automatically_derived] 479 | impl #impl_generics ::thiserror::__private::Error for #ty #ty_generics #error_where_clause { 480 | #source_method 481 | #provide_method 482 | } 483 | #display_impl 484 | #(#from_impls)* 485 | } 486 | } 487 | 488 | // Create an ident with which we can expand `impl Trait for #ident {}` on a 489 | // deprecated type without triggering deprecation warning on the generated impl. 490 | pub(crate) fn call_site_ident(ident: &Ident) -> Ident { 491 | let mut ident = ident.clone(); 492 | ident.set_span(ident.span().resolved_at(Span::call_site())); 493 | ident 494 | } 495 | 496 | fn fields_pat(fields: &[Field]) -> TokenStream { 497 | let mut members = fields.iter().map(|field| &field.member).peekable(); 498 | match members.peek() { 499 | Some(MemberUnraw::Named(_)) => quote!({ #(#members),* }), 500 | Some(MemberUnraw::Unnamed(_)) => { 501 | let vars = members.map(|member| match member { 502 | MemberUnraw::Unnamed(index) => format_ident!("_{}", index), 503 | MemberUnraw::Named(_) => unreachable!(), 504 | }); 505 | quote!((#(#vars),*)) 506 | } 507 | None => quote!({}), 508 | } 509 | } 510 | 511 | fn use_as_display(needs_as_display: bool) -> Option { 512 | if needs_as_display { 513 | Some(quote! { 514 | use ::thiserror::__private::AsDisplay as _; 515 | }) 516 | } else { 517 | None 518 | } 519 | } 520 | 521 | fn from_initializer( 522 | from_field: &Field, 523 | backtrace_field: Option<&Field>, 524 | source_var: &Ident, 525 | ) -> TokenStream { 526 | let from_member = &from_field.member; 527 | let some_source = if type_is_option(from_field.ty) { 528 | quote!(::core::option::Option::Some(#source_var)) 529 | } else { 530 | quote!(#source_var) 531 | }; 532 | let backtrace = backtrace_field.map(|backtrace_field| { 533 | let backtrace_member = &backtrace_field.member; 534 | if type_is_option(backtrace_field.ty) { 535 | quote! { 536 | #backtrace_member: ::core::option::Option::Some(::thiserror::__private::Backtrace::capture()), 537 | } 538 | } else { 539 | quote! { 540 | #backtrace_member: ::core::convert::From::from(::thiserror::__private::Backtrace::capture()), 541 | } 542 | } 543 | }); 544 | quote!({ 545 | #from_member: #some_source, 546 | #backtrace 547 | }) 548 | } 549 | 550 | fn type_is_option(ty: &Type) -> bool { 551 | type_parameter_of_option(ty).is_some() 552 | } 553 | 554 | fn unoptional_type(ty: &Type) -> TokenStream { 555 | let unoptional = type_parameter_of_option(ty).unwrap_or(ty); 556 | quote!(#unoptional) 557 | } 558 | 559 | fn type_parameter_of_option(ty: &Type) -> Option<&Type> { 560 | let path = match ty { 561 | Type::Path(ty) => &ty.path, 562 | _ => return None, 563 | }; 564 | 565 | let last = path.segments.last().unwrap(); 566 | if last.ident != "Option" { 567 | return None; 568 | } 569 | 570 | let bracketed = match &last.arguments { 571 | PathArguments::AngleBracketed(bracketed) => bracketed, 572 | _ => return None, 573 | }; 574 | 575 | if bracketed.args.len() != 1 { 576 | return None; 577 | } 578 | 579 | match &bracketed.args[0] { 580 | GenericArgument::Type(arg) => Some(arg), 581 | _ => None, 582 | } 583 | } 584 | -------------------------------------------------------------------------------- /impl/src/fallback.rs: -------------------------------------------------------------------------------- 1 | use crate::expand::call_site_ident; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | use syn::DeriveInput; 5 | 6 | pub(crate) fn expand(input: &DeriveInput, error: syn::Error) -> TokenStream { 7 | let ty = call_site_ident(&input.ident); 8 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 9 | 10 | let error = error.to_compile_error(); 11 | 12 | quote! { 13 | #error 14 | 15 | #[allow(unused_qualifications)] 16 | #[automatically_derived] 17 | impl #impl_generics ::thiserror::__private::Error for #ty #ty_generics #where_clause 18 | where 19 | // Work around trivial bounds being unstable. 20 | // https://github.com/rust-lang/rust/issues/48214 21 | for<'workaround> #ty #ty_generics: ::core::fmt::Debug, 22 | {} 23 | 24 | #[allow(unused_qualifications)] 25 | #[automatically_derived] 26 | impl #impl_generics ::core::fmt::Display for #ty #ty_generics #where_clause { 27 | fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 28 | ::core::unreachable!() 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /impl/src/fmt.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{ContainerKind, Field}; 2 | use crate::attr::{Display, Trait}; 3 | use crate::scan_expr::scan_expr; 4 | use crate::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 | -------------------------------------------------------------------------------- /impl/src/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 | -------------------------------------------------------------------------------- /impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::blocks_in_conditions, 3 | clippy::cast_lossless, 4 | clippy::cast_possible_truncation, 5 | clippy::enum_glob_use, 6 | clippy::manual_find, 7 | clippy::manual_let_else, 8 | clippy::manual_map, 9 | clippy::map_unwrap_or, 10 | clippy::module_name_repetitions, 11 | clippy::needless_pass_by_value, 12 | clippy::range_plus_one, 13 | clippy::single_match_else, 14 | clippy::struct_field_names, 15 | clippy::too_many_lines, 16 | clippy::wrong_self_convention 17 | )] 18 | 19 | extern crate proc_macro; 20 | 21 | mod ast; 22 | mod attr; 23 | mod expand; 24 | mod fallback; 25 | mod fmt; 26 | mod generics; 27 | mod prop; 28 | mod scan_expr; 29 | mod unraw; 30 | mod valid; 31 | 32 | use proc_macro::TokenStream; 33 | use syn::{parse_macro_input, DeriveInput}; 34 | 35 | #[proc_macro_derive(Error, attributes(backtrace, error, from, source))] 36 | pub fn derive_error(input: TokenStream) -> TokenStream { 37 | let input = parse_macro_input!(input as DeriveInput); 38 | expand::derive(&input).into() 39 | } 40 | -------------------------------------------------------------------------------- /impl/src/prop.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{Enum, Field, Struct, Variant}; 2 | use crate::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 distinct_backtrace_field(&self) -> Option<&Field> { 20 | let backtrace_field = self.backtrace_field()?; 21 | distinct_backtrace_field(backtrace_field, self.from_field()) 22 | } 23 | } 24 | 25 | impl Enum<'_> { 26 | pub(crate) fn has_source(&self) -> bool { 27 | self.variants 28 | .iter() 29 | .any(|variant| variant.source_field().is_some() || variant.attrs.transparent.is_some()) 30 | } 31 | 32 | pub(crate) fn has_backtrace(&self) -> bool { 33 | self.variants 34 | .iter() 35 | .any(|variant| variant.backtrace_field().is_some()) 36 | } 37 | 38 | pub(crate) fn has_display(&self) -> bool { 39 | self.attrs.display.is_some() 40 | || self.attrs.transparent.is_some() 41 | || self.attrs.fmt.is_some() 42 | || self 43 | .variants 44 | .iter() 45 | .any(|variant| variant.attrs.display.is_some() || variant.attrs.fmt.is_some()) 46 | || self 47 | .variants 48 | .iter() 49 | .all(|variant| variant.attrs.transparent.is_some()) 50 | } 51 | } 52 | 53 | impl Variant<'_> { 54 | pub(crate) fn from_field(&self) -> Option<&Field> { 55 | from_field(&self.fields) 56 | } 57 | 58 | pub(crate) fn source_field(&self) -> Option<&Field> { 59 | source_field(&self.fields) 60 | } 61 | 62 | pub(crate) fn backtrace_field(&self) -> Option<&Field> { 63 | backtrace_field(&self.fields) 64 | } 65 | 66 | pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> { 67 | let backtrace_field = self.backtrace_field()?; 68 | distinct_backtrace_field(backtrace_field, self.from_field()) 69 | } 70 | } 71 | 72 | impl Field<'_> { 73 | pub(crate) fn is_backtrace(&self) -> bool { 74 | type_is_backtrace(self.ty) 75 | } 76 | 77 | pub(crate) fn source_span(&self) -> Span { 78 | if let Some(source_attr) = &self.attrs.source { 79 | source_attr.span 80 | } else if let Some(from_attr) = &self.attrs.from { 81 | from_attr.span 82 | } else { 83 | self.member.span() 84 | } 85 | } 86 | } 87 | 88 | fn from_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { 89 | for field in fields { 90 | if field.attrs.from.is_some() { 91 | return Some(field); 92 | } 93 | } 94 | None 95 | } 96 | 97 | fn source_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { 98 | for field in fields { 99 | if field.attrs.from.is_some() || field.attrs.source.is_some() { 100 | return Some(field); 101 | } 102 | } 103 | for field in fields { 104 | match &field.member { 105 | MemberUnraw::Named(ident) if ident == "source" => return Some(field), 106 | _ => {} 107 | } 108 | } 109 | None 110 | } 111 | 112 | fn backtrace_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { 113 | for field in fields { 114 | if field.attrs.backtrace.is_some() { 115 | return Some(field); 116 | } 117 | } 118 | for field in fields { 119 | if field.is_backtrace() { 120 | return Some(field); 121 | } 122 | } 123 | None 124 | } 125 | 126 | // The #[backtrace] field, if it is not the same as the #[from] field. 127 | fn distinct_backtrace_field<'a, 'b>( 128 | backtrace_field: &'a Field<'b>, 129 | from_field: Option<&Field>, 130 | ) -> Option<&'a Field<'b>> { 131 | if from_field.map_or(false, |from_field| { 132 | from_field.member == backtrace_field.member 133 | }) { 134 | None 135 | } else { 136 | Some(backtrace_field) 137 | } 138 | } 139 | 140 | fn type_is_backtrace(ty: &Type) -> bool { 141 | let path = match ty { 142 | Type::Path(ty) => &ty.path, 143 | _ => return false, 144 | }; 145 | 146 | let last = path.segments.last().unwrap(); 147 | last.ident == "Backtrace" && last.arguments.is_empty() 148 | } 149 | -------------------------------------------------------------------------------- /impl/src/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 | -------------------------------------------------------------------------------- /impl/src/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 | -------------------------------------------------------------------------------- /impl/src/valid.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{Enum, Field, Input, Struct, Variant}; 2 | use crate::attr::Attrs; 3 | use syn::{Error, GenericArgument, PathArguments, Result, Type}; 4 | 5 | impl Input<'_> { 6 | pub(crate) fn validate(&self) -> Result<()> { 7 | match self { 8 | Input::Struct(input) => input.validate(), 9 | Input::Enum(input) => input.validate(), 10 | } 11 | } 12 | } 13 | 14 | impl Struct<'_> { 15 | fn validate(&self) -> Result<()> { 16 | check_non_field_attrs(&self.attrs)?; 17 | if let Some(transparent) = self.attrs.transparent { 18 | if self.fields.len() != 1 { 19 | return Err(Error::new_spanned( 20 | transparent.original, 21 | "#[error(transparent)] requires exactly one field", 22 | )); 23 | } 24 | if let Some(source) = self.fields.iter().find_map(|f| f.attrs.source) { 25 | return Err(Error::new_spanned( 26 | source.original, 27 | "transparent error struct can't contain #[source]", 28 | )); 29 | } 30 | } 31 | if let Some(fmt) = &self.attrs.fmt { 32 | return Err(Error::new_spanned( 33 | fmt.original, 34 | "#[error(fmt = ...)] is only supported in enums; for a struct, handwrite your own Display impl", 35 | )); 36 | } 37 | check_field_attrs(&self.fields)?; 38 | for field in &self.fields { 39 | field.validate()?; 40 | } 41 | Ok(()) 42 | } 43 | } 44 | 45 | impl Enum<'_> { 46 | fn validate(&self) -> Result<()> { 47 | check_non_field_attrs(&self.attrs)?; 48 | let has_display = self.has_display(); 49 | for variant in &self.variants { 50 | variant.validate()?; 51 | if has_display 52 | && variant.attrs.display.is_none() 53 | && variant.attrs.transparent.is_none() 54 | && variant.attrs.fmt.is_none() 55 | { 56 | return Err(Error::new_spanned( 57 | variant.original, 58 | "missing #[error(\"...\")] display attribute", 59 | )); 60 | } 61 | } 62 | Ok(()) 63 | } 64 | } 65 | 66 | impl Variant<'_> { 67 | fn validate(&self) -> Result<()> { 68 | check_non_field_attrs(&self.attrs)?; 69 | if self.attrs.transparent.is_some() { 70 | if self.fields.len() != 1 { 71 | return Err(Error::new_spanned( 72 | self.original, 73 | "#[error(transparent)] requires exactly one field", 74 | )); 75 | } 76 | if let Some(source) = self.fields.iter().find_map(|f| f.attrs.source) { 77 | return Err(Error::new_spanned( 78 | source.original, 79 | "transparent variant can't contain #[source]", 80 | )); 81 | } 82 | } 83 | check_field_attrs(&self.fields)?; 84 | for field in &self.fields { 85 | field.validate()?; 86 | } 87 | Ok(()) 88 | } 89 | } 90 | 91 | impl Field<'_> { 92 | fn validate(&self) -> Result<()> { 93 | if let Some(unexpected_display_attr) = if let Some(display) = &self.attrs.display { 94 | Some(display.original) 95 | } else if let Some(fmt) = &self.attrs.fmt { 96 | Some(fmt.original) 97 | } else { 98 | None 99 | } { 100 | return Err(Error::new_spanned( 101 | unexpected_display_attr, 102 | "not expected here; the #[error(...)] attribute belongs on top of a struct or an enum variant", 103 | )); 104 | } 105 | Ok(()) 106 | } 107 | } 108 | 109 | fn check_non_field_attrs(attrs: &Attrs) -> Result<()> { 110 | if let Some(from) = &attrs.from { 111 | return Err(Error::new_spanned( 112 | from.original, 113 | "not expected here; the #[from] attribute belongs on a specific field", 114 | )); 115 | } 116 | if let Some(source) = &attrs.source { 117 | return Err(Error::new_spanned( 118 | source.original, 119 | "not expected here; the #[source] attribute belongs on a specific field", 120 | )); 121 | } 122 | if let Some(backtrace) = &attrs.backtrace { 123 | return Err(Error::new_spanned( 124 | backtrace, 125 | "not expected here; the #[backtrace] attribute belongs on a specific field", 126 | )); 127 | } 128 | if attrs.transparent.is_some() { 129 | if let Some(display) = &attrs.display { 130 | return Err(Error::new_spanned( 131 | display.original, 132 | "cannot have both #[error(transparent)] and a display attribute", 133 | )); 134 | } 135 | if let Some(fmt) = &attrs.fmt { 136 | return Err(Error::new_spanned( 137 | fmt.original, 138 | "cannot have both #[error(transparent)] and #[error(fmt = ...)]", 139 | )); 140 | } 141 | } else if let (Some(display), Some(_)) = (&attrs.display, &attrs.fmt) { 142 | return Err(Error::new_spanned( 143 | display.original, 144 | "cannot have both #[error(fmt = ...)] and a format arguments attribute", 145 | )); 146 | } 147 | 148 | Ok(()) 149 | } 150 | 151 | fn check_field_attrs(fields: &[Field]) -> Result<()> { 152 | let mut from_field = None; 153 | let mut source_field = None; 154 | let mut backtrace_field = None; 155 | let mut has_backtrace = false; 156 | for field in fields { 157 | if let Some(from) = field.attrs.from { 158 | if from_field.is_some() { 159 | return Err(Error::new_spanned( 160 | from.original, 161 | "duplicate #[from] attribute", 162 | )); 163 | } 164 | from_field = Some(field); 165 | } 166 | if let Some(source) = field.attrs.source { 167 | if source_field.is_some() { 168 | return Err(Error::new_spanned( 169 | source.original, 170 | "duplicate #[source] attribute", 171 | )); 172 | } 173 | source_field = Some(field); 174 | } 175 | if let Some(backtrace) = field.attrs.backtrace { 176 | if backtrace_field.is_some() { 177 | return Err(Error::new_spanned( 178 | backtrace, 179 | "duplicate #[backtrace] attribute", 180 | )); 181 | } 182 | backtrace_field = Some(field); 183 | has_backtrace = true; 184 | } 185 | if let Some(transparent) = field.attrs.transparent { 186 | return Err(Error::new_spanned( 187 | transparent.original, 188 | "#[error(transparent)] needs to go outside the enum or struct, not on an individual field", 189 | )); 190 | } 191 | has_backtrace |= field.is_backtrace(); 192 | } 193 | if let (Some(from_field), Some(source_field)) = (from_field, source_field) { 194 | if from_field.member != source_field.member { 195 | return Err(Error::new_spanned( 196 | from_field.attrs.from.unwrap().original, 197 | "#[from] is only supported on the source field, not any other field", 198 | )); 199 | } 200 | } 201 | if let Some(from_field) = from_field { 202 | let max_expected_fields = match backtrace_field { 203 | Some(backtrace_field) => 1 + (from_field.member != backtrace_field.member) as usize, 204 | None => 1 + has_backtrace as usize, 205 | }; 206 | if fields.len() > max_expected_fields { 207 | return Err(Error::new_spanned( 208 | from_field.attrs.from.unwrap().original, 209 | "deriving From requires no fields other than source and backtrace", 210 | )); 211 | } 212 | } 213 | if let Some(source_field) = source_field.or(from_field) { 214 | if contains_non_static_lifetime(source_field.ty) { 215 | return Err(Error::new_spanned( 216 | &source_field.original.ty, 217 | "non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static", 218 | )); 219 | } 220 | } 221 | Ok(()) 222 | } 223 | 224 | fn contains_non_static_lifetime(ty: &Type) -> bool { 225 | match ty { 226 | Type::Path(ty) => { 227 | let bracketed = match &ty.path.segments.last().unwrap().arguments { 228 | PathArguments::AngleBracketed(bracketed) => bracketed, 229 | _ => return false, 230 | }; 231 | for arg in &bracketed.args { 232 | match arg { 233 | GenericArgument::Type(ty) if contains_non_static_lifetime(ty) => return true, 234 | GenericArgument::Lifetime(lifetime) if lifetime.ident != "static" => { 235 | return true 236 | } 237 | _ => {} 238 | } 239 | } 240 | false 241 | } 242 | Type::Reference(ty) => ty 243 | .lifetime 244 | .as_ref() 245 | .map_or(false, |lifetime| lifetime.ident != "static"), 246 | _ => false, // maybe implement later if there are common other cases 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | components = ["rust-src"] 3 | -------------------------------------------------------------------------------- /src/aserror.rs: -------------------------------------------------------------------------------- 1 | use core::error::Error; 2 | use core::panic::UnwindSafe; 3 | 4 | #[doc(hidden)] 5 | pub trait AsDynError<'a>: Sealed { 6 | fn as_dyn_error(&self) -> &(dyn Error + 'a); 7 | } 8 | 9 | impl<'a, T: Error + 'a> AsDynError<'a> for T { 10 | #[inline] 11 | fn as_dyn_error(&self) -> &(dyn Error + 'a) { 12 | self 13 | } 14 | } 15 | 16 | impl<'a> AsDynError<'a> for dyn Error + 'a { 17 | #[inline] 18 | fn as_dyn_error(&self) -> &(dyn Error + 'a) { 19 | self 20 | } 21 | } 22 | 23 | impl<'a> AsDynError<'a> for dyn Error + Send + 'a { 24 | #[inline] 25 | fn as_dyn_error(&self) -> &(dyn Error + 'a) { 26 | self 27 | } 28 | } 29 | 30 | impl<'a> AsDynError<'a> for dyn Error + Send + Sync + 'a { 31 | #[inline] 32 | fn as_dyn_error(&self) -> &(dyn Error + 'a) { 33 | self 34 | } 35 | } 36 | 37 | impl<'a> AsDynError<'a> for dyn Error + Send + Sync + UnwindSafe + 'a { 38 | #[inline] 39 | fn as_dyn_error(&self) -> &(dyn Error + 'a) { 40 | self 41 | } 42 | } 43 | 44 | #[doc(hidden)] 45 | pub trait Sealed {} 46 | impl Sealed for T {} 47 | impl Sealed for dyn Error + '_ {} 48 | impl Sealed for dyn Error + Send + '_ {} 49 | impl Sealed for dyn Error + Send + Sync + '_ {} 50 | impl Sealed for dyn Error + Send + Sync + UnwindSafe + '_ {} 51 | -------------------------------------------------------------------------------- /src/display.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Display; 2 | #[cfg(feature = "std")] 3 | use std::path::{self, Path, PathBuf}; 4 | 5 | #[doc(hidden)] 6 | pub trait AsDisplay<'a>: Sealed { 7 | // TODO: convert to generic associated type. 8 | // https://github.com/dtolnay/thiserror/pull/253 9 | type Target: Display; 10 | 11 | fn as_display(&'a self) -> Self::Target; 12 | } 13 | 14 | impl<'a, T> AsDisplay<'a> for &T 15 | where 16 | T: Display + ?Sized + 'a, 17 | { 18 | type Target = &'a T; 19 | 20 | fn as_display(&'a self) -> Self::Target { 21 | *self 22 | } 23 | } 24 | 25 | #[cfg(feature = "std")] 26 | impl<'a> AsDisplay<'a> for Path { 27 | type Target = path::Display<'a>; 28 | 29 | #[inline] 30 | fn as_display(&'a self) -> Self::Target { 31 | self.display() 32 | } 33 | } 34 | 35 | #[cfg(feature = "std")] 36 | impl<'a> AsDisplay<'a> for PathBuf { 37 | type Target = path::Display<'a>; 38 | 39 | #[inline] 40 | fn as_display(&'a self) -> Self::Target { 41 | self.display() 42 | } 43 | } 44 | 45 | #[doc(hidden)] 46 | pub trait Sealed {} 47 | impl Sealed for &T {} 48 | #[cfg(feature = "std")] 49 | impl Sealed for Path {} 50 | #[cfg(feature = "std")] 51 | impl Sealed for PathBuf {} 52 | 53 | // Add a synthetic second impl of AsDisplay to prevent the "single applicable 54 | // impl" rule from making too weird inference decision based on the single impl 55 | // for &T, which could lead to code that compiles with thiserror's std feature 56 | // off but breaks under feature unification when std is turned on by an 57 | // unrelated crate. 58 | #[cfg(not(feature = "std"))] 59 | mod placeholder { 60 | use super::{AsDisplay, Sealed}; 61 | use core::fmt::{self, Display}; 62 | 63 | pub struct Placeholder; 64 | 65 | impl<'a> AsDisplay<'a> for Placeholder { 66 | type Target = Self; 67 | 68 | #[inline] 69 | fn as_display(&'a self) -> Self::Target { 70 | Placeholder 71 | } 72 | } 73 | 74 | impl Display for Placeholder { 75 | fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { 76 | unreachable!() 77 | } 78 | } 79 | 80 | impl Sealed for Placeholder {} 81 | } 82 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![github]](https://github.com/dtolnay/thiserror) [![crates-io]](https://crates.io/crates/thiserror) [![docs-rs]](https://docs.rs/thiserror) 2 | //! 3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github 4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust 5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs 6 | //! 7 | //!
8 | //! 9 | //! This library provides a convenient derive macro for the standard library's 10 | //! [`std::error::Error`] trait. 11 | //! 12 | //!
13 | //! 14 | //! # Example 15 | //! 16 | //! ```rust 17 | //! # use std::io; 18 | //! use thiserror::Error; 19 | //! 20 | //! #[derive(Error, Debug)] 21 | //! pub enum DataStoreError { 22 | //! #[error("data store disconnected")] 23 | //! Disconnect(#[from] io::Error), 24 | //! #[error("the data for key `{0}` is not available")] 25 | //! Redaction(String), 26 | //! #[error("invalid header (expected {expected:?}, found {found:?})")] 27 | //! InvalidHeader { 28 | //! expected: String, 29 | //! found: String, 30 | //! }, 31 | //! #[error("unknown data store error")] 32 | //! Unknown, 33 | //! } 34 | //! ``` 35 | //! 36 | //!
37 | //! 38 | //! # Details 39 | //! 40 | //! - Thiserror deliberately does not appear in your public API. You get the 41 | //! same thing as if you had written an implementation of `std::error::Error` 42 | //! by hand, and switching from handwritten impls to thiserror or vice versa 43 | //! is not a breaking change. 44 | //! 45 | //! - Errors may be enums, structs with named fields, tuple structs, or unit 46 | //! structs. 47 | //! 48 | //! - A `Display` impl is generated for your error if you provide 49 | //! `#[error("...")]` messages on the struct or each variant of your enum, as 50 | //! shown above in the example. 51 | //! 52 | //! The messages support a shorthand for interpolating fields from the error. 53 | //! 54 | //! - `#[error("{var}")]` ⟶ `write!("{}", self.var)` 55 | //! - `#[error("{0}")]` ⟶ `write!("{}", self.0)` 56 | //! - `#[error("{var:?}")]` ⟶ `write!("{:?}", self.var)` 57 | //! - `#[error("{0:?}")]` ⟶ `write!("{:?}", self.0)` 58 | //! 59 | //! These shorthands can be used together with any additional format args, 60 | //! which may be arbitrary expressions. For example: 61 | //! 62 | //! ```rust 63 | //! # use core::i32; 64 | //! # use thiserror::Error; 65 | //! # 66 | //! #[derive(Error, Debug)] 67 | //! pub enum Error { 68 | //! #[error("invalid rdo_lookahead_frames {0} (expected < {max})", max = i32::MAX)] 69 | //! InvalidLookahead(u32), 70 | //! } 71 | //! ``` 72 | //! 73 | //! If one of the additional expression arguments needs to refer to a field of 74 | //! the struct or enum, then refer to named fields as `.var` and tuple fields 75 | //! as `.0`. 76 | //! 77 | //! ```rust 78 | //! # use thiserror::Error; 79 | //! # 80 | //! # fn first_char(s: &String) -> char { 81 | //! # s.chars().next().unwrap() 82 | //! # } 83 | //! # 84 | //! # #[derive(Debug)] 85 | //! # struct Limits { 86 | //! # lo: usize, 87 | //! # hi: usize, 88 | //! # } 89 | //! # 90 | //! #[derive(Error, Debug)] 91 | //! pub enum Error { 92 | //! #[error("first letter must be lowercase but was {:?}", first_char(.0))] 93 | //! WrongCase(String), 94 | //! #[error("invalid index {idx}, expected at least {} and at most {}", .limits.lo, .limits.hi)] 95 | //! OutOfBounds { idx: usize, limits: Limits }, 96 | //! } 97 | //! ``` 98 | //! 99 | //! - A `From` impl is generated for each variant that contains a `#[from]` 100 | //! attribute. 101 | //! 102 | //! The variant using `#[from]` must not contain any other fields beyond the 103 | //! source error (and possibly a backtrace — see below). Usually 104 | //! `#[from]` fields are unnamed, but `#[from]` is allowed on a named field 105 | //! too. 106 | //! 107 | //! ```rust 108 | //! # use core::fmt::{self, Display}; 109 | //! # use std::io; 110 | //! # use thiserror::Error; 111 | //! # 112 | //! # mod globset { 113 | //! # #[derive(thiserror::Error, Debug)] 114 | //! # #[error("...")] 115 | //! # pub struct Error; 116 | //! # } 117 | //! # 118 | //! #[derive(Error, Debug)] 119 | //! pub enum MyError { 120 | //! Io(#[from] io::Error), 121 | //! Glob(#[from] globset::Error), 122 | //! } 123 | //! # 124 | //! # impl Display for MyError { 125 | //! # fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 126 | //! # unimplemented!() 127 | //! # } 128 | //! # } 129 | //! ``` 130 | //! 131 | //! - The Error trait's `source()` method is implemented to return whichever 132 | //! field has a `#[source]` attribute or is named `source`, if any. This is 133 | //! for identifying the underlying lower level error that caused your error. 134 | //! 135 | //! The `#[from]` attribute always implies that the same field is `#[source]`, 136 | //! so you don't ever need to specify both attributes. 137 | //! 138 | //! Any error type that implements `std::error::Error` or dereferences to `dyn 139 | //! std::error::Error` will work as a source. 140 | //! 141 | //! ```rust 142 | //! # use core::fmt::{self, Display}; 143 | //! # use thiserror::Error; 144 | //! # 145 | //! #[derive(Error, Debug)] 146 | //! pub struct MyError { 147 | //! msg: String, 148 | //! #[source] // optional if field name is `source` 149 | //! source: anyhow::Error, 150 | //! } 151 | //! # 152 | //! # impl Display for MyError { 153 | //! # fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 154 | //! # unimplemented!() 155 | //! # } 156 | //! # } 157 | //! ``` 158 | //! 159 | //! - The Error trait's `provide()` method is implemented to provide whichever 160 | //! field has a type named `Backtrace`, if any, as a 161 | //! `std::backtrace::Backtrace`. Using `Backtrace` in errors requires a 162 | //! nightly compiler with Rust version 1.73 or newer. 163 | //! 164 | //! ```rust 165 | //! # const IGNORE: &str = stringify! { 166 | //! use std::backtrace::Backtrace; 167 | //! 168 | //! #[derive(Error, Debug)] 169 | //! pub struct MyError { 170 | //! msg: String, 171 | //! backtrace: Backtrace, // automatically detected 172 | //! } 173 | //! # }; 174 | //! ``` 175 | //! 176 | //! - If a field is both a source (named `source`, or has `#[source]` or 177 | //! `#[from]` attribute) *and* is marked `#[backtrace]`, then the Error 178 | //! trait's `provide()` method is forwarded to the source's `provide` so that 179 | //! both layers of the error share the same backtrace. The `#[backtrace]` 180 | //! attribute requires a nightly compiler with Rust version 1.73 or newer. 181 | //! 182 | //! ```rust 183 | //! # const IGNORE: &str = stringify! { 184 | //! #[derive(Error, Debug)] 185 | //! pub enum MyError { 186 | //! Io { 187 | //! #[backtrace] 188 | //! source: io::Error, 189 | //! }, 190 | //! } 191 | //! # }; 192 | //! ``` 193 | //! 194 | //! - For variants that use `#[from]` and also contain a `Backtrace` field, a 195 | //! backtrace is captured from within the `From` impl. 196 | //! 197 | //! ```rust 198 | //! # const IGNORE: &str = stringify! { 199 | //! #[derive(Error, Debug)] 200 | //! pub enum MyError { 201 | //! Io { 202 | //! #[from] 203 | //! source: io::Error, 204 | //! backtrace: Backtrace, 205 | //! }, 206 | //! } 207 | //! # }; 208 | //! ``` 209 | //! 210 | //! - Errors may use `error(transparent)` to forward the source and Display 211 | //! methods straight through to an underlying error without adding an 212 | //! additional message. This would be appropriate for enums that need an 213 | //! "anything else" variant. 214 | //! 215 | //! ``` 216 | //! # use thiserror::Error; 217 | //! # 218 | //! #[derive(Error, Debug)] 219 | //! pub enum MyError { 220 | //! # /* 221 | //! ... 222 | //! # */ 223 | //! 224 | //! #[error(transparent)] 225 | //! Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error 226 | //! } 227 | //! ``` 228 | //! 229 | //! Another use case is hiding implementation details of an error 230 | //! representation behind an opaque error type, so that the representation is 231 | //! able to evolve without breaking the crate's public API. 232 | //! 233 | //! ``` 234 | //! # use thiserror::Error; 235 | //! # 236 | //! // PublicError is public, but opaque and easy to keep compatible. 237 | //! #[derive(Error, Debug)] 238 | //! #[error(transparent)] 239 | //! pub struct PublicError(#[from] ErrorRepr); 240 | //! 241 | //! impl PublicError { 242 | //! // Accessors for anything we do want to expose publicly. 243 | //! } 244 | //! 245 | //! // Private and free to change across minor version of the crate. 246 | //! #[derive(Error, Debug)] 247 | //! enum ErrorRepr { 248 | //! # /* 249 | //! ... 250 | //! # */ 251 | //! } 252 | //! ``` 253 | //! 254 | //! - See also the [`anyhow`] library for a convenient single error type to use 255 | //! in application code. 256 | //! 257 | //! [`anyhow`]: https://github.com/dtolnay/anyhow 258 | 259 | #![no_std] 260 | #![doc(html_root_url = "https://docs.rs/thiserror/2.0.12")] 261 | #![allow( 262 | clippy::elidable_lifetime_names, 263 | clippy::module_name_repetitions, 264 | clippy::needless_lifetimes, 265 | clippy::return_self_not_must_use, 266 | clippy::wildcard_imports 267 | )] 268 | #![cfg_attr(error_generic_member_access, feature(error_generic_member_access))] 269 | 270 | #[cfg(all(thiserror_nightly_testing, not(error_generic_member_access)))] 271 | compile_error!("Build script probe failed to compile."); 272 | 273 | #[cfg(feature = "std")] 274 | extern crate std; 275 | #[cfg(feature = "std")] 276 | extern crate std as core; 277 | 278 | mod aserror; 279 | mod display; 280 | #[cfg(error_generic_member_access)] 281 | mod provide; 282 | mod var; 283 | 284 | pub use thiserror_impl::*; 285 | 286 | // Not public API. 287 | #[doc(hidden)] 288 | pub mod __private { 289 | #[doc(hidden)] 290 | pub use crate::aserror::AsDynError; 291 | #[doc(hidden)] 292 | pub use crate::display::AsDisplay; 293 | #[cfg(error_generic_member_access)] 294 | #[doc(hidden)] 295 | pub use crate::provide::ThiserrorProvide; 296 | #[doc(hidden)] 297 | pub use crate::var::Var; 298 | #[doc(hidden)] 299 | pub use core::error::Error; 300 | #[cfg(all(feature = "std", not(thiserror_no_backtrace_type)))] 301 | #[doc(hidden)] 302 | pub use std::backtrace::Backtrace; 303 | } 304 | -------------------------------------------------------------------------------- /src/provide.rs: -------------------------------------------------------------------------------- 1 | use core::error::{Error, Request}; 2 | 3 | #[doc(hidden)] 4 | pub trait ThiserrorProvide: Sealed { 5 | fn thiserror_provide<'a>(&'a self, request: &mut Request<'a>); 6 | } 7 | 8 | impl ThiserrorProvide for T 9 | where 10 | T: Error + ?Sized, 11 | { 12 | #[inline] 13 | fn thiserror_provide<'a>(&'a self, request: &mut Request<'a>) { 14 | self.provide(request); 15 | } 16 | } 17 | 18 | #[doc(hidden)] 19 | pub trait Sealed {} 20 | impl Sealed for T {} 21 | -------------------------------------------------------------------------------- /src/var.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Pointer}; 2 | 3 | pub struct Var<'a, T: ?Sized>(pub &'a T); 4 | 5 | impl<'a, T: Pointer + ?Sized> Pointer for Var<'a, T> { 6 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 7 | Pointer::fmt(self.0, formatter) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/compiletest.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::attr(not(nightly), ignore = "requires nightly")] 2 | #[cfg_attr(miri, ignore = "incompatible with miri")] 3 | #[test] 4 | fn ui() { 5 | let t = trybuild::TestCases::new(); 6 | t.compile_fail("tests/ui/*.rs"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/no-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thiserror_no_std_test" 3 | version = "0.0.0" 4 | authors = ["David Tolnay "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [lib] 9 | path = "test.rs" 10 | 11 | [dependencies] 12 | thiserror = { path = "../..", default-features = false } 13 | -------------------------------------------------------------------------------- /tests/no-std/test.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum Error { 7 | #[error("Error::E")] 8 | E(#[from] SourceError), 9 | } 10 | 11 | #[derive(Error, Debug)] 12 | #[error("SourceError {field}")] 13 | pub struct SourceError { 14 | pub field: i32, 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use crate::{Error, SourceError}; 20 | use core::error::Error as _; 21 | use core::fmt::{self, Write}; 22 | use core::mem; 23 | 24 | struct Buf<'a>(&'a mut [u8]); 25 | 26 | impl Write for Buf<'_> { 27 | fn write_str(&mut self, s: &str) -> fmt::Result { 28 | if s.len() <= self.0.len() { 29 | let (out, rest) = mem::take(&mut self.0).split_at_mut(s.len()); 30 | out.copy_from_slice(s.as_bytes()); 31 | self.0 = rest; 32 | Ok(()) 33 | } else { 34 | Err(fmt::Error) 35 | } 36 | } 37 | } 38 | 39 | #[test] 40 | fn test() { 41 | let source = SourceError { field: -1 }; 42 | let error = Error::from(source); 43 | 44 | let source = error 45 | .source() 46 | .unwrap() 47 | .downcast_ref::() 48 | .unwrap(); 49 | 50 | let mut msg = [b'~'; 17]; 51 | write!(Buf(&mut msg), "{error}").unwrap(); 52 | assert_eq!(msg, *b"Error::E~~~~~~~~~"); 53 | 54 | let mut msg = [b'~'; 17]; 55 | write!(Buf(&mut msg), "{source}").unwrap(); 56 | assert_eq!(msg, *b"SourceError -1~~~"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/test_backtrace.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "std")] 2 | #![cfg_attr(thiserror_nightly_testing, feature(error_generic_member_access))] 3 | 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug)] 7 | #[error("...")] 8 | pub struct Inner; 9 | 10 | #[cfg(thiserror_nightly_testing)] 11 | #[derive(Error, Debug)] 12 | #[error("...")] 13 | pub struct InnerBacktrace { 14 | backtrace: std::backtrace::Backtrace, 15 | } 16 | 17 | #[cfg(thiserror_nightly_testing)] 18 | pub mod structs { 19 | use super::{Inner, InnerBacktrace}; 20 | use std::backtrace::Backtrace; 21 | use std::error::{self, Error}; 22 | use std::sync::Arc; 23 | use thiserror::Error; 24 | 25 | mod not_backtrace { 26 | #[derive(Debug)] 27 | pub struct Backtrace; 28 | } 29 | 30 | #[derive(Error, Debug)] 31 | #[error("...")] 32 | pub struct PlainBacktrace { 33 | backtrace: Backtrace, 34 | } 35 | 36 | #[derive(Error, Debug)] 37 | #[error("...")] 38 | pub struct ExplicitBacktrace { 39 | #[backtrace] 40 | backtrace: Backtrace, 41 | } 42 | 43 | #[derive(Error, Debug)] 44 | #[error("...")] 45 | pub struct NotBacktrace { 46 | backtrace: crate::structs::not_backtrace::r#Backtrace, 47 | } 48 | 49 | #[derive(Error, Debug)] 50 | #[error("...")] 51 | pub struct OptBacktrace { 52 | #[backtrace] 53 | backtrace: Option, 54 | } 55 | 56 | #[derive(Error, Debug)] 57 | #[error("...")] 58 | pub struct ArcBacktrace { 59 | #[backtrace] 60 | backtrace: Arc, 61 | } 62 | 63 | #[derive(Error, Debug)] 64 | #[error("...")] 65 | pub struct BacktraceFrom { 66 | #[from] 67 | source: Inner, 68 | #[backtrace] 69 | backtrace: Backtrace, 70 | } 71 | 72 | #[derive(Error, Debug)] 73 | #[error("...")] 74 | pub struct CombinedBacktraceFrom { 75 | #[from] 76 | #[backtrace] 77 | source: InnerBacktrace, 78 | } 79 | 80 | #[derive(Error, Debug)] 81 | #[error("...")] 82 | pub struct OptBacktraceFrom { 83 | #[from] 84 | source: Inner, 85 | #[backtrace] 86 | backtrace: Option, 87 | } 88 | 89 | #[derive(Error, Debug)] 90 | #[error("...")] 91 | pub struct ArcBacktraceFrom { 92 | #[from] 93 | source: Inner, 94 | #[backtrace] 95 | backtrace: Arc, 96 | } 97 | 98 | #[derive(Error, Debug)] 99 | #[error("...")] 100 | pub struct AnyhowBacktrace { 101 | #[backtrace] 102 | source: anyhow::Error, 103 | } 104 | 105 | #[derive(Error, Debug)] 106 | #[error("...")] 107 | pub struct BoxDynErrorBacktrace { 108 | #[backtrace] 109 | source: Box, 110 | } 111 | 112 | #[test] 113 | fn test_backtrace() { 114 | let error = PlainBacktrace { 115 | backtrace: Backtrace::capture(), 116 | }; 117 | assert!(error::request_ref::(&error).is_some()); 118 | 119 | let error = ExplicitBacktrace { 120 | backtrace: Backtrace::capture(), 121 | }; 122 | assert!(error::request_ref::(&error).is_some()); 123 | 124 | let error = OptBacktrace { 125 | backtrace: Some(Backtrace::capture()), 126 | }; 127 | assert!(error::request_ref::(&error).is_some()); 128 | 129 | let error = ArcBacktrace { 130 | backtrace: Arc::new(Backtrace::capture()), 131 | }; 132 | assert!(error::request_ref::(&error).is_some()); 133 | 134 | let error = BacktraceFrom::from(Inner); 135 | assert!(error::request_ref::(&error).is_some()); 136 | 137 | let error = CombinedBacktraceFrom::from(InnerBacktrace { 138 | backtrace: Backtrace::capture(), 139 | }); 140 | assert!(error::request_ref::(&error).is_some()); 141 | 142 | let error = OptBacktraceFrom::from(Inner); 143 | assert!(error::request_ref::(&error).is_some()); 144 | 145 | let error = ArcBacktraceFrom::from(Inner); 146 | assert!(error::request_ref::(&error).is_some()); 147 | 148 | let error = AnyhowBacktrace { 149 | source: anyhow::Error::msg("..."), 150 | }; 151 | assert!(error::request_ref::(&error).is_some()); 152 | 153 | let error = BoxDynErrorBacktrace { 154 | source: Box::new(PlainBacktrace { 155 | backtrace: Backtrace::capture(), 156 | }), 157 | }; 158 | assert!(error::request_ref::(&error).is_some()); 159 | } 160 | } 161 | 162 | #[cfg(thiserror_nightly_testing)] 163 | pub mod enums { 164 | use super::{Inner, InnerBacktrace}; 165 | use std::backtrace::Backtrace; 166 | use std::error; 167 | use std::sync::Arc; 168 | use thiserror::Error; 169 | 170 | #[derive(Error, Debug)] 171 | pub enum PlainBacktrace { 172 | #[error("...")] 173 | Test { backtrace: Backtrace }, 174 | } 175 | 176 | #[derive(Error, Debug)] 177 | pub enum ExplicitBacktrace { 178 | #[error("...")] 179 | Test { 180 | #[backtrace] 181 | backtrace: Backtrace, 182 | }, 183 | } 184 | 185 | #[derive(Error, Debug)] 186 | pub enum OptBacktrace { 187 | #[error("...")] 188 | Test { 189 | #[backtrace] 190 | backtrace: Option, 191 | }, 192 | } 193 | 194 | #[derive(Error, Debug)] 195 | pub enum ArcBacktrace { 196 | #[error("...")] 197 | Test { 198 | #[backtrace] 199 | backtrace: Arc, 200 | }, 201 | } 202 | 203 | #[derive(Error, Debug)] 204 | pub enum BacktraceFrom { 205 | #[error("...")] 206 | Test { 207 | #[from] 208 | source: Inner, 209 | #[backtrace] 210 | backtrace: Backtrace, 211 | }, 212 | } 213 | 214 | #[derive(Error, Debug)] 215 | pub enum CombinedBacktraceFrom { 216 | #[error("...")] 217 | Test { 218 | #[from] 219 | #[backtrace] 220 | source: InnerBacktrace, 221 | }, 222 | } 223 | 224 | #[derive(Error, Debug)] 225 | pub enum OptBacktraceFrom { 226 | #[error("...")] 227 | Test { 228 | #[from] 229 | source: Inner, 230 | #[backtrace] 231 | backtrace: Option, 232 | }, 233 | } 234 | 235 | #[derive(Error, Debug)] 236 | pub enum ArcBacktraceFrom { 237 | #[error("...")] 238 | Test { 239 | #[from] 240 | source: Inner, 241 | #[backtrace] 242 | backtrace: Arc, 243 | }, 244 | } 245 | 246 | #[test] 247 | fn test_backtrace() { 248 | let error = PlainBacktrace::Test { 249 | backtrace: Backtrace::capture(), 250 | }; 251 | assert!(error::request_ref::(&error).is_some()); 252 | 253 | let error = ExplicitBacktrace::Test { 254 | backtrace: Backtrace::capture(), 255 | }; 256 | assert!(error::request_ref::(&error).is_some()); 257 | 258 | let error = OptBacktrace::Test { 259 | backtrace: Some(Backtrace::capture()), 260 | }; 261 | assert!(error::request_ref::(&error).is_some()); 262 | 263 | let error = ArcBacktrace::Test { 264 | backtrace: Arc::new(Backtrace::capture()), 265 | }; 266 | assert!(error::request_ref::(&error).is_some()); 267 | 268 | let error = BacktraceFrom::from(Inner); 269 | assert!(error::request_ref::(&error).is_some()); 270 | 271 | let error = CombinedBacktraceFrom::from(InnerBacktrace { 272 | backtrace: Backtrace::capture(), 273 | }); 274 | assert!(error::request_ref::(&error).is_some()); 275 | 276 | let error = OptBacktraceFrom::from(Inner); 277 | assert!(error::request_ref::(&error).is_some()); 278 | 279 | let error = ArcBacktraceFrom::from(Inner); 280 | assert!(error::request_ref::(&error).is_some()); 281 | } 282 | } 283 | 284 | #[test] 285 | #[cfg_attr( 286 | not(thiserror_nightly_testing), 287 | ignore = "requires `--cfg=thiserror_nightly_testing`" 288 | )] 289 | fn test_backtrace() {} 290 | -------------------------------------------------------------------------------- /tests/test_display.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::elidable_lifetime_names, 3 | clippy::needless_lifetimes, 4 | clippy::needless_raw_string_hashes, 5 | clippy::trivially_copy_pass_by_ref, 6 | clippy::uninlined_format_args 7 | )] 8 | 9 | use core::fmt::{self, Display}; 10 | use thiserror::Error; 11 | 12 | fn assert(expected: &str, value: T) { 13 | assert_eq!(expected, value.to_string()); 14 | } 15 | 16 | #[test] 17 | fn test_braced() { 18 | #[derive(Error, Debug)] 19 | #[error("braced error: {msg}")] 20 | struct Error { 21 | msg: String, 22 | } 23 | 24 | let msg = "T".to_owned(); 25 | assert("braced error: T", Error { msg }); 26 | } 27 | 28 | #[test] 29 | fn test_braced_unused() { 30 | #[derive(Error, Debug)] 31 | #[error("braced error")] 32 | struct Error { 33 | extra: usize, 34 | } 35 | 36 | assert("braced error", Error { extra: 0 }); 37 | } 38 | 39 | #[test] 40 | fn test_tuple() { 41 | #[derive(Error, Debug)] 42 | #[error("tuple error: {0}")] 43 | struct Error(usize); 44 | 45 | assert("tuple error: 0", Error(0)); 46 | } 47 | 48 | #[test] 49 | fn test_unit() { 50 | #[derive(Error, Debug)] 51 | #[error("unit error")] 52 | struct Error; 53 | 54 | assert("unit error", Error); 55 | } 56 | 57 | #[test] 58 | fn test_enum() { 59 | #[derive(Error, Debug)] 60 | enum Error { 61 | #[error("braced error: {id}")] 62 | Braced { id: usize }, 63 | #[error("tuple error: {0}")] 64 | Tuple(usize), 65 | #[error("unit error")] 66 | Unit, 67 | } 68 | 69 | assert("braced error: 0", Error::Braced { id: 0 }); 70 | assert("tuple error: 0", Error::Tuple(0)); 71 | assert("unit error", Error::Unit); 72 | } 73 | 74 | #[test] 75 | fn test_constants() { 76 | #[derive(Error, Debug)] 77 | #[error("{MSG}: {id:?} (code {CODE:?})")] 78 | struct Error { 79 | id: &'static str, 80 | } 81 | 82 | const MSG: &str = "failed to do"; 83 | const CODE: usize = 9; 84 | 85 | assert("failed to do: \"\" (code 9)", Error { id: "" }); 86 | } 87 | 88 | #[test] 89 | fn test_inherit() { 90 | #[derive(Error, Debug)] 91 | #[error("{0}")] 92 | enum Error { 93 | Some(&'static str), 94 | #[error("other error")] 95 | Other(&'static str), 96 | } 97 | 98 | assert("some error", Error::Some("some error")); 99 | assert("other error", Error::Other("...")); 100 | } 101 | 102 | #[test] 103 | fn test_brace_escape() { 104 | #[derive(Error, Debug)] 105 | #[error("fn main() {{}}")] 106 | struct Error; 107 | 108 | assert("fn main() {}", Error); 109 | } 110 | 111 | #[test] 112 | fn test_expr() { 113 | #[derive(Error, Debug)] 114 | #[error("1 + 1 = {}", 1 + 1)] 115 | struct Error; 116 | assert("1 + 1 = 2", Error); 117 | } 118 | 119 | #[test] 120 | fn test_nested() { 121 | #[derive(Error, Debug)] 122 | #[error("!bool = {}", not(.0))] 123 | struct Error(bool); 124 | 125 | #[allow(clippy::trivially_copy_pass_by_ref)] 126 | fn not(bool: &bool) -> bool { 127 | !*bool 128 | } 129 | 130 | assert("!bool = false", Error(true)); 131 | } 132 | 133 | #[test] 134 | fn test_match() { 135 | #[derive(Error, Debug)] 136 | #[error("{intro}: {0}", intro = match .1 { 137 | Some(n) => format!("error occurred with {}", n), 138 | None => "there was an empty error".to_owned(), 139 | })] 140 | struct Error(String, Option); 141 | 142 | assert( 143 | "error occurred with 1: ...", 144 | Error("...".to_owned(), Some(1)), 145 | ); 146 | assert( 147 | "there was an empty error: ...", 148 | Error("...".to_owned(), None), 149 | ); 150 | } 151 | 152 | #[test] 153 | fn test_nested_display() { 154 | // Same behavior as the one in `test_match`, but without String allocations. 155 | #[derive(Error, Debug)] 156 | #[error("{}", { 157 | struct Msg<'a>(&'a String, &'a Option); 158 | impl<'a> Display for Msg<'a> { 159 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 160 | match self.1 { 161 | Some(n) => write!(formatter, "error occurred with {}", n), 162 | None => write!(formatter, "there was an empty error"), 163 | }?; 164 | write!(formatter, ": {}", self.0) 165 | } 166 | } 167 | Msg(.0, .1) 168 | })] 169 | struct Error(String, Option); 170 | 171 | assert( 172 | "error occurred with 1: ...", 173 | Error("...".to_owned(), Some(1)), 174 | ); 175 | assert( 176 | "there was an empty error: ...", 177 | Error("...".to_owned(), None), 178 | ); 179 | } 180 | 181 | #[test] 182 | fn test_void() { 183 | #[allow(clippy::empty_enum)] 184 | #[derive(Error, Debug)] 185 | #[error("...")] 186 | pub enum Error {} 187 | 188 | let _: Error; 189 | } 190 | 191 | #[test] 192 | fn test_mixed() { 193 | #[derive(Error, Debug)] 194 | #[error("a={a} :: b={} :: c={c} :: d={d}", 1, c = 2, d = 3)] 195 | struct Error { 196 | a: usize, 197 | d: usize, 198 | } 199 | 200 | assert("a=0 :: b=1 :: c=2 :: d=3", Error { a: 0, d: 0 }); 201 | } 202 | 203 | #[test] 204 | fn test_ints() { 205 | #[derive(Error, Debug)] 206 | enum Error { 207 | #[error("error {0}")] 208 | Tuple(usize, usize), 209 | #[error("error {0}", '?')] 210 | Struct { v: usize }, 211 | } 212 | 213 | assert("error 9", Error::Tuple(9, 0)); 214 | assert("error ?", Error::Struct { v: 0 }); 215 | } 216 | 217 | #[test] 218 | fn test_trailing_comma() { 219 | #[derive(Error, Debug)] 220 | #[error( 221 | "error {0}", 222 | )] 223 | #[rustfmt::skip] 224 | struct Error(char); 225 | 226 | assert("error ?", Error('?')); 227 | } 228 | 229 | #[test] 230 | fn test_field() { 231 | #[derive(Debug)] 232 | struct Inner { 233 | data: usize, 234 | } 235 | 236 | #[derive(Error, Debug)] 237 | #[error("{}", .0.data)] 238 | struct Error(Inner); 239 | 240 | assert("0", Error(Inner { data: 0 })); 241 | } 242 | 243 | #[test] 244 | fn test_nested_tuple_field() { 245 | #[derive(Debug)] 246 | struct Inner(usize); 247 | 248 | #[derive(Error, Debug)] 249 | #[error("{}", .0.0)] 250 | struct Error(Inner); 251 | 252 | assert("0", Error(Inner(0))); 253 | } 254 | 255 | #[test] 256 | fn test_pointer() { 257 | #[derive(Error, Debug)] 258 | #[error("{field:p}")] 259 | pub struct Struct { 260 | field: Box, 261 | } 262 | 263 | let s = Struct { 264 | field: Box::new(-1), 265 | }; 266 | assert_eq!(s.to_string(), format!("{:p}", s.field)); 267 | } 268 | 269 | #[test] 270 | fn test_macro_rules_variant_from_call_site() { 271 | // Regression test for https://github.com/dtolnay/thiserror/issues/86 272 | 273 | macro_rules! decl_error { 274 | ($variant:ident($value:ident)) => { 275 | #[derive(Error, Debug)] 276 | pub enum Error0 { 277 | #[error("{0:?}")] 278 | $variant($value), 279 | } 280 | 281 | #[derive(Error, Debug)] 282 | #[error("{0:?}")] 283 | pub enum Error1 { 284 | $variant($value), 285 | } 286 | }; 287 | } 288 | 289 | decl_error!(Repro(u8)); 290 | 291 | assert("0", Error0::Repro(0)); 292 | assert("0", Error1::Repro(0)); 293 | } 294 | 295 | #[test] 296 | fn test_macro_rules_message_from_call_site() { 297 | // Regression test for https://github.com/dtolnay/thiserror/issues/398 298 | 299 | macro_rules! decl_error { 300 | ($($errors:tt)*) => { 301 | #[derive(Error, Debug)] 302 | pub enum Error { 303 | $($errors)* 304 | } 305 | }; 306 | } 307 | 308 | decl_error! { 309 | #[error("{0}")] 310 | Unnamed(u8), 311 | #[error("{x}")] 312 | Named { x: u8 }, 313 | } 314 | 315 | assert("0", Error::Unnamed(0)); 316 | assert("0", Error::Named { x: 0 }); 317 | } 318 | 319 | #[test] 320 | fn test_raw() { 321 | #[derive(Error, Debug)] 322 | #[error("braced raw error: {fn}")] 323 | struct Error { 324 | r#fn: &'static str, 325 | } 326 | 327 | assert("braced raw error: T", Error { r#fn: "T" }); 328 | } 329 | 330 | #[test] 331 | fn test_raw_enum() { 332 | #[derive(Error, Debug)] 333 | enum Error { 334 | #[error("braced raw error: {fn}")] 335 | Braced { r#fn: &'static str }, 336 | } 337 | 338 | assert("braced raw error: T", Error::Braced { r#fn: "T" }); 339 | } 340 | 341 | #[test] 342 | fn test_keyword() { 343 | #[derive(Error, Debug)] 344 | #[error("error: {type}", type = 1)] 345 | struct Error; 346 | 347 | assert("error: 1", Error); 348 | } 349 | 350 | #[test] 351 | fn test_self() { 352 | #[derive(Error, Debug)] 353 | #[error("error: {self:?}")] 354 | struct Error; 355 | 356 | assert("error: Error", Error); 357 | } 358 | 359 | #[test] 360 | fn test_str_special_chars() { 361 | #[derive(Error, Debug)] 362 | pub enum Error { 363 | #[error("brace left {{")] 364 | BraceLeft, 365 | #[error("brace left 2 \x7B\x7B")] 366 | BraceLeft2, 367 | #[error("brace left 3 \u{7B}\u{7B}")] 368 | BraceLeft3, 369 | #[error("brace right }}")] 370 | BraceRight, 371 | #[error("brace right 2 \x7D\x7D")] 372 | BraceRight2, 373 | #[error("brace right 3 \u{7D}\u{7D}")] 374 | BraceRight3, 375 | #[error( 376 | "new_\ 377 | line" 378 | )] 379 | NewLine, 380 | #[error("escape24 \u{78}")] 381 | Escape24, 382 | } 383 | 384 | assert("brace left {", Error::BraceLeft); 385 | assert("brace left 2 {", Error::BraceLeft2); 386 | assert("brace left 3 {", Error::BraceLeft3); 387 | assert("brace right }", Error::BraceRight); 388 | assert("brace right 2 }", Error::BraceRight2); 389 | assert("brace right 3 }", Error::BraceRight3); 390 | assert("new_line", Error::NewLine); 391 | assert("escape24 x", Error::Escape24); 392 | } 393 | 394 | #[test] 395 | fn test_raw_str() { 396 | #[derive(Error, Debug)] 397 | pub enum Error { 398 | #[error(r#"raw brace left {{"#)] 399 | BraceLeft, 400 | #[error(r#"raw brace left 2 \x7B"#)] 401 | BraceLeft2, 402 | #[error(r#"raw brace right }}"#)] 403 | BraceRight, 404 | #[error(r#"raw brace right 2 \x7D"#)] 405 | BraceRight2, 406 | } 407 | 408 | assert(r#"raw brace left {"#, Error::BraceLeft); 409 | assert(r#"raw brace left 2 \x7B"#, Error::BraceLeft2); 410 | assert(r#"raw brace right }"#, Error::BraceRight); 411 | assert(r#"raw brace right 2 \x7D"#, Error::BraceRight2); 412 | } 413 | 414 | mod util { 415 | use core::fmt::{self, Octal}; 416 | 417 | pub fn octal(value: &T, formatter: &mut fmt::Formatter) -> fmt::Result { 418 | write!(formatter, "0o{:o}", value) 419 | } 420 | } 421 | 422 | #[test] 423 | fn test_fmt_path() { 424 | fn unit(formatter: &mut fmt::Formatter) -> fmt::Result { 425 | formatter.write_str("unit=") 426 | } 427 | 428 | fn pair(k: &i32, v: &i32, formatter: &mut fmt::Formatter) -> fmt::Result { 429 | write!(formatter, "pair={k}:{v}") 430 | } 431 | 432 | #[derive(Error, Debug)] 433 | pub enum Error { 434 | #[error(fmt = unit)] 435 | Unit, 436 | #[error(fmt = pair)] 437 | Tuple(i32, i32), 438 | #[error(fmt = pair)] 439 | Entry { k: i32, v: i32 }, 440 | #[error(fmt = crate::util::octal)] 441 | I16(i16), 442 | #[error(fmt = crate::util::octal::)] 443 | I32 { n: i32 }, 444 | #[error(fmt = core::fmt::Octal::fmt)] 445 | I64(i64), 446 | #[error("...{0}")] 447 | Other(bool), 448 | } 449 | 450 | assert("unit=", Error::Unit); 451 | assert("pair=10:0", Error::Tuple(10, 0)); 452 | assert("pair=10:0", Error::Entry { k: 10, v: 0 }); 453 | assert("0o777", Error::I16(0o777)); 454 | assert("0o777", Error::I32 { n: 0o777 }); 455 | assert("777", Error::I64(0o777)); 456 | assert("...false", Error::Other(false)); 457 | } 458 | 459 | #[test] 460 | fn test_fmt_path_inherited() { 461 | #[derive(Error, Debug)] 462 | #[error(fmt = crate::util::octal)] 463 | pub enum Error { 464 | I16(i16), 465 | I32 { 466 | n: i32, 467 | }, 468 | #[error(fmt = core::fmt::Octal::fmt)] 469 | I64(i64), 470 | #[error("...{0}")] 471 | Other(bool), 472 | } 473 | 474 | assert("0o777", Error::I16(0o777)); 475 | assert("0o777", Error::I32 { n: 0o777 }); 476 | assert("777", Error::I64(0o777)); 477 | assert("...false", Error::Other(false)); 478 | } 479 | -------------------------------------------------------------------------------- /tests/test_error.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use core::fmt::{self, Display}; 4 | use std::io; 5 | use thiserror::Error; 6 | 7 | macro_rules! unimplemented_display { 8 | ($ty:ty) => { 9 | impl Display for $ty { 10 | fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { 11 | unimplemented!() 12 | } 13 | } 14 | }; 15 | } 16 | 17 | #[derive(Error, Debug)] 18 | struct BracedError { 19 | msg: String, 20 | pos: usize, 21 | } 22 | 23 | #[derive(Error, Debug)] 24 | struct TupleError(String, usize); 25 | 26 | #[derive(Error, Debug)] 27 | struct UnitError; 28 | 29 | #[derive(Error, Debug)] 30 | struct WithSource { 31 | #[source] 32 | cause: io::Error, 33 | } 34 | 35 | #[derive(Error, Debug)] 36 | struct WithAnyhow { 37 | #[source] 38 | cause: anyhow::Error, 39 | } 40 | 41 | #[derive(Error, Debug)] 42 | enum EnumError { 43 | Braced { 44 | #[source] 45 | cause: io::Error, 46 | }, 47 | Tuple(#[source] io::Error), 48 | Unit, 49 | } 50 | 51 | unimplemented_display!(BracedError); 52 | unimplemented_display!(TupleError); 53 | unimplemented_display!(UnitError); 54 | unimplemented_display!(WithSource); 55 | unimplemented_display!(WithAnyhow); 56 | unimplemented_display!(EnumError); 57 | -------------------------------------------------------------------------------- /tests/test_expr.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::iter_cloned_collect, clippy::uninlined_format_args)] 2 | 3 | use core::fmt::Display; 4 | #[cfg(feature = "std")] 5 | use std::path::PathBuf; 6 | use thiserror::Error; 7 | 8 | // Some of the elaborate cases from the rcc codebase, which is a C compiler in 9 | // Rust. https://github.com/jyn514/rcc/blob/0.8.0/src/data/error.rs 10 | #[derive(Error, Debug)] 11 | pub enum CompilerError { 12 | #[error("cannot shift {} by {maximum} or more bits (got {current})", if *.is_left { "left" } else { "right" })] 13 | TooManyShiftBits { 14 | is_left: bool, 15 | maximum: u64, 16 | current: u64, 17 | }, 18 | 19 | #[error("#error {}", (.0).iter().copied().collect::>().join(" "))] 20 | User(Vec<&'static str>), 21 | 22 | #[error("overflow while parsing {}integer literal", 23 | if let Some(signed) = .is_signed { 24 | if *signed { "signed "} else { "unsigned "} 25 | } else { 26 | "" 27 | } 28 | )] 29 | IntegerOverflow { is_signed: Option }, 30 | 31 | #[error("overflow while parsing {}integer literal", match .is_signed { 32 | Some(true) => "signed ", 33 | Some(false) => "unsigned ", 34 | None => "", 35 | })] 36 | IntegerOverflow2 { is_signed: Option }, 37 | } 38 | 39 | // Examples drawn from Rustup. 40 | #[derive(Error, Debug)] 41 | pub enum RustupError { 42 | #[error( 43 | "toolchain '{name}' does not contain component {component}{}", 44 | .suggestion 45 | .as_ref() 46 | .map_or_else(String::new, |s| format!("; did you mean '{}'?", s)), 47 | )] 48 | UnknownComponent { 49 | name: String, 50 | component: String, 51 | suggestion: Option, 52 | }, 53 | } 54 | 55 | #[track_caller] 56 | fn assert(expected: &str, value: T) { 57 | assert_eq!(expected, value.to_string()); 58 | } 59 | 60 | #[test] 61 | fn test_rcc() { 62 | assert( 63 | "cannot shift left by 32 or more bits (got 50)", 64 | CompilerError::TooManyShiftBits { 65 | is_left: true, 66 | maximum: 32, 67 | current: 50, 68 | }, 69 | ); 70 | 71 | assert("#error A B C", CompilerError::User(vec!["A", "B", "C"])); 72 | 73 | assert( 74 | "overflow while parsing signed integer literal", 75 | CompilerError::IntegerOverflow { 76 | is_signed: Some(true), 77 | }, 78 | ); 79 | } 80 | 81 | #[test] 82 | fn test_rustup() { 83 | assert( 84 | "toolchain 'nightly' does not contain component clipy; did you mean 'clippy'?", 85 | RustupError::UnknownComponent { 86 | name: "nightly".to_owned(), 87 | component: "clipy".to_owned(), 88 | suggestion: Some("clippy".to_owned()), 89 | }, 90 | ); 91 | } 92 | 93 | // Regression test for https://github.com/dtolnay/thiserror/issues/335 94 | #[cfg(feature = "std")] 95 | #[test] 96 | #[allow(non_snake_case)] 97 | fn test_assoc_type_equality_constraint() { 98 | pub trait Trait: Display { 99 | type A; 100 | } 101 | 102 | impl Trait for i32 { 103 | type A = i32; 104 | } 105 | 106 | #[derive(Error, Debug)] 107 | #[error("{A} {b}", b = &0 as &dyn Trait)] 108 | pub struct Error { 109 | pub A: PathBuf, 110 | } 111 | 112 | assert( 113 | "... 0", 114 | Error { 115 | A: PathBuf::from("..."), 116 | }, 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /tests/test_from.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::extra_unused_type_parameters)] 2 | 3 | use std::io; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug)] 7 | #[error("...")] 8 | pub struct ErrorStruct { 9 | #[from] 10 | source: io::Error, 11 | } 12 | 13 | #[derive(Error, Debug)] 14 | #[error("...")] 15 | pub struct ErrorStructOptional { 16 | #[from] 17 | source: Option, 18 | } 19 | 20 | #[derive(Error, Debug)] 21 | #[error("...")] 22 | pub struct ErrorTuple(#[from] io::Error); 23 | 24 | #[derive(Error, Debug)] 25 | #[error("...")] 26 | pub struct ErrorTupleOptional(#[from] Option); 27 | 28 | #[derive(Error, Debug)] 29 | #[error("...")] 30 | pub enum ErrorEnum { 31 | Test { 32 | #[from] 33 | source: io::Error, 34 | }, 35 | } 36 | 37 | #[derive(Error, Debug)] 38 | #[error("...")] 39 | pub enum ErrorEnumOptional { 40 | Test { 41 | #[from] 42 | source: Option, 43 | }, 44 | } 45 | 46 | #[derive(Error, Debug)] 47 | #[error("...")] 48 | pub enum Many { 49 | Any(#[from] anyhow::Error), 50 | Io(#[from] io::Error), 51 | } 52 | 53 | fn assert_impl>() {} 54 | 55 | #[test] 56 | fn test_from() { 57 | assert_impl::(); 58 | assert_impl::(); 59 | assert_impl::(); 60 | assert_impl::(); 61 | assert_impl::(); 62 | assert_impl::(); 63 | assert_impl::(); 64 | } 65 | -------------------------------------------------------------------------------- /tests/test_generics.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::needless_late_init, clippy::uninlined_format_args)] 2 | 3 | use core::fmt::{self, Debug, Display}; 4 | use core::str::FromStr; 5 | use thiserror::Error; 6 | 7 | pub struct NoFormat; 8 | 9 | #[derive(Debug)] 10 | pub struct DebugOnly; 11 | 12 | pub struct DisplayOnly; 13 | 14 | impl Display for DisplayOnly { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | f.write_str("display only") 17 | } 18 | } 19 | 20 | #[derive(Debug)] 21 | pub struct DebugAndDisplay; 22 | 23 | impl Display for DebugAndDisplay { 24 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 25 | f.write_str("debug and display") 26 | } 27 | } 28 | 29 | // Should expand to: 30 | // 31 | // impl Display for EnumDebugField 32 | // where 33 | // E: Debug; 34 | // 35 | // impl Error for EnumDebugField 36 | // where 37 | // Self: Debug + Display; 38 | // 39 | #[derive(Error, Debug)] 40 | pub enum EnumDebugGeneric { 41 | #[error("{0:?}")] 42 | FatalError(E), 43 | } 44 | 45 | // Should expand to: 46 | // 47 | // impl Display for EnumFromGeneric; 48 | // 49 | // impl Error for EnumFromGeneric 50 | // where 51 | // EnumDebugGeneric: Error + 'static, 52 | // Self: Debug + Display; 53 | // 54 | #[derive(Error, Debug)] 55 | pub enum EnumFromGeneric { 56 | #[error("enum from generic")] 57 | Source(#[from] EnumDebugGeneric), 58 | } 59 | 60 | // Should expand to: 61 | // 62 | // impl Display 63 | // for EnumCompound 64 | // where 65 | // HasDisplay: Display, 66 | // HasDebug: Debug; 67 | // 68 | // impl Error 69 | // for EnumCompound 70 | // where 71 | // Self: Debug + Display; 72 | // 73 | #[derive(Error)] 74 | pub enum EnumCompound { 75 | #[error("{0} {1:?}")] 76 | DisplayDebug(HasDisplay, HasDebug), 77 | #[error("{0}")] 78 | Display(HasDisplay, HasNeither), 79 | #[error("{1:?}")] 80 | Debug(HasNeither, HasDebug), 81 | } 82 | 83 | impl Debug for EnumCompound { 84 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 85 | f.write_str("EnumCompound") 86 | } 87 | } 88 | 89 | #[test] 90 | fn test_display_enum_compound() { 91 | let mut instance: EnumCompound; 92 | 93 | instance = EnumCompound::DisplayDebug(DisplayOnly, DebugOnly); 94 | assert_eq!(format!("{}", instance), "display only DebugOnly"); 95 | 96 | instance = EnumCompound::Display(DisplayOnly, NoFormat); 97 | assert_eq!(format!("{}", instance), "display only"); 98 | 99 | instance = EnumCompound::Debug(NoFormat, DebugOnly); 100 | assert_eq!(format!("{}", instance), "DebugOnly"); 101 | } 102 | 103 | // Should expand to: 104 | // 105 | // impl Display for EnumTransparentGeneric 106 | // where 107 | // E: Display; 108 | // 109 | // impl Error for EnumTransparentGeneric 110 | // where 111 | // E: Error, 112 | // Self: Debug + Display; 113 | // 114 | #[derive(Error, Debug)] 115 | pub enum EnumTransparentGeneric { 116 | #[error(transparent)] 117 | Other(E), 118 | } 119 | 120 | // Should expand to: 121 | // 122 | // impl Display for StructDebugGeneric 123 | // where 124 | // E: Debug; 125 | // 126 | // impl Error for StructDebugGeneric 127 | // where 128 | // Self: Debug + Display; 129 | // 130 | #[derive(Error, Debug)] 131 | #[error("{underlying:?}")] 132 | pub struct StructDebugGeneric { 133 | pub underlying: E, 134 | } 135 | 136 | // Should expand to: 137 | // 138 | // impl Error for StructFromGeneric 139 | // where 140 | // StructDebugGeneric: Error + 'static, 141 | // Self: Debug + Display; 142 | // 143 | #[derive(Error, Debug)] 144 | pub struct StructFromGeneric { 145 | #[from] 146 | pub source: StructDebugGeneric, 147 | } 148 | 149 | // Should expand to: 150 | // 151 | // impl Display for StructTransparentGeneric 152 | // where 153 | // E: Display; 154 | // 155 | // impl Error for StructTransparentGeneric 156 | // where 157 | // E: Error, 158 | // Self: Debug + Display; 159 | // 160 | #[derive(Error, Debug)] 161 | #[error(transparent)] 162 | pub struct StructTransparentGeneric(pub E); 163 | 164 | // Should expand to: 165 | // 166 | // impl Display for AssociatedTypeError 167 | // where 168 | // T::Err: Display; 169 | // 170 | // impl Error for AssociatedTypeError 171 | // where 172 | // Self: Debug + Display; 173 | // 174 | #[derive(Error, Debug)] 175 | pub enum AssociatedTypeError { 176 | #[error("couldn't parse matrix")] 177 | Other, 178 | #[error("couldn't parse entry: {0}")] 179 | EntryParseError(T::Err), 180 | } 181 | 182 | // Regression test for https://github.com/dtolnay/thiserror/issues/345 183 | #[test] 184 | fn test_no_bound_on_named_fmt() { 185 | #[derive(Error, Debug)] 186 | #[error("{thing}", thing = "...")] 187 | struct Error { 188 | thing: T, 189 | } 190 | 191 | let error = Error { thing: DebugOnly }; 192 | assert_eq!(error.to_string(), "..."); 193 | } 194 | 195 | #[test] 196 | fn test_multiple_bound() { 197 | #[derive(Error, Debug)] 198 | #[error("0x{thing:x} 0x{thing:X}")] 199 | pub struct Error { 200 | thing: T, 201 | } 202 | 203 | let error = Error { thing: 0xFFi32 }; 204 | assert_eq!(error.to_string(), "0xff 0xFF"); 205 | } 206 | -------------------------------------------------------------------------------- /tests/test_lints.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::mixed_attributes_style)] 2 | 3 | use thiserror::Error; 4 | 5 | pub use std::error::Error; 6 | 7 | #[test] 8 | fn test_allow_attributes() { 9 | #![deny(clippy::allow_attributes)] 10 | 11 | #[derive(Error, Debug)] 12 | #[error("...")] 13 | pub struct MyError(#[from] anyhow::Error); 14 | 15 | let _: MyError; 16 | } 17 | 18 | #[test] 19 | fn test_unused_qualifications() { 20 | #![deny(unused_qualifications)] 21 | 22 | // Expansion of derive(Error) macro can't know whether something like 23 | // std::error::Error is already imported in the caller's scope so it must 24 | // suppress unused_qualifications. 25 | 26 | #[derive(Error, Debug)] 27 | #[error("...")] 28 | pub struct MyError; 29 | 30 | let _: MyError; 31 | } 32 | 33 | #[test] 34 | fn test_needless_lifetimes() { 35 | #![allow(dead_code)] 36 | #![deny(clippy::elidable_lifetime_names, clippy::needless_lifetimes)] 37 | 38 | #[derive(Error, Debug)] 39 | #[error("...")] 40 | pub enum MyError<'a> { 41 | A(#[from] std::io::Error), 42 | B(&'a ()), 43 | } 44 | 45 | let _: MyError; 46 | } 47 | 48 | #[test] 49 | fn test_deprecated() { 50 | #![deny(deprecated)] 51 | 52 | #[derive(Error, Debug)] 53 | #[deprecated] 54 | #[error("...")] 55 | pub struct DeprecatedStruct; 56 | 57 | #[derive(Error, Debug)] 58 | #[error("{message} {}", .message)] 59 | pub struct DeprecatedStructField { 60 | #[deprecated] 61 | message: String, 62 | } 63 | 64 | #[derive(Error, Debug)] 65 | #[deprecated] 66 | pub enum DeprecatedEnum { 67 | #[error("...")] 68 | Variant, 69 | } 70 | 71 | #[derive(Error, Debug)] 72 | pub enum DeprecatedVariant { 73 | #[deprecated] 74 | #[error("...")] 75 | Variant, 76 | } 77 | 78 | #[derive(Error, Debug)] 79 | pub enum DeprecatedFrom { 80 | #[error(transparent)] 81 | Variant( 82 | #[from] 83 | #[allow(deprecated)] 84 | DeprecatedStruct, 85 | ), 86 | } 87 | 88 | #[allow(deprecated)] 89 | let _: DeprecatedStruct; 90 | #[allow(deprecated)] 91 | let _: DeprecatedStructField; 92 | #[allow(deprecated)] 93 | let _ = DeprecatedEnum::Variant; 94 | #[allow(deprecated)] 95 | let _ = DeprecatedVariant::Variant; 96 | } 97 | -------------------------------------------------------------------------------- /tests/test_option.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "std")] 2 | #![cfg_attr(thiserror_nightly_testing, feature(error_generic_member_access))] 3 | 4 | #[cfg(thiserror_nightly_testing)] 5 | pub mod structs { 6 | use std::backtrace::Backtrace; 7 | use thiserror::Error; 8 | 9 | #[derive(Error, Debug)] 10 | #[error("...")] 11 | pub struct OptSourceNoBacktrace { 12 | #[source] 13 | pub source: Option, 14 | } 15 | 16 | #[derive(Error, Debug)] 17 | #[error("...")] 18 | pub struct OptSourceAlwaysBacktrace { 19 | #[source] 20 | pub source: Option, 21 | pub backtrace: Backtrace, 22 | } 23 | 24 | #[derive(Error, Debug)] 25 | #[error("...")] 26 | pub struct NoSourceOptBacktrace { 27 | #[backtrace] 28 | pub backtrace: Option, 29 | } 30 | 31 | #[derive(Error, Debug)] 32 | #[error("...")] 33 | pub struct AlwaysSourceOptBacktrace { 34 | pub source: anyhow::Error, 35 | #[backtrace] 36 | pub backtrace: Option, 37 | } 38 | 39 | #[derive(Error, Debug)] 40 | #[error("...")] 41 | pub struct OptSourceOptBacktrace { 42 | #[source] 43 | pub source: Option, 44 | #[backtrace] 45 | pub backtrace: Option, 46 | } 47 | } 48 | 49 | #[cfg(thiserror_nightly_testing)] 50 | pub mod enums { 51 | use std::backtrace::Backtrace; 52 | use thiserror::Error; 53 | 54 | #[derive(Error, Debug)] 55 | pub enum OptSourceNoBacktrace { 56 | #[error("...")] 57 | Test { 58 | #[source] 59 | source: Option, 60 | }, 61 | } 62 | 63 | #[derive(Error, Debug)] 64 | pub enum OptSourceAlwaysBacktrace { 65 | #[error("...")] 66 | Test { 67 | #[source] 68 | source: Option, 69 | backtrace: Backtrace, 70 | }, 71 | } 72 | 73 | #[derive(Error, Debug)] 74 | pub enum NoSourceOptBacktrace { 75 | #[error("...")] 76 | Test { 77 | #[backtrace] 78 | backtrace: Option, 79 | }, 80 | } 81 | 82 | #[derive(Error, Debug)] 83 | pub enum AlwaysSourceOptBacktrace { 84 | #[error("...")] 85 | Test { 86 | source: anyhow::Error, 87 | #[backtrace] 88 | backtrace: Option, 89 | }, 90 | } 91 | 92 | #[derive(Error, Debug)] 93 | pub enum OptSourceOptBacktrace { 94 | #[error("...")] 95 | Test { 96 | #[source] 97 | source: Option, 98 | #[backtrace] 99 | backtrace: Option, 100 | }, 101 | } 102 | } 103 | 104 | #[test] 105 | #[cfg_attr( 106 | not(thiserror_nightly_testing), 107 | ignore = "requires `--cfg=thiserror_nightly_testing`" 108 | )] 109 | fn test_option() {} 110 | -------------------------------------------------------------------------------- /tests/test_path.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "std")] 2 | 3 | use core::fmt::Display; 4 | use ref_cast::RefCast; 5 | use std::path::{Path, PathBuf}; 6 | use thiserror::Error; 7 | 8 | #[derive(Error, Debug)] 9 | #[error("failed to read '{file}'")] 10 | struct StructPathBuf { 11 | file: PathBuf, 12 | } 13 | 14 | #[derive(Error, Debug, RefCast)] 15 | #[repr(C)] 16 | #[error("failed to read '{file}'")] 17 | struct StructPath { 18 | file: Path, 19 | } 20 | 21 | #[derive(Error, Debug)] 22 | enum EnumPathBuf { 23 | #[error("failed to read '{0}'")] 24 | Read(PathBuf), 25 | } 26 | 27 | #[derive(Error, Debug)] 28 | #[error("{tail}")] 29 | pub struct UnsizedError { 30 | pub head: i32, 31 | pub tail: str, 32 | } 33 | 34 | #[derive(Error, Debug)] 35 | pub enum BothError { 36 | #[error("display:{0} debug:{0:?}")] 37 | DisplayDebug(PathBuf), 38 | #[error("debug:{0:?} display:{0}")] 39 | DebugDisplay(PathBuf), 40 | } 41 | 42 | fn assert(expected: &str, value: T) { 43 | assert_eq!(expected, value.to_string()); 44 | } 45 | 46 | #[test] 47 | fn test_display() { 48 | let path = Path::new("/thiserror"); 49 | let file = path.to_owned(); 50 | assert("failed to read '/thiserror'", StructPathBuf { file }); 51 | let file = path.to_owned(); 52 | assert("failed to read '/thiserror'", EnumPathBuf::Read(file)); 53 | assert("failed to read '/thiserror'", StructPath::ref_cast(path)); 54 | } 55 | -------------------------------------------------------------------------------- /tests/test_source.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::io; 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug)] 6 | #[error("implicit source")] 7 | pub struct ImplicitSource { 8 | source: io::Error, 9 | } 10 | 11 | #[derive(Error, Debug)] 12 | #[error("explicit source")] 13 | pub struct ExplicitSource { 14 | source: String, 15 | #[source] 16 | io: io::Error, 17 | } 18 | 19 | #[derive(Error, Debug)] 20 | #[error("boxed source")] 21 | pub struct BoxedSource { 22 | #[source] 23 | source: Box, 24 | } 25 | 26 | #[test] 27 | fn test_implicit_source() { 28 | let io = io::Error::new(io::ErrorKind::Other, "oh no!"); 29 | let error = ImplicitSource { source: io }; 30 | error.source().unwrap().downcast_ref::().unwrap(); 31 | } 32 | 33 | #[test] 34 | fn test_explicit_source() { 35 | let io = io::Error::new(io::ErrorKind::Other, "oh no!"); 36 | let error = ExplicitSource { 37 | source: String::new(), 38 | io, 39 | }; 40 | error.source().unwrap().downcast_ref::().unwrap(); 41 | } 42 | 43 | #[test] 44 | fn test_boxed_source() { 45 | let source = Box::new(io::Error::new(io::ErrorKind::Other, "oh no!")); 46 | let error = BoxedSource { source }; 47 | error.source().unwrap().downcast_ref::().unwrap(); 48 | } 49 | 50 | macro_rules! error_from_macro { 51 | ($($variants:tt)*) => { 52 | #[derive(Error)] 53 | #[derive(Debug)] 54 | pub enum MacroSource { 55 | $($variants)* 56 | } 57 | } 58 | } 59 | 60 | // Test that we generate impls with the proper hygiene 61 | #[rustfmt::skip] 62 | error_from_macro! { 63 | #[error("Something")] 64 | Variant(#[from] io::Error) 65 | } 66 | 67 | #[test] 68 | fn test_not_source() { 69 | #[derive(Error, Debug)] 70 | #[error("{source} ==> {destination}")] 71 | pub struct NotSource { 72 | r#source: char, 73 | destination: char, 74 | } 75 | 76 | let error = NotSource { 77 | source: 'S', 78 | destination: 'D', 79 | }; 80 | assert_eq!(error.to_string(), "S ==> D"); 81 | assert!(error.source().is_none()); 82 | } 83 | -------------------------------------------------------------------------------- /tests/test_transparent.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use std::error::Error as _; 3 | use std::io; 4 | use thiserror::Error; 5 | 6 | #[test] 7 | fn test_transparent_struct() { 8 | #[derive(Error, Debug)] 9 | #[error(transparent)] 10 | struct Error(ErrorKind); 11 | 12 | #[derive(Error, Debug)] 13 | enum ErrorKind { 14 | #[error("E0")] 15 | E0, 16 | #[error("E1")] 17 | E1(#[from] io::Error), 18 | } 19 | 20 | let error = Error(ErrorKind::E0); 21 | assert_eq!("E0", error.to_string()); 22 | assert!(error.source().is_none()); 23 | 24 | let io = io::Error::new(io::ErrorKind::Other, "oh no!"); 25 | let error = Error(ErrorKind::from(io)); 26 | assert_eq!("E1", error.to_string()); 27 | error.source().unwrap().downcast_ref::().unwrap(); 28 | } 29 | 30 | #[test] 31 | fn test_transparent_enum() { 32 | #[derive(Error, Debug)] 33 | enum Error { 34 | #[error("this failed")] 35 | This, 36 | #[error(transparent)] 37 | Other(anyhow::Error), 38 | } 39 | 40 | let error = Error::This; 41 | assert_eq!("this failed", error.to_string()); 42 | 43 | let error = Error::Other(anyhow!("inner").context("outer")); 44 | assert_eq!("outer", error.to_string()); 45 | assert_eq!("inner", error.source().unwrap().to_string()); 46 | } 47 | 48 | #[test] 49 | fn test_transparent_enum_with_default_message() { 50 | #[derive(Error, Debug)] 51 | #[error("this failed: {0}_{1}")] 52 | enum Error { 53 | This(i32, i32), 54 | #[error(transparent)] 55 | Other(anyhow::Error), 56 | } 57 | 58 | let error = Error::This(-1, -1); 59 | assert_eq!("this failed: -1_-1", error.to_string()); 60 | 61 | let error = Error::Other(anyhow!("inner").context("outer")); 62 | assert_eq!("outer", error.to_string()); 63 | assert_eq!("inner", error.source().unwrap().to_string()); 64 | } 65 | 66 | #[test] 67 | fn test_anyhow() { 68 | #[derive(Error, Debug)] 69 | #[error(transparent)] 70 | struct Any(#[from] anyhow::Error); 71 | 72 | let error = Any::from(anyhow!("inner").context("outer")); 73 | assert_eq!("outer", error.to_string()); 74 | assert_eq!("inner", error.source().unwrap().to_string()); 75 | } 76 | 77 | #[test] 78 | fn test_non_static() { 79 | #[derive(Error, Debug)] 80 | #[error(transparent)] 81 | struct Error<'a> { 82 | inner: ErrorKind<'a>, 83 | } 84 | 85 | #[derive(Error, Debug)] 86 | enum ErrorKind<'a> { 87 | #[error("unexpected token: {:?}", token)] 88 | Unexpected { token: &'a str }, 89 | } 90 | 91 | let error = Error { 92 | inner: ErrorKind::Unexpected { token: "error" }, 93 | }; 94 | assert_eq!("unexpected token: \"error\"", error.to_string()); 95 | assert!(error.source().is_none()); 96 | } 97 | -------------------------------------------------------------------------------- /tests/ui/bad-field-attr.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error(transparent)] 5 | pub struct Error(#[error(transparent)] std::io::Error); 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/bad-field-attr.stderr: -------------------------------------------------------------------------------- 1 | error: #[error(transparent)] needs to go outside the enum or struct, not on an individual field 2 | --> tests/ui/bad-field-attr.rs:5:18 3 | | 4 | 5 | pub struct Error(#[error(transparent)] std::io::Error); 5 | | ^^^^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/concat-display.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | macro_rules! error_type { 4 | ($name:ident, $what:expr) => { 5 | // Use #[error("invalid {}", $what)] instead. 6 | 7 | #[derive(Error, Debug)] 8 | #[error(concat!("invalid ", $what))] 9 | pub struct $name; 10 | }; 11 | } 12 | 13 | error_type!(Error, "foo"); 14 | 15 | fn main() {} 16 | -------------------------------------------------------------------------------- /tests/ui/concat-display.stderr: -------------------------------------------------------------------------------- 1 | error: expected one of: string literal, `transparent`, `fmt` 2 | --> tests/ui/concat-display.rs:8:17 3 | | 4 | 8 | #[error(concat!("invalid ", $what))] 5 | | ^^^^^^ 6 | ... 7 | 13 | error_type!(Error, "foo"); 8 | | ------------------------- in this macro invocation 9 | | 10 | = note: this error originates in the macro `error_type` (in Nightly builds, run with -Z macro-backtrace for more info) 11 | -------------------------------------------------------------------------------- /tests/ui/display-underscore.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error("{_}")] 5 | pub struct Error; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/display-underscore.stderr: -------------------------------------------------------------------------------- 1 | error: invalid format string: invalid argument name `_` 2 | --> tests/ui/display-underscore.rs:4:11 3 | | 4 | 4 | #[error("{_}")] 5 | | ^ invalid argument name in format string 6 | | 7 | = note: argument name cannot be a single underscore 8 | -------------------------------------------------------------------------------- /tests/ui/duplicate-enum-source.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum ErrorEnum { 5 | Confusing { 6 | #[source] 7 | a: std::io::Error, 8 | #[source] 9 | b: anyhow::Error, 10 | }, 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /tests/ui/duplicate-enum-source.stderr: -------------------------------------------------------------------------------- 1 | error: duplicate #[source] attribute 2 | --> tests/ui/duplicate-enum-source.rs:8:9 3 | | 4 | 8 | #[source] 5 | | ^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/duplicate-fmt.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error("...")] 5 | #[error("...")] 6 | pub struct Error; 7 | 8 | #[derive(Error, Debug)] 9 | #[error(fmt = core::fmt::Octal::fmt)] 10 | #[error(fmt = core::fmt::LowerHex::fmt)] 11 | pub enum FmtFmt {} 12 | 13 | #[derive(Error, Debug)] 14 | #[error(fmt = core::fmt::Octal::fmt)] 15 | #[error(transparent)] 16 | pub enum FmtTransparent {} 17 | 18 | #[derive(Error, Debug)] 19 | #[error(fmt = core::fmt::Octal::fmt)] 20 | #[error("...")] 21 | pub enum FmtDisplay {} 22 | 23 | fn main() {} 24 | -------------------------------------------------------------------------------- /tests/ui/duplicate-fmt.stderr: -------------------------------------------------------------------------------- 1 | error: only one #[error(...)] attribute is allowed 2 | --> tests/ui/duplicate-fmt.rs:5:1 3 | | 4 | 5 | #[error("...")] 5 | | ^^^^^^^^^^^^^^^ 6 | 7 | error: duplicate #[error(fmt = ...)] attribute 8 | --> tests/ui/duplicate-fmt.rs:10:1 9 | | 10 | 10 | #[error(fmt = core::fmt::LowerHex::fmt)] 11 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 12 | 13 | error: cannot have both #[error(transparent)] and #[error(fmt = ...)] 14 | --> tests/ui/duplicate-fmt.rs:14:1 15 | | 16 | 14 | #[error(fmt = core::fmt::Octal::fmt)] 17 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | error: cannot have both #[error(fmt = ...)] and a format arguments attribute 20 | --> tests/ui/duplicate-fmt.rs:20:1 21 | | 22 | 20 | #[error("...")] 23 | | ^^^^^^^^^^^^^^^ 24 | -------------------------------------------------------------------------------- /tests/ui/duplicate-struct-source.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub struct ErrorStruct { 5 | #[source] 6 | a: std::io::Error, 7 | #[source] 8 | b: anyhow::Error, 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/ui/duplicate-struct-source.stderr: -------------------------------------------------------------------------------- 1 | error: duplicate #[source] attribute 2 | --> tests/ui/duplicate-struct-source.rs:7:5 3 | | 4 | 7 | #[source] 5 | | ^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/duplicate-transparent.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error(transparent)] 5 | #[error(transparent)] 6 | pub struct Error(anyhow::Error); 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/ui/duplicate-transparent.stderr: -------------------------------------------------------------------------------- 1 | error: duplicate #[error(transparent)] attribute 2 | --> tests/ui/duplicate-transparent.rs:5:1 3 | | 4 | 5 | #[error(transparent)] 5 | | ^^^^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/expression-fallback.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error("".yellow)] 5 | pub struct ArgError; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/expression-fallback.stderr: -------------------------------------------------------------------------------- 1 | error: expected `,`, found `.` 2 | --> tests/ui/expression-fallback.rs:4:11 3 | | 4 | 4 | #[error("".yellow)] 5 | | ^ expected `,` 6 | 7 | error: argument never used 8 | --> tests/ui/expression-fallback.rs:4:12 9 | | 10 | 4 | #[error("".yellow)] 11 | | -- ^^^^^^ argument never used 12 | | | 13 | | formatting specifier missing 14 | 15 | error[E0425]: cannot find value `yellow` in this scope 16 | --> tests/ui/expression-fallback.rs:4:12 17 | | 18 | 4 | #[error("".yellow)] 19 | | ^^^^^^ not found in this scope 20 | -------------------------------------------------------------------------------- /tests/ui/fallback-impl-with-display.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display}; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | #[error] 6 | pub struct MyError; 7 | 8 | impl Display for MyError { 9 | fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { 10 | unimplemented!() 11 | } 12 | } 13 | 14 | fn main() {} 15 | -------------------------------------------------------------------------------- /tests/ui/fallback-impl-with-display.stderr: -------------------------------------------------------------------------------- 1 | error: expected attribute arguments in parentheses: #[error(...)] 2 | --> tests/ui/fallback-impl-with-display.rs:5:3 3 | | 4 | 5 | #[error] 5 | | ^^^^^ 6 | 7 | error[E0119]: conflicting implementations of trait `std::fmt::Display` for type `MyError` 8 | --> tests/ui/fallback-impl-with-display.rs:4:10 9 | | 10 | 4 | #[derive(Error, Debug)] 11 | | ^^^^^ conflicting implementation for `MyError` 12 | ... 13 | 8 | impl Display for MyError { 14 | | ------------------------ first implementation here 15 | | 16 | = note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info) 17 | -------------------------------------------------------------------------------- /tests/ui/from-backtrace-backtrace.rs: -------------------------------------------------------------------------------- 1 | // https://github.com/dtolnay/thiserror/issues/163 2 | 3 | use std::backtrace::Backtrace; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug)] 7 | #[error("...")] 8 | pub struct Error( 9 | #[from] 10 | #[backtrace] 11 | std::io::Error, 12 | Backtrace, 13 | ); 14 | 15 | fn main() {} 16 | -------------------------------------------------------------------------------- /tests/ui/from-backtrace-backtrace.stderr: -------------------------------------------------------------------------------- 1 | error: deriving From requires no fields other than source and backtrace 2 | --> tests/ui/from-backtrace-backtrace.rs:9:5 3 | | 4 | 9 | #[from] 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/from-not-source.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub struct Error { 5 | #[source] 6 | source: std::io::Error, 7 | #[from] 8 | other: anyhow::Error, 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/ui/from-not-source.stderr: -------------------------------------------------------------------------------- 1 | error: #[from] is only supported on the source field, not any other field 2 | --> tests/ui/from-not-source.rs:7:5 3 | | 4 | 7 | #[from] 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/invalid-input-impl-anyway.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error] 5 | pub struct MyError; 6 | 7 | fn main() { 8 | // No error on the following line. Thiserror emits an Error impl despite the 9 | // bad attribute. 10 | _ = &MyError as &dyn std::error::Error; 11 | } 12 | -------------------------------------------------------------------------------- /tests/ui/invalid-input-impl-anyway.stderr: -------------------------------------------------------------------------------- 1 | error: expected attribute arguments in parentheses: #[error(...)] 2 | --> tests/ui/invalid-input-impl-anyway.rs:4:3 3 | | 4 | 4 | #[error] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/lifetime.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | #[error("error")] 6 | struct Error<'a>(#[from] Inner<'a>); 7 | 8 | #[derive(Error, Debug)] 9 | #[error("{0}")] 10 | struct Inner<'a>(&'a str); 11 | 12 | #[derive(Error, Debug)] 13 | enum Enum<'a> { 14 | #[error("error")] 15 | Foo(#[from] Generic<&'a str>), 16 | } 17 | 18 | #[derive(Error, Debug)] 19 | #[error("{0:?}")] 20 | struct Generic(T); 21 | 22 | fn main() -> Result<(), Error<'static>> { 23 | Err(Error(Inner("some text"))) 24 | } 25 | -------------------------------------------------------------------------------- /tests/ui/lifetime.stderr: -------------------------------------------------------------------------------- 1 | error: non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static 2 | --> tests/ui/lifetime.rs:6:26 3 | | 4 | 6 | struct Error<'a>(#[from] Inner<'a>); 5 | | ^^^^^^^^^ 6 | 7 | error: non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static 8 | --> tests/ui/lifetime.rs:15:17 9 | | 10 | 15 | Foo(#[from] Generic<&'a str>), 11 | | ^^^^^^^^^^^^^^^^ 12 | -------------------------------------------------------------------------------- /tests/ui/missing-display.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum MyError { 5 | First, 6 | Second, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/ui/missing-display.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: `MyError` doesn't implement `std::fmt::Display` 2 | --> tests/ui/missing-display.rs:4:10 3 | | 4 | 3 | #[derive(Error, Debug)] 5 | | ----- in this derive macro expansion 6 | 4 | pub enum MyError { 7 | | ^^^^^^^ `MyError` cannot be formatted with the default formatter 8 | | 9 | = help: the trait `std::fmt::Display` is not implemented for `MyError` 10 | = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead 11 | note: required by a bound in `std::error::Error` 12 | --> $RUST/core/src/error.rs 13 | | 14 | | pub trait Error: Debug + Display { 15 | | ^^^^^^^ required by this bound in `Error` 16 | = note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info) 17 | -------------------------------------------------------------------------------- /tests/ui/missing-fmt.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum Error { 5 | #[error("...")] 6 | A(usize), 7 | B(usize), 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/ui/missing-fmt.stderr: -------------------------------------------------------------------------------- 1 | error: missing #[error("...")] display attribute 2 | --> tests/ui/missing-fmt.rs:7:5 3 | | 4 | 7 | B(usize), 5 | | ^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/no-display.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug)] 4 | struct NoDisplay; 5 | 6 | #[derive(Error, Debug)] 7 | #[error("thread: {thread}")] 8 | pub struct Error { 9 | thread: NoDisplay, 10 | } 11 | 12 | #[derive(Error, Debug)] 13 | #[error("thread: {thread:o}")] 14 | pub struct ErrorOctal { 15 | thread: NoDisplay, 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /tests/ui/no-display.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the method `as_display` exists for reference `&NoDisplay`, but its trait bounds were not satisfied 2 | --> tests/ui/no-display.rs:7:9 3 | | 4 | 4 | struct NoDisplay; 5 | | ---------------- doesn't satisfy `NoDisplay: std::fmt::Display` 6 | ... 7 | 7 | #[error("thread: {thread}")] 8 | | ^^^^^^^^^^^^^^^^^^ method cannot be called on `&NoDisplay` due to unsatisfied trait bounds 9 | | 10 | = note: the following trait bounds were not satisfied: 11 | `NoDisplay: std::fmt::Display` 12 | which is required by `&NoDisplay: AsDisplay<'_>` 13 | note: the trait `std::fmt::Display` must be implemented 14 | --> $RUST/core/src/fmt/mod.rs 15 | | 16 | | pub trait Display { 17 | | ^^^^^^^^^^^^^^^^^ 18 | = help: items from traits can only be used if the trait is implemented and in scope 19 | = note: the following trait defines an item `as_display`, perhaps you need to implement it: 20 | candidate #1: `AsDisplay` 21 | 22 | error[E0277]: the trait bound `NoDisplay: Octal` is not satisfied 23 | --> tests/ui/no-display.rs:13:9 24 | | 25 | 12 | #[derive(Error, Debug)] 26 | | ----- in this derive macro expansion 27 | 13 | #[error("thread: {thread:o}")] 28 | | ^^^^^^^^^^^^^^^^^^^^ the trait `Octal` is not implemented for `NoDisplay` 29 | | 30 | = help: the following other types implement trait `Octal`: 31 | &T 32 | &mut T 33 | NonZero 34 | Saturating 35 | Wrapping 36 | i128 37 | i16 38 | i32 39 | and $N others 40 | = note: required for `&NoDisplay` to implement `Octal` 41 | note: required by a bound in `core::fmt::rt::Argument::<'_>::new_octal` 42 | --> $RUST/core/src/fmt/rt.rs 43 | | 44 | | pub const fn new_octal(x: &T) -> Argument<'_> { 45 | | ^^^^^ required by this bound in `Argument::<'_>::new_octal` 46 | = note: this error originates in the macro `$crate::format_args` which comes from the expansion of the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info) 47 | -------------------------------------------------------------------------------- /tests/ui/numbered-positional-tuple.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error("invalid rdo_lookahead_frames {0} (expected < {})", i32::MAX)] 5 | pub struct Error(u32); 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/numbered-positional-tuple.stderr: -------------------------------------------------------------------------------- 1 | error: ambiguous reference to positional arguments by number in a tuple struct; change this to a named argument 2 | --> tests/ui/numbered-positional-tuple.rs:4:61 3 | | 4 | 4 | #[error("invalid rdo_lookahead_frames {0} (expected < {})", i32::MAX)] 5 | | ^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/raw-identifier.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error("error: {r#fn}")] 5 | pub struct Error { 6 | r#fn: &'static str, 7 | } 8 | 9 | fn main() { 10 | let r#fn = "..."; 11 | let _ = format!("error: {r#fn}"); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/raw-identifier.stderr: -------------------------------------------------------------------------------- 1 | error: invalid format string: raw identifiers are not supported 2 | --> tests/ui/raw-identifier.rs:4:18 3 | | 4 | 4 | #[error("error: {r#fn}")] 5 | | --^^ 6 | | | 7 | | raw identifier used here in format string 8 | | help: remove the `r#` 9 | | 10 | = note: identifiers in format strings can be keywords and don't need to be prefixed with `r#` 11 | 12 | error: invalid format string: raw identifiers are not supported 13 | --> tests/ui/raw-identifier.rs:11:30 14 | | 15 | 11 | let _ = format!("error: {r#fn}"); 16 | | --^^ 17 | | | 18 | | raw identifier used here in format string 19 | | help: remove the `r#` 20 | | 21 | = note: identifiers in format strings can be keywords and don't need to be prefixed with `r#` 22 | -------------------------------------------------------------------------------- /tests/ui/same-from-type.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum Error { 5 | #[error("failed to open")] 6 | OpenFile(#[from] std::io::Error), 7 | #[error("failed to close")] 8 | CloseFile(#[from] std::io::Error), 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/ui/same-from-type.stderr: -------------------------------------------------------------------------------- 1 | error[E0119]: conflicting implementations of trait `From` for type `Error` 2 | --> tests/ui/same-from-type.rs:8:15 3 | | 4 | 6 | OpenFile(#[from] std::io::Error), 5 | | ------- first implementation here 6 | 7 | #[error("failed to close")] 7 | 8 | CloseFile(#[from] std::io::Error), 8 | | ^^^^^^^ conflicting implementation for `Error` 9 | -------------------------------------------------------------------------------- /tests/ui/source-enum-not-error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug)] 4 | pub struct NotError; 5 | 6 | #[derive(Error, Debug)] 7 | #[error("...")] 8 | pub enum ErrorEnum { 9 | Broken { source: NotError }, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /tests/ui/source-enum-not-error.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the method `as_dyn_error` exists for reference `&NotError`, but its trait bounds were not satisfied 2 | --> tests/ui/source-enum-not-error.rs:9:14 3 | | 4 | 4 | pub struct NotError; 5 | | ------------------- doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error` 6 | ... 7 | 9 | Broken { source: NotError }, 8 | | ^^^^^^ method cannot be called on `&NotError` due to unsatisfied trait bounds 9 | | 10 | = note: the following trait bounds were not satisfied: 11 | `NotError: std::error::Error` 12 | which is required by `NotError: AsDynError<'_>` 13 | `&NotError: std::error::Error` 14 | which is required by `&NotError: AsDynError<'_>` 15 | note: the trait `std::error::Error` must be implemented 16 | --> $RUST/core/src/error.rs 17 | | 18 | | pub trait Error: Debug + Display { 19 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | = help: items from traits can only be used if the trait is implemented and in scope 21 | = note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it: 22 | candidate #1: `AsDynError` 23 | -------------------------------------------------------------------------------- /tests/ui/source-enum-unnamed-field-not-error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug)] 4 | pub struct NotError; 5 | 6 | #[derive(Error, Debug)] 7 | #[error("...")] 8 | pub enum ErrorEnum { 9 | Broken(#[source] NotError), 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /tests/ui/source-enum-unnamed-field-not-error.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the method `as_dyn_error` exists for reference `&NotError`, but its trait bounds were not satisfied 2 | --> tests/ui/source-enum-unnamed-field-not-error.rs:9:12 3 | | 4 | 4 | pub struct NotError; 5 | | ------------------- doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error` 6 | ... 7 | 9 | Broken(#[source] NotError), 8 | | ^^^^^^^^^ method cannot be called on `&NotError` due to unsatisfied trait bounds 9 | | 10 | = note: the following trait bounds were not satisfied: 11 | `NotError: std::error::Error` 12 | which is required by `NotError: AsDynError<'_>` 13 | `&NotError: std::error::Error` 14 | which is required by `&NotError: AsDynError<'_>` 15 | note: the trait `std::error::Error` must be implemented 16 | --> $RUST/core/src/error.rs 17 | | 18 | | pub trait Error: Debug + Display { 19 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | = help: items from traits can only be used if the trait is implemented and in scope 21 | = note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it: 22 | candidate #1: `AsDynError` 23 | -------------------------------------------------------------------------------- /tests/ui/source-struct-not-error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug)] 4 | struct NotError; 5 | 6 | #[derive(Error, Debug)] 7 | #[error("...")] 8 | pub struct ErrorStruct { 9 | source: NotError, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /tests/ui/source-struct-not-error.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the method `as_dyn_error` exists for struct `NotError`, but its trait bounds were not satisfied 2 | --> tests/ui/source-struct-not-error.rs:9:5 3 | | 4 | 4 | struct NotError; 5 | | --------------- method `as_dyn_error` not found for this struct because it doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error` 6 | ... 7 | 9 | source: NotError, 8 | | ^^^^^^ method cannot be called on `NotError` due to unsatisfied trait bounds 9 | | 10 | = note: the following trait bounds were not satisfied: 11 | `NotError: std::error::Error` 12 | which is required by `NotError: AsDynError<'_>` 13 | note: the trait `std::error::Error` must be implemented 14 | --> $RUST/core/src/error.rs 15 | | 16 | | pub trait Error: Debug + Display { 17 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | = help: items from traits can only be used if the trait is implemented and in scope 19 | = note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it: 20 | candidate #1: `AsDynError` 21 | -------------------------------------------------------------------------------- /tests/ui/source-struct-unnamed-field-not-error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Debug)] 4 | struct NotError; 5 | 6 | #[derive(Error, Debug)] 7 | #[error("...")] 8 | pub struct ErrorStruct(#[source] NotError); 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/ui/source-struct-unnamed-field-not-error.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the method `as_dyn_error` exists for struct `NotError`, but its trait bounds were not satisfied 2 | --> tests/ui/source-struct-unnamed-field-not-error.rs:8:24 3 | | 4 | 4 | struct NotError; 5 | | --------------- method `as_dyn_error` not found for this struct because it doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error` 6 | ... 7 | 8 | pub struct ErrorStruct(#[source] NotError); 8 | | ^^^^^^^^^ method cannot be called on `NotError` due to unsatisfied trait bounds 9 | | 10 | = note: the following trait bounds were not satisfied: 11 | `NotError: std::error::Error` 12 | which is required by `NotError: AsDynError<'_>` 13 | note: the trait `std::error::Error` must be implemented 14 | --> $RUST/core/src/error.rs 15 | | 16 | | pub trait Error: Debug + Display { 17 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | = help: items from traits can only be used if the trait is implemented and in scope 19 | = note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it: 20 | candidate #1: `AsDynError` 21 | -------------------------------------------------------------------------------- /tests/ui/struct-with-fmt.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error(fmt = core::fmt::Octal::fmt)] 5 | pub struct Error(i32); 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/struct-with-fmt.stderr: -------------------------------------------------------------------------------- 1 | error: #[error(fmt = ...)] is only supported in enums; for a struct, handwrite your own Display impl 2 | --> tests/ui/struct-with-fmt.rs:4:1 3 | | 4 | 4 | #[error(fmt = core::fmt::Octal::fmt)] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/transparent-display.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error(transparent)] 5 | #[error("...")] 6 | pub struct Error(anyhow::Error); 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/ui/transparent-display.stderr: -------------------------------------------------------------------------------- 1 | error: cannot have both #[error(transparent)] and a display attribute 2 | --> tests/ui/transparent-display.rs:5:1 3 | | 4 | 5 | #[error("...")] 5 | | ^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/transparent-enum-many.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum Error { 5 | #[error(transparent)] 6 | Other(anyhow::Error, String), 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/ui/transparent-enum-many.stderr: -------------------------------------------------------------------------------- 1 | error: #[error(transparent)] requires exactly one field 2 | --> tests/ui/transparent-enum-many.rs:5:5 3 | | 4 | 5 | / #[error(transparent)] 5 | 6 | | Other(anyhow::Error, String), 6 | | |________________________________^ 7 | -------------------------------------------------------------------------------- /tests/ui/transparent-enum-not-error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum Error { 5 | #[error(transparent)] 6 | Other { message: String }, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/ui/transparent-enum-not-error.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the method `as_dyn_error` exists for reference `&String`, but its trait bounds were not satisfied 2 | --> tests/ui/transparent-enum-not-error.rs:5:13 3 | | 4 | 5 | #[error(transparent)] 5 | | ^^^^^^^^^^^ method cannot be called on `&String` due to unsatisfied trait bounds 6 | | 7 | ::: $RUST/alloc/src/string.rs 8 | | 9 | | pub struct String { 10 | | ----------------- doesn't satisfy `String: AsDynError<'_>` or `String: std::error::Error` 11 | | 12 | = note: the following trait bounds were not satisfied: 13 | `String: std::error::Error` 14 | which is required by `String: AsDynError<'_>` 15 | `&String: std::error::Error` 16 | which is required by `&String: AsDynError<'_>` 17 | `str: Sized` 18 | which is required by `str: AsDynError<'_>` 19 | `str: std::error::Error` 20 | which is required by `str: AsDynError<'_>` 21 | -------------------------------------------------------------------------------- /tests/ui/transparent-enum-source.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum Error { 5 | #[error(transparent)] 6 | Other(#[source] anyhow::Error), 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/ui/transparent-enum-source.stderr: -------------------------------------------------------------------------------- 1 | error: transparent variant can't contain #[source] 2 | --> tests/ui/transparent-enum-source.rs:6:11 3 | | 4 | 6 | Other(#[source] anyhow::Error), 5 | | ^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/transparent-enum-unnamed-field-not-error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum Error { 5 | #[error(transparent)] 6 | Other(String), 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/ui/transparent-enum-unnamed-field-not-error.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the method `as_dyn_error` exists for reference `&String`, but its trait bounds were not satisfied 2 | --> tests/ui/transparent-enum-unnamed-field-not-error.rs:5:13 3 | | 4 | 5 | #[error(transparent)] 5 | | ^^^^^^^^^^^ method cannot be called on `&String` due to unsatisfied trait bounds 6 | | 7 | ::: $RUST/alloc/src/string.rs 8 | | 9 | | pub struct String { 10 | | ----------------- doesn't satisfy `String: AsDynError<'_>` or `String: std::error::Error` 11 | | 12 | = note: the following trait bounds were not satisfied: 13 | `String: std::error::Error` 14 | which is required by `String: AsDynError<'_>` 15 | `&String: std::error::Error` 16 | which is required by `&String: AsDynError<'_>` 17 | `str: Sized` 18 | which is required by `str: AsDynError<'_>` 19 | `str: std::error::Error` 20 | which is required by `str: AsDynError<'_>` 21 | -------------------------------------------------------------------------------- /tests/ui/transparent-struct-many.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error(transparent)] 5 | pub struct Error { 6 | inner: anyhow::Error, 7 | what: String, 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/ui/transparent-struct-many.stderr: -------------------------------------------------------------------------------- 1 | error: #[error(transparent)] requires exactly one field 2 | --> tests/ui/transparent-struct-many.rs:4:1 3 | | 4 | 4 | #[error(transparent)] 5 | | ^^^^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/transparent-struct-not-error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error(transparent)] 5 | pub struct Error { 6 | message: String, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/ui/transparent-struct-not-error.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the method `as_dyn_error` exists for struct `String`, but its trait bounds were not satisfied 2 | --> tests/ui/transparent-struct-not-error.rs:4:9 3 | | 4 | 4 | #[error(transparent)] 5 | | ^^^^^^^^^^^ method cannot be called on `String` due to unsatisfied trait bounds 6 | | 7 | ::: $RUST/alloc/src/string.rs 8 | | 9 | | pub struct String { 10 | | ----------------- doesn't satisfy `String: AsDynError<'_>` or `String: std::error::Error` 11 | | 12 | = note: the following trait bounds were not satisfied: 13 | `String: std::error::Error` 14 | which is required by `String: AsDynError<'_>` 15 | `str: Sized` 16 | which is required by `str: AsDynError<'_>` 17 | `str: std::error::Error` 18 | which is required by `str: AsDynError<'_>` 19 | -------------------------------------------------------------------------------- /tests/ui/transparent-struct-source.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error(transparent)] 5 | pub struct Error(#[source] anyhow::Error); 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/transparent-struct-source.stderr: -------------------------------------------------------------------------------- 1 | error: transparent error struct can't contain #[source] 2 | --> tests/ui/transparent-struct-source.rs:5:18 3 | | 4 | 5 | pub struct Error(#[source] anyhow::Error); 5 | | ^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/transparent-struct-unnamed-field-not-error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error(transparent)] 5 | pub struct Error(String); 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/transparent-struct-unnamed-field-not-error.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the method `as_dyn_error` exists for struct `String`, but its trait bounds were not satisfied 2 | --> tests/ui/transparent-struct-unnamed-field-not-error.rs:4:9 3 | | 4 | 4 | #[error(transparent)] 5 | | ^^^^^^^^^^^ method cannot be called on `String` due to unsatisfied trait bounds 6 | | 7 | ::: $RUST/alloc/src/string.rs 8 | | 9 | | pub struct String { 10 | | ----------------- doesn't satisfy `String: AsDynError<'_>` or `String: std::error::Error` 11 | | 12 | = note: the following trait bounds were not satisfied: 13 | `String: std::error::Error` 14 | which is required by `String: AsDynError<'_>` 15 | `str: Sized` 16 | which is required by `str: AsDynError<'_>` 17 | `str: std::error::Error` 18 | which is required by `str: AsDynError<'_>` 19 | -------------------------------------------------------------------------------- /tests/ui/unconditional-recursion.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[error("{self}")] 5 | pub struct Error; 6 | 7 | fn main() { 8 | __FAIL__; 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/unconditional-recursion.stderr: -------------------------------------------------------------------------------- 1 | error[E0425]: cannot find value `__FAIL__` in this scope 2 | --> tests/ui/unconditional-recursion.rs:8:5 3 | | 4 | 8 | __FAIL__; 5 | | ^^^^^^^^ not found in this scope 6 | 7 | warning: function cannot return without recursing 8 | --> tests/ui/unconditional-recursion.rs:4:9 9 | | 10 | 4 | #[error("{self}")] 11 | | ^^^^^^^^ 12 | | | 13 | | cannot return without recursing 14 | | recursive call site 15 | | 16 | = help: a `loop` may express intention better if this is on purpose 17 | note: the lint level is defined here 18 | --> tests/ui/unconditional-recursion.rs:4:9 19 | | 20 | 4 | #[error("{self}")] 21 | | ^^^^^^^^ 22 | -------------------------------------------------------------------------------- /tests/ui/unexpected-field-fmt.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum Error { 5 | What { 6 | #[error("...")] 7 | io: std::io::Error, 8 | }, 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/ui/unexpected-field-fmt.stderr: -------------------------------------------------------------------------------- 1 | error: not expected here; the #[error(...)] attribute belongs on top of a struct or an enum variant 2 | --> tests/ui/unexpected-field-fmt.rs:6:9 3 | | 4 | 6 | #[error("...")] 5 | | ^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/unexpected-struct-source.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | #[source] 5 | pub struct Error; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/unexpected-struct-source.stderr: -------------------------------------------------------------------------------- 1 | error: not expected here; the #[source] attribute belongs on a specific field 2 | --> tests/ui/unexpected-struct-source.rs:4:1 3 | | 4 | 4 | #[source] 5 | | ^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/union.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error)] 4 | pub union U { 5 | msg: &'static str, 6 | num: usize, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /tests/ui/union.stderr: -------------------------------------------------------------------------------- 1 | error: union as errors are not supported 2 | --> tests/ui/union.rs:4:1 3 | | 4 | 4 | / pub union U { 5 | 5 | | msg: &'static str, 6 | 6 | | num: usize, 7 | 7 | | } 8 | | |_^ 9 | --------------------------------------------------------------------------------