Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core::error, no_std #304

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6085f29
support no_std with error-in-core
jordens May 16, 2024
c560bcc
exlude compiletest from no_std, fix no_std doctest
jordens May 16, 2024
ec51e63
undo Error->MyError renames
jordens May 16, 2024
f7f6c77
use __private namespace
jordens May 16, 2024
f1d9df3
add comment
jordens May 16, 2024
33b7a36
Merge branch 'master' into no-std
jordens May 17, 2024
3663fd1
use explicit doc(test(attr))
jordens May 22, 2024
61b1c17
error_in_core is stable
jordens Jun 14, 2024
3e792e0
{std -> core}:error: update docs/comment
jordens Jun 14, 2024
b278258
docs.rs: std is default
jordens Jun 14, 2024
8cbdc4e
bump MSRV to 1.81
jordens Jun 14, 2024
cc00ef8
Merge remote-tracking branch 'upstream/master' into no-std
jordens Sep 3, 2024
7fa183b
Revert "bump MSRV to 1.81"
jordens Sep 6, 2024
28ce40b
Revert "{std -> core}:error: update docs/comment"
jordens Sep 6, 2024
0b058bc
Revert "error_in_core is stable"
jordens Sep 6, 2024
e7e9341
minimalize changes, maintain MSRV for `std`
jordens Sep 6, 2024
bfd7357
test 1.81 and no_std
jordens Sep 6, 2024
706fb6a
ci no_std: not check but test, 1.81 not necessary
jordens Sep 6, 2024
a3d073b
compiletest: allow unused_attributes
jordens Sep 6, 2024
89008c0
prefer required-features
jordens Sep 9, 2024
c9cd3b9
compiletest doesn't need std
jordens Sep 9, 2024
0ef9d1b
impl hygiene: fully qualify all paths
jordens Sep 11, 2024
7182e3f
test_no_std: add strictly no_std test
jordens Sep 16, 2024
515bd36
test_{transparent,_lints}: also work with no-std lib
jordens Sep 18, 2024
2614b53
test_no_std: reduce
jordens Sep 18, 2024
44737a5
test_no_std: can't compile on old rust
jordens Sep 18, 2024
e779e1b
Merge remote-tracking branch 'upstream/master' into no-std
jordens Sep 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
- name: Enable nightly-only tests
run: echo RUSTFLAGS=${RUSTFLAGS}\ --cfg=thiserror_nightly_testing >> $GITHUB_ENV
if: matrix.rust == 'nightly'
- name: Test no-default-features
run: cargo test --all --no-default-features
jordens marked this conversation as resolved.
Show resolved Hide resolved
if: matrix.rust != '1.56.0'
- run: cargo test --all
- uses: actions/upload-artifact@v4
if: matrix.rust == 'nightly' && always()
Expand Down
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ license = "MIT OR Apache-2.0"
repository = "https://github.com/dtolnay/thiserror"
rust-version = "1.56"

# without the `std` feature (i.e. `no_std`): rust-version = "1.81"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in this comment, I feel we should attempt to widen our build compatibility to not break rustc < 1.81 builds importing thiserror with default-features = false.


[dependencies]
thiserror-impl = { version = "=1.0.64", path = "impl" }

Expand All @@ -26,3 +28,11 @@ members = ["impl"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
rustdoc-args = ["--generate-link-to-definition"]

[features]
std = []
default = ["std"]

[[test]]
name = "test_path"
required-features = ["std"]
60 changes: 31 additions & 29 deletions impl/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn fallback(input: &DeriveInput, error: syn::Error) -> TokenStream {

#[allow(unused_qualifications)]
#[automatically_derived]
impl #impl_generics std::error::Error for #ty #ty_generics #where_clause
impl #impl_generics ::thiserror::__private::error::Error for #ty #ty_generics #where_clause
Copy link

@dhedey dhedey Oct 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of interest, why did we choose this rather than hardcoding ::std or ::core, in the compiled output (via e.g. an #error_crate variable)?

My slight concern is that use of ::thiserror::__private may leak this __private namespace out and could confuse people debugging this. But using ::std / ::core directly it's more obvious what's failing.

where
// Work around trivial bounds being unstable.
// https://github.com/rust-lang/rust/issues/48214
Expand All @@ -62,17 +62,19 @@ fn impl_struct(input: Struct) -> TokenStream {
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!(std::error::Error));
error_inferred_bounds
.insert(only_field.ty, quote!(::thiserror::__private::error::Error));
}
let member = &only_field.member;
Some(quote_spanned! {transparent_attr.span=>
std::error::Error::source(self.#member.as_dyn_error())
::thiserror::__private::error::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!(std::error::Error + 'static));
error_inferred_bounds
.insert(ty, quote!(::thiserror::__private::error::Error + 'static));
}
let asref = if type_is_option(source_field.ty) {
Some(quote_spanned!(source.member_span()=> .as_ref()?))
Expand All @@ -90,8 +92,8 @@ fn impl_struct(input: Struct) -> TokenStream {
};
let source_method = source_body.map(|body| {
quote! {
fn source(&self) -> ::core::option::Option<&(dyn std::error::Error + 'static)> {
use thiserror::__private::AsDynError as _;
fn source(&self) -> ::core::option::Option<&(dyn ::thiserror::__private::error::Error + 'static)> {
use ::thiserror::__private::AsDynError as _;
#body
}
}
Expand All @@ -118,32 +120,32 @@ fn impl_struct(input: Struct) -> TokenStream {
} else if type_is_option(backtrace_field.ty) {
Some(quote! {
if let ::core::option::Option::Some(backtrace) = &self.#backtrace {
#request.provide_ref::<std::backtrace::Backtrace>(backtrace);
#request.provide_ref::<::std::backtrace::Backtrace>(backtrace);
}
})
} else {
Some(quote! {
#request.provide_ref::<std::backtrace::Backtrace>(&self.#backtrace);
#request.provide_ref::<::std::backtrace::Backtrace>(&self.#backtrace);
})
};
quote! {
use thiserror::__private::ThiserrorProvide as _;
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::<std::backtrace::Backtrace>(backtrace);
#request.provide_ref::<::std::backtrace::Backtrace>(backtrace);
}
}
} else {
quote! {
#request.provide_ref::<std::backtrace::Backtrace>(&self.#backtrace);
#request.provide_ref::<::std::backtrace::Backtrace>(&self.#backtrace);
}
};
quote! {
fn provide<'_request>(&'_request self, #request: &mut std::error::Request<'_request>) {
fn provide<'_request>(&'_request self, #request: &mut ::thiserror::__private::error::Request<'_request>) {
#body
}
}
Expand Down Expand Up @@ -216,7 +218,7 @@ fn impl_struct(input: Struct) -> TokenStream {
quote! {
#[allow(unused_qualifications)]
#[automatically_derived]
impl #impl_generics std::error::Error for #ty #ty_generics #error_where_clause {
impl #impl_generics ::thiserror::__private::error::Error for #ty #ty_generics #error_where_clause {
#source_method
#provide_method
}
Expand All @@ -236,11 +238,11 @@ fn impl_enum(input: Enum) -> TokenStream {
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!(std::error::Error));
error_inferred_bounds.insert(only_field.ty, quote!(::thiserror::__private::error::Error));
}
let member = &only_field.member;
let source = quote_spanned! {transparent_attr.span=>
std::error::Error::source(transparent.as_dyn_error())
::thiserror::__private::error::Error::source(transparent.as_dyn_error())
};
quote! {
#ty::#ident {#member: transparent} => #source,
Expand All @@ -249,7 +251,7 @@ fn impl_enum(input: Enum) -> TokenStream {
let source = &source_field.member;
if source_field.contains_generic {
let ty = unoptional_type(source_field.ty);
error_inferred_bounds.insert(ty, quote!(std::error::Error + 'static));
error_inferred_bounds.insert(ty, quote!(::thiserror::__private::error::Error + 'static));
}
let asref = if type_is_option(source_field.ty) {
Some(quote_spanned!(source.member_span()=> .as_ref()?))
Expand All @@ -270,8 +272,8 @@ fn impl_enum(input: Enum) -> TokenStream {
}
});
Some(quote! {
fn source(&self) -> ::core::option::Option<&(dyn std::error::Error + 'static)> {
use thiserror::__private::AsDynError as _;
fn source(&self) -> ::core::option::Option<&(dyn ::thiserror::__private::error::Error + 'static)> {
use ::thiserror::__private::AsDynError as _;
#[allow(deprecated)]
match self {
#(#arms)*
Expand Down Expand Up @@ -307,12 +309,12 @@ fn impl_enum(input: Enum) -> TokenStream {
let self_provide = if type_is_option(backtrace_field.ty) {
quote! {
if let ::core::option::Option::Some(backtrace) = backtrace {
#request.provide_ref::<std::backtrace::Backtrace>(backtrace);
#request.provide_ref::<::std::backtrace::Backtrace>(backtrace);
Copy link

@dhedey dhedey Oct 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR: It may be nice if we gave a better compiler error if a user tried to specify a backtrace when we're not in std mode, which could advise the user to enable the std feature to use this? I believe the right place for this would be in valid.rs.

... that said, it might actually cause more breakage if people are using default-features = false mode and after upgrading they get a compilation error from this "help" because they are actually running in std mode, just importing this crate incorrectly. So I think I've talked myself out of this.

So maybe safer to leave this as-is, and perhaps document that this only works if compiling with std available?

}
}
} else {
quote! {
#request.provide_ref::<std::backtrace::Backtrace>(backtrace);
#request.provide_ref::<::std::backtrace::Backtrace>(backtrace);
}
};
quote! {
Expand All @@ -321,7 +323,7 @@ fn impl_enum(input: Enum) -> TokenStream {
#source: #varsource,
..
} => {
use thiserror::__private::ThiserrorProvide as _;
use ::thiserror::__private::ThiserrorProvide as _;
#source_provide
#self_provide
}
Expand All @@ -345,7 +347,7 @@ fn impl_enum(input: Enum) -> TokenStream {
};
quote! {
#ty::#ident {#backtrace: #varsource, ..} => {
use thiserror::__private::ThiserrorProvide as _;
use ::thiserror::__private::ThiserrorProvide as _;
#source_provide
}
}
Expand All @@ -355,12 +357,12 @@ fn impl_enum(input: Enum) -> TokenStream {
let body = if type_is_option(backtrace_field.ty) {
quote! {
if let ::core::option::Option::Some(backtrace) = backtrace {
#request.provide_ref::<std::backtrace::Backtrace>(backtrace);
#request.provide_ref::<::std::backtrace::Backtrace>(backtrace);
}
}
} else {
quote! {
#request.provide_ref::<std::backtrace::Backtrace>(backtrace);
#request.provide_ref::<::std::backtrace::Backtrace>(backtrace);
}
};
quote! {
Expand All @@ -375,7 +377,7 @@ fn impl_enum(input: Enum) -> TokenStream {
}
});
Some(quote! {
fn provide<'_request>(&'_request self, #request: &mut std::error::Request<'_request>) {
fn provide<'_request>(&'_request self, #request: &mut ::thiserror::__private::error::Request<'_request>) {
#[allow(deprecated)]
match self {
#(#arms)*
Expand Down Expand Up @@ -475,7 +477,7 @@ fn impl_enum(input: Enum) -> TokenStream {
quote! {
#[allow(unused_qualifications)]
#[automatically_derived]
impl #impl_generics std::error::Error for #ty #ty_generics #error_where_clause {
impl #impl_generics ::thiserror::__private::error::Error for #ty #ty_generics #error_where_clause {
#source_method
#provide_method
}
Expand All @@ -502,7 +504,7 @@ fn fields_pat(fields: &[Field]) -> TokenStream {
fn use_as_display(needs_as_display: bool) -> Option<TokenStream> {
if needs_as_display {
Some(quote! {
use thiserror::__private::AsDisplay as _;
use ::thiserror::__private::AsDisplay as _;
})
} else {
None
Expand All @@ -520,11 +522,11 @@ fn from_initializer(from_field: &Field, backtrace_field: Option<&Field>) -> Toke
let backtrace_member = &backtrace_field.member;
if type_is_option(backtrace_field.ty) {
quote! {
#backtrace_member: ::core::option::Option::Some(std::backtrace::Backtrace::capture()),
#backtrace_member: ::core::option::Option::Some(::std::backtrace::Backtrace::capture()),
}
} else {
quote! {
#backtrace_member: ::core::convert::From::from(std::backtrace::Backtrace::capture()),
#backtrace_member: ::core::convert::From::from(::std::backtrace::Backtrace::capture()),
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion src/aserror.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::__private::error::Error;
use core::panic::UnwindSafe;
use std::error::Error;

#[doc(hidden)]
pub trait AsDynError<'a>: Sealed {
Expand Down
3 changes: 3 additions & 0 deletions src/display.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use core::fmt::Display;
#[cfg(feature = "std")]
use std::path::{self, Path, PathBuf};

#[doc(hidden)]
Expand All @@ -21,6 +22,7 @@ where
}
}

#[cfg(feature = "std")]
impl<'a> AsDisplay<'a> for Path {
type Target = path::Display<'a>;

Expand All @@ -30,6 +32,7 @@ impl<'a> AsDisplay<'a> for Path {
}
}

#[cfg(feature = "std")]
impl<'a> AsDisplay<'a> for PathBuf {
type Target = path::Display<'a>;

Expand Down
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@
clippy::wildcard_imports
)]
#![cfg_attr(error_generic_member_access, feature(error_generic_member_access))]
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(all(thiserror_nightly_testing, not(error_generic_member_access)))]
compile_error!("Build script probe failed to compile.");
Expand All @@ -287,4 +288,10 @@ pub mod __private {
#[cfg(error_generic_member_access)]
#[doc(hidden)]
pub use crate::provide::ThiserrorProvide;
#[cfg(not(feature = "std"))]
#[doc(hidden)]
pub use core::error;
#[cfg(feature = "std")]
#[doc(hidden)]
pub use std::error;
}
2 changes: 1 addition & 1 deletion src/provide.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::error::{Error, Request};
use crate::__private::error::{Error, Request};

#[doc(hidden)]
pub trait ThiserrorProvide: Sealed {
Expand Down
23 changes: 23 additions & 0 deletions tests/test_no_std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#![no_std]
use thiserror::Error;

#[derive(Error, Debug)]
#[error("io")]
pub struct IoError;

#[derive(Error, Debug)]
pub enum MyError {
#[error("A")]
A,
#[error("B {0}")]
B(#[from] IoError),
}

#[test]
#[cfg(not(feature = "std"))]
fn test_no_std() {
use core::error::Error as _;

let error = MyError::from(IoError);
error.source().unwrap().downcast_ref::<IoError>().unwrap();
}