From ba6942effbde3b26c11f3c9f8e67580b2dea1ad4 Mon Sep 17 00:00:00 2001 From: x-hgg-x <39058530+x-hgg-x@users.noreply.github.com> Date: Fri, 14 Jan 2022 14:54:36 +0100 Subject: [PATCH 1/2] Support format arguments capture --- build.rs | 21 +++++++++++++++++++++ src/lib.rs | 24 +++++++++++++++++++++++- src/macros.rs | 17 +++++++++-------- tests/test_chain.rs | 2 +- tests/test_macros.rs | 38 +++++++++++++++++++++++++++++++++++++- 5 files changed, 91 insertions(+), 11 deletions(-) diff --git a/build.rs b/build.rs index b3512df..9b9254c 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,15 @@ 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"); + } } fn compile_probe(probe: &str) -> Option { @@ -71,3 +81,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..3344642 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(anyhow_no_fmt_arguments_as_str)] + let fmt_arguments_as_str: Option<&str> = None; + #[cfg(not(anyhow_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_macros.rs b/tests/test_macros.rs index 8691ff6..27e93a9 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -2,7 +2,9 @@ mod common; use self::common::*; -use eyre::{ensure, Result}; +use eyre::{ensure, eyre, Result}; +use std::cell::Cell; +use std::future; #[test] fn test_messages() { @@ -32,3 +34,37 @@ fn test_ensure() { }; assert!(f().is_err()); } + +#[test] +fn test_temporaries() { + 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. + future::ready(eyre!("...")).await; + }); + + fn message(cell: Cell<&str>) -> &str { + cell.get() + } + + require_send_sync(async { + future::ready(eyre!(message(Cell::new("...")))).await; + }); +} + +#[test] +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()); +} From 2e758d89808bb47343479898ea5bff46c0a33bd1 Mon Sep 17 00:00:00 2001 From: x-hgg-x <39058530+x-hgg-x@users.noreply.github.com> Date: Fri, 14 Jan 2022 21:23:05 +0100 Subject: [PATCH 2/2] Fix tests --- build.rs | 4 ++++ src/lib.rs | 4 ++-- tests/test_downcast.rs | 30 ++++++++++++++++++++++++++++++ tests/test_macros.rs | 21 ++++++++++++++++++--- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/build.rs b/build.rs index 9b9254c..60371ab 100644 --- a/build.rs +++ b/build.rs @@ -63,6 +63,10 @@ fn main() { 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 { diff --git a/src/lib.rs b/src/lib.rs index 3344642..44b8057 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1141,9 +1141,9 @@ pub mod private { #[doc(hidden)] #[cold] pub fn format_err(args: Arguments<'_>) -> Report { - #[cfg(anyhow_no_fmt_arguments_as_str)] + #[cfg(eyre_no_fmt_arguments_as_str)] let fmt_arguments_as_str: Option<&str> = None; - #[cfg(not(anyhow_no_fmt_arguments_as_str))] + #[cfg(not(eyre_no_fmt_arguments_as_str))] let fmt_arguments_as_str = args.as_str(); if let Some(message) = fmt_arguments_as_str { 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 27e93a9..1244eb1 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -4,7 +4,9 @@ mod common; use self::common::*; use eyre::{ensure, eyre, Result}; use std::cell::Cell; -use std::future; +use std::future::Future; +use std::pin::Pin; +use std::task::Poll; #[test] fn test_messages() { @@ -37,6 +39,18 @@ fn test_ensure() { #[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 { @@ -44,7 +58,7 @@ fn test_temporaries() { // 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. - future::ready(eyre!("...")).await; + Ready(Some(eyre!("..."))).await; }); fn message(cell: Cell<&str>) -> &str { @@ -52,11 +66,12 @@ fn test_temporaries() { } require_send_sync(async { - future::ready(eyre!(message(Cell::new("...")))).await; + 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}");