Repository: zkat/miette (Rust)

Issue:
Converting a `Report` to a `Box<dyn Diagnostic>` wipes all `Diagnostic` information
I suspect this is a super subtle issue in the crazy vtable code, but here is a relatively compact reproduction of the issue:
```rust
use miette::{miette, Diagnostic, LabeledSpan, Severity};

fn main() {
    let report = miette!(
        severity = Severity::Error,
        code = "expected::rparen",
        help = "always close your parens",
        labels = vec![LabeledSpan::at_offset(6, "here")],
        url = "https://example.com",
        "expected closing ')'"
    );
    assert_eq!(report.to_string(), "expected closing ')'".to_owned());
    assert_eq!(report.severity(), Some(Severity::Error));
    assert!(report.code().is_some());
    assert!(report.help().is_some());
    assert!(report.labels().is_some());
    assert!(report.url().is_some());

    let report_ref: &dyn Diagnostic = report.as_ref();
    assert_eq!(report_ref.to_string(), "expected closing ')'".to_owned());
    assert_eq!(report_ref.severity(), Some(Severity::Error));
    assert!(report_ref.code().is_some());
    assert!(report_ref.help().is_some());
    assert!(report_ref.labels().is_some());
    assert!(report_ref.url().is_some());

    let report_box: Box<dyn Diagnostic> = report.into();
    assert_eq!(report_box.to_string(), "expected closing ')'".to_owned());
    assert_eq!(report_box.severity(), None);
    assert!(report_box.code().is_none());
    assert!(report_box.help().is_none());
    assert!(report_box.labels().is_none());
    assert!(report_box.url().is_none());
}
```

The `Error` / `Display` impl is still totally okay, and all of the `Diagnostic` stuff that goes though the `Deref` impl seems to work fine — both `.as_ref()` and calling things like `.code()` on a `Report` directly — so the issue does *not* seem to be present here:
```rust
impl Deref for Report {
    type Target = dyn Diagnostic + Send + Sync + 'static;

    fn deref(&self) -> &Self::Target {
        unsafe { ErrorImpl::diagnostic(self.inner.by_ref()) }
    }
}
```

**NOTE:** I'm now moving away from the example above, to the context where this issue occurs in my code!

The issue seems to come specifically from (I believe) somehow attaching the wrong vtable? Here the data of the error stays the same — the source code information is kept — but calling `.source_code()` seems to just fall back on the default `None` impl?
```rust
impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
    fn from(error: Report) -> Self {
        dbg!("impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static>");
        dbg!(&error);
        dbg!(error.source_code().is_some());
        let outer = ManuallyDrop::new(error);
        unsafe {
            // Use vtable to attach ErrorImpl<E>'s native StdError vtable for
            // the right original type E.
            let res = (vtable(outer.inner.ptr).object_boxed)(outer.inner);
            dbg!(&res);
            dbg!(res.source_code().is_some());
            res
        }
    }
}

```
Outputs, in my case:
```
[/home/tll/.cargo/git/checkouts/miette-68017d45f967249d/5f42f10/src/eyreish/error.rs:750:9] "impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static>" = "impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static>"
[/home/tll/.cargo/git/checkouts/miette-68017d45f967249d/5f42f10/src/eyreish/error.rs:751:9] &error = Error {
    source_code: NamedSource {
        name: "test",
        source: "<redacted>",
        language: None,
    ,
    errors: [
        Unexpected {
            span: Span(
                0,
                49,
            ),
            kind: "node",
            message: "unexpected node `P`",
        },
        MissingNode {
            message: "child node `elements` is required",
        },
    ],
}
[/home/tll/.cargo/git/checkouts/miette-68017d45f967249d/5f42f10/src/eyreish/error.rs:752:9] error.source_code().is_some() = true
[/home/tll/.cargo/git/checkouts/miette-68017d45f967249d/5f42f10/src/eyreish/error.rs:758:13] &res = Error {
    source_code: NamedSource {
        name: "test",
        source: "<redacted>",
        language: None,
    ,
    errors: [
        Unexpected {
            span: Span(
                0,
                49,
            ),
            kind: "node",
            message: "unexpected node `P`",
        },
        MissingNode {
            message: "child node `elements` is required",
        },
    ],
}
[/home/tll/.cargo/git/checkouts/miette-68017d45f967249d/5f42f10/src/eyreish/error.rs:759:13] res.source_code().is_some() = false
```

The `error` and `res` structs remain the same, but calling the `.source_code()` method does not!

Any pointers on where to start fixing this would be excellent! The unsafe vtable code is beyond anything I've ever done in Rust, so figuring it out is a gradual process...


Discussion:
Much more mundane than I thought... I've got a fix that I'll make a PR for!



Patch:
diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs
index 677f3684..8a5a3558 100644
--- a/src/eyreish/error.rs
+++ b/src/eyreish/error.rs
@@ -533,7 +533,8 @@ where
     E: Diagnostic + Send + Sync + 'static,
 {
     // Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
-    e.cast::<ErrorImpl<E>>().boxed()
+    let unerased = e.cast::<ErrorImpl<E>>().boxed();
+    Box::new(unerased._object)
 }
 
 // Safety: requires layout of *e to match ErrorImpl<E>.
@@ -544,7 +545,8 @@ where
     E: StdError + Send + Sync + 'static,
 {
     // Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
-    e.cast::<ErrorImpl<E>>().boxed()
+    let unerased = e.cast::<ErrorImpl<E>>().boxed();
+    Box::new(unerased._object)
 }
 
 // Safety: requires layout of *e to match ErrorImpl<E>.
@@ -716,17 +718,6 @@ impl ErasedErrorImpl {
     }
 }
 
-impl<E> StdError for ErrorImpl<E>
-where
-    E: StdError,
-{
-    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
-        unsafe { ErrorImpl::diagnostic(self.erase()).source() }
-    }
-}
-
-impl<E> Diagnostic for ErrorImpl<E> where E: Diagnostic {}
-
 impl<E> Debug for ErrorImpl<E>
 where
     E: Debug,


Test patch:
diff --git a/tests/test_boxed.rs b/tests/test_boxed.rs
index d6fa6f80..9f9f4e23 100644
--- a/tests/test_boxed.rs
+++ b/tests/test_boxed.rs
@@ -1,6 +1,7 @@
 use miette::{miette, Diagnostic, LabeledSpan, Report, SourceSpan};
 use std::error::Error as StdError;
 use std::io;
+use std::ops::Deref;
 use thiserror::Error;
 
 #[derive(Error, Debug)]
@@ -159,7 +160,7 @@ impl Diagnostic for CustomDiagnostic {
 
 #[test]
 fn test_boxed_custom_diagnostic() {
-    fn assert_report(report: &Report) {
+    fn assert_report<T: ?Sized + Diagnostic>(report: &impl Deref<Target = T>) {
         assert_eq!(
             report.source().map(|source| source.to_string()),
             Some("oh no!".to_owned()),
@@ -215,10 +216,16 @@ fn test_boxed_custom_diagnostic() {
     let main_diagnostic = Box::new(main_diagnostic) as Box<dyn Diagnostic + Send + Sync + 'static>;
     let report = miette!(main_diagnostic);
     assert_report(&report);
+
+    // Now make sure that conversion to a trait-object is lossless!
+    let report_ref: &dyn Diagnostic = report.as_ref();
+    assert_report(&report_ref);
+
+    let report_box: Box<dyn Diagnostic> = report.into();
+    assert_report(&report_box);
 }
 
 #[test]
-#[ignore = "I don't know why this isn't working but it needs fixing."]
 fn test_boxed_sources() {
     let error = MyError {
         source: io::Error::new(io::ErrorKind::Other, "oh no!"),