diff --git a/build.rs b/build.rs index b3512df..60371ab 100644 --- a/build.rs +++ b/build.rs @@ -2,6 +2,7 @@ use std::env; use std::fs; use std::path::Path; use std::process::{Command, ExitStatus}; +use std::str; // This code exercises the surface area that we expect of the std Backtrace // type. If the current toolchain is able to compile it, we go ahead and use @@ -53,6 +54,19 @@ fn main() { Some(status) if status.success() => println!("cargo:rustc-cfg=track_caller"), _ => {} } + + let rustc = match rustc_minor_version() { + Some(rustc) => rustc, + None => return, + }; + + if rustc < 52 { + println!("cargo:rustc-cfg=eyre_no_fmt_arguments_as_str"); + } + + if rustc < 58 { + println!("cargo:rustc-cfg=eyre_no_fmt_args_capture"); + } } fn compile_probe(probe: &str) -> Option { @@ -71,3 +85,14 @@ fn compile_probe(probe: &str) -> Option { .status() .ok() } + +fn rustc_minor_version() -> Option { + let rustc = env::var_os("RUSTC")?; + let output = Command::new(rustc).arg("--version").output().ok()?; + let version = str::from_utf8(&output.stdout).ok()?; + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + pieces.next()?.parse().ok() +} diff --git a/src/lib.rs b/src/lib.rs index 8ad3ed2..44b8057 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -315,6 +315,8 @@ clippy::wrong_self_convention )] +extern crate alloc; + #[macro_use] mod backtrace; mod chain; @@ -1114,8 +1116,11 @@ pub trait ContextCompat: context::private::Sealed { #[doc(hidden)] pub mod private { use crate::Report; - use core::fmt::{Debug, Display}; + use alloc::fmt; + use core::fmt::{Arguments, Debug, Display}; + pub use alloc::format; + pub use core::format_args; pub use core::result::Result::Err; #[doc(hidden)] @@ -1132,4 +1137,21 @@ pub mod private { { Report::from_adhoc(message) } + + #[doc(hidden)] + #[cold] + pub fn format_err(args: Arguments<'_>) -> Report { + #[cfg(eyre_no_fmt_arguments_as_str)] + let fmt_arguments_as_str: Option<&str> = None; + #[cfg(not(eyre_no_fmt_arguments_as_str))] + let fmt_arguments_as_str = args.as_str(); + + if let Some(message) = fmt_arguments_as_str { + // eyre!("literal"), can downcast to &'static str + Report::msg(message) + } else { + // eyre!("interpolate {var}"), can downcast to String + Report::msg(fmt::format(args)) + } + } } diff --git a/src/macros.rs b/src/macros.rs index 0a1c81f..792a646 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -148,17 +148,18 @@ macro_rules! ensure { /// ``` #[macro_export] macro_rules! eyre { - ($msg:literal $(,)?) => { - // Handle $:literal as a special case to make cargo-expanded code more - // concise in the common case. - $crate::private::new_adhoc($msg) - }; + ($msg:literal $(,)?) => ({ + let error = $crate::private::format_err($crate::private::format_args!($msg)); + error + }); ($err:expr $(,)?) => ({ use $crate::private::kind::*; - let error = $err; - (&error).eyre_kind().new(error) + let error = match $err { + error => (&error).eyre_kind().new(error), + }; + error }); ($fmt:expr, $($arg:tt)*) => { - $crate::private::new_adhoc(format!($fmt, $($arg)*)) + $crate::private::new_adhoc($crate::private::format!($fmt, $($arg)*)) }; } diff --git a/tests/test_chain.rs b/tests/test_chain.rs index dc1fb87..c75f90e 100644 --- a/tests/test_chain.rs +++ b/tests/test_chain.rs @@ -1,7 +1,7 @@ use eyre::{eyre, Report}; fn error() -> Report { - eyre!(0).wrap_err(1).wrap_err(2).wrap_err(3) + eyre!({ 0 }).wrap_err(1).wrap_err(2).wrap_err(3) } #[test] diff --git a/tests/test_downcast.rs b/tests/test_downcast.rs index f025907..714b830 100644 --- a/tests/test_downcast.rs +++ b/tests/test_downcast.rs @@ -10,10 +10,18 @@ use std::io; #[test] fn test_downcast() { + #[cfg(not(eyre_no_fmt_arguments_as_str))] assert_eq!( "oh no!", bail_literal().unwrap_err().downcast::<&str>().unwrap(), ); + + #[cfg(eyre_no_fmt_arguments_as_str)] + assert_eq!( + "oh no!", + bail_literal().unwrap_err().downcast::().unwrap(), + ); + assert_eq!( "oh no!", bail_fmt().unwrap_err().downcast::().unwrap(), @@ -30,10 +38,21 @@ fn test_downcast() { #[test] fn test_downcast_ref() { + #[cfg(not(eyre_no_fmt_arguments_as_str))] assert_eq!( "oh no!", *bail_literal().unwrap_err().downcast_ref::<&str>().unwrap(), ); + + #[cfg(eyre_no_fmt_arguments_as_str)] + assert_eq!( + "oh no!", + *bail_literal() + .unwrap_err() + .downcast_ref::() + .unwrap(), + ); + assert_eq!( "oh no!", bail_fmt().unwrap_err().downcast_ref::().unwrap(), @@ -50,10 +69,21 @@ fn test_downcast_ref() { #[test] fn test_downcast_mut() { + #[cfg(not(eyre_no_fmt_arguments_as_str))] assert_eq!( "oh no!", *bail_literal().unwrap_err().downcast_mut::<&str>().unwrap(), ); + + #[cfg(eyre_no_fmt_arguments_as_str)] + assert_eq!( + "oh no!", + *bail_literal() + .unwrap_err() + .downcast_mut::() + .unwrap(), + ); + assert_eq!( "oh no!", bail_fmt().unwrap_err().downcast_mut::().unwrap(), diff --git a/tests/test_macros.rs b/tests/test_macros.rs index 8691ff6..1244eb1 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -2,7 +2,11 @@ mod common; use self::common::*; -use eyre::{ensure, Result}; +use eyre::{ensure, eyre, Result}; +use std::cell::Cell; +use std::future::Future; +use std::pin::Pin; +use std::task::Poll; #[test] fn test_messages() { @@ -32,3 +36,50 @@ fn test_ensure() { }; assert!(f().is_err()); } + +#[test] +fn test_temporaries() { + struct Ready(Option); + + impl Unpin for Ready {} + + impl Future for Ready { + type Output = T; + + fn poll(mut self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll { + Poll::Ready(self.0.take().unwrap()) + } + } + + fn require_send_sync(_: impl Send + Sync) {} + + require_send_sync(async { + // If eyre hasn't dropped any temporary format_args it creates by the + // time it's done evaluating, those will stick around until the + // semicolon, which is on the other side of the await point, making the + // enclosing future non-Send. + Ready(Some(eyre!("..."))).await; + }); + + fn message(cell: Cell<&str>) -> &str { + cell.get() + } + + require_send_sync(async { + Ready(Some(eyre!(message(Cell::new("..."))))).await; + }); +} + +#[test] +#[cfg(not(eyre_no_fmt_args_capture))] +fn test_capture_format_args() { + let var = 42; + let err = eyre!("interpolate {var}"); + assert_eq!("interpolate 42", err.to_string()); +} + +#[test] +fn test_brace_escape() { + let err = eyre!("unterminated ${{..}} expression"); + assert_eq!("unterminated ${..} expression", err.to_string()); +}