Fix using downcast with anyhow::Error#12689
Fix using downcast with anyhow::Error#12689alexcrichton merged 2 commits intobytecodealliance:mainfrom
downcast with anyhow::Error#12689Conversation
Previously when converting `wasmtime::Error` into `anyhow::Error` it ended up breaking the `downcast` method. This is because `anyhow::Error::from_boxed` looks like it does not implement the `downcast` method which is how all errors were previously converted to `anyhow::Error`. This commit adds a new vtable method for specifically converting to `anyhow::Error` which enables using the typed construction methods of `anyhow` which preserves `downcast`-ness.
| anyhow::Error::from_boxed(e.into_boxed_dyn_error()) | ||
| e.inner.into_anyhow() |
There was a problem hiding this comment.
To make sure I understand, it isn't enough to change this impl into something like
e.downcast::<anyhow::Error>().unwrap_or_else(|e| {
anyhow::Error::from_boxed(e.into_boxed_dyn_error())
})and expose the underlying anyhow::Error (if any) instead of adding another dyn-boxing indirection because anyhow itself doesn't implement is/downcast/etc... for anyhow::Errors created via anyhow::Error::from_boxed?
There was a problem hiding this comment.
The specific case that I ran into updating Spin was that a Trap in Wasmtime was converted to wasmtime::Error and then that was converted to anyhow::Error. Calling anyhow_error.downcast::<Trap>() was failing due to being created from anyhow::Error::from_boxed which ends up severing any downcast methods.
So I believe what you've gisted will solve anyhow-and-back but wouldn't solve acquiring Wasmtime's error types which never went through anyhow
| #[cfg(feature = "anyhow")] | ||
| fn ext_into_anyhow(mut self) -> anyhow::Error { | ||
| match self.error.take() { | ||
| Some(error) => anyhow::Error::from(error).context(self.context), | ||
| None => anyhow::Error::msg(self), | ||
| } | ||
| } |
There was a problem hiding this comment.
Ah, I guess anyhow's is/downcast could never see through our context chains when all they have is a Box<dyn core::error::Error>. Okay, I get it.
| fn anyhow_preserves_downcast() -> Result<()> { | ||
| { | ||
| let e: Error = TestError(1).into(); | ||
| assert!(e.downcast_ref::<TestError>().is_some()); | ||
| let e: anyhow::Error = e.into(); | ||
| assert!(e.downcast_ref::<TestError>().is_some()); | ||
| } | ||
| { | ||
| let e = Error::from(TestError(1)).context("hi"); | ||
| assert!(e.downcast_ref::<TestError>().is_some()); | ||
| assert!(e.downcast_ref::<&str>().is_some()); | ||
| let e: anyhow::Error = e.into(); | ||
| assert!(e.downcast_ref::<TestError>().is_some()); | ||
| assert!(e.downcast_ref::<&str>().is_some()); | ||
| } | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
This makes me realize wasmtime::Error probably doesn't handle is/downcast* correctly when created from an anyhow::Error that has a context chain:
let e = anyhow::Error::from(TestError(1));
let e = e.context("str");
let e = wasmtime::Error::from(e);
assert!(e.is::<anyhow::Error>()); // should pass right now
assert!(e.is::<TestError>()); // I think will fail
assert!(e.is::<&str>()); // I think will also failAnd in fact, I don't think we can support this, given that our vtable methods can't be generic over E but instead must take TypeIds. But maybe I am missing something and you have other ideas?
I guess we could at the wasmtime::Error implementation level fall back to something like
impl wasmtime::Error {
pub fn is<E>(&self) -> bool {
let result = existing_is_impl();
#[cfg(feature = "anyhow")]
let result = result || self.downcast_ref::<anyhow::Error>().is_some_and(|e| {
e.is::<E>()
});
result
}
}?
There was a problem hiding this comment.
That's a good point, and yeah I think hooking is and downcast and friends with type parameters to explicitly handle the anyhow special case is the only way to go here. I'll see if I can't hook that up.
There was a problem hiding this comment.
That's a good point, and yeah I think hooking
isanddowncastand friends with type parameters to explicitly handle theanyhowspecial case is the only way to go here. I'll see if I can't hook that up.
Fine to do as a follow up too, ofc, if you prefer
There was a problem hiding this comment.
Hm ok yeah this is going to be a little gnarly, so I've added a test for the current behavior documenting that it's a bit buggy, but it at least preserves the anyhow roundtrip. I'll file a follow-up issue for this.
Previously when converting
wasmtime::Errorintoanyhow::Errorit ended up breaking thedowncastmethod. This is becauseanyhow::Error::from_boxedlooks like it does not implement thedowncastmethod which is how all errors were previously converted toanyhow::Error. This commit adds a new vtable method for specifically converting toanyhow::Errorwhich enables using the typed construction methods ofanyhowwhich preservesdowncast-ness.