port to meson cargo
All checks were successful
Gentoo Utils / build-oci-image (push) Successful in 22s
Gentoo Utils / grep (push) Successful in 1m1s
Gentoo Utils / check-format (push) Successful in 1m26s
Gentoo Utils / docs (push) Successful in 1m31s
Gentoo Utils / build (push) Successful in 1m43s
Gentoo Utils / test (push) Successful in 36s
Gentoo Utils / fuzz (push) Successful in 1m21s
All checks were successful
Gentoo Utils / build-oci-image (push) Successful in 22s
Gentoo Utils / grep (push) Successful in 1m1s
Gentoo Utils / check-format (push) Successful in 1m26s
Gentoo Utils / docs (push) Successful in 1m31s
Gentoo Utils / build (push) Successful in 1m43s
Gentoo Utils / test (push) Successful in 36s
Gentoo Utils / fuzz (push) Successful in 1m21s
Use the new unstable meson cargo support. This simplifies the meson.build script and allows to use crates such as clap that require picking up features from Cargo.toml. This also allows us to not embed thiserror in subprojects, and instead use a wrap file with a custom meson.build and some patches to make it compile without running its build.rs script.
This commit is contained in:
@@ -2,8 +2,6 @@ FROM gentoo/stage3:latest
|
||||
|
||||
COPY ./. /
|
||||
|
||||
ENV EGIT_CLONE_TYPE=shallow
|
||||
|
||||
RUN getuto
|
||||
|
||||
RUN emerge-webrsync
|
||||
@@ -16,14 +14,13 @@ RUN emerge \
|
||||
sys-process/parallel \
|
||||
net-libs/nodejs
|
||||
|
||||
|
||||
RUN git clone https://jturnerusa.dev/cgit/ebuilds/ /var/db/repos/spawns
|
||||
|
||||
COPY etc/portage/repos.conf /etc/portage/
|
||||
|
||||
RUN mkdir -p /var/cache/distfiles/git3-src && chown portage:portage /var/cache/distfiles/git3-src
|
||||
|
||||
RUN emerge =dev-build/meson-9999
|
||||
RUN emerge =dev-build/meson-9999::spawns
|
||||
|
||||
RUN useradd -m gentooligan
|
||||
|
||||
|
||||
1
.docker/etc/portage/package.accept_keywords/jturnerusa
Normal file
1
.docker/etc/portage/package.accept_keywords/jturnerusa
Normal file
@@ -0,0 +1 @@
|
||||
sec-keys/jturnerusa
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@
|
||||
/subprojects
|
||||
!/subprojects
|
||||
/subprojects/*
|
||||
!/subprojects/thiserror
|
||||
!/subprojects/packagefiles
|
||||
!/subprojects/*.wrap
|
||||
@@ -7,4 +7,4 @@ edition = "2024"
|
||||
mon = { git = "https://jturnerusa.dev/cgit/mon/", rev = "67861a4df8a5abdd70651d47cf265b20c41d2acc" }
|
||||
get = { git = "https://jturnerusa.dev/cgit/get/", rev = "cd5f75b65777a855ab010c3137304ac05f2e56b8" }
|
||||
itertools = "0.14.0"
|
||||
thiserror = "2.0.17"
|
||||
thiserror = { version = "2.0.17", features = ["derive"] }
|
||||
23
meson.build
23
meson.build
@@ -7,24 +7,20 @@ project(
|
||||
|
||||
rustfmt = find_program('rustfmt')
|
||||
|
||||
rust = import('rust')
|
||||
fs = import('fs')
|
||||
|
||||
sources = []
|
||||
|
||||
subdir('src')
|
||||
rust = import('rust')
|
||||
cargo = rust.workspace()
|
||||
|
||||
mon = dependency('mon-0.1-rs')
|
||||
get = dependency('get-0.1-rs')
|
||||
itertools = dependency('itertools-0.14-rs')
|
||||
thiserror = subproject('thiserror').get_variable('thiserror')
|
||||
thiserror = dependency('thiserror-2-rs')
|
||||
|
||||
gentoo_utils = static_library(
|
||||
'gentoo_utils',
|
||||
'src/lib.rs',
|
||||
dependencies: [mon, get, itertools],
|
||||
link_with: [thiserror],
|
||||
)
|
||||
gentoo_utils = cargo.package().library()
|
||||
|
||||
sources = []
|
||||
|
||||
subdir('src')
|
||||
|
||||
custom_target(
|
||||
'rustfmt',
|
||||
@@ -47,8 +43,7 @@ if get_option('docs').enabled()
|
||||
rust.doctest(
|
||||
'doctests',
|
||||
gentoo_utils,
|
||||
dependencies: [mon, get, itertools],
|
||||
link_with: [thiserror],
|
||||
dependencies: [mon, get, itertools, thiserror],
|
||||
args: ['--nocapture'],
|
||||
)
|
||||
endif
|
||||
|
||||
3
subprojects/packagefiles/syn-2-rs/meson/meson.build
Normal file
3
subprojects/packagefiles/syn-2-rs/meson/meson.build
Normal file
@@ -0,0 +1,3 @@
|
||||
extra_args += [
|
||||
'--cfg', 'feature="full"',
|
||||
]
|
||||
14
subprojects/packagefiles/thiserror-2.0.17-include.patch
Normal file
14
subprojects/packagefiles/thiserror-2.0.17-include.patch
Normal file
@@ -0,0 +1,14 @@
|
||||
diff --git a/src/lib.rs b/src/lib.rs
|
||||
index 155272d..7683f4c 100644
|
||||
--- a/src/lib.rs
|
||||
+++ b/src/lib.rs
|
||||
@@ -288,4 +288,8 @@ pub use thiserror_impl::*;
|
||||
|
||||
mod private;
|
||||
|
||||
-include!(concat!(env!("OUT_DIR"), "/private.rs"));
|
||||
+#[doc(hidden)]
|
||||
+pub mod __private_MESON {
|
||||
+ #[doc(hidden)]
|
||||
+ pub use crate::private::*;
|
||||
+}
|
||||
@@ -11,21 +11,12 @@ project(
|
||||
)
|
||||
|
||||
rust = import('rust')
|
||||
cargo = rust.workspace()
|
||||
|
||||
syn = dependency('syn-2-rs')
|
||||
proc_macro2 = dependency('proc-macro2-1-rs')
|
||||
quote = dependency('quote-1-rs')
|
||||
|
||||
impl = cargo.package('impl').proc_macro()
|
||||
|
||||
thiserror_proc_macro = rust.proc_macro(
|
||||
'thiserror_impl',
|
||||
'impl/src/lib.rs',
|
||||
dependencies: [syn, proc_macro2, quote],
|
||||
)
|
||||
|
||||
thiserror = static_library(
|
||||
'thiserror',
|
||||
'src/lib.rs',
|
||||
rust_args: ['--cfg', 'feature="std"'],
|
||||
link_with: [thiserror_proc_macro],
|
||||
)
|
||||
thiserror = cargo.package().library()
|
||||
13
subprojects/packagefiles/thiserror-impl-2.0.17-include.patch
Normal file
13
subprojects/packagefiles/thiserror-impl-2.0.17-include.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/impl/src/lib.rs b/impl/src/lib.rs
|
||||
index 25890f2..1559a41 100644
|
||||
--- a/src/lib.rs
|
||||
+++ b/src/lib.rs
|
||||
@@ -48,7 +48,7 @@ struct private;
|
||||
impl ToTokens for private {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
tokens.append(Ident::new(
|
||||
- concat!("__private", env!("CARGO_PKG_VERSION_PATCH")),
|
||||
+ concat!("__private", "_MESON"),
|
||||
Span::call_site(),
|
||||
));
|
||||
}
|
||||
10
subprojects/thiserror-2-rs.wrap
Normal file
10
subprojects/thiserror-2-rs.wrap
Normal file
@@ -0,0 +1,10 @@
|
||||
[wrap-file]
|
||||
directory = thiserror-2.0.17
|
||||
source_url = https://crates.io/api/v1/crates/thiserror/2.0.17/download
|
||||
source_filename = thiserror-2.0.17.tar.gz
|
||||
source_hash = f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8
|
||||
method = cargo
|
||||
diff_files = thiserror-2.0.17-include.patch
|
||||
|
||||
# bump this version number on every change to meson.build or the patches:
|
||||
# v2
|
||||
10
subprojects/thiserror-impl-2-rs.wrap
Normal file
10
subprojects/thiserror-impl-2-rs.wrap
Normal file
@@ -0,0 +1,10 @@
|
||||
[wrap-file]
|
||||
directory = thiserror-impl-2.0.17
|
||||
source_url = https://crates.io/api/v1/crates/thiserror-impl/2.0.17/download
|
||||
source_filename = thiserror-impl-2.0.17.tar.gz
|
||||
source_hash = 3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913
|
||||
method = cargo
|
||||
diff_files = thiserror-impl-2.0.17-include.patch
|
||||
|
||||
# bump this version number on every change to meson.build or the patches:
|
||||
# v2
|
||||
1
subprojects/thiserror/.github/FUNDING.yml
vendored
1
subprojects/thiserror/.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
||||
github: dtolnay
|
||||
128
subprojects/thiserror/.github/workflows/ci.yml
vendored
128
subprojects/thiserror/.github/workflows/ci.yml
vendored
@@ -1,128 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule: [cron: "40 1 * * *"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
pre_ci:
|
||||
uses: dtolnay/.github/.github/workflows/pre_ci.yml@master
|
||||
|
||||
test:
|
||||
name: Rust ${{matrix.rust}}
|
||||
needs: pre_ci
|
||||
if: needs.pre_ci.outputs.continue
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust: [nightly, beta, stable, 1.81.0, 1.76.0]
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{matrix.rust}}
|
||||
components: rust-src
|
||||
- name: Enable type layout randomization
|
||||
run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV
|
||||
if: matrix.rust == 'nightly'
|
||||
- name: Enable nightly-only tests
|
||||
run: echo RUSTFLAGS=${RUSTFLAGS}\ --cfg=thiserror_nightly_testing >> $GITHUB_ENV
|
||||
if: matrix.rust == 'nightly'
|
||||
- run: cargo test --workspace --exclude thiserror_no_std_test
|
||||
- run: cargo test --manifest-path tests/no-std/Cargo.toml
|
||||
if: matrix.rust != '1.76.0'
|
||||
- run: cargo test --no-default-features
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: matrix.rust == 'nightly' && always()
|
||||
with:
|
||||
name: Cargo.lock
|
||||
path: Cargo.lock
|
||||
continue-on-error: true
|
||||
|
||||
msrv:
|
||||
name: Rust 1.68.0
|
||||
needs: pre_ci
|
||||
if: needs.pre_ci.outputs.continue
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@1.68.0
|
||||
with:
|
||||
components: rust-src
|
||||
- run: cargo check
|
||||
|
||||
minimal:
|
||||
name: Minimal versions
|
||||
needs: pre_ci
|
||||
if: needs.pre_ci.outputs.continue
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- run: cargo generate-lockfile -Z minimal-versions
|
||||
- run: cargo check --locked
|
||||
|
||||
doc:
|
||||
name: Documentation
|
||||
needs: pre_ci
|
||||
if: needs.pre_ci.outputs.continue
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
- uses: dtolnay/install@cargo-docs-rs
|
||||
- run: cargo docs-rs
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: clippy, rust-src
|
||||
- run: cargo clippy --tests --workspace -- -Dclippy::all -Dclippy::pedantic
|
||||
|
||||
miri:
|
||||
name: Miri
|
||||
needs: pre_ci
|
||||
if: needs.pre_ci.outputs.continue
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@miri
|
||||
- run: cargo miri setup
|
||||
- run: cargo miri test
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-strict-provenance
|
||||
|
||||
outdated:
|
||||
name: Outdated
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: dtolnay/install@cargo-outdated
|
||||
- run: cargo outdated --workspace --exit-code 1
|
||||
2
subprojects/thiserror/.gitignore
vendored
2
subprojects/thiserror/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/target/
|
||||
/Cargo.lock
|
||||
@@ -1,50 +0,0 @@
|
||||
[package]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
authors = ["David Tolnay <dtolnay@gmail.com>"]
|
||||
categories = ["rust-patterns", "no-std"]
|
||||
description = "derive(Error)"
|
||||
documentation = "https://docs.rs/thiserror"
|
||||
edition = "2021"
|
||||
keywords = ["error", "error-handling", "derive"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/dtolnay/thiserror"
|
||||
rust-version = "1.68"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
# Std feature enables support for formatting std::path::{Path, PathBuf}
|
||||
# conveniently in an error message.
|
||||
#
|
||||
# #[derive(Error, Debug)]
|
||||
# #[error("failed to create configuration file {path}")]
|
||||
# pub struct MyError {
|
||||
# pub path: PathBuf,
|
||||
# pub source: std::io::Error,
|
||||
# }
|
||||
#
|
||||
# Without std, this would need to be written #[error("... {}", path.display())].
|
||||
std = []
|
||||
|
||||
[dependencies]
|
||||
thiserror-impl = { version = "=2.0.17", path = "impl" }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.73"
|
||||
ref-cast = "1.0.18"
|
||||
rustversion = "1.0.13"
|
||||
trybuild = { version = "1.0.108", features = ["diff"] }
|
||||
|
||||
[workspace]
|
||||
members = ["impl", "tests/no-std"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
rustdoc-args = [
|
||||
"--generate-link-to-definition",
|
||||
"--generate-macro-expansion",
|
||||
"--extern-html-root-url=core=https://doc.rust-lang.org",
|
||||
"--extern-html-root-url=alloc=https://doc.rust-lang.org",
|
||||
"--extern-html-root-url=std=https://doc.rust-lang.org",
|
||||
]
|
||||
@@ -1,176 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1,23 +0,0 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,238 +0,0 @@
|
||||
derive(Error)
|
||||
=============
|
||||
|
||||
[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/thiserror-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/thiserror)
|
||||
[<img alt="crates.io" src="https://img.shields.io/crates/v/thiserror.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/thiserror)
|
||||
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-thiserror-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/thiserror)
|
||||
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/thiserror/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/thiserror/actions?query=branch%3Amaster)
|
||||
|
||||
This library provides a convenient derive macro for the standard library's
|
||||
[`std::error::Error`] trait.
|
||||
|
||||
[`std::error::Error`]: https://doc.rust-lang.org/std/error/trait.Error.html
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
thiserror = "2"
|
||||
```
|
||||
|
||||
*Compiler support: requires rustc 1.68+*
|
||||
|
||||
<br>
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DataStoreError {
|
||||
#[error("data store disconnected")]
|
||||
Disconnect(#[from] io::Error),
|
||||
#[error("the data for key `{0}` is not available")]
|
||||
Redaction(String),
|
||||
#[error("invalid header (expected {expected:?}, found {found:?})")]
|
||||
InvalidHeader {
|
||||
expected: String,
|
||||
found: String,
|
||||
},
|
||||
#[error("unknown data store error")]
|
||||
Unknown,
|
||||
}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Details
|
||||
|
||||
- Thiserror deliberately does not appear in your public API. You get the same
|
||||
thing as if you had written an implementation of `std::error::Error` by hand,
|
||||
and switching from handwritten impls to thiserror or vice versa is not a
|
||||
breaking change.
|
||||
|
||||
- Errors may be enums, structs with named fields, tuple structs, or unit
|
||||
structs.
|
||||
|
||||
- A `Display` impl is generated for your error if you provide `#[error("...")]`
|
||||
messages on the struct or each variant of your enum, as shown above in the
|
||||
example.
|
||||
|
||||
The messages support a shorthand for interpolating fields from the error.
|
||||
|
||||
- `#[error("{var}")]` ⟶ `write!("{}", self.var)`
|
||||
- `#[error("{0}")]` ⟶ `write!("{}", self.0)`
|
||||
- `#[error("{var:?}")]` ⟶ `write!("{:?}", self.var)`
|
||||
- `#[error("{0:?}")]` ⟶ `write!("{:?}", self.0)`
|
||||
|
||||
These shorthands can be used together with any additional format args, which
|
||||
may be arbitrary expressions. For example:
|
||||
|
||||
```rust
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("invalid rdo_lookahead_frames {0} (expected < {max})", max = i32::MAX)]
|
||||
InvalidLookahead(u32),
|
||||
}
|
||||
```
|
||||
|
||||
If one of the additional expression arguments needs to refer to a field of the
|
||||
struct or enum, then refer to named fields as `.var` and tuple fields as `.0`.
|
||||
|
||||
```rust
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("first letter must be lowercase but was {:?}", first_char(.0))]
|
||||
WrongCase(String),
|
||||
#[error("invalid index {idx}, expected at least {} and at most {}", .limits.lo, .limits.hi)]
|
||||
OutOfBounds { idx: usize, limits: Limits },
|
||||
}
|
||||
```
|
||||
|
||||
- A `From` impl is generated for each variant that contains a `#[from]`
|
||||
attribute.
|
||||
|
||||
The variant using `#[from]` must not contain any other fields beyond the
|
||||
source error (and possibly a backtrace — see below). Usually `#[from]`
|
||||
fields are unnamed, but `#[from]` is allowed on a named field too.
|
||||
|
||||
```rust
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MyError {
|
||||
Io(#[from] io::Error),
|
||||
Glob(#[from] globset::Error),
|
||||
}
|
||||
```
|
||||
|
||||
- The Error trait's `source()` method is implemented to return whichever field
|
||||
has a `#[source]` attribute or is named `source`, if any. This is for
|
||||
identifying the underlying lower level error that caused your error.
|
||||
|
||||
The `#[from]` attribute always implies that the same field is `#[source]`, so
|
||||
you don't ever need to specify both attributes.
|
||||
|
||||
Any error type that implements `std::error::Error` or dereferences to `dyn
|
||||
std::error::Error` will work as a source.
|
||||
|
||||
```rust
|
||||
#[derive(Error, Debug)]
|
||||
pub struct MyError {
|
||||
msg: String,
|
||||
#[source] // optional if field name is `source`
|
||||
source: anyhow::Error,
|
||||
}
|
||||
```
|
||||
|
||||
- The Error trait's `provide()` method is implemented to provide whichever field
|
||||
has a type named `Backtrace`, if any, as a `std::backtrace::Backtrace`. Using
|
||||
`Backtrace` in errors requires a nightly compiler with Rust version 1.73 or
|
||||
newer.
|
||||
|
||||
```rust
|
||||
use std::backtrace::Backtrace;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub struct MyError {
|
||||
msg: String,
|
||||
backtrace: Backtrace, // automatically detected
|
||||
}
|
||||
```
|
||||
|
||||
- If a field is both a source (named `source`, or has `#[source]` or `#[from]`
|
||||
attribute) *and* is marked `#[backtrace]`, then the Error trait's `provide()`
|
||||
method is forwarded to the source's `provide` so that both layers of the error
|
||||
share the same backtrace. The `#[backtrace]` attribute requires a nightly
|
||||
compiler with Rust version 1.73 or newer.
|
||||
|
||||
|
||||
```rust
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MyError {
|
||||
Io {
|
||||
#[backtrace]
|
||||
source: io::Error,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- For variants that use `#[from]` and also contain a `Backtrace` field, a
|
||||
backtrace is captured from within the `From` impl.
|
||||
|
||||
```rust
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MyError {
|
||||
Io {
|
||||
#[from]
|
||||
source: io::Error,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- Errors may use `error(transparent)` to forward the source and Display methods
|
||||
straight through to an underlying error without adding an additional message.
|
||||
This would be appropriate for enums that need an "anything else" variant.
|
||||
|
||||
```rust
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MyError {
|
||||
...
|
||||
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error
|
||||
}
|
||||
```
|
||||
|
||||
Another use case is hiding implementation details of an error representation
|
||||
behind an opaque error type, so that the representation is able to evolve
|
||||
without breaking the crate's public API.
|
||||
|
||||
```rust
|
||||
// PublicError is public, but opaque and easy to keep compatible.
|
||||
#[derive(Error, Debug)]
|
||||
#[error(transparent)]
|
||||
pub struct PublicError(#[from] ErrorRepr);
|
||||
|
||||
impl PublicError {
|
||||
// Accessors for anything we do want to expose publicly.
|
||||
}
|
||||
|
||||
// Private and free to change across minor version of the crate.
|
||||
#[derive(Error, Debug)]
|
||||
enum ErrorRepr {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
- See also the [`anyhow`] library for a convenient single error type to use in
|
||||
application code.
|
||||
|
||||
[`anyhow`]: https://github.com/dtolnay/anyhow
|
||||
|
||||
<br>
|
||||
|
||||
## Comparison to anyhow
|
||||
|
||||
Use thiserror if you care about designing your own dedicated error type(s) so
|
||||
that the caller receives exactly the information that you choose in the event of
|
||||
failure. This most often applies to library-like code. Use [Anyhow] if you don't
|
||||
care what error type your functions return, you just want it to be easy. This is
|
||||
common in application-like code.
|
||||
|
||||
[Anyhow]: https://github.com/dtolnay/anyhow
|
||||
|
||||
<br>
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
|
||||
be dual licensed as above, without any additional terms or conditions.
|
||||
</sub>
|
||||
@@ -1,33 +0,0 @@
|
||||
// This code exercises the surface area that we expect of the Error generic
|
||||
// member access API. If the current toolchain is able to compile it, then
|
||||
// thiserror is able to provide backtrace support.
|
||||
|
||||
#![no_std]
|
||||
#![feature(error_generic_member_access)]
|
||||
|
||||
use core::error::{Error, Request};
|
||||
use core::fmt::{self, Debug, Display};
|
||||
|
||||
struct MyError(Thing);
|
||||
struct Thing;
|
||||
|
||||
impl Debug for MyError {
|
||||
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MyError {
|
||||
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for MyError {
|
||||
fn provide<'a>(&'a self, request: &mut Request<'a>) {
|
||||
request.provide_ref(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Include in sccache cache key.
|
||||
const _: Option<&str> = option_env!("RUSTC_BOOTSTRAP");
|
||||
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
authors = ["David Tolnay <dtolnay@gmail.com>"]
|
||||
description = "Implementation detail of the `thiserror` crate"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/dtolnay/thiserror"
|
||||
rust-version = "1.68"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.74"
|
||||
quote = "1.0.35"
|
||||
syn = "2.0.87"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
rustdoc-args = [
|
||||
"--generate-link-to-definition",
|
||||
"--generate-macro-expansion",
|
||||
"--extern-html-root-url=core=https://doc.rust-lang.org",
|
||||
"--extern-html-root-url=alloc=https://doc.rust-lang.org",
|
||||
"--extern-html-root-url=std=https://doc.rust-lang.org",
|
||||
"--extern-html-root-url=proc_macro=https://doc.rust-lang.org",
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
../LICENSE-APACHE
|
||||
@@ -1 +0,0 @@
|
||||
../LICENSE-MIT
|
||||
@@ -1,185 +0,0 @@
|
||||
use crate::attr::{self, Attrs};
|
||||
use crate::generics::ParamsInScope;
|
||||
use crate::unraw::{IdentUnraw, MemberUnraw};
|
||||
use proc_macro2::Span;
|
||||
use std::fmt::{self, Display};
|
||||
use syn::{
|
||||
Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Generics, Ident, Index, Result, Type,
|
||||
};
|
||||
|
||||
pub enum Input<'a> {
|
||||
Struct(Struct<'a>),
|
||||
Enum(Enum<'a>),
|
||||
}
|
||||
|
||||
pub struct Struct<'a> {
|
||||
pub attrs: Attrs<'a>,
|
||||
pub ident: Ident,
|
||||
pub generics: &'a Generics,
|
||||
pub fields: Vec<Field<'a>>,
|
||||
}
|
||||
|
||||
pub struct Enum<'a> {
|
||||
pub attrs: Attrs<'a>,
|
||||
pub ident: Ident,
|
||||
pub generics: &'a Generics,
|
||||
pub variants: Vec<Variant<'a>>,
|
||||
}
|
||||
|
||||
pub struct Variant<'a> {
|
||||
pub original: &'a syn::Variant,
|
||||
pub attrs: Attrs<'a>,
|
||||
pub ident: Ident,
|
||||
pub fields: Vec<Field<'a>>,
|
||||
}
|
||||
|
||||
pub struct Field<'a> {
|
||||
pub original: &'a syn::Field,
|
||||
pub attrs: Attrs<'a>,
|
||||
pub member: MemberUnraw,
|
||||
pub ty: &'a Type,
|
||||
pub contains_generic: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ContainerKind {
|
||||
Struct,
|
||||
TupleStruct,
|
||||
UnitStruct,
|
||||
StructVariant,
|
||||
TupleVariant,
|
||||
UnitVariant,
|
||||
}
|
||||
|
||||
impl<'a> Input<'a> {
|
||||
pub fn from_syn(node: &'a DeriveInput) -> Result<Self> {
|
||||
match &node.data {
|
||||
Data::Struct(data) => Struct::from_syn(node, data).map(Input::Struct),
|
||||
Data::Enum(data) => Enum::from_syn(node, data).map(Input::Enum),
|
||||
Data::Union(_) => Err(Error::new_spanned(
|
||||
node,
|
||||
"union as errors are not supported",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Struct<'a> {
|
||||
fn from_syn(node: &'a DeriveInput, data: &'a DataStruct) -> Result<Self> {
|
||||
let mut attrs = attr::get(&node.attrs)?;
|
||||
let scope = ParamsInScope::new(&node.generics);
|
||||
let fields = Field::multiple_from_syn(&data.fields, &scope)?;
|
||||
if let Some(display) = &mut attrs.display {
|
||||
let container = ContainerKind::from_struct(data);
|
||||
display.expand_shorthand(&fields, container)?;
|
||||
}
|
||||
Ok(Struct {
|
||||
attrs,
|
||||
ident: node.ident.clone(),
|
||||
generics: &node.generics,
|
||||
fields,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Enum<'a> {
|
||||
fn from_syn(node: &'a DeriveInput, data: &'a DataEnum) -> Result<Self> {
|
||||
let attrs = attr::get(&node.attrs)?;
|
||||
let scope = ParamsInScope::new(&node.generics);
|
||||
let variants = data
|
||||
.variants
|
||||
.iter()
|
||||
.map(|node| {
|
||||
let mut variant = Variant::from_syn(node, &scope)?;
|
||||
if variant.attrs.display.is_none()
|
||||
&& variant.attrs.transparent.is_none()
|
||||
&& variant.attrs.fmt.is_none()
|
||||
{
|
||||
variant.attrs.display.clone_from(&attrs.display);
|
||||
variant.attrs.transparent = attrs.transparent;
|
||||
variant.attrs.fmt.clone_from(&attrs.fmt);
|
||||
}
|
||||
if let Some(display) = &mut variant.attrs.display {
|
||||
let container = ContainerKind::from_variant(node);
|
||||
display.expand_shorthand(&variant.fields, container)?;
|
||||
}
|
||||
Ok(variant)
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
Ok(Enum {
|
||||
attrs,
|
||||
ident: node.ident.clone(),
|
||||
generics: &node.generics,
|
||||
variants,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Variant<'a> {
|
||||
fn from_syn(node: &'a syn::Variant, scope: &ParamsInScope<'a>) -> Result<Self> {
|
||||
let attrs = attr::get(&node.attrs)?;
|
||||
Ok(Variant {
|
||||
original: node,
|
||||
attrs,
|
||||
ident: node.ident.clone(),
|
||||
fields: Field::multiple_from_syn(&node.fields, scope)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Field<'a> {
|
||||
fn multiple_from_syn(fields: &'a Fields, scope: &ParamsInScope<'a>) -> Result<Vec<Self>> {
|
||||
fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| Field::from_syn(i, field, scope))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn from_syn(i: usize, node: &'a syn::Field, scope: &ParamsInScope<'a>) -> Result<Self> {
|
||||
Ok(Field {
|
||||
original: node,
|
||||
attrs: attr::get(&node.attrs)?,
|
||||
member: match &node.ident {
|
||||
Some(name) => MemberUnraw::Named(IdentUnraw::new(name.clone())),
|
||||
None => MemberUnraw::Unnamed(Index {
|
||||
index: i as u32,
|
||||
span: Span::call_site(),
|
||||
}),
|
||||
},
|
||||
ty: &node.ty,
|
||||
contains_generic: scope.intersects(&node.ty),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ContainerKind {
|
||||
fn from_struct(node: &DataStruct) -> Self {
|
||||
match node.fields {
|
||||
Fields::Named(_) => ContainerKind::Struct,
|
||||
Fields::Unnamed(_) => ContainerKind::TupleStruct,
|
||||
Fields::Unit => ContainerKind::UnitStruct,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_variant(node: &syn::Variant) -> Self {
|
||||
match node.fields {
|
||||
Fields::Named(_) => ContainerKind::StructVariant,
|
||||
Fields::Unnamed(_) => ContainerKind::TupleVariant,
|
||||
Fields::Unit => ContainerKind::UnitVariant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ContainerKind {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str(match self {
|
||||
ContainerKind::Struct => "struct",
|
||||
ContainerKind::TupleStruct => "tuple struct",
|
||||
ContainerKind::UnitStruct => "unit struct",
|
||||
ContainerKind::StructVariant => "struct variant",
|
||||
ContainerKind::TupleVariant => "tuple variant",
|
||||
ContainerKind::UnitVariant => "unit variant",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
use proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
||||
use std::collections::BTreeSet as Set;
|
||||
use syn::parse::discouraged::Speculative;
|
||||
use syn::parse::{End, ParseStream};
|
||||
use syn::{
|
||||
braced, bracketed, parenthesized, token, Attribute, Error, ExprPath, Ident, Index, LitFloat,
|
||||
LitInt, LitStr, Meta, Result, Token,
|
||||
};
|
||||
|
||||
pub struct Attrs<'a> {
|
||||
pub display: Option<Display<'a>>,
|
||||
pub source: Option<Source<'a>>,
|
||||
pub backtrace: Option<&'a Attribute>,
|
||||
pub from: Option<From<'a>>,
|
||||
pub transparent: Option<Transparent<'a>>,
|
||||
pub fmt: Option<Fmt<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Display<'a> {
|
||||
pub original: &'a Attribute,
|
||||
pub fmt: LitStr,
|
||||
pub args: TokenStream,
|
||||
pub requires_fmt_machinery: bool,
|
||||
pub has_bonus_display: bool,
|
||||
pub infinite_recursive: bool,
|
||||
pub implied_bounds: Set<(usize, Trait)>,
|
||||
pub bindings: Vec<(Ident, TokenStream)>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Source<'a> {
|
||||
pub original: &'a Attribute,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct From<'a> {
|
||||
pub original: &'a Attribute,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Transparent<'a> {
|
||||
pub original: &'a Attribute,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Fmt<'a> {
|
||||
pub original: &'a Attribute,
|
||||
pub path: ExprPath,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
pub enum Trait {
|
||||
Debug,
|
||||
Display,
|
||||
Octal,
|
||||
LowerHex,
|
||||
UpperHex,
|
||||
Pointer,
|
||||
Binary,
|
||||
LowerExp,
|
||||
UpperExp,
|
||||
}
|
||||
|
||||
pub fn get(input: &[Attribute]) -> Result<Attrs> {
|
||||
let mut attrs = Attrs {
|
||||
display: None,
|
||||
source: None,
|
||||
backtrace: None,
|
||||
from: None,
|
||||
transparent: None,
|
||||
fmt: None,
|
||||
};
|
||||
|
||||
for attr in input {
|
||||
if attr.path().is_ident("error") {
|
||||
parse_error_attribute(&mut attrs, attr)?;
|
||||
} else if attr.path().is_ident("source") {
|
||||
attr.meta.require_path_only()?;
|
||||
if attrs.source.is_some() {
|
||||
return Err(Error::new_spanned(attr, "duplicate #[source] attribute"));
|
||||
}
|
||||
let span = (attr.pound_token.span)
|
||||
.join(attr.bracket_token.span.join())
|
||||
.unwrap_or(attr.path().get_ident().unwrap().span());
|
||||
attrs.source = Some(Source {
|
||||
original: attr,
|
||||
span,
|
||||
});
|
||||
} else if attr.path().is_ident("backtrace") {
|
||||
attr.meta.require_path_only()?;
|
||||
if attrs.backtrace.is_some() {
|
||||
return Err(Error::new_spanned(attr, "duplicate #[backtrace] attribute"));
|
||||
}
|
||||
attrs.backtrace = Some(attr);
|
||||
} else if attr.path().is_ident("from") {
|
||||
match attr.meta {
|
||||
Meta::Path(_) => {}
|
||||
Meta::List(_) | Meta::NameValue(_) => {
|
||||
// Assume this is meant for derive_more crate or something.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if attrs.from.is_some() {
|
||||
return Err(Error::new_spanned(attr, "duplicate #[from] attribute"));
|
||||
}
|
||||
let span = (attr.pound_token.span)
|
||||
.join(attr.bracket_token.span.join())
|
||||
.unwrap_or(attr.path().get_ident().unwrap().span());
|
||||
attrs.from = Some(From {
|
||||
original: attr,
|
||||
span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(attrs)
|
||||
}
|
||||
|
||||
fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Result<()> {
|
||||
mod kw {
|
||||
syn::custom_keyword!(transparent);
|
||||
syn::custom_keyword!(fmt);
|
||||
}
|
||||
|
||||
attr.parse_args_with(|input: ParseStream| {
|
||||
let lookahead = input.lookahead1();
|
||||
let fmt = if lookahead.peek(LitStr) {
|
||||
input.parse::<LitStr>()?
|
||||
} else if lookahead.peek(kw::transparent) {
|
||||
let kw: kw::transparent = input.parse()?;
|
||||
if attrs.transparent.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
attr,
|
||||
"duplicate #[error(transparent)] attribute",
|
||||
));
|
||||
}
|
||||
attrs.transparent = Some(Transparent {
|
||||
original: attr,
|
||||
span: kw.span,
|
||||
});
|
||||
return Ok(());
|
||||
} else if lookahead.peek(kw::fmt) {
|
||||
input.parse::<kw::fmt>()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
let path: ExprPath = input.parse()?;
|
||||
if attrs.fmt.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
attr,
|
||||
"duplicate #[error(fmt = ...)] attribute",
|
||||
));
|
||||
}
|
||||
attrs.fmt = Some(Fmt {
|
||||
original: attr,
|
||||
path,
|
||||
});
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(lookahead.error());
|
||||
};
|
||||
|
||||
let args = if input.is_empty() || input.peek(Token![,]) && input.peek2(End) {
|
||||
input.parse::<Option<Token![,]>>()?;
|
||||
TokenStream::new()
|
||||
} else {
|
||||
parse_token_expr(input, false)?
|
||||
};
|
||||
|
||||
let requires_fmt_machinery = !args.is_empty();
|
||||
|
||||
let display = Display {
|
||||
original: attr,
|
||||
fmt,
|
||||
args,
|
||||
requires_fmt_machinery,
|
||||
has_bonus_display: false,
|
||||
infinite_recursive: false,
|
||||
implied_bounds: Set::new(),
|
||||
bindings: Vec::new(),
|
||||
};
|
||||
if attrs.display.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
attr,
|
||||
"only one #[error(...)] attribute is allowed",
|
||||
));
|
||||
}
|
||||
attrs.display = Some(display);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result<TokenStream> {
|
||||
let mut tokens = Vec::new();
|
||||
while !input.is_empty() {
|
||||
if input.peek(token::Group) {
|
||||
let group: TokenTree = input.parse()?;
|
||||
tokens.push(group);
|
||||
begin_expr = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if begin_expr && input.peek(Token![.]) {
|
||||
if input.peek2(Ident) {
|
||||
input.parse::<Token![.]>()?;
|
||||
begin_expr = false;
|
||||
continue;
|
||||
} else if input.peek2(LitInt) {
|
||||
input.parse::<Token![.]>()?;
|
||||
let int: Index = input.parse()?;
|
||||
tokens.push({
|
||||
let ident = format_ident!("_{}", int.index, span = int.span);
|
||||
TokenTree::Ident(ident)
|
||||
});
|
||||
begin_expr = false;
|
||||
continue;
|
||||
} else if input.peek2(LitFloat) {
|
||||
let ahead = input.fork();
|
||||
ahead.parse::<Token![.]>()?;
|
||||
let float: LitFloat = ahead.parse()?;
|
||||
let repr = float.to_string();
|
||||
let mut indices = repr.split('.').map(syn::parse_str::<Index>);
|
||||
if let (Some(Ok(first)), Some(Ok(second)), None) =
|
||||
(indices.next(), indices.next(), indices.next())
|
||||
{
|
||||
input.advance_to(&ahead);
|
||||
tokens.push({
|
||||
let ident = format_ident!("_{}", first, span = float.span());
|
||||
TokenTree::Ident(ident)
|
||||
});
|
||||
tokens.push({
|
||||
let mut punct = Punct::new('.', Spacing::Alone);
|
||||
punct.set_span(float.span());
|
||||
TokenTree::Punct(punct)
|
||||
});
|
||||
tokens.push({
|
||||
let mut literal = Literal::u32_unsuffixed(second.index);
|
||||
literal.set_span(float.span());
|
||||
TokenTree::Literal(literal)
|
||||
});
|
||||
begin_expr = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
begin_expr = input.peek(Token![break])
|
||||
|| input.peek(Token![continue])
|
||||
|| input.peek(Token![if])
|
||||
|| input.peek(Token![in])
|
||||
|| input.peek(Token![match])
|
||||
|| input.peek(Token![mut])
|
||||
|| input.peek(Token![return])
|
||||
|| input.peek(Token![while])
|
||||
|| input.peek(Token![+])
|
||||
|| input.peek(Token![&])
|
||||
|| input.peek(Token![!])
|
||||
|| input.peek(Token![^])
|
||||
|| input.peek(Token![,])
|
||||
|| input.peek(Token![/])
|
||||
|| input.peek(Token![=])
|
||||
|| input.peek(Token![>])
|
||||
|| input.peek(Token![<])
|
||||
|| input.peek(Token![|])
|
||||
|| input.peek(Token![%])
|
||||
|| input.peek(Token![;])
|
||||
|| input.peek(Token![*])
|
||||
|| input.peek(Token![-]);
|
||||
|
||||
let token: TokenTree = if input.peek(token::Paren) {
|
||||
let content;
|
||||
let delimiter = parenthesized!(content in input);
|
||||
let nested = parse_token_expr(&content, true)?;
|
||||
let mut group = Group::new(Delimiter::Parenthesis, nested);
|
||||
group.set_span(delimiter.span.join());
|
||||
TokenTree::Group(group)
|
||||
} else if input.peek(token::Brace) {
|
||||
let content;
|
||||
let delimiter = braced!(content in input);
|
||||
let nested = parse_token_expr(&content, true)?;
|
||||
let mut group = Group::new(Delimiter::Brace, nested);
|
||||
group.set_span(delimiter.span.join());
|
||||
TokenTree::Group(group)
|
||||
} else if input.peek(token::Bracket) {
|
||||
let content;
|
||||
let delimiter = bracketed!(content in input);
|
||||
let nested = parse_token_expr(&content, true)?;
|
||||
let mut group = Group::new(Delimiter::Bracket, nested);
|
||||
group.set_span(delimiter.span.join());
|
||||
TokenTree::Group(group)
|
||||
} else {
|
||||
input.parse()?
|
||||
};
|
||||
tokens.push(token);
|
||||
}
|
||||
Ok(TokenStream::from_iter(tokens))
|
||||
}
|
||||
|
||||
impl ToTokens for Display<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
if self.infinite_recursive {
|
||||
let span = self.fmt.span();
|
||||
tokens.extend(quote_spanned! {span=>
|
||||
#[warn(unconditional_recursion)]
|
||||
fn _fmt() { _fmt() }
|
||||
});
|
||||
}
|
||||
|
||||
let fmt = &self.fmt;
|
||||
let args = &self.args;
|
||||
|
||||
// Currently `write!(f, "text")` produces less efficient code than
|
||||
// `f.write_str("text")`. We recognize the case when the format string
|
||||
// has no braces and no interpolated values, and generate simpler code.
|
||||
let write = if self.requires_fmt_machinery {
|
||||
quote! {
|
||||
::core::write!(__formatter, #fmt #args)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
__formatter.write_str(#fmt)
|
||||
}
|
||||
};
|
||||
|
||||
tokens.extend(if self.bindings.is_empty() {
|
||||
write
|
||||
} else {
|
||||
let locals = self.bindings.iter().map(|(local, _value)| local);
|
||||
let values = self.bindings.iter().map(|(_local, value)| value);
|
||||
quote! {
|
||||
match (#(#values,)*) {
|
||||
(#(#locals,)*) => #write
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Trait {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let trait_name = match self {
|
||||
Trait::Debug => "Debug",
|
||||
Trait::Display => "Display",
|
||||
Trait::Octal => "Octal",
|
||||
Trait::LowerHex => "LowerHex",
|
||||
Trait::UpperHex => "UpperHex",
|
||||
Trait::Pointer => "Pointer",
|
||||
Trait::Binary => "Binary",
|
||||
Trait::LowerExp => "LowerExp",
|
||||
Trait::UpperExp => "UpperExp",
|
||||
};
|
||||
let ident = Ident::new(trait_name, Span::call_site());
|
||||
tokens.extend(quote!(::core::fmt::#ident));
|
||||
}
|
||||
}
|
||||
@@ -1,584 +0,0 @@
|
||||
use crate::ast::{Enum, Field, Input, Struct};
|
||||
use crate::attr::Trait;
|
||||
use crate::fallback;
|
||||
use crate::generics::InferredBounds;
|
||||
use crate::private;
|
||||
use crate::unraw::MemberUnraw;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
||||
use std::collections::BTreeSet as Set;
|
||||
use syn::{DeriveInput, GenericArgument, PathArguments, Result, Token, Type};
|
||||
|
||||
pub fn derive(input: &DeriveInput) -> TokenStream {
|
||||
match try_expand(input) {
|
||||
Ok(expanded) => expanded,
|
||||
// If there are invalid attributes in the input, expand to an Error impl
|
||||
// anyway to minimize spurious secondary errors in other code that uses
|
||||
// this type as an Error.
|
||||
Err(error) => fallback::expand(input, error),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_expand(input: &DeriveInput) -> Result<TokenStream> {
|
||||
let input = Input::from_syn(input)?;
|
||||
input.validate()?;
|
||||
Ok(match input {
|
||||
Input::Struct(input) => impl_struct(input),
|
||||
Input::Enum(input) => impl_enum(input),
|
||||
})
|
||||
}
|
||||
|
||||
fn impl_struct(input: Struct) -> TokenStream {
|
||||
let ty = call_site_ident(&input.ident);
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let mut error_inferred_bounds = InferredBounds::new();
|
||||
|
||||
let source_body = if let Some(transparent_attr) = &input.attrs.transparent {
|
||||
let only_field = &input.fields[0];
|
||||
if only_field.contains_generic {
|
||||
error_inferred_bounds.insert(only_field.ty, quote!(::thiserror::#private::Error));
|
||||
}
|
||||
let member = &only_field.member;
|
||||
Some(quote_spanned! {transparent_attr.span=>
|
||||
::thiserror::#private::Error::source(self.#member.as_dyn_error())
|
||||
})
|
||||
} else if let Some(source_field) = input.source_field() {
|
||||
let source = &source_field.member;
|
||||
if source_field.contains_generic {
|
||||
let ty = unoptional_type(source_field.ty);
|
||||
error_inferred_bounds.insert(ty, quote!(::thiserror::#private::Error + 'static));
|
||||
}
|
||||
let asref = if type_is_option(source_field.ty) {
|
||||
Some(quote_spanned!(source.span()=> .as_ref()?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let dyn_error = quote_spanned! {source_field.source_span()=>
|
||||
self.#source #asref.as_dyn_error()
|
||||
};
|
||||
Some(quote! {
|
||||
::core::option::Option::Some(#dyn_error)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let source_method = source_body.map(|body| {
|
||||
quote! {
|
||||
fn source(&self) -> ::core::option::Option<&(dyn ::thiserror::#private::Error + 'static)> {
|
||||
use ::thiserror::#private::AsDynError as _;
|
||||
#body
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let provide_method = input.backtrace_field().map(|backtrace_field| {
|
||||
let request = quote!(request);
|
||||
let backtrace = &backtrace_field.member;
|
||||
let body = if let Some(source_field) = input.source_field() {
|
||||
let source = &source_field.member;
|
||||
let source_provide = if type_is_option(source_field.ty) {
|
||||
quote_spanned! {source.span()=>
|
||||
if let ::core::option::Option::Some(source) = &self.#source {
|
||||
source.thiserror_provide(#request);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {source.span()=>
|
||||
self.#source.thiserror_provide(#request);
|
||||
}
|
||||
};
|
||||
let self_provide = if source == backtrace {
|
||||
None
|
||||
} else if type_is_option(backtrace_field.ty) {
|
||||
Some(quote! {
|
||||
if let ::core::option::Option::Some(backtrace) = &self.#backtrace {
|
||||
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Some(quote! {
|
||||
#request.provide_ref::<::thiserror::#private::Backtrace>(&self.#backtrace);
|
||||
})
|
||||
};
|
||||
quote! {
|
||||
use ::thiserror::#private::ThiserrorProvide as _;
|
||||
#source_provide
|
||||
#self_provide
|
||||
}
|
||||
} else if type_is_option(backtrace_field.ty) {
|
||||
quote! {
|
||||
if let ::core::option::Option::Some(backtrace) = &self.#backtrace {
|
||||
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#request.provide_ref::<::thiserror::#private::Backtrace>(&self.#backtrace);
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
fn provide<'_request>(&'_request self, #request: &mut ::core::error::Request<'_request>) {
|
||||
#body
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut display_implied_bounds = Set::new();
|
||||
let display_body = if input.attrs.transparent.is_some() {
|
||||
let only_field = &input.fields[0].member;
|
||||
display_implied_bounds.insert((0, Trait::Display));
|
||||
Some(quote! {
|
||||
::core::fmt::Display::fmt(&self.#only_field, __formatter)
|
||||
})
|
||||
} else if let Some(display) = &input.attrs.display {
|
||||
display_implied_bounds.clone_from(&display.implied_bounds);
|
||||
let use_as_display = use_as_display(display.has_bonus_display);
|
||||
let pat = fields_pat(&input.fields);
|
||||
Some(quote! {
|
||||
#use_as_display
|
||||
#[allow(unused_variables, deprecated)]
|
||||
let Self #pat = self;
|
||||
#display
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let display_impl = display_body.map(|body| {
|
||||
let mut display_inferred_bounds = InferredBounds::new();
|
||||
for (field, bound) in display_implied_bounds {
|
||||
let field = &input.fields[field];
|
||||
if field.contains_generic {
|
||||
display_inferred_bounds.insert(field.ty, bound);
|
||||
}
|
||||
}
|
||||
let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics);
|
||||
quote! {
|
||||
#[allow(unused_qualifications)]
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::core::fmt::Display for #ty #ty_generics #display_where_clause {
|
||||
#[allow(clippy::used_underscore_binding)]
|
||||
fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||
#body
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let from_impl = input.from_field().map(|from_field| {
|
||||
let span = from_field.attrs.from.unwrap().span;
|
||||
let backtrace_field = input.distinct_backtrace_field();
|
||||
let from = unoptional_type(from_field.ty);
|
||||
let source_var = Ident::new("source", span);
|
||||
let body = from_initializer(from_field, backtrace_field, &source_var);
|
||||
let from_function = quote! {
|
||||
fn from(#source_var: #from) -> Self {
|
||||
#ty #body
|
||||
}
|
||||
};
|
||||
let from_impl = quote_spanned! {span=>
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::core::convert::From<#from> for #ty #ty_generics #where_clause {
|
||||
#from_function
|
||||
}
|
||||
};
|
||||
Some(quote! {
|
||||
#[allow(
|
||||
deprecated,
|
||||
unused_qualifications,
|
||||
clippy::elidable_lifetime_names,
|
||||
clippy::needless_lifetimes,
|
||||
)]
|
||||
#from_impl
|
||||
})
|
||||
});
|
||||
|
||||
if input.generics.type_params().next().is_some() {
|
||||
let self_token = <Token![Self]>::default();
|
||||
error_inferred_bounds.insert(self_token, Trait::Debug);
|
||||
error_inferred_bounds.insert(self_token, Trait::Display);
|
||||
}
|
||||
let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics);
|
||||
|
||||
quote! {
|
||||
#[allow(unused_qualifications)]
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::thiserror::#private::Error for #ty #ty_generics #error_where_clause {
|
||||
#source_method
|
||||
#provide_method
|
||||
}
|
||||
#display_impl
|
||||
#from_impl
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_enum(input: Enum) -> TokenStream {
|
||||
let ty = call_site_ident(&input.ident);
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let mut error_inferred_bounds = InferredBounds::new();
|
||||
|
||||
let source_method = if input.has_source() {
|
||||
let arms = input.variants.iter().map(|variant| {
|
||||
let ident = &variant.ident;
|
||||
if let Some(transparent_attr) = &variant.attrs.transparent {
|
||||
let only_field = &variant.fields[0];
|
||||
if only_field.contains_generic {
|
||||
error_inferred_bounds.insert(only_field.ty, quote!(::thiserror::#private::Error));
|
||||
}
|
||||
let member = &only_field.member;
|
||||
let source = quote_spanned! {transparent_attr.span=>
|
||||
::thiserror::#private::Error::source(transparent.as_dyn_error())
|
||||
};
|
||||
quote! {
|
||||
#ty::#ident {#member: transparent} => #source,
|
||||
}
|
||||
} else if let Some(source_field) = variant.source_field() {
|
||||
let source = &source_field.member;
|
||||
if source_field.contains_generic {
|
||||
let ty = unoptional_type(source_field.ty);
|
||||
error_inferred_bounds.insert(ty, quote!(::thiserror::#private::Error + 'static));
|
||||
}
|
||||
let asref = if type_is_option(source_field.ty) {
|
||||
Some(quote_spanned!(source.span()=> .as_ref()?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let varsource = quote!(source);
|
||||
let dyn_error = quote_spanned! {source_field.source_span()=>
|
||||
#varsource #asref.as_dyn_error()
|
||||
};
|
||||
quote! {
|
||||
#ty::#ident {#source: #varsource, ..} => ::core::option::Option::Some(#dyn_error),
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#ty::#ident {..} => ::core::option::Option::None,
|
||||
}
|
||||
}
|
||||
});
|
||||
Some(quote! {
|
||||
fn source(&self) -> ::core::option::Option<&(dyn ::thiserror::#private::Error + 'static)> {
|
||||
use ::thiserror::#private::AsDynError as _;
|
||||
#[allow(deprecated)]
|
||||
match self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let provide_method = if input.has_backtrace() {
|
||||
let request = quote!(request);
|
||||
let arms = input.variants.iter().map(|variant| {
|
||||
let ident = &variant.ident;
|
||||
match (variant.backtrace_field(), variant.source_field()) {
|
||||
(Some(backtrace_field), Some(source_field))
|
||||
if backtrace_field.attrs.backtrace.is_none() =>
|
||||
{
|
||||
let backtrace = &backtrace_field.member;
|
||||
let source = &source_field.member;
|
||||
let varsource = quote!(source);
|
||||
let source_provide = if type_is_option(source_field.ty) {
|
||||
quote_spanned! {source.span()=>
|
||||
if let ::core::option::Option::Some(source) = #varsource {
|
||||
source.thiserror_provide(#request);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {source.span()=>
|
||||
#varsource.thiserror_provide(#request);
|
||||
}
|
||||
};
|
||||
let self_provide = if type_is_option(backtrace_field.ty) {
|
||||
quote! {
|
||||
if let ::core::option::Option::Some(backtrace) = backtrace {
|
||||
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
#ty::#ident {
|
||||
#backtrace: backtrace,
|
||||
#source: #varsource,
|
||||
..
|
||||
} => {
|
||||
use ::thiserror::#private::ThiserrorProvide as _;
|
||||
#source_provide
|
||||
#self_provide
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(backtrace_field), Some(source_field))
|
||||
if backtrace_field.member == source_field.member =>
|
||||
{
|
||||
let backtrace = &backtrace_field.member;
|
||||
let varsource = quote!(source);
|
||||
let source_provide = if type_is_option(source_field.ty) {
|
||||
quote_spanned! {backtrace.span()=>
|
||||
if let ::core::option::Option::Some(source) = #varsource {
|
||||
source.thiserror_provide(#request);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {backtrace.span()=>
|
||||
#varsource.thiserror_provide(#request);
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
#ty::#ident {#backtrace: #varsource, ..} => {
|
||||
use ::thiserror::#private::ThiserrorProvide as _;
|
||||
#source_provide
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(backtrace_field), _) => {
|
||||
let backtrace = &backtrace_field.member;
|
||||
let body = if type_is_option(backtrace_field.ty) {
|
||||
quote! {
|
||||
if let ::core::option::Option::Some(backtrace) = backtrace {
|
||||
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
#ty::#ident {#backtrace: backtrace, ..} => {
|
||||
#body
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, _) => quote! {
|
||||
#ty::#ident {..} => {}
|
||||
},
|
||||
}
|
||||
});
|
||||
Some(quote! {
|
||||
fn provide<'_request>(&'_request self, #request: &mut ::core::error::Request<'_request>) {
|
||||
#[allow(deprecated)]
|
||||
match self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let display_impl = if input.has_display() {
|
||||
let mut display_inferred_bounds = InferredBounds::new();
|
||||
let has_bonus_display = input.variants.iter().any(|v| {
|
||||
v.attrs
|
||||
.display
|
||||
.as_ref()
|
||||
.map_or(false, |display| display.has_bonus_display)
|
||||
});
|
||||
let use_as_display = use_as_display(has_bonus_display);
|
||||
let void_deref = if input.variants.is_empty() {
|
||||
Some(quote!(*))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let arms = input.variants.iter().map(|variant| {
|
||||
let mut display_implied_bounds = Set::new();
|
||||
let display = if let Some(display) = &variant.attrs.display {
|
||||
display_implied_bounds.clone_from(&display.implied_bounds);
|
||||
display.to_token_stream()
|
||||
} else if let Some(fmt) = &variant.attrs.fmt {
|
||||
let fmt_path = &fmt.path;
|
||||
let vars = variant.fields.iter().map(|field| match &field.member {
|
||||
MemberUnraw::Named(ident) => ident.to_local(),
|
||||
MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
|
||||
});
|
||||
quote!(#fmt_path(#(#vars,)* __formatter))
|
||||
} else {
|
||||
let only_field = match &variant.fields[0].member {
|
||||
MemberUnraw::Named(ident) => ident.to_local(),
|
||||
MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
|
||||
};
|
||||
display_implied_bounds.insert((0, Trait::Display));
|
||||
quote!(::core::fmt::Display::fmt(#only_field, __formatter))
|
||||
};
|
||||
for (field, bound) in display_implied_bounds {
|
||||
let field = &variant.fields[field];
|
||||
if field.contains_generic {
|
||||
display_inferred_bounds.insert(field.ty, bound);
|
||||
}
|
||||
}
|
||||
let ident = &variant.ident;
|
||||
let pat = fields_pat(&variant.fields);
|
||||
quote! {
|
||||
#ty::#ident #pat => #display
|
||||
}
|
||||
});
|
||||
let arms = arms.collect::<Vec<_>>();
|
||||
let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics);
|
||||
Some(quote! {
|
||||
#[allow(unused_qualifications)]
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::core::fmt::Display for #ty #ty_generics #display_where_clause {
|
||||
fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||
#use_as_display
|
||||
#[allow(unused_variables, deprecated, clippy::used_underscore_binding)]
|
||||
match #void_deref self {
|
||||
#(#arms,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let from_impls = input.variants.iter().filter_map(|variant| {
|
||||
let from_field = variant.from_field()?;
|
||||
let span = from_field.attrs.from.unwrap().span;
|
||||
let backtrace_field = variant.distinct_backtrace_field();
|
||||
let variant = &variant.ident;
|
||||
let from = unoptional_type(from_field.ty);
|
||||
let source_var = Ident::new("source", span);
|
||||
let body = from_initializer(from_field, backtrace_field, &source_var);
|
||||
let from_function = quote! {
|
||||
fn from(#source_var: #from) -> Self {
|
||||
#ty::#variant #body
|
||||
}
|
||||
};
|
||||
let from_impl = quote_spanned! {span=>
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::core::convert::From<#from> for #ty #ty_generics #where_clause {
|
||||
#from_function
|
||||
}
|
||||
};
|
||||
Some(quote! {
|
||||
#[allow(
|
||||
deprecated,
|
||||
unused_qualifications,
|
||||
clippy::elidable_lifetime_names,
|
||||
clippy::needless_lifetimes,
|
||||
)]
|
||||
#from_impl
|
||||
})
|
||||
});
|
||||
|
||||
if input.generics.type_params().next().is_some() {
|
||||
let self_token = <Token![Self]>::default();
|
||||
error_inferred_bounds.insert(self_token, Trait::Debug);
|
||||
error_inferred_bounds.insert(self_token, Trait::Display);
|
||||
}
|
||||
let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics);
|
||||
|
||||
quote! {
|
||||
#[allow(unused_qualifications)]
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::thiserror::#private::Error for #ty #ty_generics #error_where_clause {
|
||||
#source_method
|
||||
#provide_method
|
||||
}
|
||||
#display_impl
|
||||
#(#from_impls)*
|
||||
}
|
||||
}
|
||||
|
||||
// Create an ident with which we can expand `impl Trait for #ident {}` on a
|
||||
// deprecated type without triggering deprecation warning on the generated impl.
|
||||
pub(crate) fn call_site_ident(ident: &Ident) -> Ident {
|
||||
let mut ident = ident.clone();
|
||||
ident.set_span(ident.span().resolved_at(Span::call_site()));
|
||||
ident
|
||||
}
|
||||
|
||||
fn fields_pat(fields: &[Field]) -> TokenStream {
|
||||
let mut members = fields.iter().map(|field| &field.member).peekable();
|
||||
match members.peek() {
|
||||
Some(MemberUnraw::Named(_)) => quote!({ #(#members),* }),
|
||||
Some(MemberUnraw::Unnamed(_)) => {
|
||||
let vars = members.map(|member| match member {
|
||||
MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
|
||||
MemberUnraw::Named(_) => unreachable!(),
|
||||
});
|
||||
quote!((#(#vars),*))
|
||||
}
|
||||
None => quote!({}),
|
||||
}
|
||||
}
|
||||
|
||||
fn use_as_display(needs_as_display: bool) -> Option<TokenStream> {
|
||||
if needs_as_display {
|
||||
Some(quote! {
|
||||
use ::thiserror::#private::AsDisplay as _;
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn from_initializer(
|
||||
from_field: &Field,
|
||||
backtrace_field: Option<&Field>,
|
||||
source_var: &Ident,
|
||||
) -> TokenStream {
|
||||
let from_member = &from_field.member;
|
||||
let some_source = if type_is_option(from_field.ty) {
|
||||
quote!(::core::option::Option::Some(#source_var))
|
||||
} else {
|
||||
quote!(#source_var)
|
||||
};
|
||||
let backtrace = backtrace_field.map(|backtrace_field| {
|
||||
let backtrace_member = &backtrace_field.member;
|
||||
if type_is_option(backtrace_field.ty) {
|
||||
quote! {
|
||||
#backtrace_member: ::core::option::Option::Some(::thiserror::#private::Backtrace::capture()),
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#backtrace_member: ::core::convert::From::from(::thiserror::#private::Backtrace::capture()),
|
||||
}
|
||||
}
|
||||
});
|
||||
quote!({
|
||||
#from_member: #some_source,
|
||||
#backtrace
|
||||
})
|
||||
}
|
||||
|
||||
fn type_is_option(ty: &Type) -> bool {
|
||||
type_parameter_of_option(ty).is_some()
|
||||
}
|
||||
|
||||
fn unoptional_type(ty: &Type) -> TokenStream {
|
||||
let unoptional = type_parameter_of_option(ty).unwrap_or(ty);
|
||||
quote!(#unoptional)
|
||||
}
|
||||
|
||||
fn type_parameter_of_option(ty: &Type) -> Option<&Type> {
|
||||
let path = match ty {
|
||||
Type::Path(ty) => &ty.path,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let last = path.segments.last().unwrap();
|
||||
if last.ident != "Option" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bracketed = match &last.arguments {
|
||||
PathArguments::AngleBracketed(bracketed) => bracketed,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if bracketed.args.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
match &bracketed.args[0] {
|
||||
GenericArgument::Type(arg) => Some(arg),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
use crate::expand::call_site_ident;
|
||||
use crate::private;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::DeriveInput;
|
||||
|
||||
pub(crate) fn expand(input: &DeriveInput, error: syn::Error) -> TokenStream {
|
||||
let ty = call_site_ident(&input.ident);
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
let error = error.to_compile_error();
|
||||
|
||||
quote! {
|
||||
#error
|
||||
|
||||
#[allow(unused_qualifications)]
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::thiserror::#private::Error for #ty #ty_generics #where_clause
|
||||
where
|
||||
// Work around trivial bounds being unstable.
|
||||
// https://github.com/rust-lang/rust/issues/48214
|
||||
for<'workaround> #ty #ty_generics: ::core::fmt::Debug,
|
||||
{}
|
||||
|
||||
#[allow(unused_qualifications)]
|
||||
#[automatically_derived]
|
||||
impl #impl_generics ::core::fmt::Display for #ty #ty_generics #where_clause {
|
||||
fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||
::core::unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,323 +0,0 @@
|
||||
use crate::ast::{ContainerKind, Field};
|
||||
use crate::attr::{Display, Trait};
|
||||
use crate::private;
|
||||
use crate::scan_expr::scan_expr;
|
||||
use crate::unraw::{IdentUnraw, MemberUnraw};
|
||||
use proc_macro2::{Delimiter, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens as _};
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::iter;
|
||||
use syn::ext::IdentExt;
|
||||
use syn::parse::discouraged::Speculative;
|
||||
use syn::parse::{Error, ParseStream, Parser, Result};
|
||||
use syn::{Expr, Ident, Index, LitStr, Token};
|
||||
|
||||
impl Display<'_> {
|
||||
pub fn expand_shorthand(&mut self, fields: &[Field], container: ContainerKind) -> Result<()> {
|
||||
let raw_args = self.args.clone();
|
||||
let FmtArguments {
|
||||
named: user_named_args,
|
||||
first_unnamed,
|
||||
} = explicit_named_args.parse2(raw_args).unwrap();
|
||||
|
||||
let mut member_index = HashMap::new();
|
||||
let mut extra_positional_arguments_allowed = true;
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
member_index.insert(&field.member, i);
|
||||
extra_positional_arguments_allowed &= matches!(&field.member, MemberUnraw::Named(_));
|
||||
}
|
||||
|
||||
let span = self.fmt.span();
|
||||
let fmt = self.fmt.value();
|
||||
let mut read = fmt.as_str();
|
||||
let mut out = String::new();
|
||||
let mut has_bonus_display = false;
|
||||
let mut infinite_recursive = false;
|
||||
let mut implied_bounds = BTreeSet::new();
|
||||
let mut bindings = Vec::new();
|
||||
let mut macro_named_args = BTreeSet::new();
|
||||
|
||||
self.requires_fmt_machinery = self.requires_fmt_machinery || fmt.contains('}');
|
||||
|
||||
while let Some(brace) = read.find('{') {
|
||||
self.requires_fmt_machinery = true;
|
||||
out += &read[..brace + 1];
|
||||
read = &read[brace + 1..];
|
||||
if read.starts_with('{') {
|
||||
out.push('{');
|
||||
read = &read[1..];
|
||||
continue;
|
||||
}
|
||||
let next = match read.chars().next() {
|
||||
Some(next) => next,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let member = match next {
|
||||
'0'..='9' => {
|
||||
let int = take_int(&mut read);
|
||||
if !extra_positional_arguments_allowed {
|
||||
if let Some(first_unnamed) = &first_unnamed {
|
||||
let msg = format!("ambiguous reference to positional arguments by number in a {container}; change this to a named argument");
|
||||
return Err(Error::new_spanned(first_unnamed, msg));
|
||||
}
|
||||
}
|
||||
match int.parse::<u32>() {
|
||||
Ok(index) => MemberUnraw::Unnamed(Index { index, span }),
|
||||
Err(_) => return Ok(()),
|
||||
}
|
||||
}
|
||||
'a'..='z' | 'A'..='Z' | '_' => {
|
||||
if read.starts_with("r#") {
|
||||
continue;
|
||||
}
|
||||
let repr = take_ident(&mut read);
|
||||
if repr == "_" {
|
||||
// Invalid. Let rustc produce the diagnostic.
|
||||
out += repr;
|
||||
continue;
|
||||
}
|
||||
let ident = IdentUnraw::new(Ident::new(repr, span));
|
||||
if user_named_args.contains(&ident) {
|
||||
// Refers to a named argument written by the user, not to field.
|
||||
out += repr;
|
||||
continue;
|
||||
}
|
||||
MemberUnraw::Named(ident)
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
let end_spec = match read.find('}') {
|
||||
Some(end_spec) => end_spec,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let mut bonus_display = false;
|
||||
let bound = match read[..end_spec].chars().next_back() {
|
||||
Some('?') => Trait::Debug,
|
||||
Some('o') => Trait::Octal,
|
||||
Some('x') => Trait::LowerHex,
|
||||
Some('X') => Trait::UpperHex,
|
||||
Some('p') => Trait::Pointer,
|
||||
Some('b') => Trait::Binary,
|
||||
Some('e') => Trait::LowerExp,
|
||||
Some('E') => Trait::UpperExp,
|
||||
Some(_) => Trait::Display,
|
||||
None => {
|
||||
bonus_display = true;
|
||||
has_bonus_display = true;
|
||||
Trait::Display
|
||||
}
|
||||
};
|
||||
infinite_recursive |= member == *"self" && bound == Trait::Display;
|
||||
let field = match member_index.get(&member) {
|
||||
Some(&field) => field,
|
||||
None => {
|
||||
out += &member.to_string();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
implied_bounds.insert((field, bound));
|
||||
let formatvar_prefix = if bonus_display {
|
||||
"__display"
|
||||
} else if bound == Trait::Pointer {
|
||||
"__pointer"
|
||||
} else {
|
||||
"__field"
|
||||
};
|
||||
let mut formatvar = IdentUnraw::new(match &member {
|
||||
MemberUnraw::Unnamed(index) => format_ident!("{}{}", formatvar_prefix, index),
|
||||
MemberUnraw::Named(ident) => {
|
||||
format_ident!("{}_{}", formatvar_prefix, ident.to_string())
|
||||
}
|
||||
});
|
||||
while user_named_args.contains(&formatvar) {
|
||||
formatvar = IdentUnraw::new(format_ident!("_{}", formatvar.to_string()));
|
||||
}
|
||||
formatvar.set_span(span);
|
||||
out += &formatvar.to_string();
|
||||
if !macro_named_args.insert(formatvar.clone()) {
|
||||
// Already added to bindings by a previous use.
|
||||
continue;
|
||||
}
|
||||
let mut binding_value = match &member {
|
||||
MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
|
||||
MemberUnraw::Named(ident) => ident.to_local(),
|
||||
};
|
||||
binding_value.set_span(span.resolved_at(fields[field].member.span()));
|
||||
let wrapped_binding_value = if bonus_display {
|
||||
quote_spanned!(span=> #binding_value.as_display())
|
||||
} else if bound == Trait::Pointer {
|
||||
quote!(::thiserror::#private::Var(#binding_value))
|
||||
} else {
|
||||
binding_value.into_token_stream()
|
||||
};
|
||||
bindings.push((formatvar.to_local(), wrapped_binding_value));
|
||||
}
|
||||
|
||||
out += read;
|
||||
self.fmt = LitStr::new(&out, self.fmt.span());
|
||||
self.has_bonus_display = has_bonus_display;
|
||||
self.infinite_recursive = infinite_recursive;
|
||||
self.implied_bounds = implied_bounds;
|
||||
self.bindings = bindings;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct FmtArguments {
|
||||
named: BTreeSet<IdentUnraw>,
|
||||
first_unnamed: Option<TokenStream>,
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
|
||||
let ahead = input.fork();
|
||||
if let Ok(set) = try_explicit_named_args(&ahead) {
|
||||
input.advance_to(&ahead);
|
||||
return Ok(set);
|
||||
}
|
||||
|
||||
let ahead = input.fork();
|
||||
if let Ok(set) = fallback_explicit_named_args(&ahead) {
|
||||
input.advance_to(&ahead);
|
||||
return Ok(set);
|
||||
}
|
||||
|
||||
input.parse::<TokenStream>().unwrap();
|
||||
Ok(FmtArguments {
|
||||
named: BTreeSet::new(),
|
||||
first_unnamed: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn try_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
|
||||
let mut syn_full = None;
|
||||
let mut args = FmtArguments {
|
||||
named: BTreeSet::new(),
|
||||
first_unnamed: None,
|
||||
};
|
||||
|
||||
while !input.is_empty() {
|
||||
input.parse::<Token![,]>()?;
|
||||
if input.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut begin_unnamed = None;
|
||||
if input.peek(Ident::peek_any) && input.peek2(Token![=]) && !input.peek2(Token![==]) {
|
||||
let ident: IdentUnraw = input.parse()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
args.named.insert(ident);
|
||||
} else {
|
||||
begin_unnamed = Some(input.fork());
|
||||
}
|
||||
|
||||
let ahead = input.fork();
|
||||
if *syn_full.get_or_insert_with(is_syn_full) && ahead.parse::<Expr>().is_ok() {
|
||||
input.advance_to(&ahead);
|
||||
} else {
|
||||
scan_expr(input)?;
|
||||
}
|
||||
|
||||
if let Some(begin_unnamed) = begin_unnamed {
|
||||
if args.first_unnamed.is_none() {
|
||||
args.first_unnamed = Some(between(&begin_unnamed, input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn fallback_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
|
||||
let mut args = FmtArguments {
|
||||
named: BTreeSet::new(),
|
||||
first_unnamed: None,
|
||||
};
|
||||
|
||||
while !input.is_empty() {
|
||||
if input.peek(Token![,])
|
||||
&& input.peek2(Ident::peek_any)
|
||||
&& input.peek3(Token![=])
|
||||
&& !input.peek3(Token![==])
|
||||
{
|
||||
input.parse::<Token![,]>()?;
|
||||
let ident: IdentUnraw = input.parse()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
args.named.insert(ident);
|
||||
} else {
|
||||
input.parse::<TokenTree>()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn is_syn_full() -> bool {
|
||||
// Expr::Block contains syn::Block which contains Vec<syn::Stmt>. In the
|
||||
// current version of Syn, syn::Stmt is exhaustive and could only plausibly
|
||||
// represent `trait Trait {}` in Stmt::Item which contains syn::Item. Most
|
||||
// of the point of syn's non-"full" mode is to avoid compiling Item and the
|
||||
// entire expansive syntax tree it comprises. So the following expression
|
||||
// being parsed to Expr::Block is a reliable indication that "full" is
|
||||
// enabled.
|
||||
let test = quote!({
|
||||
trait Trait {}
|
||||
});
|
||||
match syn::parse2(test) {
|
||||
Ok(Expr::Verbatim(_)) | Err(_) => false,
|
||||
Ok(Expr::Block(_)) => true,
|
||||
Ok(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn take_int<'a>(read: &mut &'a str) -> &'a str {
|
||||
let mut int_len = 0;
|
||||
for ch in read.chars() {
|
||||
match ch {
|
||||
'0'..='9' => int_len += 1,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
let (int, rest) = read.split_at(int_len);
|
||||
*read = rest;
|
||||
int
|
||||
}
|
||||
|
||||
fn take_ident<'a>(read: &mut &'a str) -> &'a str {
|
||||
let mut ident_len = 0;
|
||||
for ch in read.chars() {
|
||||
match ch {
|
||||
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident_len += 1,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
let (ident, rest) = read.split_at(ident_len);
|
||||
*read = rest;
|
||||
ident
|
||||
}
|
||||
|
||||
fn between<'a>(begin: ParseStream<'a>, end: ParseStream<'a>) -> TokenStream {
|
||||
let end = end.cursor();
|
||||
let mut cursor = begin.cursor();
|
||||
let mut tokens = TokenStream::new();
|
||||
|
||||
while cursor < end {
|
||||
let (tt, next) = cursor.token_tree().unwrap();
|
||||
|
||||
if end < next {
|
||||
if let Some((inside, _span, _after)) = cursor.group(Delimiter::None) {
|
||||
cursor = inside;
|
||||
continue;
|
||||
}
|
||||
if tokens.is_empty() {
|
||||
tokens.extend(iter::once(tt));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
tokens.extend(iter::once(tt));
|
||||
cursor = next;
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::{BTreeMap as Map, BTreeSet as Set};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{parse_quote, GenericArgument, Generics, Ident, PathArguments, Token, Type, WhereClause};
|
||||
|
||||
pub struct ParamsInScope<'a> {
|
||||
names: Set<&'a Ident>,
|
||||
}
|
||||
|
||||
impl<'a> ParamsInScope<'a> {
|
||||
pub fn new(generics: &'a Generics) -> Self {
|
||||
ParamsInScope {
|
||||
names: generics.type_params().map(|param| ¶m.ident).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersects(&self, ty: &Type) -> bool {
|
||||
let mut found = false;
|
||||
crawl(self, ty, &mut found);
|
||||
found
|
||||
}
|
||||
}
|
||||
|
||||
fn crawl(in_scope: &ParamsInScope, ty: &Type, found: &mut bool) {
|
||||
if let Type::Path(ty) = ty {
|
||||
if let Some(qself) = &ty.qself {
|
||||
crawl(in_scope, &qself.ty, found);
|
||||
} else {
|
||||
let front = ty.path.segments.first().unwrap();
|
||||
if front.arguments.is_none() && in_scope.names.contains(&front.ident) {
|
||||
*found = true;
|
||||
}
|
||||
}
|
||||
for segment in &ty.path.segments {
|
||||
if let PathArguments::AngleBracketed(arguments) = &segment.arguments {
|
||||
for arg in &arguments.args {
|
||||
if let GenericArgument::Type(ty) = arg {
|
||||
crawl(in_scope, ty, found);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InferredBounds {
|
||||
bounds: Map<String, (Set<String>, Punctuated<TokenStream, Token![+]>)>,
|
||||
order: Vec<TokenStream>,
|
||||
}
|
||||
|
||||
impl InferredBounds {
|
||||
pub fn new() -> Self {
|
||||
InferredBounds {
|
||||
bounds: Map::new(),
|
||||
order: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, ty: impl ToTokens, bound: impl ToTokens) {
|
||||
let ty = ty.to_token_stream();
|
||||
let bound = bound.to_token_stream();
|
||||
let entry = self.bounds.entry(ty.to_string());
|
||||
if let Entry::Vacant(_) = entry {
|
||||
self.order.push(ty);
|
||||
}
|
||||
let (set, tokens) = entry.or_default();
|
||||
if set.insert(bound.to_string()) {
|
||||
tokens.push(bound);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn augment_where_clause(&self, generics: &Generics) -> WhereClause {
|
||||
let mut generics = generics.clone();
|
||||
let where_clause = generics.make_where_clause();
|
||||
for ty in &self.order {
|
||||
let (_set, bounds) = &self.bounds[&ty.to_string()];
|
||||
where_clause.predicates.push(parse_quote!(#ty: #bounds));
|
||||
}
|
||||
generics.where_clause.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
#![allow(
|
||||
clippy::blocks_in_conditions,
|
||||
clippy::cast_lossless,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::enum_glob_use,
|
||||
clippy::expl_impl_clone_on_copy, // https://github.com/rust-lang/rust-clippy/issues/15842
|
||||
clippy::manual_find,
|
||||
clippy::manual_let_else,
|
||||
clippy::manual_map,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::range_plus_one,
|
||||
clippy::single_match_else,
|
||||
clippy::struct_field_names,
|
||||
clippy::too_many_lines,
|
||||
clippy::wrong_self_convention
|
||||
)]
|
||||
#![allow(unknown_lints, mismatched_lifetime_syntaxes)]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
mod ast;
|
||||
mod attr;
|
||||
mod expand;
|
||||
mod fallback;
|
||||
mod fmt;
|
||||
mod generics;
|
||||
mod prop;
|
||||
mod scan_expr;
|
||||
mod unraw;
|
||||
mod valid;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{ToTokens, TokenStreamExt as _};
|
||||
use syn::{DeriveInput, parse_macro_input};
|
||||
|
||||
#[proc_macro_derive(Error, attributes(backtrace, error, from, source))]
|
||||
pub fn derive_error(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
expand::derive(&input).into()
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
struct private;
|
||||
|
||||
impl ToTokens for private {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
tokens.append(Ident::new(concat!("__private"), Span::call_site()));
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
use crate::ast::{Enum, Field, Struct, Variant};
|
||||
use crate::unraw::MemberUnraw;
|
||||
use proc_macro2::Span;
|
||||
use syn::Type;
|
||||
|
||||
impl Struct<'_> {
|
||||
pub(crate) fn from_field(&self) -> Option<&Field> {
|
||||
from_field(&self.fields)
|
||||
}
|
||||
|
||||
pub(crate) fn source_field(&self) -> Option<&Field> {
|
||||
source_field(&self.fields)
|
||||
}
|
||||
|
||||
pub(crate) fn backtrace_field(&self) -> Option<&Field> {
|
||||
backtrace_field(&self.fields)
|
||||
}
|
||||
|
||||
pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> {
|
||||
let backtrace_field = self.backtrace_field()?;
|
||||
distinct_backtrace_field(backtrace_field, self.from_field())
|
||||
}
|
||||
}
|
||||
|
||||
impl Enum<'_> {
|
||||
pub(crate) fn has_source(&self) -> bool {
|
||||
self.variants
|
||||
.iter()
|
||||
.any(|variant| variant.source_field().is_some() || variant.attrs.transparent.is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn has_backtrace(&self) -> bool {
|
||||
self.variants
|
||||
.iter()
|
||||
.any(|variant| variant.backtrace_field().is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn has_display(&self) -> bool {
|
||||
self.attrs.display.is_some()
|
||||
|| self.attrs.transparent.is_some()
|
||||
|| self.attrs.fmt.is_some()
|
||||
|| self
|
||||
.variants
|
||||
.iter()
|
||||
.any(|variant| variant.attrs.display.is_some() || variant.attrs.fmt.is_some())
|
||||
|| self
|
||||
.variants
|
||||
.iter()
|
||||
.all(|variant| variant.attrs.transparent.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
impl Variant<'_> {
|
||||
pub(crate) fn from_field(&self) -> Option<&Field> {
|
||||
from_field(&self.fields)
|
||||
}
|
||||
|
||||
pub(crate) fn source_field(&self) -> Option<&Field> {
|
||||
source_field(&self.fields)
|
||||
}
|
||||
|
||||
pub(crate) fn backtrace_field(&self) -> Option<&Field> {
|
||||
backtrace_field(&self.fields)
|
||||
}
|
||||
|
||||
pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> {
|
||||
let backtrace_field = self.backtrace_field()?;
|
||||
distinct_backtrace_field(backtrace_field, self.from_field())
|
||||
}
|
||||
}
|
||||
|
||||
impl Field<'_> {
|
||||
pub(crate) fn is_backtrace(&self) -> bool {
|
||||
type_is_backtrace(self.ty)
|
||||
}
|
||||
|
||||
pub(crate) fn source_span(&self) -> Span {
|
||||
if let Some(source_attr) = &self.attrs.source {
|
||||
source_attr.span
|
||||
} else if let Some(from_attr) = &self.attrs.from {
|
||||
from_attr.span
|
||||
} else {
|
||||
self.member.span()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
|
||||
for field in fields {
|
||||
if field.attrs.from.is_some() {
|
||||
return Some(field);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn source_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
|
||||
for field in fields {
|
||||
if field.attrs.from.is_some() || field.attrs.source.is_some() {
|
||||
return Some(field);
|
||||
}
|
||||
}
|
||||
for field in fields {
|
||||
match &field.member {
|
||||
MemberUnraw::Named(ident) if ident == "source" => return Some(field),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn backtrace_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
|
||||
for field in fields {
|
||||
if field.attrs.backtrace.is_some() {
|
||||
return Some(field);
|
||||
}
|
||||
}
|
||||
for field in fields {
|
||||
if field.is_backtrace() {
|
||||
return Some(field);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// The #[backtrace] field, if it is not the same as the #[from] field.
|
||||
fn distinct_backtrace_field<'a, 'b>(
|
||||
backtrace_field: &'a Field<'b>,
|
||||
from_field: Option<&Field>,
|
||||
) -> Option<&'a Field<'b>> {
|
||||
if from_field.map_or(false, |from_field| {
|
||||
from_field.member == backtrace_field.member
|
||||
}) {
|
||||
None
|
||||
} else {
|
||||
Some(backtrace_field)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_is_backtrace(ty: &Type) -> bool {
|
||||
let path = match ty {
|
||||
Type::Path(ty) => &ty.path,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let last = path.segments.last().unwrap();
|
||||
last.ident == "Backtrace" && last.arguments.is_empty()
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
use self::{Action::*, Input::*};
|
||||
use proc_macro2::{Delimiter, Ident, Spacing, TokenTree};
|
||||
use syn::parse::{ParseStream, Result};
|
||||
use syn::{AngleBracketedGenericArguments, BinOp, Expr, ExprPath, Lifetime, Lit, Token, Type};
|
||||
|
||||
enum Input {
|
||||
Keyword(&'static str),
|
||||
Punct(&'static str),
|
||||
ConsumeAny,
|
||||
ConsumeBinOp,
|
||||
ConsumeBrace,
|
||||
ConsumeDelimiter,
|
||||
ConsumeIdent,
|
||||
ConsumeLifetime,
|
||||
ConsumeLiteral,
|
||||
ConsumeNestedBrace,
|
||||
ExpectPath,
|
||||
ExpectTurbofish,
|
||||
ExpectType,
|
||||
CanBeginExpr,
|
||||
Otherwise,
|
||||
Empty,
|
||||
}
|
||||
|
||||
enum Action {
|
||||
SetState(&'static [(Input, Action)]),
|
||||
IncDepth,
|
||||
DecDepth,
|
||||
Finish,
|
||||
}
|
||||
|
||||
static INIT: [(Input, Action); 28] = [
|
||||
(ConsumeDelimiter, SetState(&POSTFIX)),
|
||||
(Keyword("async"), SetState(&ASYNC)),
|
||||
(Keyword("break"), SetState(&BREAK_LABEL)),
|
||||
(Keyword("const"), SetState(&CONST)),
|
||||
(Keyword("continue"), SetState(&CONTINUE)),
|
||||
(Keyword("for"), SetState(&FOR)),
|
||||
(Keyword("if"), IncDepth),
|
||||
(Keyword("let"), SetState(&PATTERN)),
|
||||
(Keyword("loop"), SetState(&BLOCK)),
|
||||
(Keyword("match"), IncDepth),
|
||||
(Keyword("move"), SetState(&CLOSURE)),
|
||||
(Keyword("return"), SetState(&RETURN)),
|
||||
(Keyword("static"), SetState(&CLOSURE)),
|
||||
(Keyword("unsafe"), SetState(&BLOCK)),
|
||||
(Keyword("while"), IncDepth),
|
||||
(Keyword("yield"), SetState(&RETURN)),
|
||||
(Keyword("_"), SetState(&POSTFIX)),
|
||||
(Punct("!"), SetState(&INIT)),
|
||||
(Punct("#"), SetState(&[(ConsumeDelimiter, SetState(&INIT))])),
|
||||
(Punct("&"), SetState(&REFERENCE)),
|
||||
(Punct("*"), SetState(&INIT)),
|
||||
(Punct("-"), SetState(&INIT)),
|
||||
(Punct("..="), SetState(&INIT)),
|
||||
(Punct(".."), SetState(&RANGE)),
|
||||
(Punct("|"), SetState(&CLOSURE_ARGS)),
|
||||
(ConsumeLifetime, SetState(&[(Punct(":"), SetState(&INIT))])),
|
||||
(ConsumeLiteral, SetState(&POSTFIX)),
|
||||
(ExpectPath, SetState(&PATH)),
|
||||
];
|
||||
|
||||
static POSTFIX: [(Input, Action); 10] = [
|
||||
(Keyword("as"), SetState(&[(ExpectType, SetState(&POSTFIX))])),
|
||||
(Punct("..="), SetState(&INIT)),
|
||||
(Punct(".."), SetState(&RANGE)),
|
||||
(Punct("."), SetState(&DOT)),
|
||||
(Punct("?"), SetState(&POSTFIX)),
|
||||
(ConsumeBinOp, SetState(&INIT)),
|
||||
(Punct("="), SetState(&INIT)),
|
||||
(ConsumeNestedBrace, SetState(&IF_THEN)),
|
||||
(ConsumeDelimiter, SetState(&POSTFIX)),
|
||||
(Empty, Finish),
|
||||
];
|
||||
|
||||
static ASYNC: [(Input, Action); 3] = [
|
||||
(Keyword("move"), SetState(&ASYNC)),
|
||||
(Punct("|"), SetState(&CLOSURE_ARGS)),
|
||||
(ConsumeBrace, SetState(&POSTFIX)),
|
||||
];
|
||||
|
||||
static BLOCK: [(Input, Action); 1] = [(ConsumeBrace, SetState(&POSTFIX))];
|
||||
|
||||
static BREAK_LABEL: [(Input, Action); 2] = [
|
||||
(ConsumeLifetime, SetState(&BREAK_VALUE)),
|
||||
(Otherwise, SetState(&BREAK_VALUE)),
|
||||
];
|
||||
|
||||
static BREAK_VALUE: [(Input, Action); 3] = [
|
||||
(ConsumeNestedBrace, SetState(&IF_THEN)),
|
||||
(CanBeginExpr, SetState(&INIT)),
|
||||
(Otherwise, SetState(&POSTFIX)),
|
||||
];
|
||||
|
||||
static CLOSURE: [(Input, Action); 6] = [
|
||||
(Keyword("async"), SetState(&CLOSURE)),
|
||||
(Keyword("move"), SetState(&CLOSURE)),
|
||||
(Punct(","), SetState(&CLOSURE)),
|
||||
(Punct(">"), SetState(&CLOSURE)),
|
||||
(Punct("|"), SetState(&CLOSURE_ARGS)),
|
||||
(ConsumeLifetime, SetState(&CLOSURE)),
|
||||
];
|
||||
|
||||
static CLOSURE_ARGS: [(Input, Action); 2] = [
|
||||
(Punct("|"), SetState(&CLOSURE_RET)),
|
||||
(ConsumeAny, SetState(&CLOSURE_ARGS)),
|
||||
];
|
||||
|
||||
static CLOSURE_RET: [(Input, Action); 2] = [
|
||||
(Punct("->"), SetState(&[(ExpectType, SetState(&BLOCK))])),
|
||||
(Otherwise, SetState(&INIT)),
|
||||
];
|
||||
|
||||
static CONST: [(Input, Action); 2] = [
|
||||
(Punct("|"), SetState(&CLOSURE_ARGS)),
|
||||
(ConsumeBrace, SetState(&POSTFIX)),
|
||||
];
|
||||
|
||||
static CONTINUE: [(Input, Action); 2] = [
|
||||
(ConsumeLifetime, SetState(&POSTFIX)),
|
||||
(Otherwise, SetState(&POSTFIX)),
|
||||
];
|
||||
|
||||
static DOT: [(Input, Action); 3] = [
|
||||
(Keyword("await"), SetState(&POSTFIX)),
|
||||
(ConsumeIdent, SetState(&METHOD)),
|
||||
(ConsumeLiteral, SetState(&POSTFIX)),
|
||||
];
|
||||
|
||||
static FOR: [(Input, Action); 2] = [
|
||||
(Punct("<"), SetState(&CLOSURE)),
|
||||
(Otherwise, SetState(&PATTERN)),
|
||||
];
|
||||
|
||||
static IF_ELSE: [(Input, Action); 2] = [(Keyword("if"), SetState(&INIT)), (ConsumeBrace, DecDepth)];
|
||||
static IF_THEN: [(Input, Action); 2] =
|
||||
[(Keyword("else"), SetState(&IF_ELSE)), (Otherwise, DecDepth)];
|
||||
|
||||
static METHOD: [(Input, Action); 1] = [(ExpectTurbofish, SetState(&POSTFIX))];
|
||||
|
||||
static PATH: [(Input, Action); 4] = [
|
||||
(Punct("!="), SetState(&INIT)),
|
||||
(Punct("!"), SetState(&INIT)),
|
||||
(ConsumeNestedBrace, SetState(&IF_THEN)),
|
||||
(Otherwise, SetState(&POSTFIX)),
|
||||
];
|
||||
|
||||
static PATTERN: [(Input, Action); 15] = [
|
||||
(ConsumeDelimiter, SetState(&PATTERN)),
|
||||
(Keyword("box"), SetState(&PATTERN)),
|
||||
(Keyword("in"), IncDepth),
|
||||
(Keyword("mut"), SetState(&PATTERN)),
|
||||
(Keyword("ref"), SetState(&PATTERN)),
|
||||
(Keyword("_"), SetState(&PATTERN)),
|
||||
(Punct("!"), SetState(&PATTERN)),
|
||||
(Punct("&"), SetState(&PATTERN)),
|
||||
(Punct("..="), SetState(&PATTERN)),
|
||||
(Punct(".."), SetState(&PATTERN)),
|
||||
(Punct("="), SetState(&INIT)),
|
||||
(Punct("@"), SetState(&PATTERN)),
|
||||
(Punct("|"), SetState(&PATTERN)),
|
||||
(ConsumeLiteral, SetState(&PATTERN)),
|
||||
(ExpectPath, SetState(&PATTERN)),
|
||||
];
|
||||
|
||||
static RANGE: [(Input, Action); 6] = [
|
||||
(Punct("..="), SetState(&INIT)),
|
||||
(Punct(".."), SetState(&RANGE)),
|
||||
(Punct("."), SetState(&DOT)),
|
||||
(ConsumeNestedBrace, SetState(&IF_THEN)),
|
||||
(Empty, Finish),
|
||||
(Otherwise, SetState(&INIT)),
|
||||
];
|
||||
|
||||
static RAW: [(Input, Action); 3] = [
|
||||
(Keyword("const"), SetState(&INIT)),
|
||||
(Keyword("mut"), SetState(&INIT)),
|
||||
(Otherwise, SetState(&POSTFIX)),
|
||||
];
|
||||
|
||||
static REFERENCE: [(Input, Action); 3] = [
|
||||
(Keyword("mut"), SetState(&INIT)),
|
||||
(Keyword("raw"), SetState(&RAW)),
|
||||
(Otherwise, SetState(&INIT)),
|
||||
];
|
||||
|
||||
static RETURN: [(Input, Action); 2] = [
|
||||
(CanBeginExpr, SetState(&INIT)),
|
||||
(Otherwise, SetState(&POSTFIX)),
|
||||
];
|
||||
|
||||
pub(crate) fn scan_expr(input: ParseStream) -> Result<()> {
|
||||
let mut state = INIT.as_slice();
|
||||
let mut depth = 0usize;
|
||||
'table: loop {
|
||||
for rule in state {
|
||||
if match rule.0 {
|
||||
Input::Keyword(expected) => input.step(|cursor| match cursor.ident() {
|
||||
Some((ident, rest)) if ident == expected => Ok((true, rest)),
|
||||
_ => Ok((false, *cursor)),
|
||||
})?,
|
||||
Input::Punct(expected) => input.step(|cursor| {
|
||||
let begin = *cursor;
|
||||
let mut cursor = begin;
|
||||
for (i, ch) in expected.chars().enumerate() {
|
||||
match cursor.punct() {
|
||||
Some((punct, _)) if punct.as_char() != ch => break,
|
||||
Some((_, rest)) if i == expected.len() - 1 => {
|
||||
return Ok((true, rest));
|
||||
}
|
||||
Some((punct, rest)) if punct.spacing() == Spacing::Joint => {
|
||||
cursor = rest;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok((false, begin))
|
||||
})?,
|
||||
Input::ConsumeAny => input.parse::<Option<TokenTree>>()?.is_some(),
|
||||
Input::ConsumeBinOp => input.parse::<BinOp>().is_ok(),
|
||||
Input::ConsumeBrace | Input::ConsumeNestedBrace => {
|
||||
(matches!(rule.0, Input::ConsumeBrace) || depth > 0)
|
||||
&& input.step(|cursor| match cursor.group(Delimiter::Brace) {
|
||||
Some((_inside, _span, rest)) => Ok((true, rest)),
|
||||
None => Ok((false, *cursor)),
|
||||
})?
|
||||
}
|
||||
Input::ConsumeDelimiter => input.step(|cursor| match cursor.any_group() {
|
||||
Some((_inside, _delimiter, _span, rest)) => Ok((true, rest)),
|
||||
None => Ok((false, *cursor)),
|
||||
})?,
|
||||
Input::ConsumeIdent => input.parse::<Option<Ident>>()?.is_some(),
|
||||
Input::ConsumeLifetime => input.parse::<Option<Lifetime>>()?.is_some(),
|
||||
Input::ConsumeLiteral => input.parse::<Option<Lit>>()?.is_some(),
|
||||
Input::ExpectPath => {
|
||||
input.parse::<ExprPath>()?;
|
||||
true
|
||||
}
|
||||
Input::ExpectTurbofish => {
|
||||
if input.peek(Token![::]) {
|
||||
input.parse::<AngleBracketedGenericArguments>()?;
|
||||
}
|
||||
true
|
||||
}
|
||||
Input::ExpectType => {
|
||||
Type::without_plus(input)?;
|
||||
true
|
||||
}
|
||||
Input::CanBeginExpr => Expr::peek(input),
|
||||
Input::Otherwise => true,
|
||||
Input::Empty => input.is_empty() || input.peek(Token![,]),
|
||||
} {
|
||||
state = match rule.1 {
|
||||
Action::SetState(next) => next,
|
||||
Action::IncDepth => (depth += 1, &INIT).1,
|
||||
Action::DecDepth => (depth -= 1, &POSTFIX).1,
|
||||
Action::Finish => return if depth == 0 { Ok(()) } else { break },
|
||||
};
|
||||
continue 'table;
|
||||
}
|
||||
}
|
||||
return Err(input.error("unsupported expression"));
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Display};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use syn::ext::IdentExt as _;
|
||||
use syn::parse::{Parse, ParseStream, Result};
|
||||
use syn::Index;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct IdentUnraw(Ident);
|
||||
|
||||
impl IdentUnraw {
|
||||
pub fn new(ident: Ident) -> Self {
|
||||
IdentUnraw(ident)
|
||||
}
|
||||
|
||||
pub fn to_local(&self) -> Ident {
|
||||
let unraw = self.0.unraw();
|
||||
let repr = unraw.to_string();
|
||||
if syn::parse_str::<Ident>(&repr).is_err() {
|
||||
if let "_" | "super" | "self" | "Self" | "crate" = repr.as_str() {
|
||||
// Some identifiers are never allowed to appear as raw, like r#self and r#_.
|
||||
} else {
|
||||
return Ident::new_raw(&repr, Span::call_site());
|
||||
}
|
||||
}
|
||||
unraw
|
||||
}
|
||||
|
||||
pub fn set_span(&mut self, span: Span) {
|
||||
self.0.set_span(span);
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IdentUnraw {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
Display::fmt(&self.0.unraw(), formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for IdentUnraw {}
|
||||
|
||||
impl PartialEq for IdentUnraw {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
PartialEq::eq(&self.0.unraw(), &other.0.unraw())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for IdentUnraw {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.0 == other
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for IdentUnraw {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
Ord::cmp(&self.0.unraw(), &other.0.unraw())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for IdentUnraw {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(Self::cmp(self, other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for IdentUnraw {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
input.call(Ident::parse_any).map(IdentUnraw::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for IdentUnraw {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.0.unraw().to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum MemberUnraw {
|
||||
Named(IdentUnraw),
|
||||
Unnamed(Index),
|
||||
}
|
||||
|
||||
impl MemberUnraw {
|
||||
pub fn span(&self) -> Span {
|
||||
match self {
|
||||
MemberUnraw::Named(ident) => ident.0.span(),
|
||||
MemberUnraw::Unnamed(index) => index.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MemberUnraw {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
MemberUnraw::Named(this) => Display::fmt(this, formatter),
|
||||
MemberUnraw::Unnamed(this) => Display::fmt(&this.index, formatter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MemberUnraw {}
|
||||
|
||||
impl PartialEq for MemberUnraw {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(MemberUnraw::Named(this), MemberUnraw::Named(other)) => this == other,
|
||||
(MemberUnraw::Unnamed(this), MemberUnraw::Unnamed(other)) => this == other,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for MemberUnraw {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
match self {
|
||||
MemberUnraw::Named(this) => this == other,
|
||||
MemberUnraw::Unnamed(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for MemberUnraw {
|
||||
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||
match self {
|
||||
MemberUnraw::Named(ident) => ident.0.unraw().hash(hasher),
|
||||
MemberUnraw::Unnamed(index) => index.hash(hasher),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for MemberUnraw {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
MemberUnraw::Named(ident) => ident.to_local().to_tokens(tokens),
|
||||
MemberUnraw::Unnamed(index) => index.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
use crate::ast::{Enum, Field, Input, Struct, Variant};
|
||||
use crate::attr::Attrs;
|
||||
use syn::{Error, GenericArgument, PathArguments, Result, Type};
|
||||
|
||||
impl Input<'_> {
|
||||
pub(crate) fn validate(&self) -> Result<()> {
|
||||
match self {
|
||||
Input::Struct(input) => input.validate(),
|
||||
Input::Enum(input) => input.validate(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Struct<'_> {
|
||||
fn validate(&self) -> Result<()> {
|
||||
check_non_field_attrs(&self.attrs)?;
|
||||
if let Some(transparent) = self.attrs.transparent {
|
||||
if self.fields.len() != 1 {
|
||||
return Err(Error::new_spanned(
|
||||
transparent.original,
|
||||
"#[error(transparent)] requires exactly one field",
|
||||
));
|
||||
}
|
||||
if let Some(source) = self.fields.iter().find_map(|f| f.attrs.source) {
|
||||
return Err(Error::new_spanned(
|
||||
source.original,
|
||||
"transparent error struct can't contain #[source]",
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(fmt) = &self.attrs.fmt {
|
||||
return Err(Error::new_spanned(
|
||||
fmt.original,
|
||||
"#[error(fmt = ...)] is only supported in enums; for a struct, handwrite your own Display impl",
|
||||
));
|
||||
}
|
||||
check_field_attrs(&self.fields)?;
|
||||
for field in &self.fields {
|
||||
field.validate()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Enum<'_> {
|
||||
fn validate(&self) -> Result<()> {
|
||||
check_non_field_attrs(&self.attrs)?;
|
||||
let has_display = self.has_display();
|
||||
for variant in &self.variants {
|
||||
variant.validate()?;
|
||||
if has_display
|
||||
&& variant.attrs.display.is_none()
|
||||
&& variant.attrs.transparent.is_none()
|
||||
&& variant.attrs.fmt.is_none()
|
||||
{
|
||||
return Err(Error::new_spanned(
|
||||
variant.original,
|
||||
"missing #[error(\"...\")] display attribute",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Variant<'_> {
|
||||
fn validate(&self) -> Result<()> {
|
||||
check_non_field_attrs(&self.attrs)?;
|
||||
if self.attrs.transparent.is_some() {
|
||||
if self.fields.len() != 1 {
|
||||
return Err(Error::new_spanned(
|
||||
self.original,
|
||||
"#[error(transparent)] requires exactly one field",
|
||||
));
|
||||
}
|
||||
if let Some(source) = self.fields.iter().find_map(|f| f.attrs.source) {
|
||||
return Err(Error::new_spanned(
|
||||
source.original,
|
||||
"transparent variant can't contain #[source]",
|
||||
));
|
||||
}
|
||||
}
|
||||
check_field_attrs(&self.fields)?;
|
||||
for field in &self.fields {
|
||||
field.validate()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Field<'_> {
|
||||
fn validate(&self) -> Result<()> {
|
||||
if let Some(unexpected_display_attr) = if let Some(display) = &self.attrs.display {
|
||||
Some(display.original)
|
||||
} else if let Some(fmt) = &self.attrs.fmt {
|
||||
Some(fmt.original)
|
||||
} else {
|
||||
None
|
||||
} {
|
||||
return Err(Error::new_spanned(
|
||||
unexpected_display_attr,
|
||||
"not expected here; the #[error(...)] attribute belongs on top of a struct or an enum variant",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn check_non_field_attrs(attrs: &Attrs) -> Result<()> {
|
||||
if let Some(from) = &attrs.from {
|
||||
return Err(Error::new_spanned(
|
||||
from.original,
|
||||
"not expected here; the #[from] attribute belongs on a specific field",
|
||||
));
|
||||
}
|
||||
if let Some(source) = &attrs.source {
|
||||
return Err(Error::new_spanned(
|
||||
source.original,
|
||||
"not expected here; the #[source] attribute belongs on a specific field",
|
||||
));
|
||||
}
|
||||
if let Some(backtrace) = &attrs.backtrace {
|
||||
return Err(Error::new_spanned(
|
||||
backtrace,
|
||||
"not expected here; the #[backtrace] attribute belongs on a specific field",
|
||||
));
|
||||
}
|
||||
if attrs.transparent.is_some() {
|
||||
if let Some(display) = &attrs.display {
|
||||
return Err(Error::new_spanned(
|
||||
display.original,
|
||||
"cannot have both #[error(transparent)] and a display attribute",
|
||||
));
|
||||
}
|
||||
if let Some(fmt) = &attrs.fmt {
|
||||
return Err(Error::new_spanned(
|
||||
fmt.original,
|
||||
"cannot have both #[error(transparent)] and #[error(fmt = ...)]",
|
||||
));
|
||||
}
|
||||
} else if let (Some(display), Some(_)) = (&attrs.display, &attrs.fmt) {
|
||||
return Err(Error::new_spanned(
|
||||
display.original,
|
||||
"cannot have both #[error(fmt = ...)] and a format arguments attribute",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_field_attrs(fields: &[Field]) -> Result<()> {
|
||||
let mut from_field = None;
|
||||
let mut source_field = None;
|
||||
let mut backtrace_field = None;
|
||||
let mut has_backtrace = false;
|
||||
for field in fields {
|
||||
if let Some(from) = field.attrs.from {
|
||||
if from_field.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
from.original,
|
||||
"duplicate #[from] attribute",
|
||||
));
|
||||
}
|
||||
from_field = Some(field);
|
||||
}
|
||||
if let Some(source) = field.attrs.source {
|
||||
if source_field.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
source.original,
|
||||
"duplicate #[source] attribute",
|
||||
));
|
||||
}
|
||||
source_field = Some(field);
|
||||
}
|
||||
if let Some(backtrace) = field.attrs.backtrace {
|
||||
if backtrace_field.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
backtrace,
|
||||
"duplicate #[backtrace] attribute",
|
||||
));
|
||||
}
|
||||
backtrace_field = Some(field);
|
||||
has_backtrace = true;
|
||||
}
|
||||
if let Some(transparent) = field.attrs.transparent {
|
||||
return Err(Error::new_spanned(
|
||||
transparent.original,
|
||||
"#[error(transparent)] needs to go outside the enum or struct, not on an individual field",
|
||||
));
|
||||
}
|
||||
has_backtrace |= field.is_backtrace();
|
||||
}
|
||||
if let (Some(from_field), Some(source_field)) = (from_field, source_field) {
|
||||
if from_field.member != source_field.member {
|
||||
return Err(Error::new_spanned(
|
||||
from_field.attrs.from.unwrap().original,
|
||||
"#[from] is only supported on the source field, not any other field",
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(from_field) = from_field {
|
||||
let max_expected_fields = match backtrace_field {
|
||||
Some(backtrace_field) => 1 + (from_field.member != backtrace_field.member) as usize,
|
||||
None => 1 + has_backtrace as usize,
|
||||
};
|
||||
if fields.len() > max_expected_fields {
|
||||
return Err(Error::new_spanned(
|
||||
from_field.attrs.from.unwrap().original,
|
||||
"deriving From requires no fields other than source and backtrace",
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(source_field) = source_field.or(from_field) {
|
||||
if contains_non_static_lifetime(source_field.ty) {
|
||||
return Err(Error::new_spanned(
|
||||
&source_field.original.ty,
|
||||
"non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn contains_non_static_lifetime(ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Path(ty) => {
|
||||
let bracketed = match &ty.path.segments.last().unwrap().arguments {
|
||||
PathArguments::AngleBracketed(bracketed) => bracketed,
|
||||
_ => return false,
|
||||
};
|
||||
for arg in &bracketed.args {
|
||||
match arg {
|
||||
GenericArgument::Type(ty) if contains_non_static_lifetime(ty) => return true,
|
||||
GenericArgument::Lifetime(lifetime) if lifetime.ident != "static" => {
|
||||
return true
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
Type::Reference(ty) => ty
|
||||
.lifetime
|
||||
.as_ref()
|
||||
.map_or(false, |lifetime| lifetime.ident != "static"),
|
||||
_ => false, // maybe implement later if there are common other cases
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
components = ["rust-src"]
|
||||
@@ -1,50 +0,0 @@
|
||||
use core::error::Error;
|
||||
use core::panic::UnwindSafe;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait AsDynError<'a>: Sealed {
|
||||
fn as_dyn_error(&self) -> &(dyn Error + 'a);
|
||||
}
|
||||
|
||||
impl<'a, T: Error + 'a> AsDynError<'a> for T {
|
||||
#[inline]
|
||||
fn as_dyn_error(&self) -> &(dyn Error + 'a) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsDynError<'a> for dyn Error + 'a {
|
||||
#[inline]
|
||||
fn as_dyn_error(&self) -> &(dyn Error + 'a) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsDynError<'a> for dyn Error + Send + 'a {
|
||||
#[inline]
|
||||
fn as_dyn_error(&self) -> &(dyn Error + 'a) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsDynError<'a> for dyn Error + Send + Sync + 'a {
|
||||
#[inline]
|
||||
fn as_dyn_error(&self) -> &(dyn Error + 'a) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsDynError<'a> for dyn Error + Send + Sync + UnwindSafe + 'a {
|
||||
#[inline]
|
||||
fn as_dyn_error(&self) -> &(dyn Error + 'a) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait Sealed {}
|
||||
impl<T: Error> Sealed for T {}
|
||||
impl Sealed for dyn Error + '_ {}
|
||||
impl Sealed for dyn Error + Send + '_ {}
|
||||
impl Sealed for dyn Error + Send + Sync + '_ {}
|
||||
impl Sealed for dyn Error + Send + Sync + UnwindSafe + '_ {}
|
||||
@@ -1,82 +0,0 @@
|
||||
use core::fmt::Display;
|
||||
#[cfg(feature = "std")]
|
||||
use std::path::{self, Path, PathBuf};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait AsDisplay<'a>: Sealed {
|
||||
// TODO: convert to generic associated type.
|
||||
// https://github.com/dtolnay/thiserror/pull/253
|
||||
type Target: Display;
|
||||
|
||||
fn as_display(&'a self) -> Self::Target;
|
||||
}
|
||||
|
||||
impl<'a, T> AsDisplay<'a> for &T
|
||||
where
|
||||
T: Display + ?Sized + 'a,
|
||||
{
|
||||
type Target = &'a T;
|
||||
|
||||
fn as_display(&'a self) -> Self::Target {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<'a> AsDisplay<'a> for Path {
|
||||
type Target = path::Display<'a>;
|
||||
|
||||
#[inline]
|
||||
fn as_display(&'a self) -> Self::Target {
|
||||
self.display()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<'a> AsDisplay<'a> for PathBuf {
|
||||
type Target = path::Display<'a>;
|
||||
|
||||
#[inline]
|
||||
fn as_display(&'a self) -> Self::Target {
|
||||
self.display()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait Sealed {}
|
||||
impl<T: Display + ?Sized> Sealed for &T {}
|
||||
#[cfg(feature = "std")]
|
||||
impl Sealed for Path {}
|
||||
#[cfg(feature = "std")]
|
||||
impl Sealed for PathBuf {}
|
||||
|
||||
// Add a synthetic second impl of AsDisplay to prevent the "single applicable
|
||||
// impl" rule from making too weird inference decision based on the single impl
|
||||
// for &T, which could lead to code that compiles with thiserror's std feature
|
||||
// off but breaks under feature unification when std is turned on by an
|
||||
// unrelated crate.
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod placeholder {
|
||||
use super::{AsDisplay, Sealed};
|
||||
use core::fmt::{self, Display};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Placeholder;
|
||||
|
||||
impl<'a> AsDisplay<'a> for Placeholder {
|
||||
type Target = Self;
|
||||
|
||||
#[inline]
|
||||
fn as_display(&'a self) -> Self::Target {
|
||||
Placeholder
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Placeholder {
|
||||
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sealed for Placeholder {}
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
//! [![github]](https://github.com/dtolnay/thiserror) [![crates-io]](https://crates.io/crates/thiserror) [![docs-rs]](https://docs.rs/thiserror)
|
||||
//!
|
||||
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
|
||||
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
|
||||
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! This library provides a convenient derive macro for the standard library's
|
||||
//! [`std::error::Error`] trait.
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use std::io;
|
||||
//! use thiserror::Error;
|
||||
//!
|
||||
//! #[derive(Error, Debug)]
|
||||
//! pub enum DataStoreError {
|
||||
//! #[error("data store disconnected")]
|
||||
//! Disconnect(#[from] io::Error),
|
||||
//! #[error("the data for key `{0}` is not available")]
|
||||
//! Redaction(String),
|
||||
//! #[error("invalid header (expected {expected:?}, found {found:?})")]
|
||||
//! InvalidHeader {
|
||||
//! expected: String,
|
||||
//! found: String,
|
||||
//! },
|
||||
//! #[error("unknown data store error")]
|
||||
//! Unknown,
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # Details
|
||||
//!
|
||||
//! - Thiserror deliberately does not appear in your public API. You get the
|
||||
//! same thing as if you had written an implementation of
|
||||
//! [`std::error::Error`] by hand, and switching from handwritten impls to
|
||||
//! thiserror or vice versa is not a breaking change.
|
||||
//!
|
||||
//! - Errors may be enums, structs with named fields, tuple structs, or unit
|
||||
//! structs.
|
||||
//!
|
||||
//! - A [`Display`] impl is generated for your error if you provide
|
||||
//! `#[error("...")]` messages on the struct or each variant of your enum, as
|
||||
//! shown above in the example.
|
||||
//!
|
||||
//! The messages support a shorthand for interpolating fields from the error.
|
||||
//!
|
||||
//! - `#[error("{var}")]` ⟶ `write!("{}", self.var)`
|
||||
//! - `#[error("{0}")]` ⟶ `write!("{}", self.0)`
|
||||
//! - `#[error("{var:?}")]` ⟶ `write!("{:?}", self.var)`
|
||||
//! - `#[error("{0:?}")]` ⟶ `write!("{:?}", self.0)`
|
||||
//!
|
||||
//! These shorthands can be used together with any additional format args,
|
||||
//! which may be arbitrary expressions. For example:
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use core::i32;
|
||||
//! # use thiserror::Error;
|
||||
//! #
|
||||
//! #[derive(Error, Debug)]
|
||||
//! pub enum Error {
|
||||
//! #[error("invalid rdo_lookahead_frames {0} (expected < {max})", max = i32::MAX)]
|
||||
//! InvalidLookahead(u32),
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! If one of the additional expression arguments needs to refer to a field of
|
||||
//! the struct or enum, then refer to named fields as `.var` and tuple fields
|
||||
//! as `.0`.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use thiserror::Error;
|
||||
//! #
|
||||
//! # fn first_char(s: &String) -> char {
|
||||
//! # s.chars().next().unwrap()
|
||||
//! # }
|
||||
//! #
|
||||
//! # #[derive(Debug)]
|
||||
//! # struct Limits {
|
||||
//! # lo: usize,
|
||||
//! # hi: usize,
|
||||
//! # }
|
||||
//! #
|
||||
//! #[derive(Error, Debug)]
|
||||
//! pub enum Error {
|
||||
//! #[error("first letter must be lowercase but was {:?}", first_char(.0))]
|
||||
//! WrongCase(String),
|
||||
//! #[error("invalid index {idx}, expected at least {} and at most {}", .limits.lo, .limits.hi)]
|
||||
//! OutOfBounds { idx: usize, limits: Limits },
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! - A [`From`] impl is generated for each variant that contains a `#[from]`
|
||||
//! attribute.
|
||||
//!
|
||||
//! The variant using `#[from]` must not contain any other fields beyond the
|
||||
//! source error (and possibly a backtrace — see below). Usually
|
||||
//! `#[from]` fields are unnamed, but `#[from]` is allowed on a named field
|
||||
//! too.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use core::fmt::{self, Display};
|
||||
//! # use std::io;
|
||||
//! # use thiserror::Error;
|
||||
//! #
|
||||
//! # mod globset {
|
||||
//! # #[derive(thiserror::Error, Debug)]
|
||||
//! # #[error("...")]
|
||||
//! # pub struct Error;
|
||||
//! # }
|
||||
//! #
|
||||
//! #[derive(Error, Debug)]
|
||||
//! pub enum MyError {
|
||||
//! Io(#[from] io::Error),
|
||||
//! Glob(#[from] globset::Error),
|
||||
//! }
|
||||
//! #
|
||||
//! # impl Display for MyError {
|
||||
//! # fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
//! # unimplemented!()
|
||||
//! # }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! - The Error trait's [`source()`] method is implemented to return whichever
|
||||
//! field has a `#[source]` attribute or is named `source`, if any. This is
|
||||
//! for identifying the underlying lower level error that caused your error.
|
||||
//!
|
||||
//! The `#[from]` attribute always implies that the same field is `#[source]`,
|
||||
//! so you don't ever need to specify both attributes.
|
||||
//!
|
||||
//! Any error type that implements `std::error::Error` or dereferences to `dyn
|
||||
//! std::error::Error` will work as a source.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use core::fmt::{self, Display};
|
||||
//! # use thiserror::Error;
|
||||
//! #
|
||||
//! #[derive(Error, Debug)]
|
||||
//! pub struct MyError {
|
||||
//! msg: String,
|
||||
//! #[source] // optional if field name is `source`
|
||||
//! source: anyhow::Error,
|
||||
//! }
|
||||
//! #
|
||||
//! # impl Display for MyError {
|
||||
//! # fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
//! # unimplemented!()
|
||||
//! # }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! - The Error trait's [`provide()`] method is implemented to provide whichever
|
||||
//! field has a type named `Backtrace`, if any, as a
|
||||
//! [`std::backtrace::Backtrace`]. Using `Backtrace` in errors requires a
|
||||
//! nightly compiler with Rust version 1.73 or newer.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # const IGNORE: &str = stringify! {
|
||||
//! use std::backtrace::Backtrace;
|
||||
//!
|
||||
//! #[derive(Error, Debug)]
|
||||
//! pub struct MyError {
|
||||
//! msg: String,
|
||||
//! backtrace: Backtrace, // automatically detected
|
||||
//! }
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! - If a field is both a source (named `source`, or has `#[source]` or
|
||||
//! `#[from]` attribute) *and* is marked `#[backtrace]`, then the Error
|
||||
//! trait's [`provide()`] method is forwarded to the source's `provide` so
|
||||
//! that both layers of the error share the same backtrace. The `#[backtrace]`
|
||||
//! attribute requires a nightly compiler with Rust version 1.73 or newer.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # const IGNORE: &str = stringify! {
|
||||
//! #[derive(Error, Debug)]
|
||||
//! pub enum MyError {
|
||||
//! Io {
|
||||
//! #[backtrace]
|
||||
//! source: io::Error,
|
||||
//! },
|
||||
//! }
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! - For variants that use `#[from]` and also contain a `Backtrace` field, a
|
||||
//! backtrace is captured from within the `From` impl.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # const IGNORE: &str = stringify! {
|
||||
//! #[derive(Error, Debug)]
|
||||
//! pub enum MyError {
|
||||
//! Io {
|
||||
//! #[from]
|
||||
//! source: io::Error,
|
||||
//! backtrace: Backtrace,
|
||||
//! },
|
||||
//! }
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! - Errors may use `error(transparent)` to forward the source and [`Display`]
|
||||
//! methods straight through to an underlying error without adding an
|
||||
//! additional message. This would be appropriate for enums that need an
|
||||
//! "anything else" variant.
|
||||
//!
|
||||
//! ```
|
||||
//! # use thiserror::Error;
|
||||
//! #
|
||||
//! #[derive(Error, Debug)]
|
||||
//! pub enum MyError {
|
||||
//! # /*
|
||||
//! ...
|
||||
//! # */
|
||||
//!
|
||||
//! #[error(transparent)]
|
||||
//! Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Another use case is hiding implementation details of an error
|
||||
//! representation behind an opaque error type, so that the representation is
|
||||
//! able to evolve without breaking the crate's public API.
|
||||
//!
|
||||
//! ```
|
||||
//! # use thiserror::Error;
|
||||
//! #
|
||||
//! // PublicError is public, but opaque and easy to keep compatible.
|
||||
//! #[derive(Error, Debug)]
|
||||
//! #[error(transparent)]
|
||||
//! pub struct PublicError(#[from] ErrorRepr);
|
||||
//!
|
||||
//! impl PublicError {
|
||||
//! // Accessors for anything we do want to expose publicly.
|
||||
//! }
|
||||
//!
|
||||
//! // Private and free to change across minor version of the crate.
|
||||
//! #[derive(Error, Debug)]
|
||||
//! enum ErrorRepr {
|
||||
//! # /*
|
||||
//! ...
|
||||
//! # */
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! - See also the [`anyhow`] library for a convenient single error type to use
|
||||
//! in application code.
|
||||
//!
|
||||
//! [`anyhow`]: https://github.com/dtolnay/anyhow
|
||||
//! [`source()`]: std::error::Error::source
|
||||
//! [`provide()`]: std::error::Error::provide
|
||||
//! [`Display`]: std::fmt::Display
|
||||
|
||||
#![no_std]
|
||||
#![doc(html_root_url = "https://docs.rs/thiserror/2.0.17")]
|
||||
#![allow(
|
||||
clippy::elidable_lifetime_names,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::needless_lifetimes,
|
||||
clippy::return_self_not_must_use,
|
||||
clippy::wildcard_imports
|
||||
)]
|
||||
#![cfg_attr(error_generic_member_access, feature(error_generic_member_access))]
|
||||
|
||||
#[cfg(all(thiserror_nightly_testing, not(error_generic_member_access)))]
|
||||
compile_error!("Build script probe failed to compile.");
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate std;
|
||||
#[cfg(feature = "std")]
|
||||
extern crate std as core;
|
||||
|
||||
mod aserror;
|
||||
mod display;
|
||||
#[cfg(error_generic_member_access)]
|
||||
mod provide;
|
||||
mod var;
|
||||
|
||||
pub use thiserror_impl::*;
|
||||
|
||||
mod private;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod __private {
|
||||
#[doc(hidden)]
|
||||
pub use crate::private::*;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#[doc(hidden)]
|
||||
pub use crate::aserror::AsDynError;
|
||||
#[doc(hidden)]
|
||||
pub use crate::display::AsDisplay;
|
||||
#[cfg(error_generic_member_access)]
|
||||
#[doc(hidden)]
|
||||
pub use crate::provide::ThiserrorProvide;
|
||||
#[doc(hidden)]
|
||||
pub use crate::var::Var;
|
||||
#[doc(hidden)]
|
||||
pub use core::error::Error;
|
||||
#[cfg(all(feature = "std", not(thiserror_no_backtrace_type)))]
|
||||
#[doc(hidden)]
|
||||
pub use std::backtrace::Backtrace;
|
||||
@@ -1,20 +0,0 @@
|
||||
use core::error::{Error, Request};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait ThiserrorProvide: Sealed {
|
||||
fn thiserror_provide<'a>(&'a self, request: &mut Request<'a>);
|
||||
}
|
||||
|
||||
impl<T> ThiserrorProvide for T
|
||||
where
|
||||
T: Error + ?Sized,
|
||||
{
|
||||
#[inline]
|
||||
fn thiserror_provide<'a>(&'a self, request: &mut Request<'a>) {
|
||||
self.provide(request);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait Sealed {}
|
||||
impl<T: Error + ?Sized> Sealed for T {}
|
||||
@@ -1,9 +0,0 @@
|
||||
use core::fmt::{self, Pointer};
|
||||
|
||||
pub struct Var<'a, T: ?Sized>(pub &'a T);
|
||||
|
||||
impl<'a, T: Pointer + ?Sized> Pointer for Var<'a, T> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
Pointer::fmt(self.0, formatter)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
#[rustversion::attr(not(nightly), ignore = "requires nightly")]
|
||||
#[cfg_attr(miri, ignore = "incompatible with miri")]
|
||||
#[test]
|
||||
fn ui() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/*.rs");
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "thiserror_no_std_test"
|
||||
version = "0.0.0"
|
||||
authors = ["David Tolnay <dtolnay@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "test.rs"
|
||||
|
||||
[dependencies]
|
||||
thiserror = { path = "../..", default-features = false }
|
||||
@@ -1,58 +0,0 @@
|
||||
#![no_std]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Error::E")]
|
||||
E(#[from] SourceError),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("SourceError {field}")]
|
||||
pub struct SourceError {
|
||||
pub field: i32,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Error, SourceError};
|
||||
use core::error::Error as _;
|
||||
use core::fmt::{self, Write};
|
||||
use core::mem;
|
||||
|
||||
struct Buf<'a>(&'a mut [u8]);
|
||||
|
||||
impl Write for Buf<'_> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
if s.len() <= self.0.len() {
|
||||
let (out, rest) = mem::take(&mut self.0).split_at_mut(s.len());
|
||||
out.copy_from_slice(s.as_bytes());
|
||||
self.0 = rest;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(fmt::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let source = SourceError { field: -1 };
|
||||
let error = Error::from(source);
|
||||
|
||||
let source = error
|
||||
.source()
|
||||
.unwrap()
|
||||
.downcast_ref::<SourceError>()
|
||||
.unwrap();
|
||||
|
||||
let mut msg = [b'~'; 17];
|
||||
write!(Buf(&mut msg), "{error}").unwrap();
|
||||
assert_eq!(msg, *b"Error::E~~~~~~~~~");
|
||||
|
||||
let mut msg = [b'~'; 17];
|
||||
write!(Buf(&mut msg), "{source}").unwrap();
|
||||
assert_eq!(msg, *b"SourceError -1~~~");
|
||||
}
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
#![cfg(feature = "std")]
|
||||
#![cfg_attr(thiserror_nightly_testing, feature(error_generic_member_access))]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct Inner;
|
||||
|
||||
#[cfg(thiserror_nightly_testing)]
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct InnerBacktrace {
|
||||
backtrace: std::backtrace::Backtrace,
|
||||
}
|
||||
|
||||
#[cfg(thiserror_nightly_testing)]
|
||||
pub mod structs {
|
||||
use super::{Inner, InnerBacktrace};
|
||||
use std::backtrace::Backtrace;
|
||||
use std::error::{self, Error};
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
mod not_backtrace {
|
||||
#[derive(Debug)]
|
||||
pub struct Backtrace;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct PlainBacktrace {
|
||||
backtrace: Backtrace,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct ExplicitBacktrace {
|
||||
#[backtrace]
|
||||
backtrace: Backtrace,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct NotBacktrace {
|
||||
backtrace: crate::structs::not_backtrace::r#Backtrace,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct OptBacktrace {
|
||||
#[backtrace]
|
||||
backtrace: Option<Backtrace>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct ArcBacktrace {
|
||||
#[backtrace]
|
||||
backtrace: Arc<Backtrace>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct BacktraceFrom {
|
||||
#[from]
|
||||
source: Inner,
|
||||
#[backtrace]
|
||||
backtrace: Backtrace,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct CombinedBacktraceFrom {
|
||||
#[from]
|
||||
#[backtrace]
|
||||
source: InnerBacktrace,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct OptBacktraceFrom {
|
||||
#[from]
|
||||
source: Inner,
|
||||
#[backtrace]
|
||||
backtrace: Option<Backtrace>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct ArcBacktraceFrom {
|
||||
#[from]
|
||||
source: Inner,
|
||||
#[backtrace]
|
||||
backtrace: Arc<Backtrace>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct AnyhowBacktrace {
|
||||
#[backtrace]
|
||||
source: anyhow::Error,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct BoxDynErrorBacktrace {
|
||||
#[backtrace]
|
||||
source: Box<dyn Error>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backtrace() {
|
||||
let error = PlainBacktrace {
|
||||
backtrace: Backtrace::capture(),
|
||||
};
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = ExplicitBacktrace {
|
||||
backtrace: Backtrace::capture(),
|
||||
};
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = OptBacktrace {
|
||||
backtrace: Some(Backtrace::capture()),
|
||||
};
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = ArcBacktrace {
|
||||
backtrace: Arc::new(Backtrace::capture()),
|
||||
};
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = BacktraceFrom::from(Inner);
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = CombinedBacktraceFrom::from(InnerBacktrace {
|
||||
backtrace: Backtrace::capture(),
|
||||
});
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = OptBacktraceFrom::from(Inner);
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = ArcBacktraceFrom::from(Inner);
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = AnyhowBacktrace {
|
||||
source: anyhow::Error::msg("..."),
|
||||
};
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = BoxDynErrorBacktrace {
|
||||
source: Box::new(PlainBacktrace {
|
||||
backtrace: Backtrace::capture(),
|
||||
}),
|
||||
};
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(thiserror_nightly_testing)]
|
||||
pub mod enums {
|
||||
use super::{Inner, InnerBacktrace};
|
||||
use std::backtrace::Backtrace;
|
||||
use std::error;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PlainBacktrace {
|
||||
#[error("...")]
|
||||
Test { backtrace: Backtrace },
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ExplicitBacktrace {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[backtrace]
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum OptBacktrace {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[backtrace]
|
||||
backtrace: Option<Backtrace>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ArcBacktrace {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[backtrace]
|
||||
backtrace: Arc<Backtrace>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BacktraceFrom {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[from]
|
||||
source: Inner,
|
||||
#[backtrace]
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CombinedBacktraceFrom {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[from]
|
||||
#[backtrace]
|
||||
source: InnerBacktrace,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum OptBacktraceFrom {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[from]
|
||||
source: Inner,
|
||||
#[backtrace]
|
||||
backtrace: Option<Backtrace>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ArcBacktraceFrom {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[from]
|
||||
source: Inner,
|
||||
#[backtrace]
|
||||
backtrace: Arc<Backtrace>,
|
||||
},
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backtrace() {
|
||||
let error = PlainBacktrace::Test {
|
||||
backtrace: Backtrace::capture(),
|
||||
};
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = ExplicitBacktrace::Test {
|
||||
backtrace: Backtrace::capture(),
|
||||
};
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = OptBacktrace::Test {
|
||||
backtrace: Some(Backtrace::capture()),
|
||||
};
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = ArcBacktrace::Test {
|
||||
backtrace: Arc::new(Backtrace::capture()),
|
||||
};
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = BacktraceFrom::from(Inner);
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = CombinedBacktraceFrom::from(InnerBacktrace {
|
||||
backtrace: Backtrace::capture(),
|
||||
});
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = OptBacktraceFrom::from(Inner);
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
|
||||
let error = ArcBacktraceFrom::from(Inner);
|
||||
assert!(error::request_ref::<Backtrace>(&error).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
not(thiserror_nightly_testing),
|
||||
ignore = "requires `--cfg=thiserror_nightly_testing`"
|
||||
)]
|
||||
fn test_backtrace() {}
|
||||
@@ -1,478 +0,0 @@
|
||||
#![allow(
|
||||
clippy::elidable_lifetime_names,
|
||||
clippy::needless_lifetimes,
|
||||
clippy::needless_raw_string_hashes,
|
||||
clippy::trivially_copy_pass_by_ref,
|
||||
clippy::uninlined_format_args
|
||||
)]
|
||||
|
||||
use core::fmt::{self, Display};
|
||||
use thiserror::Error;
|
||||
|
||||
fn assert<T: Display>(expected: &str, value: T) {
|
||||
assert_eq!(expected, value.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_braced() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("braced error: {msg}")]
|
||||
struct Error {
|
||||
msg: String,
|
||||
}
|
||||
|
||||
let msg = "T".to_owned();
|
||||
assert("braced error: T", Error { msg });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_braced_unused() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("braced error")]
|
||||
struct Error {
|
||||
extra: usize,
|
||||
}
|
||||
|
||||
assert("braced error", Error { extra: 0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("tuple error: {0}")]
|
||||
struct Error(usize);
|
||||
|
||||
assert("tuple error: 0", Error(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unit() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("unit error")]
|
||||
struct Error;
|
||||
|
||||
assert("unit error", Error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum() {
|
||||
#[derive(Error, Debug)]
|
||||
enum Error {
|
||||
#[error("braced error: {id}")]
|
||||
Braced { id: usize },
|
||||
#[error("tuple error: {0}")]
|
||||
Tuple(usize),
|
||||
#[error("unit error")]
|
||||
Unit,
|
||||
}
|
||||
|
||||
assert("braced error: 0", Error::Braced { id: 0 });
|
||||
assert("tuple error: 0", Error::Tuple(0));
|
||||
assert("unit error", Error::Unit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constants() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{MSG}: {id:?} (code {CODE:?})")]
|
||||
struct Error {
|
||||
id: &'static str,
|
||||
}
|
||||
|
||||
const MSG: &str = "failed to do";
|
||||
const CODE: usize = 9;
|
||||
|
||||
assert("failed to do: \"\" (code 9)", Error { id: "" });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inherit() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{0}")]
|
||||
enum Error {
|
||||
Some(&'static str),
|
||||
#[error("other error")]
|
||||
Other(&'static str),
|
||||
}
|
||||
|
||||
assert("some error", Error::Some("some error"));
|
||||
assert("other error", Error::Other("..."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_brace_escape() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("fn main() {{}}")]
|
||||
struct Error;
|
||||
|
||||
assert("fn main() {}", Error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("1 + 1 = {}", 1 + 1)]
|
||||
struct Error;
|
||||
assert("1 + 1 = 2", Error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("!bool = {}", not(.0))]
|
||||
struct Error(bool);
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
fn not(bool: &bool) -> bool {
|
||||
!*bool
|
||||
}
|
||||
|
||||
assert("!bool = false", Error(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{intro}: {0}", intro = match .1 {
|
||||
Some(n) => format!("error occurred with {}", n),
|
||||
None => "there was an empty error".to_owned(),
|
||||
})]
|
||||
struct Error(String, Option<usize>);
|
||||
|
||||
assert(
|
||||
"error occurred with 1: ...",
|
||||
Error("...".to_owned(), Some(1)),
|
||||
);
|
||||
assert(
|
||||
"there was an empty error: ...",
|
||||
Error("...".to_owned(), None),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_display() {
|
||||
// Same behavior as the one in `test_match`, but without String allocations.
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{}", {
|
||||
struct Msg<'a>(&'a String, &'a Option<usize>);
|
||||
impl<'a> Display for Msg<'a> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.1 {
|
||||
Some(n) => write!(formatter, "error occurred with {}", n),
|
||||
None => write!(formatter, "there was an empty error"),
|
||||
}?;
|
||||
write!(formatter, ": {}", self.0)
|
||||
}
|
||||
}
|
||||
Msg(.0, .1)
|
||||
})]
|
||||
struct Error(String, Option<usize>);
|
||||
|
||||
assert(
|
||||
"error occurred with 1: ...",
|
||||
Error("...".to_owned(), Some(1)),
|
||||
);
|
||||
assert(
|
||||
"there was an empty error: ...",
|
||||
Error("...".to_owned(), None),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_void() {
|
||||
#[allow(clippy::empty_enums)]
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub enum Error {}
|
||||
|
||||
let _: Error;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixed() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("a={a} :: b={} :: c={c} :: d={d}", 1, c = 2, d = 3)]
|
||||
struct Error {
|
||||
a: usize,
|
||||
d: usize,
|
||||
}
|
||||
|
||||
assert("a=0 :: b=1 :: c=2 :: d=3", Error { a: 0, d: 0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ints() {
|
||||
#[derive(Error, Debug)]
|
||||
enum Error {
|
||||
#[error("error {0}")]
|
||||
Tuple(usize, usize),
|
||||
#[error("error {0}", '?')]
|
||||
Struct { v: usize },
|
||||
}
|
||||
|
||||
assert("error 9", Error::Tuple(9, 0));
|
||||
assert("error ?", Error::Struct { v: 0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trailing_comma() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error(
|
||||
"error {0}",
|
||||
)]
|
||||
#[rustfmt::skip]
|
||||
struct Error(char);
|
||||
|
||||
assert("error ?", Error('?'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_field() {
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
data: usize,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{}", .0.data)]
|
||||
struct Error(Inner);
|
||||
|
||||
assert("0", Error(Inner { data: 0 }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_tuple_field() {
|
||||
#[derive(Debug)]
|
||||
struct Inner(usize);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{}", .0.0)]
|
||||
struct Error(Inner);
|
||||
|
||||
assert("0", Error(Inner(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pointer() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{field:p}")]
|
||||
pub struct Struct {
|
||||
field: Box<i32>,
|
||||
}
|
||||
|
||||
let s = Struct {
|
||||
field: Box::new(-1),
|
||||
};
|
||||
assert_eq!(s.to_string(), format!("{:p}", s.field));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_rules_variant_from_call_site() {
|
||||
// Regression test for https://github.com/dtolnay/thiserror/issues/86
|
||||
|
||||
macro_rules! decl_error {
|
||||
($variant:ident($value:ident)) => {
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error0 {
|
||||
#[error("{0:?}")]
|
||||
$variant($value),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{0:?}")]
|
||||
pub enum Error1 {
|
||||
$variant($value),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
decl_error!(Repro(u8));
|
||||
|
||||
assert("0", Error0::Repro(0));
|
||||
assert("0", Error1::Repro(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_rules_message_from_call_site() {
|
||||
// Regression test for https://github.com/dtolnay/thiserror/issues/398
|
||||
|
||||
macro_rules! decl_error {
|
||||
($($errors:tt)*) => {
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
$($errors)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
decl_error! {
|
||||
#[error("{0}")]
|
||||
Unnamed(u8),
|
||||
#[error("{x}")]
|
||||
Named { x: u8 },
|
||||
}
|
||||
|
||||
assert("0", Error::Unnamed(0));
|
||||
assert("0", Error::Named { x: 0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("braced raw error: {fn}")]
|
||||
struct Error {
|
||||
r#fn: &'static str,
|
||||
}
|
||||
|
||||
assert("braced raw error: T", Error { r#fn: "T" });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_enum() {
|
||||
#[derive(Error, Debug)]
|
||||
enum Error {
|
||||
#[error("braced raw error: {fn}")]
|
||||
Braced { r#fn: &'static str },
|
||||
}
|
||||
|
||||
assert("braced raw error: T", Error::Braced { r#fn: "T" });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyword() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("error: {type}", type = 1)]
|
||||
struct Error;
|
||||
|
||||
assert("error: 1", Error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_self() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("error: {self:?}")]
|
||||
struct Error;
|
||||
|
||||
assert("error: Error", Error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_str_special_chars() {
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("brace left {{")]
|
||||
BraceLeft,
|
||||
#[error("brace left 2 \x7B\x7B")]
|
||||
BraceLeft2,
|
||||
#[error("brace left 3 \u{7B}\u{7B}")]
|
||||
BraceLeft3,
|
||||
#[error("brace right }}")]
|
||||
BraceRight,
|
||||
#[error("brace right 2 \x7D\x7D")]
|
||||
BraceRight2,
|
||||
#[error("brace right 3 \u{7D}\u{7D}")]
|
||||
BraceRight3,
|
||||
#[error(
|
||||
"new_\
|
||||
line"
|
||||
)]
|
||||
NewLine,
|
||||
#[error("escape24 \u{78}")]
|
||||
Escape24,
|
||||
}
|
||||
|
||||
assert("brace left {", Error::BraceLeft);
|
||||
assert("brace left 2 {", Error::BraceLeft2);
|
||||
assert("brace left 3 {", Error::BraceLeft3);
|
||||
assert("brace right }", Error::BraceRight);
|
||||
assert("brace right 2 }", Error::BraceRight2);
|
||||
assert("brace right 3 }", Error::BraceRight3);
|
||||
assert("new_line", Error::NewLine);
|
||||
assert("escape24 x", Error::Escape24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_str() {
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(r#"raw brace left {{"#)]
|
||||
BraceLeft,
|
||||
#[error(r#"raw brace left 2 \x7B"#)]
|
||||
BraceLeft2,
|
||||
#[error(r#"raw brace right }}"#)]
|
||||
BraceRight,
|
||||
#[error(r#"raw brace right 2 \x7D"#)]
|
||||
BraceRight2,
|
||||
}
|
||||
|
||||
assert(r#"raw brace left {"#, Error::BraceLeft);
|
||||
assert(r#"raw brace left 2 \x7B"#, Error::BraceLeft2);
|
||||
assert(r#"raw brace right }"#, Error::BraceRight);
|
||||
assert(r#"raw brace right 2 \x7D"#, Error::BraceRight2);
|
||||
}
|
||||
|
||||
mod util {
|
||||
use core::fmt::{self, Octal};
|
||||
|
||||
pub fn octal<T: Octal>(value: &T, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "0o{:o}", value)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt_path() {
|
||||
fn unit(formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("unit=")
|
||||
}
|
||||
|
||||
fn pair(k: &i32, v: &i32, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "pair={k}:{v}")
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(fmt = unit)]
|
||||
Unit,
|
||||
#[error(fmt = pair)]
|
||||
Tuple(i32, i32),
|
||||
#[error(fmt = pair)]
|
||||
Entry { k: i32, v: i32 },
|
||||
#[error(fmt = crate::util::octal)]
|
||||
I16(i16),
|
||||
#[error(fmt = crate::util::octal::<i32>)]
|
||||
I32 { n: i32 },
|
||||
#[error(fmt = core::fmt::Octal::fmt)]
|
||||
I64(i64),
|
||||
#[error("...{0}")]
|
||||
Other(bool),
|
||||
}
|
||||
|
||||
assert("unit=", Error::Unit);
|
||||
assert("pair=10:0", Error::Tuple(10, 0));
|
||||
assert("pair=10:0", Error::Entry { k: 10, v: 0 });
|
||||
assert("0o777", Error::I16(0o777));
|
||||
assert("0o777", Error::I32 { n: 0o777 });
|
||||
assert("777", Error::I64(0o777));
|
||||
assert("...false", Error::Other(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt_path_inherited() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error(fmt = crate::util::octal)]
|
||||
pub enum Error {
|
||||
I16(i16),
|
||||
I32 {
|
||||
n: i32,
|
||||
},
|
||||
#[error(fmt = core::fmt::Octal::fmt)]
|
||||
I64(i64),
|
||||
#[error("...{0}")]
|
||||
Other(bool),
|
||||
}
|
||||
|
||||
assert("0o777", Error::I16(0o777));
|
||||
assert("0o777", Error::I32 { n: 0o777 });
|
||||
assert("777", Error::I64(0o777));
|
||||
assert("...false", Error::Other(false));
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use core::fmt::{self, Display};
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
macro_rules! unimplemented_display {
|
||||
($ty:ty) => {
|
||||
impl Display for $ty {
|
||||
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
struct BracedError {
|
||||
msg: String,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
struct TupleError(String, usize);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
struct UnitError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
struct WithSource {
|
||||
#[source]
|
||||
cause: io::Error,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
struct WithAnyhow {
|
||||
#[source]
|
||||
cause: anyhow::Error,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum EnumError {
|
||||
Braced {
|
||||
#[source]
|
||||
cause: io::Error,
|
||||
},
|
||||
Tuple(#[source] io::Error),
|
||||
Unit,
|
||||
}
|
||||
|
||||
unimplemented_display!(BracedError);
|
||||
unimplemented_display!(TupleError);
|
||||
unimplemented_display!(UnitError);
|
||||
unimplemented_display!(WithSource);
|
||||
unimplemented_display!(WithAnyhow);
|
||||
unimplemented_display!(EnumError);
|
||||
@@ -1,118 +0,0 @@
|
||||
#![allow(clippy::iter_cloned_collect, clippy::uninlined_format_args)]
|
||||
|
||||
use core::fmt::Display;
|
||||
#[cfg(feature = "std")]
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
// Some of the elaborate cases from the rcc codebase, which is a C compiler in
|
||||
// Rust. https://github.com/jyn514/rcc/blob/0.8.0/src/data/error.rs
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CompilerError {
|
||||
#[error("cannot shift {} by {maximum} or more bits (got {current})", if *.is_left { "left" } else { "right" })]
|
||||
TooManyShiftBits {
|
||||
is_left: bool,
|
||||
maximum: u64,
|
||||
current: u64,
|
||||
},
|
||||
|
||||
#[error("#error {}", (.0).iter().copied().collect::<Vec<_>>().join(" "))]
|
||||
User(Vec<&'static str>),
|
||||
|
||||
#[error("overflow while parsing {}integer literal",
|
||||
if let Some(signed) = .is_signed {
|
||||
if *signed { "signed "} else { "unsigned "}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)]
|
||||
IntegerOverflow { is_signed: Option<bool> },
|
||||
|
||||
#[error("overflow while parsing {}integer literal", match .is_signed {
|
||||
Some(true) => "signed ",
|
||||
Some(false) => "unsigned ",
|
||||
None => "",
|
||||
})]
|
||||
IntegerOverflow2 { is_signed: Option<bool> },
|
||||
}
|
||||
|
||||
// Examples drawn from Rustup.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RustupError {
|
||||
#[error(
|
||||
"toolchain '{name}' does not contain component {component}{}",
|
||||
.suggestion
|
||||
.as_ref()
|
||||
.map_or_else(String::new, |s| format!("; did you mean '{}'?", s)),
|
||||
)]
|
||||
UnknownComponent {
|
||||
name: String,
|
||||
component: String,
|
||||
suggestion: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert<T: Display>(expected: &str, value: T) {
|
||||
assert_eq!(expected, value.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rcc() {
|
||||
assert(
|
||||
"cannot shift left by 32 or more bits (got 50)",
|
||||
CompilerError::TooManyShiftBits {
|
||||
is_left: true,
|
||||
maximum: 32,
|
||||
current: 50,
|
||||
},
|
||||
);
|
||||
|
||||
assert("#error A B C", CompilerError::User(vec!["A", "B", "C"]));
|
||||
|
||||
assert(
|
||||
"overflow while parsing signed integer literal",
|
||||
CompilerError::IntegerOverflow {
|
||||
is_signed: Some(true),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rustup() {
|
||||
assert(
|
||||
"toolchain 'nightly' does not contain component clipy; did you mean 'clippy'?",
|
||||
RustupError::UnknownComponent {
|
||||
name: "nightly".to_owned(),
|
||||
component: "clipy".to_owned(),
|
||||
suggestion: Some("clippy".to_owned()),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/dtolnay/thiserror/issues/335
|
||||
#[cfg(feature = "std")]
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_assoc_type_equality_constraint() {
|
||||
pub trait Trait<T>: Display {
|
||||
type A;
|
||||
}
|
||||
|
||||
impl<T> Trait<T> for i32 {
|
||||
type A = i32;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{A} {b}", b = &0 as &dyn Trait<i32, A = i32>)]
|
||||
pub struct Error {
|
||||
pub A: PathBuf,
|
||||
}
|
||||
|
||||
assert(
|
||||
"... 0",
|
||||
Error {
|
||||
A: PathBuf::from("..."),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
#![allow(clippy::extra_unused_type_parameters)]
|
||||
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct ErrorStruct {
|
||||
#[from]
|
||||
source: io::Error,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct ErrorStructOptional {
|
||||
#[from]
|
||||
source: Option<io::Error>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct ErrorTuple(#[from] io::Error);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct ErrorTupleOptional(#[from] Option<io::Error>);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub enum ErrorEnum {
|
||||
Test {
|
||||
#[from]
|
||||
source: io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub enum ErrorEnumOptional {
|
||||
Test {
|
||||
#[from]
|
||||
source: Option<io::Error>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub enum Many {
|
||||
Any(#[from] anyhow::Error),
|
||||
Io(#[from] io::Error),
|
||||
}
|
||||
|
||||
fn assert_impl<T: From<io::Error>>() {}
|
||||
|
||||
#[test]
|
||||
fn test_from() {
|
||||
assert_impl::<ErrorStruct>();
|
||||
assert_impl::<ErrorStructOptional>();
|
||||
assert_impl::<ErrorTuple>();
|
||||
assert_impl::<ErrorTupleOptional>();
|
||||
assert_impl::<ErrorEnum>();
|
||||
assert_impl::<ErrorEnumOptional>();
|
||||
assert_impl::<Many>();
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
#![allow(clippy::needless_late_init, clippy::uninlined_format_args)]
|
||||
|
||||
use core::fmt::{self, Debug, Display};
|
||||
use core::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
pub struct NoFormat;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugOnly;
|
||||
|
||||
pub struct DisplayOnly;
|
||||
|
||||
impl Display for DisplayOnly {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("display only")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugAndDisplay;
|
||||
|
||||
impl Display for DebugAndDisplay {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("debug and display")
|
||||
}
|
||||
}
|
||||
|
||||
// Should expand to:
|
||||
//
|
||||
// impl<E> Display for EnumDebugField<E>
|
||||
// where
|
||||
// E: Debug;
|
||||
//
|
||||
// impl<E> Error for EnumDebugField<E>
|
||||
// where
|
||||
// Self: Debug + Display;
|
||||
//
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EnumDebugGeneric<E> {
|
||||
#[error("{0:?}")]
|
||||
FatalError(E),
|
||||
}
|
||||
|
||||
// Should expand to:
|
||||
//
|
||||
// impl<E> Display for EnumFromGeneric<E>;
|
||||
//
|
||||
// impl<E> Error for EnumFromGeneric<E>
|
||||
// where
|
||||
// EnumDebugGeneric<E>: Error + 'static,
|
||||
// Self: Debug + Display;
|
||||
//
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EnumFromGeneric<E> {
|
||||
#[error("enum from generic")]
|
||||
Source(#[from] EnumDebugGeneric<E>),
|
||||
}
|
||||
|
||||
// Should expand to:
|
||||
//
|
||||
// impl<HasDisplay, HasDebug, HasNeither> Display
|
||||
// for EnumCompound<HasDisplay, HasDebug, HasNeither>
|
||||
// where
|
||||
// HasDisplay: Display,
|
||||
// HasDebug: Debug;
|
||||
//
|
||||
// impl<HasDisplay, HasDebug, HasNeither> Error
|
||||
// for EnumCompound<HasDisplay, HasDebug, HasNeither>
|
||||
// where
|
||||
// Self: Debug + Display;
|
||||
//
|
||||
#[derive(Error)]
|
||||
pub enum EnumCompound<HasDisplay, HasDebug, HasNeither> {
|
||||
#[error("{0} {1:?}")]
|
||||
DisplayDebug(HasDisplay, HasDebug),
|
||||
#[error("{0}")]
|
||||
Display(HasDisplay, HasNeither),
|
||||
#[error("{1:?}")]
|
||||
Debug(HasNeither, HasDebug),
|
||||
}
|
||||
|
||||
impl<HasDisplay, HasDebug, HasNeither> Debug for EnumCompound<HasDisplay, HasDebug, HasNeither> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("EnumCompound")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_enum_compound() {
|
||||
let mut instance: EnumCompound<DisplayOnly, DebugOnly, NoFormat>;
|
||||
|
||||
instance = EnumCompound::DisplayDebug(DisplayOnly, DebugOnly);
|
||||
assert_eq!(format!("{}", instance), "display only DebugOnly");
|
||||
|
||||
instance = EnumCompound::Display(DisplayOnly, NoFormat);
|
||||
assert_eq!(format!("{}", instance), "display only");
|
||||
|
||||
instance = EnumCompound::Debug(NoFormat, DebugOnly);
|
||||
assert_eq!(format!("{}", instance), "DebugOnly");
|
||||
}
|
||||
|
||||
// Should expand to:
|
||||
//
|
||||
// impl<E> Display for EnumTransparentGeneric<E>
|
||||
// where
|
||||
// E: Display;
|
||||
//
|
||||
// impl<E> Error for EnumTransparentGeneric<E>
|
||||
// where
|
||||
// E: Error,
|
||||
// Self: Debug + Display;
|
||||
//
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EnumTransparentGeneric<E> {
|
||||
#[error(transparent)]
|
||||
Other(E),
|
||||
}
|
||||
|
||||
// Should expand to:
|
||||
//
|
||||
// impl<E> Display for StructDebugGeneric<E>
|
||||
// where
|
||||
// E: Debug;
|
||||
//
|
||||
// impl<E> Error for StructDebugGeneric<E>
|
||||
// where
|
||||
// Self: Debug + Display;
|
||||
//
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{underlying:?}")]
|
||||
pub struct StructDebugGeneric<E> {
|
||||
pub underlying: E,
|
||||
}
|
||||
|
||||
// Should expand to:
|
||||
//
|
||||
// impl<E> Error for StructFromGeneric<E>
|
||||
// where
|
||||
// StructDebugGeneric<E>: Error + 'static,
|
||||
// Self: Debug + Display;
|
||||
//
|
||||
#[derive(Error, Debug)]
|
||||
pub struct StructFromGeneric<E> {
|
||||
#[from]
|
||||
pub source: StructDebugGeneric<E>,
|
||||
}
|
||||
|
||||
// Should expand to:
|
||||
//
|
||||
// impl<E> Display for StructTransparentGeneric<E>
|
||||
// where
|
||||
// E: Display;
|
||||
//
|
||||
// impl<E> Error for StructTransparentGeneric<E>
|
||||
// where
|
||||
// E: Error,
|
||||
// Self: Debug + Display;
|
||||
//
|
||||
#[derive(Error, Debug)]
|
||||
#[error(transparent)]
|
||||
pub struct StructTransparentGeneric<E>(pub E);
|
||||
|
||||
// Should expand to:
|
||||
//
|
||||
// impl<T: FromStr> Display for AssociatedTypeError<T>
|
||||
// where
|
||||
// T::Err: Display;
|
||||
//
|
||||
// impl<T: FromStr> Error for AssociatedTypeError<T>
|
||||
// where
|
||||
// Self: Debug + Display;
|
||||
//
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AssociatedTypeError<T: FromStr> {
|
||||
#[error("couldn't parse matrix")]
|
||||
Other,
|
||||
#[error("couldn't parse entry: {0}")]
|
||||
EntryParseError(T::Err),
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/dtolnay/thiserror/issues/345
|
||||
#[test]
|
||||
fn test_no_bound_on_named_fmt() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{thing}", thing = "...")]
|
||||
struct Error<T> {
|
||||
thing: T,
|
||||
}
|
||||
|
||||
let error = Error { thing: DebugOnly };
|
||||
assert_eq!(error.to_string(), "...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_bound() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("0x{thing:x} 0x{thing:X}")]
|
||||
pub struct Error<T> {
|
||||
thing: T,
|
||||
}
|
||||
|
||||
let error = Error { thing: 0xFFi32 };
|
||||
assert_eq!(error.to_string(), "0xff 0xFF");
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
#![allow(clippy::mixed_attributes_style)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub use std::error::Error;
|
||||
|
||||
#[test]
|
||||
fn test_allow_attributes() {
|
||||
#![deny(clippy::allow_attributes)]
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct MyError(#[from] anyhow::Error);
|
||||
|
||||
let _: MyError;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unused_qualifications() {
|
||||
#![deny(unused_qualifications)]
|
||||
|
||||
// Expansion of derive(Error) macro can't know whether something like
|
||||
// std::error::Error is already imported in the caller's scope so it must
|
||||
// suppress unused_qualifications.
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct MyError;
|
||||
|
||||
let _: MyError;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_needless_lifetimes() {
|
||||
#![allow(dead_code)]
|
||||
#![deny(clippy::elidable_lifetime_names, clippy::needless_lifetimes)]
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub enum MyError<'a> {
|
||||
A(#[from] std::io::Error),
|
||||
B(&'a ()),
|
||||
}
|
||||
|
||||
let _: MyError;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deprecated() {
|
||||
#![deny(deprecated)]
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[deprecated]
|
||||
#[error("...")]
|
||||
pub struct DeprecatedStruct;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{message} {}", .message)]
|
||||
pub struct DeprecatedStructField {
|
||||
#[deprecated]
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[deprecated]
|
||||
pub enum DeprecatedEnum {
|
||||
#[error("...")]
|
||||
Variant,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DeprecatedVariant {
|
||||
#[deprecated]
|
||||
#[error("...")]
|
||||
Variant,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DeprecatedFrom {
|
||||
#[error(transparent)]
|
||||
Variant(
|
||||
#[from]
|
||||
#[allow(deprecated)]
|
||||
DeprecatedStruct,
|
||||
),
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let _: DeprecatedStruct;
|
||||
#[allow(deprecated)]
|
||||
let _: DeprecatedStructField;
|
||||
#[allow(deprecated)]
|
||||
let _ = DeprecatedEnum::Variant;
|
||||
#[allow(deprecated)]
|
||||
let _ = DeprecatedVariant::Variant;
|
||||
#[allow(deprecated)]
|
||||
let _ = DeprecatedFrom::Variant(DeprecatedStruct);
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
#![cfg(feature = "std")]
|
||||
#![cfg_attr(thiserror_nightly_testing, feature(error_generic_member_access))]
|
||||
|
||||
#[cfg(thiserror_nightly_testing)]
|
||||
pub mod structs {
|
||||
use std::backtrace::Backtrace;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct OptSourceNoBacktrace {
|
||||
#[source]
|
||||
pub source: Option<anyhow::Error>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct OptSourceAlwaysBacktrace {
|
||||
#[source]
|
||||
pub source: Option<anyhow::Error>,
|
||||
pub backtrace: Backtrace,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct NoSourceOptBacktrace {
|
||||
#[backtrace]
|
||||
pub backtrace: Option<Backtrace>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct AlwaysSourceOptBacktrace {
|
||||
pub source: anyhow::Error,
|
||||
#[backtrace]
|
||||
pub backtrace: Option<Backtrace>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct OptSourceOptBacktrace {
|
||||
#[source]
|
||||
pub source: Option<anyhow::Error>,
|
||||
#[backtrace]
|
||||
pub backtrace: Option<Backtrace>,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(thiserror_nightly_testing)]
|
||||
pub mod enums {
|
||||
use std::backtrace::Backtrace;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum OptSourceNoBacktrace {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[source]
|
||||
source: Option<anyhow::Error>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum OptSourceAlwaysBacktrace {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[source]
|
||||
source: Option<anyhow::Error>,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum NoSourceOptBacktrace {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[backtrace]
|
||||
backtrace: Option<Backtrace>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AlwaysSourceOptBacktrace {
|
||||
#[error("...")]
|
||||
Test {
|
||||
source: anyhow::Error,
|
||||
#[backtrace]
|
||||
backtrace: Option<Backtrace>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum OptSourceOptBacktrace {
|
||||
#[error("...")]
|
||||
Test {
|
||||
#[source]
|
||||
source: Option<anyhow::Error>,
|
||||
#[backtrace]
|
||||
backtrace: Option<Backtrace>,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
not(thiserror_nightly_testing),
|
||||
ignore = "requires `--cfg=thiserror_nightly_testing`"
|
||||
)]
|
||||
fn test_option() {}
|
||||
@@ -1,54 +0,0 @@
|
||||
#![cfg(feature = "std")]
|
||||
|
||||
use core::fmt::Display;
|
||||
use ref_cast::RefCast;
|
||||
use std::path::{Path, PathBuf};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("failed to read '{file}'")]
|
||||
struct StructPathBuf {
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, RefCast)]
|
||||
#[repr(C)]
|
||||
#[error("failed to read '{file}'")]
|
||||
struct StructPath {
|
||||
file: Path,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum EnumPathBuf {
|
||||
#[error("failed to read '{0}'")]
|
||||
Read(PathBuf),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{tail}")]
|
||||
pub struct UnsizedError {
|
||||
pub head: i32,
|
||||
pub tail: str,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BothError {
|
||||
#[error("display:{0} debug:{0:?}")]
|
||||
DisplayDebug(PathBuf),
|
||||
#[error("debug:{0:?} display:{0}")]
|
||||
DebugDisplay(PathBuf),
|
||||
}
|
||||
|
||||
fn assert<T: Display>(expected: &str, value: T) {
|
||||
assert_eq!(expected, value.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
let path = Path::new("/thiserror");
|
||||
let file = path.to_owned();
|
||||
assert("failed to read '/thiserror'", StructPathBuf { file });
|
||||
let file = path.to_owned();
|
||||
assert("failed to read '/thiserror'", EnumPathBuf::Read(file));
|
||||
assert("failed to read '/thiserror'", StructPath::ref_cast(path));
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
use std::error::Error as StdError;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("implicit source")]
|
||||
pub struct ImplicitSource {
|
||||
source: io::Error,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("explicit source")]
|
||||
pub struct ExplicitSource {
|
||||
source: String,
|
||||
#[source]
|
||||
io: io::Error,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("boxed source")]
|
||||
pub struct BoxedSource {
|
||||
#[source]
|
||||
source: Box<dyn StdError + Send + 'static>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_implicit_source() {
|
||||
let io = io::Error::new(io::ErrorKind::Other, "oh no!");
|
||||
let error = ImplicitSource { source: io };
|
||||
error.source().unwrap().downcast_ref::<io::Error>().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_explicit_source() {
|
||||
let io = io::Error::new(io::ErrorKind::Other, "oh no!");
|
||||
let error = ExplicitSource {
|
||||
source: String::new(),
|
||||
io,
|
||||
};
|
||||
error.source().unwrap().downcast_ref::<io::Error>().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boxed_source() {
|
||||
let source = Box::new(io::Error::new(io::ErrorKind::Other, "oh no!"));
|
||||
let error = BoxedSource { source };
|
||||
error.source().unwrap().downcast_ref::<io::Error>().unwrap();
|
||||
}
|
||||
|
||||
macro_rules! error_from_macro {
|
||||
($($variants:tt)*) => {
|
||||
#[derive(Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum MacroSource {
|
||||
$($variants)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that we generate impls with the proper hygiene
|
||||
#[rustfmt::skip]
|
||||
error_from_macro! {
|
||||
#[error("Something")]
|
||||
Variant(#[from] io::Error)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_source() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{source} ==> {destination}")]
|
||||
pub struct NotSource {
|
||||
r#source: char,
|
||||
destination: char,
|
||||
}
|
||||
|
||||
let error = NotSource {
|
||||
source: 'S',
|
||||
destination: 'D',
|
||||
};
|
||||
assert_eq!(error.to_string(), "S ==> D");
|
||||
assert!(error.source().is_none());
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use std::error::Error as _;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[test]
|
||||
fn test_transparent_struct() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error(transparent)]
|
||||
struct Error(ErrorKind);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum ErrorKind {
|
||||
#[error("E0")]
|
||||
E0,
|
||||
#[error("E1")]
|
||||
E1(#[from] io::Error),
|
||||
}
|
||||
|
||||
let error = Error(ErrorKind::E0);
|
||||
assert_eq!("E0", error.to_string());
|
||||
assert!(error.source().is_none());
|
||||
|
||||
let io = io::Error::new(io::ErrorKind::Other, "oh no!");
|
||||
let error = Error(ErrorKind::from(io));
|
||||
assert_eq!("E1", error.to_string());
|
||||
error.source().unwrap().downcast_ref::<io::Error>().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transparent_enum() {
|
||||
#[derive(Error, Debug)]
|
||||
enum Error {
|
||||
#[error("this failed")]
|
||||
This,
|
||||
#[error(transparent)]
|
||||
Other(anyhow::Error),
|
||||
}
|
||||
|
||||
let error = Error::This;
|
||||
assert_eq!("this failed", error.to_string());
|
||||
|
||||
let error = Error::Other(anyhow!("inner").context("outer"));
|
||||
assert_eq!("outer", error.to_string());
|
||||
assert_eq!("inner", error.source().unwrap().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transparent_enum_with_default_message() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("this failed: {0}_{1}")]
|
||||
enum Error {
|
||||
This(i32, i32),
|
||||
#[error(transparent)]
|
||||
Other(anyhow::Error),
|
||||
}
|
||||
|
||||
let error = Error::This(-1, -1);
|
||||
assert_eq!("this failed: -1_-1", error.to_string());
|
||||
|
||||
let error = Error::Other(anyhow!("inner").context("outer"));
|
||||
assert_eq!("outer", error.to_string());
|
||||
assert_eq!("inner", error.source().unwrap().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anyhow() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error(transparent)]
|
||||
struct Any(#[from] anyhow::Error);
|
||||
|
||||
let error = Any::from(anyhow!("inner").context("outer"));
|
||||
assert_eq!("outer", error.to_string());
|
||||
assert_eq!("inner", error.source().unwrap().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_static() {
|
||||
#[derive(Error, Debug)]
|
||||
#[error(transparent)]
|
||||
struct Error<'a> {
|
||||
inner: ErrorKind<'a>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum ErrorKind<'a> {
|
||||
#[error("unexpected token: {:?}", token)]
|
||||
Unexpected { token: &'a str },
|
||||
}
|
||||
|
||||
let error = Error {
|
||||
inner: ErrorKind::Unexpected { token: "error" },
|
||||
};
|
||||
assert_eq!("unexpected token: \"error\"", error.to_string());
|
||||
assert!(error.source().is_none());
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error(transparent)]
|
||||
pub struct Error(#[error(transparent)] std::io::Error);
|
||||
|
||||
fn main() {}
|
||||
@@ -1,5 +0,0 @@
|
||||
error: #[error(transparent)] needs to go outside the enum or struct, not on an individual field
|
||||
--> tests/ui/bad-field-attr.rs:5:18
|
||||
|
|
||||
5 | pub struct Error(#[error(transparent)] std::io::Error);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -1,15 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
macro_rules! error_type {
|
||||
($name:ident, $what:expr) => {
|
||||
// Use #[error("invalid {}", $what)] instead.
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error(concat!("invalid ", $what))]
|
||||
pub struct $name;
|
||||
};
|
||||
}
|
||||
|
||||
error_type!(Error, "foo");
|
||||
|
||||
fn main() {}
|
||||
@@ -1,10 +0,0 @@
|
||||
error: expected one of: string literal, `transparent`, `fmt`
|
||||
--> tests/ui/concat-display.rs:8:17
|
||||
|
|
||||
8 | #[error(concat!("invalid ", $what))]
|
||||
| ^^^^^^
|
||||
...
|
||||
13 | error_type!(Error, "foo");
|
||||
| ------------------------- in this macro invocation
|
||||
|
|
||||
= note: this error originates in the macro `error_type` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
@@ -1,7 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{_}")]
|
||||
pub struct Error;
|
||||
|
||||
fn main() {}
|
||||
@@ -1,7 +0,0 @@
|
||||
error: invalid format string: invalid argument name `_`
|
||||
--> tests/ui/display-underscore.rs:4:11
|
||||
|
|
||||
4 | #[error("{_}")]
|
||||
| ^ invalid argument name in format string
|
||||
|
|
||||
= note: argument name cannot be a single underscore
|
||||
@@ -1,13 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ErrorEnum {
|
||||
Confusing {
|
||||
#[source]
|
||||
a: std::io::Error,
|
||||
#[source]
|
||||
b: anyhow::Error,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,5 +0,0 @@
|
||||
error: duplicate #[source] attribute
|
||||
--> tests/ui/duplicate-enum-source.rs:8:9
|
||||
|
|
||||
8 | #[source]
|
||||
| ^^^^^^^^^
|
||||
@@ -1,23 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
#[error("...")]
|
||||
pub struct Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error(fmt = core::fmt::Octal::fmt)]
|
||||
#[error(fmt = core::fmt::LowerHex::fmt)]
|
||||
pub enum FmtFmt {}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error(fmt = core::fmt::Octal::fmt)]
|
||||
#[error(transparent)]
|
||||
pub enum FmtTransparent {}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error(fmt = core::fmt::Octal::fmt)]
|
||||
#[error("...")]
|
||||
pub enum FmtDisplay {}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,23 +0,0 @@
|
||||
error: only one #[error(...)] attribute is allowed
|
||||
--> tests/ui/duplicate-fmt.rs:5:1
|
||||
|
|
||||
5 | #[error("...")]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
||||
error: duplicate #[error(fmt = ...)] attribute
|
||||
--> tests/ui/duplicate-fmt.rs:10:1
|
||||
|
|
||||
10 | #[error(fmt = core::fmt::LowerHex::fmt)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: cannot have both #[error(transparent)] and #[error(fmt = ...)]
|
||||
--> tests/ui/duplicate-fmt.rs:14:1
|
||||
|
|
||||
14 | #[error(fmt = core::fmt::Octal::fmt)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: cannot have both #[error(fmt = ...)] and a format arguments attribute
|
||||
--> tests/ui/duplicate-fmt.rs:20:1
|
||||
|
|
||||
20 | #[error("...")]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
@@ -1,11 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub struct ErrorStruct {
|
||||
#[source]
|
||||
a: std::io::Error,
|
||||
#[source]
|
||||
b: anyhow::Error,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,5 +0,0 @@
|
||||
error: duplicate #[source] attribute
|
||||
--> tests/ui/duplicate-struct-source.rs:7:5
|
||||
|
|
||||
7 | #[source]
|
||||
| ^^^^^^^^^
|
||||
@@ -1,8 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error(transparent)]
|
||||
#[error(transparent)]
|
||||
pub struct Error(anyhow::Error);
|
||||
|
||||
fn main() {}
|
||||
@@ -1,5 +0,0 @@
|
||||
error: duplicate #[error(transparent)] attribute
|
||||
--> tests/ui/duplicate-transparent.rs:5:1
|
||||
|
|
||||
5 | #[error(transparent)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -1,7 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("".yellow)]
|
||||
pub struct ArgError;
|
||||
|
||||
fn main() {}
|
||||
@@ -1,24 +0,0 @@
|
||||
error: expected `,`, found `.`
|
||||
--> tests/ui/expression-fallback.rs:4:11
|
||||
|
|
||||
4 | #[error("".yellow)]
|
||||
| ^ expected `,`
|
||||
|
||||
error: argument never used
|
||||
--> tests/ui/expression-fallback.rs:4:12
|
||||
|
|
||||
4 | #[error("".yellow)]
|
||||
| -- ^^^^^^ argument never used
|
||||
| |
|
||||
| formatting specifier missing
|
||||
|
|
||||
help: format specifiers use curly braces, consider adding a format specifier
|
||||
|
|
||||
4 | #[error("{}".yellow)]
|
||||
| ++
|
||||
|
||||
error[E0425]: cannot find value `yellow` in this scope
|
||||
--> tests/ui/expression-fallback.rs:4:12
|
||||
|
|
||||
4 | #[error("".yellow)]
|
||||
| ^^^^^^ not found in this scope
|
||||
@@ -1,14 +0,0 @@
|
||||
use core::fmt::{self, Display};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error]
|
||||
pub struct MyError;
|
||||
|
||||
impl Display for MyError {
|
||||
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,16 +0,0 @@
|
||||
error: expected attribute arguments in parentheses: #[error(...)]
|
||||
--> tests/ui/fallback-impl-with-display.rs:5:3
|
||||
|
|
||||
5 | #[error]
|
||||
| ^^^^^
|
||||
|
||||
error[E0119]: conflicting implementations of trait `std::fmt::Display` for type `MyError`
|
||||
--> tests/ui/fallback-impl-with-display.rs:4:10
|
||||
|
|
||||
4 | #[derive(Error, Debug)]
|
||||
| ^^^^^ conflicting implementation for `MyError`
|
||||
...
|
||||
8 | impl Display for MyError {
|
||||
| ------------------------ first implementation here
|
||||
|
|
||||
= note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
@@ -1,15 +0,0 @@
|
||||
// https://github.com/dtolnay/thiserror/issues/163
|
||||
|
||||
use std::backtrace::Backtrace;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct Error(
|
||||
#[from]
|
||||
#[backtrace]
|
||||
std::io::Error,
|
||||
Backtrace,
|
||||
);
|
||||
|
||||
fn main() {}
|
||||
@@ -1,5 +0,0 @@
|
||||
error: deriving From requires no fields other than source and backtrace
|
||||
--> tests/ui/from-backtrace-backtrace.rs:9:5
|
||||
|
|
||||
9 | #[from]
|
||||
| ^^^^^^^
|
||||
@@ -1,11 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub struct Error {
|
||||
#[source]
|
||||
source: std::io::Error,
|
||||
#[from]
|
||||
other: anyhow::Error,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,5 +0,0 @@
|
||||
error: #[from] is only supported on the source field, not any other field
|
||||
--> tests/ui/from-not-source.rs:7:5
|
||||
|
|
||||
7 | #[from]
|
||||
| ^^^^^^^
|
||||
@@ -1,11 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error]
|
||||
pub struct MyError;
|
||||
|
||||
fn main() {
|
||||
// No error on the following line. Thiserror emits an Error impl despite the
|
||||
// bad attribute.
|
||||
_ = &MyError as &dyn std::error::Error;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
error: expected attribute arguments in parentheses: #[error(...)]
|
||||
--> tests/ui/invalid-input-impl-anyway.rs:4:3
|
||||
|
|
||||
4 | #[error]
|
||||
| ^^^^^
|
||||
@@ -1,24 +0,0 @@
|
||||
use core::fmt::Debug;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("error")]
|
||||
struct Error<'a>(#[from] Inner<'a>);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{0}")]
|
||||
struct Inner<'a>(&'a str);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum Enum<'a> {
|
||||
#[error("error")]
|
||||
Foo(#[from] Generic<&'a str>),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{0:?}")]
|
||||
struct Generic<T: Debug>(T);
|
||||
|
||||
fn main() -> Result<(), Error<'static>> {
|
||||
Err(Error(Inner("some text")))
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
error: non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static
|
||||
--> tests/ui/lifetime.rs:6:26
|
||||
|
|
||||
6 | struct Error<'a>(#[from] Inner<'a>);
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static
|
||||
--> tests/ui/lifetime.rs:15:17
|
||||
|
|
||||
15 | Foo(#[from] Generic<&'a str>),
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
@@ -1,9 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MyError {
|
||||
First,
|
||||
Second,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,19 +0,0 @@
|
||||
error[E0277]: `MyError` doesn't implement `std::fmt::Display`
|
||||
--> tests/ui/missing-display.rs:4:10
|
||||
|
|
||||
3 | #[derive(Error, Debug)]
|
||||
| ----- in this derive macro expansion
|
||||
4 | pub enum MyError {
|
||||
| ^^^^^^^ unsatisfied trait bound
|
||||
|
|
||||
help: the trait `std::fmt::Display` is not implemented for `MyError`
|
||||
--> tests/ui/missing-display.rs:4:1
|
||||
|
|
||||
4 | pub enum MyError {
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
note: required by a bound in `std::error::Error`
|
||||
--> $RUST/core/src/error.rs
|
||||
|
|
||||
| pub trait Error: Debug + Display {
|
||||
| ^^^^^^^ required by this bound in `Error`
|
||||
= note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
@@ -1,10 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("...")]
|
||||
A(usize),
|
||||
B(usize),
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,5 +0,0 @@
|
||||
error: missing #[error("...")] display attribute
|
||||
--> tests/ui/missing-fmt.rs:7:5
|
||||
|
|
||||
7 | B(usize),
|
||||
| ^^^^^^^^
|
||||
@@ -1,18 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NoDisplay;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("thread: {thread}")]
|
||||
pub struct Error {
|
||||
thread: NoDisplay,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("thread: {thread:o}")]
|
||||
pub struct ErrorOctal {
|
||||
thread: NoDisplay,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,46 +0,0 @@
|
||||
error[E0599]: the method `as_display` exists for reference `&NoDisplay`, but its trait bounds were not satisfied
|
||||
--> tests/ui/no-display.rs:7:9
|
||||
|
|
||||
4 | struct NoDisplay;
|
||||
| ---------------- doesn't satisfy `NoDisplay: std::fmt::Display`
|
||||
...
|
||||
7 | #[error("thread: {thread}")]
|
||||
| ^^^^^^^^^^^^^^^^^^ method cannot be called on `&NoDisplay` due to unsatisfied trait bounds
|
||||
|
|
||||
= note: the following trait bounds were not satisfied:
|
||||
`NoDisplay: std::fmt::Display`
|
||||
which is required by `&NoDisplay: AsDisplay<'_>`
|
||||
note: the trait `std::fmt::Display` must be implemented
|
||||
--> $RUST/core/src/fmt/mod.rs
|
||||
|
|
||||
| pub trait Display: PointeeSized {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `as_display`, perhaps you need to implement it:
|
||||
candidate #1: `AsDisplay`
|
||||
|
||||
error[E0277]: the trait bound `NoDisplay: Octal` is not satisfied
|
||||
--> tests/ui/no-display.rs:13:9
|
||||
|
|
||||
12 | #[derive(Error, Debug)]
|
||||
| ----- in this derive macro expansion
|
||||
13 | #[error("thread: {thread:o}")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound
|
||||
|
|
||||
help: the trait `Octal` is not implemented for `NoDisplay`
|
||||
--> tests/ui/no-display.rs:4:1
|
||||
|
|
||||
4 | struct NoDisplay;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
= help: the following other types implement trait `Octal`:
|
||||
&T
|
||||
&mut T
|
||||
NonZero<T>
|
||||
Saturating<T>
|
||||
Wrapping<T>
|
||||
i128
|
||||
i16
|
||||
i32
|
||||
and $N others
|
||||
= note: required for `&NoDisplay` to implement `Octal`
|
||||
= 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)
|
||||
@@ -1,7 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("invalid rdo_lookahead_frames {0} (expected < {})", i32::MAX)]
|
||||
pub struct Error(u32);
|
||||
|
||||
fn main() {}
|
||||
@@ -1,5 +0,0 @@
|
||||
error: ambiguous reference to positional arguments by number in a tuple struct; change this to a named argument
|
||||
--> tests/ui/numbered-positional-tuple.rs:4:61
|
||||
|
|
||||
4 | #[error("invalid rdo_lookahead_frames {0} (expected < {})", i32::MAX)]
|
||||
| ^^^^^^^^
|
||||
@@ -1,12 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("error: {r#fn}")]
|
||||
pub struct Error {
|
||||
r#fn: &'static str,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let r#fn = "...";
|
||||
let _ = format!("error: {r#fn}");
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
error: invalid format string: raw identifiers are not supported
|
||||
--> tests/ui/raw-identifier.rs:4:18
|
||||
|
|
||||
4 | #[error("error: {r#fn}")]
|
||||
| --^^
|
||||
| |
|
||||
| raw identifier used here in format string
|
||||
| help: remove the `r#`
|
||||
|
|
||||
= note: identifiers in format strings can be keywords and don't need to be prefixed with `r#`
|
||||
|
||||
error: invalid format string: raw identifiers are not supported
|
||||
--> tests/ui/raw-identifier.rs:11:30
|
||||
|
|
||||
11 | let _ = format!("error: {r#fn}");
|
||||
| --^^
|
||||
| |
|
||||
| raw identifier used here in format string
|
||||
| help: remove the `r#`
|
||||
|
|
||||
= note: identifiers in format strings can be keywords and don't need to be prefixed with `r#`
|
||||
@@ -1,11 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("failed to open")]
|
||||
OpenFile(#[from] std::io::Error),
|
||||
#[error("failed to close")]
|
||||
CloseFile(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,8 +0,0 @@
|
||||
error[E0119]: conflicting implementations of trait `From<std::io::Error>` for type `Error`
|
||||
--> tests/ui/same-from-type.rs:8:15
|
||||
|
|
||||
6 | OpenFile(#[from] std::io::Error),
|
||||
| ------- first implementation here
|
||||
7 | #[error("failed to close")]
|
||||
8 | CloseFile(#[from] std::io::Error),
|
||||
| ^^^^^^^ conflicting implementation for `Error`
|
||||
@@ -1,12 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NotError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub enum ErrorEnum {
|
||||
Broken { source: NotError },
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,22 +0,0 @@
|
||||
error[E0599]: the method `as_dyn_error` exists for reference `&NotError`, but its trait bounds were not satisfied
|
||||
--> tests/ui/source-enum-not-error.rs:9:14
|
||||
|
|
||||
4 | pub struct NotError;
|
||||
| ------------------- doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error`
|
||||
...
|
||||
9 | Broken { source: NotError },
|
||||
| ^^^^^^ method cannot be called on `&NotError` due to unsatisfied trait bounds
|
||||
|
|
||||
= note: the following trait bounds were not satisfied:
|
||||
`NotError: std::error::Error`
|
||||
which is required by `NotError: AsDynError<'_>`
|
||||
`&NotError: std::error::Error`
|
||||
which is required by `&NotError: AsDynError<'_>`
|
||||
note: the trait `std::error::Error` must be implemented
|
||||
--> $RUST/core/src/error.rs
|
||||
|
|
||||
| pub trait Error: Debug + Display {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it:
|
||||
candidate #1: `AsDynError`
|
||||
@@ -1,12 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NotError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub enum ErrorEnum {
|
||||
Broken(#[source] NotError),
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,22 +0,0 @@
|
||||
error[E0599]: the method `as_dyn_error` exists for reference `&NotError`, but its trait bounds were not satisfied
|
||||
--> tests/ui/source-enum-unnamed-field-not-error.rs:9:12
|
||||
|
|
||||
4 | pub struct NotError;
|
||||
| ------------------- doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error`
|
||||
...
|
||||
9 | Broken(#[source] NotError),
|
||||
| ^^^^^^^^^ method cannot be called on `&NotError` due to unsatisfied trait bounds
|
||||
|
|
||||
= note: the following trait bounds were not satisfied:
|
||||
`NotError: std::error::Error`
|
||||
which is required by `NotError: AsDynError<'_>`
|
||||
`&NotError: std::error::Error`
|
||||
which is required by `&NotError: AsDynError<'_>`
|
||||
note: the trait `std::error::Error` must be implemented
|
||||
--> $RUST/core/src/error.rs
|
||||
|
|
||||
| pub trait Error: Debug + Display {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it:
|
||||
candidate #1: `AsDynError`
|
||||
@@ -1,12 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NotError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct ErrorStruct {
|
||||
source: NotError,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,20 +0,0 @@
|
||||
error[E0599]: the method `as_dyn_error` exists for struct `NotError`, but its trait bounds were not satisfied
|
||||
--> tests/ui/source-struct-not-error.rs:9:5
|
||||
|
|
||||
4 | struct NotError;
|
||||
| --------------- method `as_dyn_error` not found for this struct because it doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error`
|
||||
...
|
||||
9 | source: NotError,
|
||||
| ^^^^^^ method cannot be called on `NotError` due to unsatisfied trait bounds
|
||||
|
|
||||
= note: the following trait bounds were not satisfied:
|
||||
`NotError: std::error::Error`
|
||||
which is required by `NotError: AsDynError<'_>`
|
||||
note: the trait `std::error::Error` must be implemented
|
||||
--> $RUST/core/src/error.rs
|
||||
|
|
||||
| pub trait Error: Debug + Display {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it:
|
||||
candidate #1: `AsDynError`
|
||||
@@ -1,10 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NotError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("...")]
|
||||
pub struct ErrorStruct(#[source] NotError);
|
||||
|
||||
fn main() {}
|
||||
@@ -1,20 +0,0 @@
|
||||
error[E0599]: the method `as_dyn_error` exists for struct `NotError`, but its trait bounds were not satisfied
|
||||
--> tests/ui/source-struct-unnamed-field-not-error.rs:8:24
|
||||
|
|
||||
4 | struct NotError;
|
||||
| --------------- method `as_dyn_error` not found for this struct because it doesn't satisfy `NotError: AsDynError<'_>` or `NotError: std::error::Error`
|
||||
...
|
||||
8 | pub struct ErrorStruct(#[source] NotError);
|
||||
| ^^^^^^^^^ method cannot be called on `NotError` due to unsatisfied trait bounds
|
||||
|
|
||||
= note: the following trait bounds were not satisfied:
|
||||
`NotError: std::error::Error`
|
||||
which is required by `NotError: AsDynError<'_>`
|
||||
note: the trait `std::error::Error` must be implemented
|
||||
--> $RUST/core/src/error.rs
|
||||
|
|
||||
| pub trait Error: Debug + Display {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `as_dyn_error`, perhaps you need to implement it:
|
||||
candidate #1: `AsDynError`
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user