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...

Pull request:
Fix #369 (Loss of `Diagnostic` information when `Report` is converted to `Box<dyn Diagnostic>`)
Fixes #369

The culprit was:

https://github.com/zkat/miette/blob/ea4296dacec3b0e4762281d9d115c1bd69ecfac3/src/eyreish/error.rs#L728

In conjunction with:

https://github.com/zkat/miette/blob/ea4296dacec3b0e4762281d9d115c1bd69ecfac3/src/eyreish/error.rs#L531-L537

Which converts a `Box<ErrorImpl<E>>` into the `Box<dyn Diagnostic>`, hence the resultant object ends up with the empty `Diagnostic` impl defined above!

This fix moves the `_object` out of the `Box<ErrorImpl<E>>` to create a `Box<E>` before conversion to `Box<dyn Diagnostic>`, attaching the correct `Diagnostic` impl for the *original* error `E`.

Miri seems happy, and I've actually only added safe code, even though it's in an `unsafe` function because of `.boxed()` which I left untouched.

---

As a side note, the only reason converting to `Box<dyn Error>` worked is because of this manual pass-through done here:
https://github.com/zkat/miette/blob/ea4296dacec3b0e4762281d9d115c1bd69ecfac3/src/eyreish/error.rs#L719-L726

But now that we aren't ever trying to convert `ErrorImpl<E>`s into trait objects anymore — directly converting `E`s instead — I've deleted that unused code.

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 057bd9e..01b3a71 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,7 @@ unicode-width = "0.1.11"
 cfg-if = "1.0.0"
 
 owo-colors = { version = "4.0.0", optional = true }
-textwrap = { version = "0.16.0", optional = true }
+textwrap = { version = "0.16.0", default-features = false, features = ["unicode-linebreak", "unicode-width"], optional = true }
 supports-hyperlinks = { version = "3.0.0", optional = true }
 supports-color = { version = "3.0.0", optional = true }
 supports-unicode = { version = "3.0.0", optional = true }
diff --git a/README.md b/README.md
index 88f8b99..ef351f2 100644
--- a/README.md
+++ b/README.md
@@ -163,13 +163,11 @@ Narratable printout:
 \
 diagnostic error code: oops::my::bad (link)
 Error: oops!
-
 \
 Begin snippet for bad_file.rs starting
 at line 2, column 3
 \
 snippet line 1: source
-
 \
 snippet line 2:  text
     highlight starting at line 1, column 3: This bit here
diff --git a/src/chain.rs b/src/chain.rs
index 7a66383..369ff7d 100644
--- a/src/chain.rs
+++ b/src/chain.rs
@@ -44,6 +44,7 @@ pub(crate) enum ChainState<'a> {
 }
 
 impl<'a> Chain<'a> {
+    #[cold]
     pub(crate) fn new(head: &'a (dyn StdError + 'static)) -> Self {
         Chain {
             state: ChainState::Linked { next: Some(head) },
diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs
index 677f368..94e4128 100644
--- a/src/eyreish/error.rs
+++ b/src/eyreish/error.rs
@@ -21,6 +21,7 @@ impl Report {
     /// If the error type does not provide a backtrace, a backtrace will be
     /// created here to ensure that a backtrace exists.
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     pub fn new<E>(error: E) -> Self
     where
         E: Diagnostic + Send + Sync + 'static,
@@ -66,6 +67,7 @@ impl Report {
     /// }
     /// ```
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     pub fn msg<M>(message: M) -> Self
     where
         M: Display + Debug + Send + Sync + 'static,
@@ -86,6 +88,7 @@ impl Report {
     }
 
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     pub(crate) fn from_std<E>(error: E) -> Self
     where
         E: Diagnostic + Send + Sync + 'static,
@@ -107,6 +110,7 @@ impl Report {
     }
 
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     pub(crate) fn from_adhoc<M>(message: M) -> Self
     where
         M: Display + Debug + Send + Sync + 'static,
@@ -131,6 +135,7 @@ impl Report {
     }
 
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     pub(crate) fn from_msg<D, E>(msg: D, error: E) -> Self
     where
         D: Display + Send + Sync + 'static,
@@ -155,6 +160,7 @@ impl Report {
     }
 
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     pub(crate) fn from_boxed(error: Box<dyn Diagnostic + Send + Sync>) -> Self {
         use super::wrapper::BoxedError;
         let error = BoxedError(error);
@@ -180,6 +186,7 @@ impl Report {
     //
     // Unsafe because the given vtable must have sensible behavior on the error
     // value of type E.
+    #[cold]
     unsafe fn construct<E>(
         error: E,
         vtable: &'static ErrorVTable,
@@ -262,6 +269,7 @@ impl Report {
     ///     None
     /// }
     /// ```
+    #[cold]
     pub fn chain(&self) -> Chain<'_> {
         unsafe { ErrorImpl::chain(self.inner.by_ref()) }
     }
@@ -410,7 +418,7 @@ impl Report {
     }
 
     /// Provide source code for this error
-    pub fn with_source_code(self, source_code: impl SourceCode + Send + Sync + 'static) -> Report {
+    pub fn with_source_code(self, source_code: impl SourceCode + 'static) -> Report {
         WithSourceCode {
             source_code,
             error: self,
@@ -424,6 +432,7 @@ where
     E: Diagnostic + Send + Sync + 'static,
 {
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     fn from(error: E) -> Self {
         Report::from_std(error)
     }
@@ -533,7 +542,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 +554,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>.
@@ -711,22 +722,12 @@ impl ErasedErrorImpl {
             .deref_mut()
     }
 
+    #[cold]
     pub(crate) unsafe fn chain(this: Ref<'_, Self>) -> Chain<'_> {
         Chain::new(Self::error(this))
     }
 }
 
-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,
@@ -746,6 +747,7 @@ where
 }
 
 impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
+    #[cold]
     fn from(error: Report) -> Self {
         let outer = ManuallyDrop::new(error);
         unsafe {
@@ -757,6 +759,7 @@ impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
 }
 
 impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
+    #[cold]
     fn from(error: Report) -> Self {
         let outer = ManuallyDrop::new(error);
         unsafe {
@@ -768,12 +771,14 @@ impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
 }
 
 impl From<Report> for Box<dyn Diagnostic + 'static> {
+    #[cold]
     fn from(error: Report) -> Self {
         Box::<dyn Diagnostic + Send + Sync>::from(error)
     }
 }
 
 impl From<Report> for Box<dyn StdError + 'static> {
+    #[cold]
     fn from(error: Report) -> Self {
         Box::<dyn StdError + Send + Sync>::from(error)
     }
diff --git a/src/eyreish/kind.rs b/src/eyreish/kind.rs
index ce60b50..6b0f5ec 100644
--- a/src/eyreish/kind.rs
+++ b/src/eyreish/kind.rs
@@ -63,6 +63,7 @@ impl<T> AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'stat
 
 impl Adhoc {
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     pub fn new<M>(self, message: M) -> Report
     where
         M: Display + Debug + Send + Sync + 'static,
@@ -84,6 +85,7 @@ impl<E> TraitKind for E where E: Into<Report> {}
 
 impl Trait {
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     pub fn new<E>(self, error: E) -> Report
     where
         E: Into<Report>,
@@ -105,6 +107,7 @@ impl BoxedKind for Box<dyn Diagnostic + Send + Sync> {}
 
 impl Boxed {
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     pub fn new(self, error: Box<dyn Diagnostic + Send + Sync>) -> Report {
         Report::from_boxed(error)
     }
diff --git a/src/eyreish/mod.rs b/src/eyreish/mod.rs
index 544c240..2dfbf71 100644
--- a/src/eyreish/mod.rs
+++ b/src/eyreish/mod.rs
@@ -57,7 +57,7 @@ pub struct Report {
 unsafe impl Sync for Report {}
 unsafe impl Send for Report {}
 
-///
+#[allow(missing_docs)]
 pub type ErrorHook =
     Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;
 
@@ -109,7 +109,7 @@ fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandl
 }
 
 impl dyn ReportHandler {
-    ///
+    #[allow(missing_docs)]
     pub fn is<T: ReportHandler>(&self) -> bool {
         // Get `TypeId` of the type this function is instantiated with.
         let t = core::any::TypeId::of::<T>();
@@ -121,7 +121,7 @@ impl dyn ReportHandler {
         t == concrete
     }
 
-    ///
+    #[allow(missing_docs)]
     pub fn downcast_ref<T: ReportHandler>(&self) -> Option<&T> {
         if self.is::<T>() {
             unsafe { Some(&*(self as *const dyn ReportHandler as *const T)) }
@@ -130,7 +130,7 @@ impl dyn ReportHandler {
         }
     }
 
-    ///
+    #[allow(missing_docs)]
     pub fn downcast_mut<T: ReportHandler>(&mut self) -> Option<&mut T> {
         if self.is::<T>() {
             unsafe { Some(&mut *(self as *mut dyn ReportHandler as *mut T)) }
@@ -475,6 +475,7 @@ pub mod private {
     }
 
     #[cfg_attr(track_caller, track_caller)]
+    #[cold]
     pub fn new_adhoc<M>(message: M) -> Report
     where
         M: Display + Debug + Send + Sync + 'static,
diff --git a/src/eyreish/wrapper.rs b/src/eyreish/wrapper.rs
index d940fed..62cf7a4 100644
--- a/src/eyreish/wrapper.rs
+++ b/src/eyreish/wrapper.rs
@@ -215,18 +215,6 @@ mod tests {
         }
     }
 
-    #[derive(Error, Debug)]
-    #[error("outer")]
-    struct Outer {
-        pub(crate) errors: Vec<Inner>,
-    }
-
-    impl Diagnostic for Outer {
-        fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
-            Some(Box::new(self.errors.iter().map(|e| e as _)))
-        }
-    }
-
     #[test]
     fn no_override() {
         let inner_source = "hello world";
@@ -254,6 +242,18 @@ mod tests {
     #[test]
     #[cfg(feature = "fancy")]
     fn two_source_codes() {
+        #[derive(Error, Debug)]
+        #[error("outer")]
+        struct Outer {
+            pub(crate) errors: Vec<Inner>,
+        }
+
+        impl Diagnostic for Outer {
+            fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
+                Some(Box::new(self.errors.iter().map(|e| e as _)))
+            }
+        }
+
         let inner_source = "hello world";
         let outer_source = "abc";
 
diff --git a/src/handler.rs b/src/handler.rs
index 95382c0..efbee86 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -11,22 +11,17 @@ use crate::ThemeCharacters;
 use crate::ThemeStyles;
 
 /// Settings to control the color format used for graphical rendering.
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
 pub enum RgbColors {
     /// Use RGB colors even if the terminal does not support them
     Always,
     /// Use RGB colors instead of ANSI if the terminal supports RGB
     Preferred,
     /// Always use ANSI, regardless of terminal support for RGB
+    #[default]
     Never,
 }
 
-impl Default for RgbColors {
-    fn default() -> RgbColors {
-        RgbColors::Never
-    }
-}
-
 /**
 Create a custom [`MietteHandler`] from options.
 
diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs
index 9c1d7ab..714e576 100644
--- a/src/handlers/graphical.rs
+++ b/src/handlers/graphical.rs
@@ -159,7 +159,7 @@ impl GraphicalReportHandler {
         self
     }
 
-    /// Sets the word splitter to usewhen wrapping.
+    /// Sets the word splitter to use when wrapping.
     pub fn with_word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
         self.word_splitter = Some(word_splitter);
         self
@@ -359,7 +359,7 @@ impl GraphicalReportHandler {
 
                         // If there was no header, remove the leading newline
                         let inner = inner.trim_start_matches('\n');
-                        writeln!(f, "{}", self.wrap(&inner, opts))?;
+                        writeln!(f, "{}", self.wrap(inner, opts))?;
                     }
                     ErrorKind::StdError(err) => {
                         writeln!(f, "{}", self.wrap(&err.to_string(), opts))?;
@@ -667,7 +667,7 @@ impl GraphicalReportHandler {
                     f,
                     max_gutter,
                     line,
-                    &labels,
+                    labels,
                     LabelRenderMode::SingleLine,
                 )?;
 
@@ -683,7 +683,7 @@ impl GraphicalReportHandler {
                     f,
                     max_gutter,
                     line,
-                    &labels,
+                    labels,
                     LabelRenderMode::MultiLineFirst,
                 )?;
 
@@ -701,7 +701,7 @@ impl GraphicalReportHandler {
                         f,
                         max_gutter,
                         line,
-                        &labels,
+                        labels,
                         LabelRenderMode::MultiLineRest,
                     )?;
                     self.render_multi_line_end_single(
@@ -714,13 +714,7 @@ impl GraphicalReportHandler {
             }
         } else {
             // gutter _again_
-            self.render_highlight_gutter(
-                f,
-                max_gutter,
-                line,
-                &labels,
-                LabelRenderMode::SingleLine,
-            )?;
+            self.render_highlight_gutter(f, max_gutter, line, labels, LabelRenderMode::SingleLine)?;
             // has no label
             writeln!(f, "{}", self.theme.characters.hbar.style(label.style))?;
         }
@@ -894,12 +888,10 @@ impl GraphicalReportHandler {
                     } else {
                         result.push_str(opts.initial_indent);
                     }
+                } else if line.trim().is_empty() {
+                    result.push_str(trimmed_indent);
                 } else {
-                    if line.trim().is_empty() {
-                        result.push_str(trimmed_indent);
-                    } else {
-                        result.push_str(opts.subsequent_indent);
-                    }
+                    result.push_str(opts.subsequent_indent);
                 }
                 result.push_str(line);
             }
@@ -1196,9 +1188,9 @@ impl GraphicalReportHandler {
         let mut column = context_data.column();
         let mut offset = context_data.span().offset();
         let mut line_offset = offset;
+        let mut line_str = String::with_capacity(context.len());
+        let mut lines = Vec::with_capacity(1);
         let mut iter = context.chars().peekable();
-        let mut line_str = String::new();
-        let mut lines = Vec::new();
         while let Some(char) = iter.next() {
             offset += char.len_utf8();
             let mut at_end_of_file = false;
diff --git a/src/handlers/narratable.rs b/src/handlers/narratable.rs
index c809124..f8d36ae 100644
--- a/src/handlers/narratable.rs
+++ b/src/handlers/narratable.rs
@@ -295,9 +295,9 @@ impl NarratableReportHandler {
         let mut column = context_data.column();
         let mut offset = context_data.span().offset();
         let mut line_offset = offset;
+        let mut line_str = String::with_capacity(context.len());
+        let mut lines = Vec::with_capacity(1);
         let mut iter = context.chars().peekable();
-        let mut line_str = String::new();
-        let mut lines = Vec::new();
         while let Some(char) = iter.next() {
             offset += char.len_utf8();
             let mut at_end_of_file = false;
diff --git a/src/handlers/theme.rs b/src/handlers/theme.rs
index 892ffc2..360b2da 100644
--- a/src/handlers/theme.rs
+++ b/src/handlers/theme.rs
@@ -71,7 +71,7 @@ impl Default for GraphicalTheme {
     fn default() -> Self {
         match std::env::var("NO_COLOR") {
             _ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
-                Self::ascii()
+                Self::none()
             }
             Ok(string) if string != "0" => Self::unicode_nocolor(),
             _ => Self::unicode(),
diff --git a/src/highlighters/syntect.rs b/src/highlighters/syntect.rs
index 57ebadf..69ed015 100644
--- a/src/highlighters/syntect.rs
+++ b/src/highlighters/syntect.rs
@@ -1,6 +1,7 @@
 use std::path::Path;
 
 // all syntect imports are explicitly qualified, but their paths are shortened for convenience
+#[allow(clippy::module_inception)]
 mod syntect {
     pub(super) use syntect::{
         highlighting::{
@@ -96,7 +97,7 @@ impl SyntectHighlighter {
         }
         // finally, attempt to guess syntax based on first line
         return self.syntax_set.find_syntax_by_first_line(
-            &std::str::from_utf8(contents.data())
+            std::str::from_utf8(contents.data())
                 .ok()?
                 .split('\n')
                 .next()?,
@@ -116,13 +117,13 @@ pub(crate) struct SyntectHighlighterState<'h> {
 
 impl<'h> HighlighterState for SyntectHighlighterState<'h> {
     fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>> {
-        if let Ok(ops) = self.parse_state.parse_line(line, &self.syntax_set) {
+        if let Ok(ops) = self.parse_state.parse_line(line, self.syntax_set) {
             let use_bg_color = self.use_bg_color;
             syntect::HighlightIterator::new(
                 &mut self.highlight_state,
                 &ops,
                 line,
-                &mut self.highlighter,
+                &self.highlighter,
             )
             .map(|(style, str)| (convert_style(style, use_bg_color).style(str)))
             .collect()
diff --git a/src/lib.rs b/src/lib.rs
index 2533fe9..8946990 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,6 @@
 #![deny(missing_docs, missing_debug_implementations, nonstandard_style)]
 #![warn(unreachable_pub, rust_2018_idioms)]
+#![allow(unexpected_cfgs)]
 //! You run miette? You run her code like the software? Oh. Oh! Error code for
 //! coder! Error code for One Thousand Lines!
 //!
@@ -162,13 +163,11 @@
 //! \
 //! diagnostic error code: oops::my::bad (link)
 //! Error: oops!
-//!
 //! \
 //! Begin snippet for bad_file.rs starting
 //! at line 2, column 3
 //! \
 //! snippet line 1: source
-//!
 //! \
 //! snippet line 2:  text
 //!     highlight starting at line 1, column 3: This bit here
diff --git a/src/panic.rs b/src/panic.rs
index ad98cac..c258b32 100644
--- a/src/panic.rs
+++ b/src/panic.rs
@@ -12,7 +12,7 @@ pub fn set_panic_hook() {
             message = msg.to_string();
         }
         if let Some(msg) = payload.downcast_ref::<String>() {
-            message = msg.clone();
+            message.clone_from(msg);
         }
         let mut report: Result<()> = Err(Panic(message).into());
         if let Some(loc) = info.location() {
diff --git a/src/protocol.rs b/src/protocol.rs
index 589cd0b..d3ee9ca 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -614,6 +614,28 @@ impl From<std::ops::Range<ByteOffset>> for SourceSpan {
     }
 }
 
+impl From<std::ops::RangeInclusive<ByteOffset>> for SourceSpan {
+    /// # Panics
+    ///
+    /// Panics if the total length of the inclusive range would overflow a
+    /// `usize`. This will only occur with the range `0..=usize::MAX`.
+    fn from(range: std::ops::RangeInclusive<ByteOffset>) -> Self {
+        let (start, end) = range.clone().into_inner();
+        Self {
+            offset: start.into(),
+            length: if range.is_empty() {
+                0
+            } else {
+                // will not overflow because `is_empty() == false` guarantees
+                // that `start <= end`
+                (end - start)
+                    .checked_add(1)
+                    .expect("length of inclusive range should fit in a usize")
+            },
+        }
+    }
+}
+
 impl From<SourceOffset> for SourceSpan {
     fn from(offset: SourceOffset) -> Self {
         Self { offset, length: 0 }


Test patch:
diff --git a/tests/test_boxed.rs b/tests/test_boxed.rs
index d6fa6f8..9f9f4e2 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!"),