Skip to content

Commit

Permalink
Support Error in no_std environments (#268)
Browse files Browse the repository at this point in the history
Resolves #261 

## Synopsis

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.

In passing this also fixes actually running the nightly error tests.
They were not actually run anymore because there was no `build.rs` file
in the root of the repo, only in the `impl` package. So the `nightly`
config was not available in tests.

Co-authored-by: tyranron <tyranron@gmail.com>
  • Loading branch information
JelteF and tyranron committed Jul 5, 2023
1 parent 2e3ddc2 commit d1ff0cf
Show file tree
Hide file tree
Showing 13 changed files with 84 additions and 25 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ jobs:

test-features:
name: test features
strategy:
fail-fast: false
matrix:
std: ["std", "no_std"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -128,7 +132,7 @@ jobs:
go install github.com/pelletier/go-toml/cmd/tomljson@latest
echo "$HOME/go/bin" >> $GITHUB_PATH
- run: ci/test_all_features.sh
- run: ci/test_all_features.sh ${{ matrix.std }}



Expand Down
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
9 changes: 6 additions & 3 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 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();
}
10 changes: 8 additions & 2 deletions ci/test_all_features.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
#!/usr/bin/env bash

std=''
if [ "${1:-}" = 'std' ]; then
std=',std'
fi

set -euxo pipefail

for feature in $(tomljson Cargo.toml | jq --raw-output '.features | keys[]' | grep -v 'default\|std\|testing-helpers'); do
cargo test -p derive_more --tests --no-default-features --features "$feature,testing-helpers";
for feature in $(tomljson Cargo.toml | jq --raw-output '.features | keys[]' | grep -v 'default\|std\|full\|testing-helpers'); do
cargo +nightly test -p derive_more --tests --no-default-features --features "$feature$std,testing-helpers"
done
2 changes: 1 addition & 1 deletion impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ unicode-xid = { version = "0.2.2", optional = true }
rustc_version = { version = "0.4", optional = true }

[dev-dependencies]
derive_more = { path = "..", features = ["add", "debug", "error", "from_str", "not", "try_into", "try_unwrap"] }
derive_more = { path = "..", features = ["add", "debug", "error", "from_str", "not", "std", "try_into", "try_unwrap"] }
itertools = "0.11.0"

[badges]
Expand Down
14 changes: 13 additions & 1 deletion impl/doc/error.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,25 @@ 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:
```ignore
#![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
24 changes: 12 additions & 12 deletions impl/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub fn expand(
let state = State::with_attr_params(
input,
trait_name,
quote! { ::std::error },
quote! { ::derive_more::__private::Error },
trait_name.to_lowercase(),
allowed_attr_params(),
)?;
Expand All @@ -39,7 +39,7 @@ pub fn expand(

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

let render = quote! {
#[automatically_derived]
impl #impl_generics ::std::error::Error for #ident #ty_generics #where_clause {
impl #impl_generics ::derive_more::__private::Error for #ident #ty_generics #where_clause {
#source
#provide
}
Expand Down Expand Up @@ -207,7 +207,7 @@ impl<'input, 'state> ParsedFields<'input, 'state> {
let source_provider = self.source.map(|source| {
let source_expr = &self.data.members[source];
quote! {
::std::error::Error::provide(&#source_expr, demand);
::derive_more::__private::Error::provide(&#source_expr, demand);
}
});
let backtrace_provider = self
Expand All @@ -217,7 +217,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 @@ -237,7 +237,7 @@ impl<'input, 'state> ParsedFields<'input, 'state> {
let pattern = self.data.matcher(&[source], &[quote! { source }]);
Some(quote! {
#pattern => {
::std::error::Error::provide(source, demand);
::derive_more::__private::Error::provide(source, demand);
}
})
}
Expand All @@ -248,16 +248,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);
::derive_more::__private::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
6 changes: 6 additions & 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 Expand Up @@ -77,6 +78,11 @@ mod vendor;
#[doc(hidden)]
#[cfg(feature = "error")]
pub mod __private {
#[cfg(not(feature = "std"))]
pub use ::core::error::Error;
#[cfg(feature = "std")]
pub use ::std::error::Error;

pub use crate::vendor::thiserror::aserror::AsDynError;
}

Expand Down
6 changes: 5 additions & 1 deletion src/vendor/thiserror/aserror.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#[cfg(not(feature = "std"))]
use core::error::Error;
#[cfg(feature = "std")]
use std::error::Error;
use std::panic::UnwindSafe;

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(feature = "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(feature = "std")]
#[test]
fn named_implicit_boxed_source() {
let err = TestErr::NamedImplicitBoxedSource {
Expand Down
11 changes: 7 additions & 4 deletions tests/error/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#[cfg(not(feature = "std"))]
use core::error::Error;
#[cfg(feature = "std")]
use std::error::Error;

use derive_more::Error;
Expand Down Expand Up @@ -29,17 +32,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 +53,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 d1ff0cf

Please sign in to comment.