Allow disabling std dependency on 1.81+

This commit is contained in:
David Tolnay 2024-11-05 19:43:34 -05:00
parent 8277ec4a73
commit d8ed5fbc2f
No known key found for this signature in database
GPG key ID: F9BA143B95FF6D82
10 changed files with 146 additions and 9 deletions

View file

@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rust: [nightly, beta, stable, 1.70.0]
rust: [nightly, beta, stable, 1.81.0, 1.70.0]
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
@ -38,7 +38,9 @@ jobs:
- name: Enable nightly-only tests
run: echo RUSTFLAGS=${RUSTFLAGS}\ --cfg=thiserror_nightly_testing >> $GITHUB_ENV
if: matrix.rust == 'nightly'
- run: cargo test --all
- run: cargo test --workspace --exclude thiserror_no_std_test
- run: cargo test --manifest-path tests/no-std/Cargo.toml
if: matrix.rust != '1.70.0'
- uses: actions/upload-artifact@v4
if: matrix.rust == 'nightly' && always()
with:

View file

@ -11,6 +11,22 @@ license = "MIT OR Apache-2.0"
repository = "https://github.com/dtolnay/thiserror"
rust-version = "1.61"
[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 = "=1.0.68", path = "impl" }
@ -21,7 +37,7 @@ rustversion = "1.0.13"
trybuild = { version = "1.0.81", features = ["diff"] }
[workspace]
members = ["impl"]
members = ["impl", "tests/no-std"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View file

@ -57,7 +57,14 @@ fn main() {
println!("cargo:rerun-if-env-changed=RUSTC_BOOTSTRAP");
}
let rustc = match rustc_minor_version() {
// core::error::Error stabilized in Rust 1.81
// https://blog.rust-lang.org/2024/09/05/Rust-1.81.0.html#coreerrorerror
let rustc = rustc_minor_version();
if cfg!(not(feature = "std")) && rustc.map_or(false, |rustc| rustc < 81) {
println!("cargo:rustc-cfg=feature=\"std\"");
}
let rustc = match rustc {
Some(rustc) => rustc,
None => return,
};

View file

@ -2,6 +2,7 @@
// 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};

View file

@ -1,5 +1,5 @@
use core::error::Error;
use core::panic::UnwindSafe;
use std::error::Error;
#[doc(hidden)]
pub trait AsDynError<'a>: Sealed {

View file

@ -1,4 +1,5 @@
use core::fmt::Display;
#[cfg(feature = "std")]
use std::path::{self, Path, PathBuf};
#[doc(hidden)]
@ -21,6 +22,7 @@ where
}
}
#[cfg(feature = "std")]
impl<'a> AsDisplay<'a> for Path {
type Target = path::Display<'a>;
@ -30,6 +32,7 @@ impl<'a> AsDisplay<'a> for Path {
}
}
#[cfg(feature = "std")]
impl<'a> AsDisplay<'a> for PathBuf {
type Target = path::Display<'a>;
@ -42,5 +45,37 @@ impl<'a> AsDisplay<'a> for PathBuf {
#[doc(hidden)]
pub trait Sealed {}
impl<T: Display> 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};
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 {}
}

View file

@ -258,6 +258,7 @@
//!
//! [`anyhow`]: https://github.com/dtolnay/anyhow
#![no_std]
#![doc(html_root_url = "https://docs.rs/thiserror/1.0.68")]
#![allow(
clippy::module_name_repetitions,
@ -270,6 +271,11 @@
#[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)]
@ -287,9 +293,9 @@ pub mod __private {
#[cfg(error_generic_member_access)]
#[doc(hidden)]
pub use crate::provide::ThiserrorProvide;
#[cfg(not(thiserror_no_backtrace_type))]
#[doc(hidden)]
pub use core::error::Error;
#[cfg(all(feature = "std", not(thiserror_no_backtrace_type)))]
#[doc(hidden)]
pub use std::backtrace::Backtrace;
#[doc(hidden)]
pub use std::error::Error;
}

View file

@ -1,4 +1,4 @@
use std::error::{Error, Request};
use core::error::{Error, Request};
#[doc(hidden)]
pub trait ThiserrorProvide: Sealed {

12
tests/no-std/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[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 }

58
tests/no-std/test.rs Normal file
View file

@ -0,0 +1,58 @@
#![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~~~");
}
}