Skip to content

Commit

Permalink
Support Error in no_std environments
Browse files Browse the repository at this point in the history
The `Error` derive can be made to work well for the most part in
`no_std` environments by enabling `#![feature(error_in_core)]`. This
changes the `Error` derive slightly to import `Error` and related
traits from core, when the `std` feature is disabled.

Fixes #261
  • Loading branch information
JelteF committed Jul 1, 2023
1 parent 37cae22 commit c0bc2fd
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 22 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
should prevent code style linters from attempting to modify the generated
code.
- Upgrade to `syn` 2.0.
- The `Error` derive now works in nightly `no_std` environments when enabling
`#![feature(error_in_core)]`.

### Fixed

Expand Down
11 changes: 7 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ members = ["impl"]
[dependencies]
derive_more-impl = { version = "=0.99.17", path = "impl" }

[build-dependencies]
rustc_version = { version = "0.4", optional = true }

[dev-dependencies]
rustversion = "1.0"
trybuild = "1.0.56"
Expand All @@ -50,7 +53,7 @@ debug = ["derive_more-impl/debug"]
deref = ["derive_more-impl/deref"]
deref_mut = ["derive_more-impl/deref_mut"]
display = ["derive_more-impl/display"]
error = ["derive_more-impl/error", "std"]
error = ["derive_more-impl/error"]
from = ["derive_more-impl/from"]
from_str = ["derive_more-impl/from_str"]
index = ["derive_more-impl/index"]
Expand All @@ -67,7 +70,7 @@ is_variant = ["derive_more-impl/is_variant"]
unwrap = ["derive_more-impl/unwrap"]
try_unwrap = ["derive_more-impl/try_unwrap"]

std = []
std = ["derive_more-impl/std"]
full = [
"add_assign",
"add",
Expand Down Expand Up @@ -96,7 +99,7 @@ full = [
"try_unwrap",
]

testing-helpers = ["derive_more-impl/testing-helpers"]
testing-helpers = ["derive_more-impl/testing-helpers", "dep:rustc_version"]

[[test]]
name = "add_assign"
Expand Down Expand Up @@ -151,7 +154,7 @@ required-features = ["display"]
[[test]]
name = "error"
path = "tests/error_tests.rs"
required-features = ["error", "std"]
required-features = ["error"]

[[test]]
name = "from"
Expand Down
15 changes: 15 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#[cfg(not(feature = "testing-helpers"))]
fn detect_nightly() {}

#[cfg(feature = "testing-helpers")]
fn detect_nightly() {
use rustc_version::{version_meta, Channel};

if version_meta().unwrap().channel == Channel::Nightly {
println!("cargo:rustc-cfg=nightly");
}
}

fn main() {
detect_nightly();
}
2 changes: 2 additions & 0 deletions impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,6 @@ is_variant = ["dep:convert_case"]
unwrap = ["dep:convert_case"]
try_unwrap = ["dep:convert_case"]

std = []

testing-helpers = ["dep:rustc_version"]
12 changes: 11 additions & 1 deletion impl/doc/error.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,23 @@ ignored for one of these methods by using `#[error(not(backtrace))]` or
`#[error(not(source))]`.


### What works in `no_std`?

If you want to use the `Error` derive on `no_std` environments, then you need to
compile with nightly and enable this feature:
```rust
#![feature(error_in_core)]
```

Backtraces don't work though, because the `Backtrace` type is only available in
`std`.


## Example usage

```rust
# #![cfg_attr(nightly, feature(error_generic_member_access, provide_any))]
// Nightly requires enabling this features:
// Nightly requires enabling these features:
// #![feature(error_generic_member_access, provide_any)]
# #[cfg(not(nightly))] fn main() {}
# #[cfg(nightly)] fn main() {
Expand Down
38 changes: 26 additions & 12 deletions impl/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ use crate::utils::{
State,
};

#[cfg(feature = "std")]
fn error_module() -> TokenStream {
quote! { ::std::error }
}

#[cfg(not(feature = "std"))]
fn error_module() -> TokenStream {
quote! { ::core::error }
}

pub fn expand(
input: &syn::DeriveInput,
trait_name: &'static str,
Expand All @@ -15,10 +25,12 @@ pub fn expand(
ident, generics, ..
} = input;

let error_mod = error_module();

let state = State::with_attr_params(
input,
trait_name,
quote! { ::std::error },
error_mod.clone(),
trait_name.to_lowercase(),
allowed_attr_params(),
)?;
Expand All @@ -39,7 +51,7 @@ pub fn expand(

let source = source.map(|source| {
quote! {
fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
fn source(&self) -> Option<&(dyn #error_mod::Error + 'static)> {
use ::derive_more::__private::AsDynError;
#source
}
Expand All @@ -48,7 +60,7 @@ pub fn expand(

let provide = provide.map(|provide| {
quote! {
fn provide<'_demand>(&'_demand self, demand: &mut ::std::any::Demand<'_demand>) {
fn provide<'_demand>(&'_demand self, demand: &mut ::core::any::Demand<'_demand>) {
#provide
}
}
Expand All @@ -62,7 +74,7 @@ pub fn expand(
&generics,
quote! {
where
#ident #ty_generics: ::std::fmt::Debug + ::std::fmt::Display
#ident #ty_generics: ::core::fmt::Debug + ::core::fmt::Display
},
);
}
Expand All @@ -73,7 +85,7 @@ pub fn expand(
&generics,
quote! {
where
#(#bounds: ::std::fmt::Debug + ::std::fmt::Display + ::std::error::Error + 'static),*
#(#bounds: ::core::fmt::Debug + ::core::fmt::Display + #error_mod::Error + 'static),*
},
);
}
Expand All @@ -82,7 +94,7 @@ pub fn expand(

let render = quote! {
#[automatically_derived]
impl #impl_generics ::std::error::Error for #ident #ty_generics #where_clause {
impl #impl_generics #error_mod::Error for #ident #ty_generics #where_clause {
#source
#provide
}
Expand Down Expand Up @@ -203,11 +215,12 @@ impl<'input, 'state> ParsedFields<'input, 'state> {

fn render_provide_as_struct(&self) -> Option<TokenStream> {
let backtrace = self.backtrace?;
let error_mod = error_module();

let source_provider = self.source.map(|source| {
let source_expr = &self.data.members[source];
quote! {
::std::error::Error::provide(&#source_expr, demand);
#error_mod::Error::provide(&#source_expr, demand);
}
});
let backtrace_provider = self
Expand All @@ -217,7 +230,7 @@ impl<'input, 'state> ParsedFields<'input, 'state> {
.then(|| {
let backtrace_expr = &self.data.members[backtrace];
quote! {
demand.provide_ref::<std::backtrace::Backtrace>(&#backtrace_expr);
demand.provide_ref::<::std::backtrace::Backtrace>(&#backtrace_expr);
}
});

Expand All @@ -231,13 +244,14 @@ impl<'input, 'state> ParsedFields<'input, 'state> {

fn render_provide_as_enum_variant_match_arm(&self) -> Option<TokenStream> {
let backtrace = self.backtrace?;
let error_mod = error_module();

match self.source {
Some(source) if source == backtrace => {
let pattern = self.data.matcher(&[source], &[quote! { source }]);
Some(quote! {
#pattern => {
::std::error::Error::provide(source, demand);
#error_mod::Error::provide(source, demand);
}
})
}
Expand All @@ -248,16 +262,16 @@ impl<'input, 'state> ParsedFields<'input, 'state> {
);
Some(quote! {
#pattern => {
demand.provide_ref::<std::backtrace::Backtrace>(backtrace);
::std::error::Error::provide(source, demand);
demand.provide_ref::<::std::backtrace::Backtrace>(backtrace);
#error_mod::Error::provide(source, demand);
}
})
}
None => {
let pattern = self.data.matcher(&[backtrace], &[quote! { backtrace }]);
Some(quote! {
#pattern => {
demand.provide_ref::<std::backtrace::Backtrace>(backtrace);
demand.provide_ref::<::std::backtrace::Backtrace>(backtrace);
}
})
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(all(not(feature = "std"), feature = "error"), feature(error_in_core))]
// These links overwrite the ones in `README.md`
// to become proper intra-doc links in Rust docs.
//! [`From`]: crate::From
Expand Down
7 changes: 6 additions & 1 deletion src/vendor/thiserror/aserror.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#[cfg(feature = "std")]
use std::error::Error;
use std::panic::UnwindSafe;

#[cfg(not(feature = "std"))]
use core::error::Error;

use core::panic::UnwindSafe;

pub trait AsDynError<'a>: Sealed {
fn as_dyn_error(&self) -> &(dyn Error + 'a);
Expand Down
2 changes: 2 additions & 0 deletions tests/error/derives_for_enums_with_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum TestErr {
source: SimpleErr,
field: i32,
},
#[cfg(std)]
NamedImplicitBoxedSource {
source: Box<dyn Error + Send + 'static>,
field: i32,
Expand Down Expand Up @@ -98,6 +99,7 @@ fn named_implicit_source() {
assert!(err.source().unwrap().is::<SimpleErr>());
}

#[cfg(std)]
#[test]
fn named_implicit_boxed_source() {
let err = TestErr::NamedImplicitBoxedSource {
Expand Down
12 changes: 8 additions & 4 deletions tests/error/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#[cfg(feature = "std")]
use std::error::Error;

#[cfg(not(feature = "std"))]
use core::error::Error;

use derive_more::Error;

/// Derives `std::fmt::Display` for structs/enums.
Expand Down Expand Up @@ -29,17 +33,17 @@ use derive_more::Error;
/// ```
macro_rules! derive_display {
(@fmt) => {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
write!(f, "")
}
};
($type:ident) => {
impl ::std::fmt::Display for $type {
impl ::core::fmt::Display for $type {
derive_display!(@fmt);
}
};
($type:ident, $($type_parameters:ident),*) => {
impl<$($type_parameters),*> ::std::fmt::Display for $type<$($type_parameters),*> {
impl<$($type_parameters),*> ::core::fmt::Display for $type<$($type_parameters),*> {
derive_display!(@fmt);
}
};
Expand All @@ -50,7 +54,7 @@ mod derives_for_generic_enums_with_source;
mod derives_for_generic_structs_with_source;
mod derives_for_structs_with_source;

#[cfg(nightly)]
#[cfg(all(feature = "std", nightly))]
mod nightly;

derive_display!(SimpleErr);
Expand Down
2 changes: 2 additions & 0 deletions tests/error_tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(nightly, feature(error_generic_member_access, provide_any))]
#![cfg_attr(not(feature = "std"), feature(error_in_core))]

mod error;

0 comments on commit c0bc2fd

Please sign in to comment.