Skip to content

Commit

Permalink
fixup: innermost_source_matches can handle dereferencing boxed errors
Browse files Browse the repository at this point in the history
Basic error reporting and debug representations of things hide `Box`es.
It's also not trivial to downcast into them, particularly in the context
of a macro like this!

Non-trivial does not mean impossible, though. Adding this capability
to the macro means that we can avoid an ugly workaround in the test suite.
  • Loading branch information
coriolinus committed Dec 20, 2024
1 parent c84da0b commit 8cd3806
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 23 deletions.
22 changes: 1 addition & 21 deletions crypto/src/mls/credential/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,27 +233,7 @@ mod tests {
let err = try_talk(&case, Some(&x509_test_chain), alice_identifier, bob_identifier)
.await
.unwrap_err();
// assert!(innermost_source_matches!(err, Error::InvalidIdentity));
//
// The above should work, in this case. But it fails
// for mysterious reasons that I do not understand.
// Digging into it, the `downcast_ref` internal to the `innermost_source_matches`
// macro produces `None`, so Rust thinks that it's a different error internally.
// But changing the debug/display implementations on `Error::InvalidIdentity`
// also changes the implementations on the inner type; I'm reasonably confident
// that it is in fact this enum and variant.
//
// So let's abuse the debug implementation, on the assumption
// that nobody is going to make a different unrelated error
// which just happens to share the same debug name.
assert!({
let mut err: &dyn std::error::Error = &err;
while let Some(inner) = err.source() {
err = inner;
}

format!("{err:?}") == "InvalidIdentity"
})
assert!(innermost_source_matches!(err, Error::InvalidIdentity, deref Box<Error>: *));
}

#[apply(all_cred_cipher)]
Expand Down
27 changes: 25 additions & 2 deletions crypto/src/test_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,42 @@ pub const GROUP_SAMPLE_SIZE: usize = 9;
/// Trace up the error's source chain, and return whether the innermost matches the
/// provided pattern, and guard if supplied.
///
/// Syntax matches that of [std::matches].
/// Basic syntax matches that of [`std::matches`].
///
/// In case the innermost error of your type is wrapped in a `Box` or similar, you can use
/// an expanded syntax: after the pattern or guard expression, a third argument like
/// `deref Box<ExpectedType>: *`. If you have a more deeply nested type, you can add as
/// many deref operations (stars) as you need.
///
/// We can't write `fn innermost_source` because Rust can't prove that the innermost
/// error lives as long as the original error, and demands that it live as long as
/// `'static`, which is unhelpful. But we can inline the whole thing with a macro, as here.
macro_rules! innermost_source_matches {
// sure would be nice if we didn't have to write the whole body of this macro twice here.
// doesn't work though: pass the `matches!` line as a simple `matches!` expression, and
// `err` is out of scope in the outer context.
// pass it as a lambda expression taking `err` as a function, and rustc decides that somehow
// we're causing borrowed data to escape from a closure's scope.
($err:expr, $pattern:pat $(if $guard:expr)? $(,)?) => {{
let mut err: &dyn std::error::Error = &$err;
while let Some(inner) = err.source() {
err = inner;
}

let outcome = matches!(err.downcast_ref(), Some($pattern) $($guard)?);
let outcome = matches!(err.downcast_ref(), Some($pattern) $(if $guard)?);
if !outcome {
eprintln!("{err:?}: {err}");
}

outcome
}};
($err:expr, $pattern:pat $(if $guard:expr)?, deref $t:ty : $($deref:tt)* $(,)?) => {{
let mut err: &dyn std::error::Error = &$err;
while let Some(inner) = err.source() {
err = inner;
}

let outcome = matches!(err.downcast_ref::<$t>().map(|t| &*$($deref)* t), Some($pattern) $(if $guard)?);
if !outcome {
eprintln!("{err:?}: {err}");
}
Expand Down

0 comments on commit 8cd3806

Please sign in to comment.