Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support format arguments capture #66

Merged
merged 2 commits into from
Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<ExitStatus> {
Expand All @@ -71,3 +85,14 @@ fn compile_probe(probe: &str) -> Option<ExitStatus> {
.status()
.ok()
}

fn rustc_minor_version() -> Option<u32> {
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()
}
24 changes: 23 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@
clippy::wrong_self_convention
)]

extern crate alloc;

#[macro_use]
mod backtrace;
mod chain;
Expand Down Expand Up @@ -1114,8 +1116,11 @@ pub trait ContextCompat<T>: 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)]
Expand All @@ -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))
}
}
}
17 changes: 9 additions & 8 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)*))
};
}
2 changes: 1 addition & 1 deletion tests/test_chain.rs
Original file line number Diff line number Diff line change
@@ -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]
Expand Down
30 changes: 30 additions & 0 deletions tests/test_downcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<String>().unwrap(),
);

assert_eq!(
"oh no!",
bail_fmt().unwrap_err().downcast::<String>().unwrap(),
Expand All @@ -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::<String>()
.unwrap(),
);

assert_eq!(
"oh no!",
bail_fmt().unwrap_err().downcast_ref::<String>().unwrap(),
Expand All @@ -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::<String>()
.unwrap(),
);

assert_eq!(
"oh no!",
bail_fmt().unwrap_err().downcast_mut::<String>().unwrap(),
Expand Down
53 changes: 52 additions & 1 deletion tests/test_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -32,3 +36,50 @@ fn test_ensure() {
};
assert!(f().is_err());
}

#[test]
fn test_temporaries() {
struct Ready<T>(Option<T>);

impl<T> Unpin for Ready<T> {}

impl<T> Future for Ready<T> {
type Output = T;

fn poll(mut self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll<T> {
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());
}