From 1b6a04ca5504955c571d1c97504fb45ea0befee4 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Mon, 8 Jan 2024 01:21:28 +0400 Subject: Initial vendor packages Signed-off-by: Valentin Popov --- vendor/miette/src/chain.rs | 117 ++++ vendor/miette/src/diagnostic_chain.rs | 93 +++ vendor/miette/src/error.rs | 27 + vendor/miette/src/eyreish/context.rs | 217 +++++++ vendor/miette/src/eyreish/error.rs | 810 +++++++++++++++++++++++ vendor/miette/src/eyreish/fmt.rs | 20 + vendor/miette/src/eyreish/into_diagnostic.rs | 33 + vendor/miette/src/eyreish/kind.rs | 111 ++++ vendor/miette/src/eyreish/macros.rs | 300 +++++++++ vendor/miette/src/eyreish/mod.rs | 485 ++++++++++++++ vendor/miette/src/eyreish/ptr.rs | 188 ++++++ vendor/miette/src/eyreish/wrapper.rs | 234 +++++++ vendor/miette/src/handler.rs | 323 ++++++++++ vendor/miette/src/handlers/debug.rs | 71 +++ vendor/miette/src/handlers/graphical.rs | 920 +++++++++++++++++++++++++++ vendor/miette/src/handlers/json.rs | 182 ++++++ vendor/miette/src/handlers/mod.rs | 24 + vendor/miette/src/handlers/narratable.rs | 423 ++++++++++++ vendor/miette/src/handlers/theme.rs | 275 ++++++++ vendor/miette/src/lib.rs | 682 ++++++++++++++++++++ vendor/miette/src/macro_helpers.rs | 38 ++ vendor/miette/src/miette_diagnostic.rs | 365 +++++++++++ vendor/miette/src/named_source.rs | 61 ++ vendor/miette/src/panic.rs | 86 +++ vendor/miette/src/protocol.rs | 697 ++++++++++++++++++++ vendor/miette/src/source_impls.rs | 301 +++++++++ 26 files changed, 7083 insertions(+) create mode 100644 vendor/miette/src/chain.rs create mode 100644 vendor/miette/src/diagnostic_chain.rs create mode 100644 vendor/miette/src/error.rs create mode 100644 vendor/miette/src/eyreish/context.rs create mode 100644 vendor/miette/src/eyreish/error.rs create mode 100644 vendor/miette/src/eyreish/fmt.rs create mode 100644 vendor/miette/src/eyreish/into_diagnostic.rs create mode 100644 vendor/miette/src/eyreish/kind.rs create mode 100644 vendor/miette/src/eyreish/macros.rs create mode 100644 vendor/miette/src/eyreish/mod.rs create mode 100644 vendor/miette/src/eyreish/ptr.rs create mode 100644 vendor/miette/src/eyreish/wrapper.rs create mode 100644 vendor/miette/src/handler.rs create mode 100644 vendor/miette/src/handlers/debug.rs create mode 100644 vendor/miette/src/handlers/graphical.rs create mode 100644 vendor/miette/src/handlers/json.rs create mode 100644 vendor/miette/src/handlers/mod.rs create mode 100644 vendor/miette/src/handlers/narratable.rs create mode 100644 vendor/miette/src/handlers/theme.rs create mode 100644 vendor/miette/src/lib.rs create mode 100644 vendor/miette/src/macro_helpers.rs create mode 100644 vendor/miette/src/miette_diagnostic.rs create mode 100644 vendor/miette/src/named_source.rs create mode 100644 vendor/miette/src/panic.rs create mode 100644 vendor/miette/src/protocol.rs create mode 100644 vendor/miette/src/source_impls.rs (limited to 'vendor/miette/src') diff --git a/vendor/miette/src/chain.rs b/vendor/miette/src/chain.rs new file mode 100644 index 0000000..7a66383 --- /dev/null +++ b/vendor/miette/src/chain.rs @@ -0,0 +1,117 @@ +/*! +Iterate over error `.source()` chains. + +NOTE: This module is taken wholesale from . +*/ +use std::error::Error as StdError; +use std::vec; + +use ChainState::*; + +/// Iterator of a chain of source errors. +/// +/// This type is the iterator returned by [`Report::chain`]. +/// +/// # Example +/// +/// ``` +/// use miette::Report; +/// use std::io; +/// +/// pub fn underlying_io_error_kind(error: &Report) -> Option { +/// for cause in error.chain() { +/// if let Some(io_error) = cause.downcast_ref::() { +/// return Some(io_error.kind()); +/// } +/// } +/// None +/// } +/// ``` +#[derive(Clone)] +#[allow(missing_debug_implementations)] +pub struct Chain<'a> { + state: crate::chain::ChainState<'a>, +} + +#[derive(Clone)] +pub(crate) enum ChainState<'a> { + Linked { + next: Option<&'a (dyn StdError + 'static)>, + }, + Buffered { + rest: vec::IntoIter<&'a (dyn StdError + 'static)>, + }, +} + +impl<'a> Chain<'a> { + pub(crate) fn new(head: &'a (dyn StdError + 'static)) -> Self { + Chain { + state: ChainState::Linked { next: Some(head) }, + } + } +} + +impl<'a> Iterator for Chain<'a> { + type Item = &'a (dyn StdError + 'static); + + fn next(&mut self) -> Option { + match &mut self.state { + Linked { next } => { + let error = (*next)?; + *next = error.source(); + Some(error) + } + Buffered { rest } => rest.next(), + } + } + + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } +} + +impl DoubleEndedIterator for Chain<'_> { + fn next_back(&mut self) -> Option { + match &mut self.state { + Linked { mut next } => { + let mut rest = Vec::new(); + while let Some(cause) = next { + next = cause.source(); + rest.push(cause); + } + let mut rest = rest.into_iter(); + let last = rest.next_back(); + self.state = Buffered { rest }; + last + } + Buffered { rest } => rest.next_back(), + } + } +} + +impl ExactSizeIterator for Chain<'_> { + fn len(&self) -> usize { + match &self.state { + Linked { mut next } => { + let mut len = 0; + while let Some(cause) = next { + next = cause.source(); + len += 1; + } + len + } + Buffered { rest } => rest.len(), + } + } +} + +impl Default for Chain<'_> { + fn default() -> Self { + Chain { + state: ChainState::Buffered { + rest: Vec::new().into_iter(), + }, + } + } +} diff --git a/vendor/miette/src/diagnostic_chain.rs b/vendor/miette/src/diagnostic_chain.rs new file mode 100644 index 0000000..1e5e0c2 --- /dev/null +++ b/vendor/miette/src/diagnostic_chain.rs @@ -0,0 +1,93 @@ +/*! +Iterate over error `.diagnostic_source()` chains. +*/ + +use crate::protocol::Diagnostic; + +/// Iterator of a chain of cause errors. +#[derive(Clone, Default)] +#[allow(missing_debug_implementations)] +pub(crate) struct DiagnosticChain<'a> { + state: Option>, +} + +impl<'a> DiagnosticChain<'a> { + pub(crate) fn from_diagnostic(head: &'a dyn Diagnostic) -> Self { + DiagnosticChain { + state: Some(ErrorKind::Diagnostic(head)), + } + } + + pub(crate) fn from_stderror(head: &'a (dyn std::error::Error + 'static)) -> Self { + DiagnosticChain { + state: Some(ErrorKind::StdError(head)), + } + } +} + +impl<'a> Iterator for DiagnosticChain<'a> { + type Item = ErrorKind<'a>; + + fn next(&mut self) -> Option { + if let Some(err) = self.state.take() { + self.state = err.get_nested(); + Some(err) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } +} + +impl ExactSizeIterator for DiagnosticChain<'_> { + fn len(&self) -> usize { + fn depth(d: Option<&ErrorKind<'_>>) -> usize { + match d { + Some(d) => 1 + depth(d.get_nested().as_ref()), + None => 0, + } + } + + depth(self.state.as_ref()) + } +} + +#[derive(Clone)] +pub(crate) enum ErrorKind<'a> { + Diagnostic(&'a dyn Diagnostic), + StdError(&'a (dyn std::error::Error + 'static)), +} + +impl<'a> ErrorKind<'a> { + fn get_nested(&self) -> Option> { + match self { + ErrorKind::Diagnostic(d) => d + .diagnostic_source() + .map(ErrorKind::Diagnostic) + .or_else(|| d.source().map(ErrorKind::StdError)), + ErrorKind::StdError(e) => e.source().map(ErrorKind::StdError), + } + } +} + +impl<'a> std::fmt::Debug for ErrorKind<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ErrorKind::Diagnostic(d) => d.fmt(f), + ErrorKind::StdError(e) => e.fmt(f), + } + } +} + +impl<'a> std::fmt::Display for ErrorKind<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ErrorKind::Diagnostic(d) => d.fmt(f), + ErrorKind::StdError(e) => e.fmt(f), + } + } +} diff --git a/vendor/miette/src/error.rs b/vendor/miette/src/error.rs new file mode 100644 index 0000000..56041ca --- /dev/null +++ b/vendor/miette/src/error.rs @@ -0,0 +1,27 @@ +use std::io; + +use thiserror::Error; + +use crate::{self as miette, Diagnostic}; + +/** +Error enum for miette. Used by certain operations in the protocol. +*/ +#[derive(Debug, Diagnostic, Error)] +pub enum MietteError { + /// Wrapper around [`std::io::Error`]. This is returned when something went + /// wrong while reading a [`SourceCode`](crate::SourceCode). + #[error(transparent)] + #[diagnostic(code(miette::io_error), url(docsrs))] + IoError(#[from] io::Error), + + /// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the + /// bounds of a given [`SourceCode`](crate::SourceCode). + #[error("The given offset is outside the bounds of its Source")] + #[diagnostic( + code(miette::span_out_of_bounds), + help("Double-check your spans. Do you have an off-by-one error?"), + url(docsrs) + )] + OutOfBounds, +} diff --git a/vendor/miette/src/eyreish/context.rs b/vendor/miette/src/eyreish/context.rs new file mode 100644 index 0000000..3d9238b --- /dev/null +++ b/vendor/miette/src/eyreish/context.rs @@ -0,0 +1,217 @@ +use super::error::{ContextError, ErrorImpl}; +use super::{Report, WrapErr}; +use core::fmt::{self, Debug, Display, Write}; + +use std::error::Error as StdError; + +use crate::{Diagnostic, LabeledSpan}; + +mod ext { + use super::*; + + pub trait Diag { + #[cfg_attr(track_caller, track_caller)] + fn ext_report(self, msg: D) -> Report + where + D: Display + Send + Sync + 'static; + } + + impl Diag for E + where + E: Diagnostic + Send + Sync + 'static, + { + fn ext_report(self, msg: D) -> Report + where + D: Display + Send + Sync + 'static, + { + Report::from_msg(msg, self) + } + } + + impl Diag for Report { + fn ext_report(self, msg: D) -> Report + where + D: Display + Send + Sync + 'static, + { + self.wrap_err(msg) + } + } +} + +impl WrapErr for Result +where + E: ext::Diag + Send + Sync + 'static, +{ + fn wrap_err(self, msg: D) -> Result + where + D: Display + Send + Sync + 'static, + { + match self { + Ok(t) => Ok(t), + Err(e) => Err(e.ext_report(msg)), + } + } + + fn wrap_err_with(self, msg: F) -> Result + where + D: Display + Send + Sync + 'static, + F: FnOnce() -> D, + { + match self { + Ok(t) => Ok(t), + Err(e) => Err(e.ext_report(msg())), + } + } + + fn context(self, msg: D) -> Result + where + D: Display + Send + Sync + 'static, + { + self.wrap_err(msg) + } + + fn with_context(self, msg: F) -> Result + where + D: Display + Send + Sync + 'static, + F: FnOnce() -> D, + { + self.wrap_err_with(msg) + } +} + +impl Debug for ContextError +where + D: Display, + E: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Error") + .field("msg", &Quoted(&self.msg)) + .field("source", &self.error) + .finish() + } +} + +impl Display for ContextError +where + D: Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.msg, f) + } +} + +impl StdError for ContextError +where + D: Display, + E: StdError + 'static, +{ + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(&self.error) + } +} + +impl StdError for ContextError +where + D: Display, +{ + fn source(&self) -> Option<&(dyn StdError + 'static)> { + unsafe { Some(ErrorImpl::error(self.error.inner.by_ref())) } + } +} + +impl Diagnostic for ContextError +where + D: Display, + E: Diagnostic + 'static, +{ + fn code<'a>(&'a self) -> Option> { + self.error.code() + } + + fn severity(&self) -> Option { + self.error.severity() + } + + fn help<'a>(&'a self) -> Option> { + self.error.help() + } + + fn url<'a>(&'a self) -> Option> { + self.error.url() + } + + fn labels<'a>(&'a self) -> Option + 'a>> { + self.error.labels() + } + + fn source_code(&self) -> Option<&dyn crate::SourceCode> { + self.error.source_code() + } + + fn related<'a>(&'a self) -> Option + 'a>> { + self.error.related() + } +} + +impl Diagnostic for ContextError +where + D: Display, +{ + fn code<'a>(&'a self) -> Option> { + unsafe { ErrorImpl::diagnostic(self.error.inner.by_ref()).code() } + } + + fn severity(&self) -> Option { + unsafe { ErrorImpl::diagnostic(self.error.inner.by_ref()).severity() } + } + + fn help<'a>(&'a self) -> Option> { + unsafe { ErrorImpl::diagnostic(self.error.inner.by_ref()).help() } + } + + fn url<'a>(&'a self) -> Option> { + unsafe { ErrorImpl::diagnostic(self.error.inner.by_ref()).url() } + } + + fn labels<'a>(&'a self) -> Option + 'a>> { + unsafe { ErrorImpl::diagnostic(self.error.inner.by_ref()).labels() } + } + + fn source_code(&self) -> Option<&dyn crate::SourceCode> { + self.error.source_code() + } + + fn related<'a>(&'a self) -> Option + 'a>> { + self.error.related() + } +} + +struct Quoted(D); + +impl Debug for Quoted +where + D: Display, +{ + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_char('"')?; + Quoted(&mut *formatter).write_fmt(format_args!("{}", self.0))?; + formatter.write_char('"')?; + Ok(()) + } +} + +impl Write for Quoted<&mut fmt::Formatter<'_>> { + fn write_str(&mut self, s: &str) -> fmt::Result { + Display::fmt(&s.escape_debug(), self.0) + } +} + +pub(crate) mod private { + use super::*; + + pub trait Sealed {} + + impl Sealed for Result where E: ext::Diag {} + impl Sealed for Option {} +} diff --git a/vendor/miette/src/eyreish/error.rs b/vendor/miette/src/eyreish/error.rs new file mode 100644 index 0000000..6b0dc34 --- /dev/null +++ b/vendor/miette/src/eyreish/error.rs @@ -0,0 +1,810 @@ +use core::any::TypeId; +use core::fmt::{self, Debug, Display}; +use core::mem::ManuallyDrop; +use core::ptr::{self, NonNull}; +use std::error::Error as StdError; + +use super::ptr::{Mut, Own, Ref}; +use super::Report; +use super::ReportHandler; +use crate::chain::Chain; +use crate::eyreish::wrapper::WithSourceCode; +use crate::{Diagnostic, SourceCode}; +use core::ops::{Deref, DerefMut}; + +impl Report { + /// Create a new error object from any error type. + /// + /// The error type must be thread safe and `'static`, so that the `Report` + /// will be as well. + /// + /// 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)] + pub fn new(error: E) -> Self + where + E: Diagnostic + Send + Sync + 'static, + { + Report::from_std(error) + } + + /// Create a new error object from a printable error message. + /// + /// If the argument implements std::error::Error, prefer `Report::new` + /// instead which preserves the underlying error's cause chain and + /// backtrace. If the argument may or may not implement std::error::Error + /// now or in the future, use `miette!(err)` which handles either way + /// correctly. + /// + /// `Report::msg("...")` is equivalent to `miette!("...")` but occasionally + /// convenient in places where a function is preferable over a macro, such + /// as iterator or stream combinators: + /// + /// ``` + /// # mod ffi { + /// # pub struct Input; + /// # pub struct Output; + /// # pub async fn do_some_work(_: Input) -> Result { + /// # unimplemented!() + /// # } + /// # } + /// # + /// # use ffi::{Input, Output}; + /// # + /// use futures::stream::{Stream, StreamExt, TryStreamExt}; + /// use miette::{Report, Result}; + /// + /// async fn demo(stream: S) -> Result> + /// where + /// S: Stream, + /// { + /// stream + /// .then(ffi::do_some_work) // returns Result + /// .map_err(Report::msg) + /// .try_collect() + /// .await + /// } + /// ``` + #[cfg_attr(track_caller, track_caller)] + pub fn msg(message: M) -> Self + where + M: Display + Debug + Send + Sync + 'static, + { + Report::from_adhoc(message) + } + + /// Create a new error object from a boxed [`Diagnostic`]. + /// + /// The boxed type must be thread safe and 'static, so that the `Report` + /// will be as well. + /// + /// Boxed `Diagnostic`s don't implement `Diagnostic` themselves due to trait coherence issues. + /// This method allows you to create a `Report` from a boxed `Diagnostic`. + #[cfg_attr(track_caller, track_caller)] + pub fn new_boxed(error: Box) -> Self { + Report::from_boxed(error) + } + + #[cfg_attr(track_caller, track_caller)] + pub(crate) fn from_std(error: E) -> Self + where + E: Diagnostic + Send + Sync + 'static, + { + let vtable = &ErrorVTable { + object_drop: object_drop::, + object_ref: object_ref::, + object_ref_stderr: object_ref_stderr::, + object_boxed: object_boxed::, + object_boxed_stderr: object_boxed_stderr::, + object_downcast: object_downcast::, + object_drop_rest: object_drop_front::, + }; + + // Safety: passing vtable that operates on the right type E. + let handler = Some(super::capture_handler(&error)); + + unsafe { Report::construct(error, vtable, handler) } + } + + #[cfg_attr(track_caller, track_caller)] + pub(crate) fn from_adhoc(message: M) -> Self + where + M: Display + Debug + Send + Sync + 'static, + { + use super::wrapper::MessageError; + let error: MessageError = MessageError(message); + let vtable = &ErrorVTable { + object_drop: object_drop::>, + object_ref: object_ref::>, + object_ref_stderr: object_ref_stderr::>, + object_boxed: object_boxed::>, + object_boxed_stderr: object_boxed_stderr::>, + object_downcast: object_downcast::, + object_drop_rest: object_drop_front::, + }; + + // Safety: MessageError is repr(transparent) so it is okay for the + // vtable to allow casting the MessageError to M. + let handler = Some(super::capture_handler(&error)); + + unsafe { Report::construct(error, vtable, handler) } + } + + #[cfg_attr(track_caller, track_caller)] + pub(crate) fn from_msg(msg: D, error: E) -> Self + where + D: Display + Send + Sync + 'static, + E: Diagnostic + Send + Sync + 'static, + { + let error: ContextError = ContextError { msg, error }; + + let vtable = &ErrorVTable { + object_drop: object_drop::>, + object_ref: object_ref::>, + object_ref_stderr: object_ref_stderr::>, + object_boxed: object_boxed::>, + object_boxed_stderr: object_boxed_stderr::>, + object_downcast: context_downcast::, + object_drop_rest: context_drop_rest::, + }; + + // Safety: passing vtable that operates on the right type. + let handler = Some(super::capture_handler(&error)); + + unsafe { Report::construct(error, vtable, handler) } + } + + #[cfg_attr(track_caller, track_caller)] + pub(crate) fn from_boxed(error: Box) -> Self { + use super::wrapper::BoxedError; + let error = BoxedError(error); + let handler = Some(super::capture_handler(&error)); + + let vtable = &ErrorVTable { + object_drop: object_drop::, + object_ref: object_ref::, + object_ref_stderr: object_ref_stderr::, + object_boxed: object_boxed::, + object_boxed_stderr: object_boxed_stderr::, + object_downcast: object_downcast::>, + object_drop_rest: object_drop_front::>, + }; + + // Safety: BoxedError is repr(transparent) so it is okay for the vtable + // to allow casting to Box. + unsafe { Report::construct(error, vtable, handler) } + } + + // Takes backtrace as argument rather than capturing it here so that the + // user sees one fewer layer of wrapping noise in the backtrace. + // + // Unsafe because the given vtable must have sensible behavior on the error + // value of type E. + unsafe fn construct( + error: E, + vtable: &'static ErrorVTable, + handler: Option>, + ) -> Self + where + E: Diagnostic + Send + Sync + 'static, + { + let inner = Box::new(ErrorImpl { + vtable, + handler, + _object: error, + }); + // Erase the concrete type of E from the compile-time type system. This + // is equivalent to the safe unsize coercion from Box> to + // Box> except that the + // result is a thin pointer. The necessary behavior for manipulating the + // underlying ErrorImpl is preserved in the vtable provided by the + // caller rather than a builtin fat pointer vtable. + let inner = Own::new(inner).cast::(); + Report { inner } + } + + /// Create a new error from an error message to wrap the existing error. + /// + /// For attaching a higher level error message to a `Result` as it is + /// propagated, the [crate::WrapErr] extension trait may be more + /// convenient than this function. + /// + /// The primary reason to use `error.wrap_err(...)` instead of + /// `result.wrap_err(...)` via the `WrapErr` trait would be if the + /// message needs to depend on some data held by the underlying error: + pub fn wrap_err(self, msg: D) -> Self + where + D: Display + Send + Sync + 'static, + { + let handler = unsafe { self.inner.by_mut().deref_mut().handler.take() }; + let error: ContextError = ContextError { msg, error: self }; + + let vtable = &ErrorVTable { + object_drop: object_drop::>, + object_ref: object_ref::>, + object_ref_stderr: object_ref_stderr::>, + object_boxed: object_boxed::>, + object_boxed_stderr: object_boxed_stderr::>, + object_downcast: context_chain_downcast::, + object_drop_rest: context_chain_drop_rest::, + }; + + // Safety: passing vtable that operates on the right type. + unsafe { Report::construct(error, vtable, handler) } + } + + /// Compatibility re-export of wrap_err for interop with `anyhow` + pub fn context(self, msg: D) -> Self + where + D: Display + Send + Sync + 'static, + { + self.wrap_err(msg) + } + + /// An iterator of the chain of source errors contained by this Report. + /// + /// This iterator will visit every error in the cause chain of this error + /// object, beginning with the error that this error object was created + /// from. + /// + /// # Example + /// + /// ``` + /// use miette::Report; + /// use std::io; + /// + /// pub fn underlying_io_error_kind(error: &Report) -> Option { + /// for cause in error.chain() { + /// if let Some(io_error) = cause.downcast_ref::() { + /// return Some(io_error.kind()); + /// } + /// } + /// None + /// } + /// ``` + pub fn chain(&self) -> Chain<'_> { + unsafe { ErrorImpl::chain(self.inner.by_ref()) } + } + + /// The lowest level cause of this error — this error's cause's + /// cause's cause etc. + /// + /// The root cause is the last error in the iterator produced by + /// [`chain()`](Report::chain). + pub fn root_cause(&self) -> &(dyn StdError + 'static) { + self.chain().last().unwrap() + } + + /// Returns true if `E` is the type held by this error object. + /// + /// For errors constructed from messages, this method returns true if `E` + /// matches the type of the message `D` **or** the type of the error on + /// which the message has been attached. For details about the + /// interaction between message and downcasting, [see here]. + /// + /// [see here]: trait.WrapErr.html#effect-on-downcasting + pub fn is(&self) -> bool + where + E: Display + Debug + Send + Sync + 'static, + { + self.downcast_ref::().is_some() + } + + /// Attempt to downcast the error object to a concrete type. + pub fn downcast(self) -> Result + where + E: Display + Debug + Send + Sync + 'static, + { + let target = TypeId::of::(); + let inner = self.inner.by_mut(); + unsafe { + // Use vtable to find NonNull<()> which points to a value of type E + // somewhere inside the data structure. + let addr = match (vtable(inner.ptr).object_downcast)(inner.by_ref(), target) { + Some(addr) => addr.by_mut().extend(), + None => return Err(self), + }; + + // Prepare to read E out of the data structure. We'll drop the rest + // of the data structure separately so that E is not dropped. + let outer = ManuallyDrop::new(self); + + // Read E from where the vtable found it. + let error = addr.cast::().read(); + + // Drop rest of the data structure outside of E. + (vtable(outer.inner.ptr).object_drop_rest)(outer.inner, target); + + Ok(error) + } + } + + /// Downcast this error object by reference. + /// + /// # Example + /// + /// ``` + /// # use miette::{Report, miette}; + /// # use std::fmt::{self, Display}; + /// # use std::task::Poll; + /// # + /// # #[derive(Debug)] + /// # enum DataStoreError { + /// # Censored(()), + /// # } + /// # + /// # impl Display for DataStoreError { + /// # fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + /// # unimplemented!() + /// # } + /// # } + /// # + /// # impl std::error::Error for DataStoreError {} + /// # + /// # const REDACTED_CONTENT: () = (); + /// # + /// # let error: Report = miette!("..."); + /// # let root_cause = &error; + /// # + /// # let ret = + /// // If the error was caused by redaction, then return a tombstone instead + /// // of the content. + /// match root_cause.downcast_ref::() { + /// Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)), + /// None => Err(error), + /// } + /// # ; + /// ``` + pub fn downcast_ref(&self) -> Option<&E> + where + E: Display + Debug + Send + Sync + 'static, + { + let target = TypeId::of::(); + unsafe { + // Use vtable to find NonNull<()> which points to a value of type E + // somewhere inside the data structure. + let addr = (vtable(self.inner.ptr).object_downcast)(self.inner.by_ref(), target)?; + Some(addr.cast::().deref()) + } + } + + /// Downcast this error object by mutable reference. + pub fn downcast_mut(&mut self) -> Option<&mut E> + where + E: Display + Debug + Send + Sync + 'static, + { + let target = TypeId::of::(); + unsafe { + // Use vtable to find NonNull<()> which points to a value of type E + // somewhere inside the data structure. + let addr = + (vtable(self.inner.ptr).object_downcast)(self.inner.by_ref(), target)?.by_mut(); + Some(addr.cast::().deref_mut()) + } + } + + /// Get a reference to the Handler for this Report. + pub fn handler(&self) -> &dyn ReportHandler { + unsafe { + self.inner + .by_ref() + .deref() + .handler + .as_ref() + .unwrap() + .as_ref() + } + } + + /// Get a mutable reference to the Handler for this Report. + pub fn handler_mut(&mut self) -> &mut dyn ReportHandler { + unsafe { + self.inner + .by_mut() + .deref_mut() + .handler + .as_mut() + .unwrap() + .as_mut() + } + } + + /// Provide source code for this error + pub fn with_source_code(self, source_code: impl SourceCode + Send + Sync + 'static) -> Report { + WithSourceCode { + source_code, + error: self, + } + .into() + } +} + +impl From for Report +where + E: Diagnostic + Send + Sync + 'static, +{ + #[cfg_attr(track_caller, track_caller)] + fn from(error: E) -> Self { + Report::from_std(error) + } +} + +impl Deref for Report { + type Target = dyn Diagnostic + Send + Sync + 'static; + + fn deref(&self) -> &Self::Target { + unsafe { ErrorImpl::diagnostic(self.inner.by_ref()) } + } +} + +impl DerefMut for Report { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { ErrorImpl::diagnostic_mut(self.inner.by_mut()) } + } +} + +impl Display for Report { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + unsafe { ErrorImpl::display(self.inner.by_ref(), formatter) } + } +} + +impl Debug for Report { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + unsafe { ErrorImpl::debug(self.inner.by_ref(), formatter) } + } +} + +impl Drop for Report { + fn drop(&mut self) { + unsafe { + // Invoke the vtable's drop behavior. + (vtable(self.inner.ptr).object_drop)(self.inner); + } + } +} + +struct ErrorVTable { + object_drop: unsafe fn(Own), + object_ref: + unsafe fn(Ref<'_, ErasedErrorImpl>) -> Ref<'_, dyn Diagnostic + Send + Sync + 'static>, + object_ref_stderr: + unsafe fn(Ref<'_, ErasedErrorImpl>) -> Ref<'_, dyn StdError + Send + Sync + 'static>, + #[allow(clippy::type_complexity)] + object_boxed: unsafe fn(Own) -> Box, + #[allow(clippy::type_complexity)] + object_boxed_stderr: + unsafe fn(Own) -> Box, + object_downcast: unsafe fn(Ref<'_, ErasedErrorImpl>, TypeId) -> Option>, + object_drop_rest: unsafe fn(Own, TypeId), +} + +// Safety: requires layout of *e to match ErrorImpl. +unsafe fn object_drop(e: Own) { + // Cast back to ErrorImpl so that the allocator receives the correct + // Layout to deallocate the Box's memory. + let unerased = e.cast::>().boxed(); + drop(unerased); +} + +// Safety: requires layout of *e to match ErrorImpl. +unsafe fn object_drop_front(e: Own, target: TypeId) { + // Drop the fields of ErrorImpl other than E as well as the Box allocation, + // without dropping E itself. This is used by downcast after doing a + // ptr::read to take ownership of the E. + let _ = target; + let unerased = e.cast::>>().boxed(); + drop(unerased); +} + +// Safety: requires layout of *e to match ErrorImpl. +unsafe fn object_ref( + e: Ref<'_, ErasedErrorImpl>, +) -> Ref<'_, dyn Diagnostic + Send + Sync + 'static> +where + E: Diagnostic + Send + Sync + 'static, +{ + // Attach E's native StdError vtable onto a pointer to self._object. + let unerased = e.cast::>(); + + Ref::from_raw(NonNull::new_unchecked( + ptr::addr_of!((*unerased.as_ptr())._object) as *mut E, + )) +} + +// Safety: requires layout of *e to match ErrorImpl. +unsafe fn object_ref_stderr( + e: Ref<'_, ErasedErrorImpl>, +) -> Ref<'_, dyn StdError + Send + Sync + 'static> +where + E: StdError + Send + Sync + 'static, +{ + // Attach E's native StdError vtable onto a pointer to self._object. + let unerased = e.cast::>(); + + Ref::from_raw(NonNull::new_unchecked( + ptr::addr_of!((*unerased.as_ptr())._object) as *mut E, + )) +} + +// Safety: requires layout of *e to match ErrorImpl. +unsafe fn object_boxed(e: Own) -> Box +where + E: Diagnostic + Send + Sync + 'static, +{ + // Attach ErrorImpl's native StdError vtable. The StdError impl is below. + e.cast::>().boxed() +} + +// Safety: requires layout of *e to match ErrorImpl. +unsafe fn object_boxed_stderr( + e: Own, +) -> Box +where + E: StdError + Send + Sync + 'static, +{ + // Attach ErrorImpl's native StdError vtable. The StdError impl is below. + e.cast::>().boxed() +} + +// Safety: requires layout of *e to match ErrorImpl. +unsafe fn object_downcast(e: Ref<'_, ErasedErrorImpl>, target: TypeId) -> Option> +where + E: 'static, +{ + if TypeId::of::() == target { + // Caller is looking for an E pointer and e is ErrorImpl, take a + // pointer to its E field. + let unerased = e.cast::>(); + + Some( + Ref::from_raw(NonNull::new_unchecked( + ptr::addr_of!((*unerased.as_ptr())._object) as *mut E, + )) + .cast::<()>(), + ) + } else { + None + } +} + +// Safety: requires layout of *e to match ErrorImpl>. +unsafe fn context_downcast(e: Ref<'_, ErasedErrorImpl>, target: TypeId) -> Option> +where + D: 'static, + E: 'static, +{ + if TypeId::of::() == target { + let unerased = e.cast::>>().deref(); + Some(Ref::new(&unerased._object.msg).cast::<()>()) + } else if TypeId::of::() == target { + let unerased = e.cast::>>().deref(); + Some(Ref::new(&unerased._object.error).cast::<()>()) + } else { + None + } +} + +// Safety: requires layout of *e to match ErrorImpl>. +unsafe fn context_drop_rest(e: Own, target: TypeId) +where + D: 'static, + E: 'static, +{ + // Called after downcasting by value to either the D or the E and doing a + // ptr::read to take ownership of that value. + if TypeId::of::() == target { + let unerased = e + .cast::, E>>>() + .boxed(); + drop(unerased); + } else { + let unerased = e + .cast::>>>() + .boxed(); + drop(unerased); + } +} + +// Safety: requires layout of *e to match ErrorImpl>. +unsafe fn context_chain_downcast( + e: Ref<'_, ErasedErrorImpl>, + target: TypeId, +) -> Option> +where + D: 'static, +{ + let unerased = e.cast::>>().deref(); + if TypeId::of::() == target { + Some(Ref::new(&unerased._object.msg).cast::<()>()) + } else { + // Recurse down the context chain per the inner error's vtable. + let source = &unerased._object.error; + (vtable(source.inner.ptr).object_downcast)(source.inner.by_ref(), target) + } +} + +// Safety: requires layout of *e to match ErrorImpl>. +unsafe fn context_chain_drop_rest(e: Own, target: TypeId) +where + D: 'static, +{ + // Called after downcasting by value to either the D or one of the causes + // and doing a ptr::read to take ownership of that value. + if TypeId::of::() == target { + let unerased = e + .cast::, Report>>>() + .boxed(); + // Drop the entire rest of the data structure rooted in the next Report. + drop(unerased); + } else { + let unerased = e + .cast::>>>() + .boxed(); + // Read out a ManuallyDrop> from the next error. + let inner = unerased._object.error.inner; + drop(unerased); + let vtable = vtable(inner.ptr); + // Recursively drop the next error using the same target typeid. + (vtable.object_drop_rest)(inner, target); + } +} + +// repr C to ensure that E remains in the final position. +#[repr(C)] +pub(crate) struct ErrorImpl { + vtable: &'static ErrorVTable, + pub(crate) handler: Option>, + // NOTE: Don't use directly. Use only through vtable. Erased type may have + // different alignment. + _object: E, +} + +// repr C to ensure that ContextError has the same layout as +// ContextError, E> and ContextError>. +#[repr(C)] +pub(crate) struct ContextError { + pub(crate) msg: D, + pub(crate) error: E, +} + +type ErasedErrorImpl = ErrorImpl<()>; + +// Safety: `ErrorVTable` must be the first field of `ErrorImpl` +unsafe fn vtable(p: NonNull) -> &'static ErrorVTable { + (p.as_ptr() as *const &'static ErrorVTable).read() +} + +impl ErrorImpl { + fn erase(&self) -> Ref<'_, ErasedErrorImpl> { + // Erase the concrete type of E but preserve the vtable in self.vtable + // for manipulating the resulting thin pointer. This is analogous to an + // unsize coercion. + Ref::new(self).cast::() + } +} + +impl ErasedErrorImpl { + pub(crate) unsafe fn error<'a>( + this: Ref<'a, Self>, + ) -> &'a (dyn StdError + Send + Sync + 'static) { + // Use vtable to attach E's native StdError vtable for the right + // original type E. + (vtable(this.ptr).object_ref_stderr)(this).deref() + } + + pub(crate) unsafe fn diagnostic<'a>( + this: Ref<'a, Self>, + ) -> &'a (dyn Diagnostic + Send + Sync + 'static) { + // Use vtable to attach E's native StdError vtable for the right + // original type E. + (vtable(this.ptr).object_ref)(this).deref() + } + + pub(crate) unsafe fn diagnostic_mut<'a>( + this: Mut<'a, Self>, + ) -> &'a mut (dyn Diagnostic + Send + Sync + 'static) { + // Use vtable to attach E's native StdError vtable for the right + // original type E. + (vtable(this.ptr).object_ref)(this.by_ref()) + .by_mut() + .deref_mut() + } + + pub(crate) unsafe fn chain(this: Ref<'_, Self>) -> Chain<'_> { + Chain::new(Self::error(this)) + } +} + +impl StdError for ErrorImpl +where + E: StdError, +{ + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + unsafe { ErrorImpl::diagnostic(self.erase()).source() } + } +} + +impl Diagnostic for ErrorImpl where E: Diagnostic {} + +impl Debug for ErrorImpl +where + E: Debug, +{ + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + unsafe { ErrorImpl::debug(self.erase(), formatter) } + } +} + +impl Display for ErrorImpl +where + E: Display, +{ + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + unsafe { Display::fmt(ErrorImpl::diagnostic(self.erase()), formatter) } + } +} + +impl From for Box { + fn from(error: Report) -> Self { + let outer = ManuallyDrop::new(error); + unsafe { + // Use vtable to attach ErrorImpl's native StdError vtable for + // the right original type E. + (vtable(outer.inner.ptr).object_boxed)(outer.inner) + } + } +} + +impl From for Box { + fn from(error: Report) -> Self { + let outer = ManuallyDrop::new(error); + unsafe { + // Use vtable to attach ErrorImpl's native StdError vtable for + // the right original type E. + (vtable(outer.inner.ptr).object_boxed_stderr)(outer.inner) + } + } +} + +impl From for Box { + fn from(error: Report) -> Self { + Box::::from(error) + } +} + +impl From for Box { + fn from(error: Report) -> Self { + Box::::from(error) + } +} + +impl AsRef for Report { + fn as_ref(&self) -> &(dyn Diagnostic + Send + Sync + 'static) { + &**self + } +} + +impl AsRef for Report { + fn as_ref(&self) -> &(dyn Diagnostic + 'static) { + &**self + } +} + +impl AsRef for Report { + fn as_ref(&self) -> &(dyn StdError + Send + Sync + 'static) { + unsafe { ErrorImpl::error(self.inner.by_ref()) } + } +} + +impl AsRef for Report { + fn as_ref(&self) -> &(dyn StdError + 'static) { + unsafe { ErrorImpl::error(self.inner.by_ref()) } + } +} + +impl std::borrow::Borrow for Report { + fn borrow(&self) -> &(dyn Diagnostic + 'static) { + self.as_ref() + } +} diff --git a/vendor/miette/src/eyreish/fmt.rs b/vendor/miette/src/eyreish/fmt.rs new file mode 100644 index 0000000..9e385d1 --- /dev/null +++ b/vendor/miette/src/eyreish/fmt.rs @@ -0,0 +1,20 @@ +use super::{error::ErrorImpl, ptr::Ref}; +use core::fmt; + +impl ErrorImpl<()> { + pub(crate) unsafe fn display(this: Ref<'_, Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result { + this.deref() + .handler + .as_ref() + .map(|handler| handler.display(Self::error(this), f)) + .unwrap_or_else(|| core::fmt::Display::fmt(Self::diagnostic(this), f)) + } + + pub(crate) unsafe fn debug(this: Ref<'_, Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result { + this.deref() + .handler + .as_ref() + .map(|handler| handler.debug(Self::diagnostic(this), f)) + .unwrap_or_else(|| core::fmt::Debug::fmt(Self::diagnostic(this), f)) + } +} diff --git a/vendor/miette/src/eyreish/into_diagnostic.rs b/vendor/miette/src/eyreish/into_diagnostic.rs new file mode 100644 index 0000000..6480013 --- /dev/null +++ b/vendor/miette/src/eyreish/into_diagnostic.rs @@ -0,0 +1,33 @@ +use thiserror::Error; + +use crate::{Diagnostic, Report}; + +/// Convenience [`Diagnostic`] that can be used as an "anonymous" wrapper for +/// Errors. This is intended to be paired with [`IntoDiagnostic`]. +#[derive(Debug, Error)] +#[error(transparent)] +struct DiagnosticError(Box); +impl Diagnostic for DiagnosticError {} + +/** +Convenience trait that adds a [`.into_diagnostic()`](IntoDiagnostic::into_diagnostic) method that converts a type implementing +[`std::error::Error`] to a [`Result`]. + +## Warning + +Calling this on a type implementing [`Diagnostic`] will reduce it to the common denominator of +[`std::error::Error`]. Meaning all extra information provided by [`Diagnostic`] will be +inaccessible. If you have a type implementing [`Diagnostic`] consider simply returning it or using +[`Into`] or the [`Try`](std::ops::Try) operator (`?`). +*/ +pub trait IntoDiagnostic { + /// Converts [`Result`] types that return regular [`std::error::Error`]s + /// into a [`Result`] that returns a [`Diagnostic`]. + fn into_diagnostic(self) -> Result; +} + +impl IntoDiagnostic for Result { + fn into_diagnostic(self) -> Result { + self.map_err(|e| DiagnosticError(Box::new(e)).into()) + } +} diff --git a/vendor/miette/src/eyreish/kind.rs b/vendor/miette/src/eyreish/kind.rs new file mode 100644 index 0000000..ce60b50 --- /dev/null +++ b/vendor/miette/src/eyreish/kind.rs @@ -0,0 +1,111 @@ +#![allow(missing_debug_implementations, missing_docs)] +// Tagged dispatch mechanism for resolving the behavior of `miette!($expr)`. +// +// When miette! is given a single expr argument to turn into miette::Report, we +// want the resulting Report to pick up the input's implementation of source() +// and backtrace() if it has a std::error::Error impl, otherwise require nothing +// more than Display and Debug. +// +// Expressed in terms of specialization, we want something like: +// +// trait EyreNew { +// fn new(self) -> Report; +// } +// +// impl EyreNew for T +// where +// T: Display + Debug + Send + Sync + 'static, +// { +// default fn new(self) -> Report { +// /* no std error impl */ +// } +// } +// +// impl EyreNew for T +// where +// T: std::error::Error + Send + Sync + 'static, +// { +// fn new(self) -> Report { +// /* use std error's source() and backtrace() */ +// } +// } +// +// Since specialization is not stable yet, instead we rely on autoref behavior +// of method resolution to perform tagged dispatch. Here we have two traits +// AdhocKind and TraitKind that both have an miette_kind() method. AdhocKind is +// implemented whether or not the caller's type has a std error impl, while +// TraitKind is implemented only when a std error impl does exist. The ambiguity +// is resolved by AdhocKind requiring an extra autoref so that it has lower +// precedence. +// +// The miette! macro will set up the call in this form: +// +// #[allow(unused_imports)] +// use $crate::private::{AdhocKind, TraitKind}; +// let error = $msg; +// (&error).miette_kind().new(error) + +use super::Report; +use core::fmt::{Debug, Display}; + +use crate::Diagnostic; + +pub struct Adhoc; + +pub trait AdhocKind: Sized { + #[inline] + fn miette_kind(&self) -> Adhoc { + Adhoc + } +} + +impl AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'static {} + +impl Adhoc { + #[cfg_attr(track_caller, track_caller)] + pub fn new(self, message: M) -> Report + where + M: Display + Debug + Send + Sync + 'static, + { + Report::from_adhoc(message) + } +} + +pub struct Trait; + +pub trait TraitKind: Sized { + #[inline] + fn miette_kind(&self) -> Trait { + Trait + } +} + +impl TraitKind for E where E: Into {} + +impl Trait { + #[cfg_attr(track_caller, track_caller)] + pub fn new(self, error: E) -> Report + where + E: Into, + { + error.into() + } +} + +pub struct Boxed; + +pub trait BoxedKind: Sized { + #[inline] + fn miette_kind(&self) -> Boxed { + Boxed + } +} + +impl BoxedKind for Box {} + +impl Boxed { + #[cfg_attr(track_caller, track_caller)] + pub fn new(self, error: Box) -> Report { + Report::from_boxed(error) + } +} diff --git a/vendor/miette/src/eyreish/macros.rs b/vendor/miette/src/eyreish/macros.rs new file mode 100644 index 0000000..e13309f --- /dev/null +++ b/vendor/miette/src/eyreish/macros.rs @@ -0,0 +1,300 @@ +/// Return early with an error. +/// +/// This macro is equivalent to `return Err(From::from($err))`. +/// +/// # Example +/// +/// ``` +/// # use miette::{bail, Result}; +/// # +/// # fn has_permission(user: usize, resource: usize) -> bool { +/// # true +/// # } +/// # +/// # fn main() -> Result<()> { +/// # let user = 0; +/// # let resource = 0; +/// # +/// if !has_permission(user, resource) { +#[cfg_attr( + not(feature = "no-format-args-capture"), + doc = r#" bail!("permission denied for accessing {resource}");"# +)] +#[cfg_attr( + feature = "no-format-args-capture", + doc = r#" bail!("permission denied for accessing {}", resource);"# +)] +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// ``` +/// # use miette::{bail, Result}; +/// # use thiserror::Error; +/// # +/// # const MAX_DEPTH: usize = 1; +/// # +/// #[derive(Error, Debug)] +/// enum ScienceError { +/// #[error("recursion limit exceeded")] +/// RecursionLimitExceeded, +/// # #[error("...")] +/// # More = (stringify! { +/// ... +/// # }, 1).1, +/// } +/// +/// # fn main() -> Result<()> { +/// # let depth = 0; +/// # let err: &'static dyn std::error::Error = &ScienceError::RecursionLimitExceeded; +/// # +/// if depth > MAX_DEPTH { +/// bail!(ScienceError::RecursionLimitExceeded); +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// ``` +/// use miette::{bail, Result, Severity}; +/// +/// fn divide(x: f64, y: f64) -> Result { +/// if y.abs() < 1e-3 { +/// bail!( +/// severity = Severity::Warning, +#[cfg_attr( + not(feature = "no-format-args-capture"), + doc = r#" "dividing by value ({y}) close to 0""# +)] +#[cfg_attr( + feature = "no-format-args-capture", + doc = r#" "dividing by value ({}) close to 0", y"# +)] +/// ); +/// } +/// Ok(x / y) +/// } +/// ``` +#[macro_export] +macro_rules! bail { + ($($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => { + return $crate::private::Err( + $crate::miette!($($key = $value,)* $fmt $($arg)*) + ); + }; + ($err:expr $(,)?) => { + return $crate::private::Err($crate::miette!($err)); + }; +} + +/// Return early with an error if a condition is not satisfied. +/// +/// This macro is equivalent to `if !$cond { return Err(From::from($err)); }`. +/// +/// Analogously to `assert!`, `ensure!` takes a condition and exits the function +/// if the condition fails. Unlike `assert!`, `ensure!` returns an `Error` +/// rather than panicking. +/// +/// # Example +/// +/// ``` +/// # use miette::{ensure, Result}; +/// # +/// # fn main() -> Result<()> { +/// # let user = 0; +/// # +/// ensure!(user == 0, "only user 0 is allowed"); +/// # Ok(()) +/// # } +/// ``` +/// +/// ``` +/// # use miette::{ensure, Result}; +/// # use thiserror::Error; +/// # +/// # const MAX_DEPTH: usize = 1; +/// # +/// #[derive(Error, Debug)] +/// enum ScienceError { +/// #[error("recursion limit exceeded")] +/// RecursionLimitExceeded, +/// # #[error("...")] +/// # More = (stringify! { +/// ... +/// # }, 1).1, +/// } +/// +/// # fn main() -> Result<()> { +/// # let depth = 0; +/// # +/// ensure!(depth <= MAX_DEPTH, ScienceError::RecursionLimitExceeded); +/// # Ok(()) +/// # } +/// ``` +/// +/// ``` +/// use miette::{ensure, Result, Severity}; +/// +/// fn divide(x: f64, y: f64) -> Result { +/// ensure!( +/// y.abs() >= 1e-3, +/// severity = Severity::Warning, +#[cfg_attr( + not(feature = "no-format-args-capture"), + doc = r#" "dividing by value ({y}) close to 0""# +)] +#[cfg_attr( + feature = "no-format-args-capture", + doc = r#" "dividing by value ({}) close to 0", y"# +)] +/// ); +/// Ok(x / y) +/// } +/// ``` +#[macro_export] +macro_rules! ensure { + ($cond:expr, $($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => { + if !$cond { + return $crate::private::Err( + $crate::miette!($($key = $value,)* $fmt $($arg)*) + ); + } + }; + ($cond:expr, $err:expr $(,)?) => { + if !$cond { + return $crate::private::Err($crate::miette!($err)); + } + }; +} + +/// Construct an ad-hoc [`Report`]. +/// +/// # Examples +/// +/// With string literal and interpolation: +/// ``` +/// # use miette::miette; +/// let x = 1; +/// let y = 2; +#[cfg_attr( + not(feature = "no-format-args-capture"), + doc = r#"let report = miette!("{x} + {} = {z}", y, z = x + y);"# +)] +#[cfg_attr( + feature = "no-format-args-capture", + doc = r#"let report = miette!("{} + {} = {z}", x, y, z = x + y);"# +)] +/// +/// assert_eq!(report.to_string().as_str(), "1 + 2 = 3"); +/// +/// let z = x + y; +#[cfg_attr( + not(feature = "no-format-args-capture"), + doc = r#"let report = miette!("{x} + {y} = {z}");"# +)] +#[cfg_attr( + feature = "no-format-args-capture", + doc = r#"let report = miette!("{} + {} = {}", x, y, z);"# +)] +/// assert_eq!(report.to_string().as_str(), "1 + 2 = 3"); +/// ``` +/// +/// With [`diagnostic!`]-like arguments: +/// ``` +/// use miette::{miette, LabeledSpan, Severity}; +/// +/// let source = "(2 + 2".to_string(); +/// let report = miette!( +/// // Those fields are optional +/// severity = Severity::Error, +/// code = "expected::rparen", +/// help = "always close your parens", +/// labels = vec![LabeledSpan::at_offset(6, "here")], +/// url = "https://example.com", +/// // Rest of the arguments are passed to `format!` +/// // to form diagnostic message +/// "expected closing ')'" +/// ) +/// .with_source_code(source); +/// ``` +/// +/// ## `anyhow`/`eyre` Users +/// +/// You can just replace `use`s of the `anyhow!`/`eyre!` macros with `miette!`. +/// +/// [`diagnostic!`]: crate::diagnostic! +/// [`Report`]: crate::Report +#[macro_export] +macro_rules! miette { + ($($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => { + $crate::Report::from( + $crate::diagnostic!($($key = $value,)* $fmt $($arg)*) + ) + }; + ($err:expr $(,)?) => ({ + use $crate::private::kind::*; + let error = $err; + (&error).miette_kind().new(error) + }); +} + +/// Construct a [`MietteDiagnostic`] in more user-friendly way. +/// +/// # Examples +/// ``` +/// use miette::{diagnostic, LabeledSpan, Severity}; +/// +/// let source = "(2 + 2".to_string(); +/// let diag = diagnostic!( +/// // Those fields are optional +/// severity = Severity::Error, +/// code = "expected::rparen", +/// help = "always close your parens", +/// labels = vec![LabeledSpan::at_offset(6, "here")], +/// url = "https://example.com", +/// // Rest of the arguments are passed to `format!` +/// // to form diagnostic message +/// "expected closing ')'", +/// ); +/// ``` +/// Diagnostic without any fields: +/// ``` +/// # use miette::diagnostic; +/// let x = 1; +/// let y = 2; +/// +#[cfg_attr( + not(feature = "no-format-args-capture"), + doc = r#" let diag = diagnostic!("{x} + {} = {z}", y, z = x + y);"# +)] +#[cfg_attr( + feature = "no-format-args-capture", + doc = r#" let diag = diagnostic!("{} + {} = {z}", x, y, z = x + y);"# +)] +/// assert_eq!(diag.message, "1 + 2 = 3"); +/// +/// let z = x + y; +#[cfg_attr( + not(feature = "no-format-args-capture"), + doc = r#"let diag = diagnostic!("{x} + {y} = {z}");"# +)] +#[cfg_attr( + feature = "no-format-args-capture", + doc = r#"let diag = diagnostic!("{} + {} = {}", x, y, z);"# +)] +/// assert_eq!(diag.message, "1 + 2 = 3"); +/// ``` +/// +/// [`MietteDiagnostic`]: crate::MietteDiagnostic +#[macro_export] +macro_rules! diagnostic { + ($fmt:literal $($arg:tt)*) => {{ + $crate::MietteDiagnostic::new(format!($fmt $($arg)*)) + }}; + ($($key:ident = $value:expr,)+ $fmt:literal $($arg:tt)*) => {{ + let mut diag = $crate::MietteDiagnostic::new(format!($fmt $($arg)*)); + $(diag.$key = Some($value.into());)* + diag + }}; +} diff --git a/vendor/miette/src/eyreish/mod.rs b/vendor/miette/src/eyreish/mod.rs new file mode 100644 index 0000000..0efceed --- /dev/null +++ b/vendor/miette/src/eyreish/mod.rs @@ -0,0 +1,485 @@ +#![cfg_attr(doc_cfg, feature(doc_cfg))] +#![allow( + clippy::needless_doctest_main, + clippy::new_ret_no_self, + clippy::wrong_self_convention +)] +use core::fmt::Display; + +use std::error::Error as StdError; + +use once_cell::sync::OnceCell; + +#[allow(unreachable_pub)] +pub use into_diagnostic::*; +#[doc(hidden)] +#[allow(unreachable_pub)] +pub use Report as ErrReport; +/// Compatibility re-export of `Report` for interop with `anyhow` +#[allow(unreachable_pub)] +pub use Report as Error; +#[doc(hidden)] +#[allow(unreachable_pub)] +pub use ReportHandler as EyreContext; +/// Compatibility re-export of `WrapErr` for interop with `anyhow` +#[allow(unreachable_pub)] +pub use WrapErr as Context; + +#[cfg(not(feature = "fancy-no-backtrace"))] +use crate::DebugReportHandler; +use crate::Diagnostic; +#[cfg(feature = "fancy-no-backtrace")] +use crate::MietteHandler; + +use error::ErrorImpl; + +use self::ptr::Own; + +mod context; +mod error; +mod fmt; +mod into_diagnostic; +mod kind; +mod macros; +mod ptr; +mod wrapper; + +/** +Core Diagnostic wrapper type. + +## `eyre` Users + +You can just replace `use`s of `eyre::Report` with `miette::Report`. +*/ +pub struct Report { + inner: Own>, +} + +unsafe impl Sync for Report {} +unsafe impl Send for Report {} + +/// +pub type ErrorHook = + Box Box + Sync + Send + 'static>; + +static HOOK: OnceCell = OnceCell::new(); + +/// Error indicating that [`set_hook()`] was unable to install the provided +/// [`ErrorHook`]. +#[derive(Debug)] +pub struct InstallError; + +impl core::fmt::Display for InstallError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("cannot install provided ErrorHook, a hook has already been installed") + } +} + +impl StdError for InstallError {} +impl Diagnostic for InstallError {} + +/** +Set the error hook. +*/ +pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { + HOOK.set(hook).map_err(|_| InstallError) +} + +#[cfg_attr(track_caller, track_caller)] +#[cfg_attr(not(track_caller), allow(unused_mut))] +fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box { + let hook = HOOK.get_or_init(|| Box::new(get_default_printer)).as_ref(); + + #[cfg(track_caller)] + { + let mut handler = hook(error); + handler.track_caller(std::panic::Location::caller()); + handler + } + #[cfg(not(track_caller))] + { + hook(error) + } +} + +fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box { + #[cfg(feature = "fancy-no-backtrace")] + return Box::new(MietteHandler::new()); + #[cfg(not(feature = "fancy-no-backtrace"))] + return Box::new(DebugReportHandler::new()); +} + +impl dyn ReportHandler { + /// + pub fn is(&self) -> bool { + // Get `TypeId` of the type this function is instantiated with. + let t = core::any::TypeId::of::(); + + // Get `TypeId` of the type in the trait object (`self`). + let concrete = self.type_id(); + + // Compare both `TypeId`s on equality. + t == concrete + } + + /// + pub fn downcast_ref(&self) -> Option<&T> { + if self.is::() { + unsafe { Some(&*(self as *const dyn ReportHandler as *const T)) } + } else { + None + } + } + + /// + pub fn downcast_mut(&mut self) -> Option<&mut T> { + if self.is::() { + unsafe { Some(&mut *(self as *mut dyn ReportHandler as *mut T)) } + } else { + None + } + } +} + +/// Error Report Handler trait for customizing `miette::Report` +pub trait ReportHandler: core::any::Any + Send + Sync { + /// Define the report format + /// + /// Used to override the report format of `miette::Report` + /// + /// # Example + /// + /// ```rust + /// use indenter::indented; + /// use miette::{Diagnostic, ReportHandler}; + /// + /// pub struct Handler; + /// + /// impl ReportHandler for Handler { + /// fn debug( + /// &self, + /// error: &dyn Diagnostic, + /// f: &mut core::fmt::Formatter<'_>, + /// ) -> core::fmt::Result { + /// use core::fmt::Write as _; + /// + /// if f.alternate() { + /// return core::fmt::Debug::fmt(error, f); + /// } + /// + /// write!(f, "{}", error)?; + /// + /// Ok(()) + /// } + /// } + /// ``` + fn debug( + &self, + error: &(dyn Diagnostic), + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result; + + /// Override for the `Display` format + fn display( + &self, + error: &(dyn StdError + 'static), + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + write!(f, "{}", error)?; + + if f.alternate() { + for cause in crate::chain::Chain::new(error).skip(1) { + write!(f, ": {}", cause)?; + } + } + + Ok(()) + } + + /// Store the location of the caller who constructed this error report + #[allow(unused_variables)] + fn track_caller(&mut self, location: &'static std::panic::Location<'static>) {} +} + +/// type alias for `Result` +/// +/// This is a reasonable return type to use throughout your application but also +/// for `main()`. If you do, failures will be printed along with a backtrace if +/// one was captured. +/// +/// `miette::Result` may be used with one *or* two type parameters. +/// +/// ```rust +/// use miette::Result; +/// +/// # const IGNORE: &str = stringify! { +/// fn demo1() -> Result {...} +/// // ^ equivalent to std::result::Result +/// +/// fn demo2() -> Result {...} +/// // ^ equivalent to std::result::Result +/// # }; +/// ``` +/// +/// # Example +/// +/// ``` +/// # pub trait Deserialize {} +/// # +/// # mod serde_json { +/// # use super::Deserialize; +/// # use std::io; +/// # +/// # pub fn from_str(json: &str) -> io::Result { +/// # unimplemented!() +/// # } +/// # } +/// # +/// # #[derive(Debug)] +/// # struct ClusterMap; +/// # +/// # impl Deserialize for ClusterMap {} +/// # +/// use miette::{IntoDiagnostic, Result}; +/// +/// fn main() -> Result<()> { +/// # return Ok(()); +/// let config = std::fs::read_to_string("cluster.json").into_diagnostic()?; +/// let map: ClusterMap = serde_json::from_str(&config).into_diagnostic()?; +/// println!("cluster info: {:#?}", map); +/// Ok(()) +/// } +/// ``` +/// +/// ## `anyhow`/`eyre` Users +/// +/// You can just replace `use`s of `anyhow::Result`/`eyre::Result` with +/// `miette::Result`. +pub type Result = core::result::Result; + +/// Provides the [`wrap_err()`](WrapErr::wrap_err) method for [`Result`]. +/// +/// This trait is sealed and cannot be implemented for types outside of +/// `miette`. +/// +/// # Example +/// +/// ``` +/// use miette::{WrapErr, IntoDiagnostic, Result}; +/// use std::{fs, path::PathBuf}; +/// +/// pub struct ImportantThing { +/// path: PathBuf, +/// } +/// +/// impl ImportantThing { +/// # const IGNORE: &'static str = stringify! { +/// pub fn detach(&mut self) -> Result<()> {...} +/// # }; +/// # fn detach(&mut self) -> Result<()> { +/// # unimplemented!() +/// # } +/// } +/// +/// pub fn do_it(mut it: ImportantThing) -> Result> { +/// it.detach().wrap_err("Failed to detach the important thing")?; +/// +/// let path = &it.path; +/// let content = fs::read(path) +/// .into_diagnostic() +/// .wrap_err_with(|| format!( +/// "Failed to read instrs from {}", +/// path.display()) +/// )?; +/// +/// Ok(content) +/// } +/// ``` +/// +/// When printed, the outermost error would be printed first and the lower +/// level underlying causes would be enumerated below. +/// +/// ```console +/// Error: Failed to read instrs from ./path/to/instrs.json +/// +/// Caused by: +/// No such file or directory (os error 2) +/// ``` +/// +/// # Wrapping Types That Do Not Implement `Error` +/// +/// For example `&str` and `Box`. +/// +/// Due to restrictions for coherence `Report` cannot implement `From` for types +/// that don't implement `Error`. Attempts to do so will give `"this type might +/// implement Error in the future"` as an error. As such, `wrap_err()`, which +/// uses `From` under the hood, cannot be used to wrap these types. Instead we +/// encourage you to use the combinators provided for `Result` in `std`/`core`. +/// +/// For example, instead of this: +/// +/// ```rust,compile_fail +/// use std::error::Error; +/// use miette::{WrapErr, Report}; +/// +/// fn wrap_example(err: Result<(), Box>) +/// -> Result<(), Report> +/// { +/// err.wrap_err("saw a downstream error") +/// } +/// ``` +/// +/// We encourage you to write this: +/// +/// ```rust +/// use miette::{miette, Report, WrapErr}; +/// use std::error::Error; +/// +/// fn wrap_example(err: Result<(), Box>) -> Result<(), Report> { +/// err.map_err(|e| miette!(e)) +/// .wrap_err("saw a downstream error") +/// } +/// ``` +/// +/// # Effect on Downcasting +/// +/// After attaching a message of type `D` onto an error of type `E`, the +/// resulting `miette::Error` may be downcast to `D` **or** to `E`. +/// +/// That is, in codebases that rely on downcasting, `miette`'s `wrap_err()` +/// supports both of the following use cases: +/// +/// - **Attaching messages whose type is insignificant onto errors whose type +/// is used in downcasts.** +/// +/// In other error libraries whose `wrap_err()` is not designed this way, it +/// can be risky to introduce messages to existing code because new message +/// might break existing working downcasts. In miette, any downcast that +/// worked before adding the message will continue to work after you add a +/// message, so you should freely wrap errors wherever it would be helpful. +/// +/// ``` +/// # use miette::bail; +/// # use thiserror::Error; +/// # +/// # #[derive(Error, Debug)] +/// # #[error("???")] +/// # struct SuspiciousError; +/// # +/// # fn helper() -> Result<()> { +/// # bail!(SuspiciousError); +/// # } +/// # +/// use miette::{WrapErr, Result}; +/// +/// fn do_it() -> Result<()> { +/// helper().wrap_err("Failed to complete the work")?; +/// # const IGNORE: &str = stringify! { +/// ... +/// # }; +/// # unreachable!() +/// } +/// +/// fn main() { +/// let err = do_it().unwrap_err(); +/// if let Some(e) = err.downcast_ref::() { +/// // If helper() returned SuspiciousError, this downcast will +/// // correctly succeed even with the message in between. +/// # return; +/// } +/// # panic!("expected downcast to succeed"); +/// } +/// ``` +/// +/// - **Attaching message whose type is used in downcasts onto errors whose +/// type is insignificant.** +/// +/// Some codebases prefer to use machine-readable messages to categorize +/// lower level errors in a way that will be actionable to higher levels of +/// the application. +/// +/// ``` +/// # use miette::bail; +/// # use thiserror::Error; +/// # +/// # #[derive(Error, Debug)] +/// # #[error("???")] +/// # struct HelperFailed; +/// # +/// # fn helper() -> Result<()> { +/// # bail!("no such file or directory"); +/// # } +/// # +/// use miette::{WrapErr, Result}; +/// +/// fn do_it() -> Result<()> { +/// helper().wrap_err(HelperFailed)?; +/// # const IGNORE: &str = stringify! { +/// ... +/// # }; +/// # unreachable!() +/// } +/// +/// fn main() { +/// let err = do_it().unwrap_err(); +/// if let Some(e) = err.downcast_ref::() { +/// // If helper failed, this downcast will succeed because +/// // HelperFailed is the message that has been attached to +/// // that error. +/// # return; +/// } +/// # panic!("expected downcast to succeed"); +/// } +/// ``` +pub trait WrapErr: context::private::Sealed { + /// Wrap the error value with a new adhoc error + #[cfg_attr(track_caller, track_caller)] + fn wrap_err(self, msg: D) -> Result + where + D: Display + Send + Sync + 'static; + + /// Wrap the error value with a new adhoc error that is evaluated lazily + /// only once an error does occur. + #[cfg_attr(track_caller, track_caller)] + fn wrap_err_with(self, f: F) -> Result + where + D: Display + Send + Sync + 'static, + F: FnOnce() -> D; + + /// Compatibility re-export of `wrap_err()` for interop with `anyhow` + #[cfg_attr(track_caller, track_caller)] + fn context(self, msg: D) -> Result + where + D: Display + Send + Sync + 'static; + + /// Compatibility re-export of `wrap_err_with()` for interop with `anyhow` + #[cfg_attr(track_caller, track_caller)] + fn with_context(self, f: F) -> Result + where + D: Display + Send + Sync + 'static, + F: FnOnce() -> D; +} + +// Private API. Referenced by macro-generated code. +#[doc(hidden)] +pub mod private { + use super::Report; + use core::fmt::{Debug, Display}; + + pub use core::result::Result::Err; + + #[doc(hidden)] + pub mod kind { + pub use super::super::kind::{AdhocKind, TraitKind}; + + pub use super::super::kind::BoxedKind; + } + + #[cfg_attr(track_caller, track_caller)] + pub fn new_adhoc(message: M) -> Report + where + M: Display + Debug + Send + Sync + 'static, + { + Report::from_adhoc(message) + } +} diff --git a/vendor/miette/src/eyreish/ptr.rs b/vendor/miette/src/eyreish/ptr.rs new file mode 100644 index 0000000..fa954d1 --- /dev/null +++ b/vendor/miette/src/eyreish/ptr.rs @@ -0,0 +1,188 @@ +use std::{marker::PhantomData, ptr::NonNull}; + +#[repr(transparent)] +/// A raw pointer that owns its pointee +pub(crate) struct Own +where + T: ?Sized, +{ + pub(crate) ptr: NonNull, +} + +unsafe impl Send for Own where T: ?Sized {} +unsafe impl Sync for Own where T: ?Sized {} + +impl Copy for Own where T: ?Sized {} + +impl Clone for Own +where + T: ?Sized, +{ + fn clone(&self) -> Self { + *self + } +} + +impl Own +where + T: ?Sized, +{ + pub(crate) fn new(ptr: Box) -> Self { + Own { + ptr: unsafe { NonNull::new_unchecked(Box::into_raw(ptr)) }, + } + } + + pub(crate) fn cast(self) -> Own { + Own { + ptr: self.ptr.cast(), + } + } + + pub(crate) unsafe fn boxed(self) -> Box { + Box::from_raw(self.ptr.as_ptr()) + } + + pub(crate) const fn by_ref<'a>(&self) -> Ref<'a, T> { + Ref { + ptr: self.ptr, + lifetime: PhantomData, + } + } + + pub(crate) fn by_mut<'a>(self) -> Mut<'a, T> { + Mut { + ptr: self.ptr, + lifetime: PhantomData, + } + } +} + +#[allow(explicit_outlives_requirements)] +#[repr(transparent)] +/// A raw pointer that represents a shared borrow of its pointee +pub(crate) struct Ref<'a, T> +where + T: ?Sized, +{ + pub(crate) ptr: NonNull, + lifetime: PhantomData<&'a T>, +} + +impl<'a, T> Copy for Ref<'a, T> where T: ?Sized {} + +impl<'a, T> Clone for Ref<'a, T> +where + T: ?Sized, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Ref<'a, T> +where + T: ?Sized, +{ + pub(crate) fn new(ptr: &'a T) -> Self { + Ref { + ptr: NonNull::from(ptr), + lifetime: PhantomData, + } + } + + pub(crate) const fn from_raw(ptr: NonNull) -> Self { + Ref { + ptr, + lifetime: PhantomData, + } + } + + pub(crate) fn cast(self) -> Ref<'a, U::Target> { + Ref { + ptr: self.ptr.cast(), + lifetime: PhantomData, + } + } + + pub(crate) fn by_mut(self) -> Mut<'a, T> { + Mut { + ptr: self.ptr, + lifetime: PhantomData, + } + } + + pub(crate) const fn as_ptr(self) -> *const T { + self.ptr.as_ptr() as *const T + } + + pub(crate) unsafe fn deref(self) -> &'a T { + &*self.ptr.as_ptr() + } +} + +#[allow(explicit_outlives_requirements)] +#[repr(transparent)] +/// A raw pointer that represents a unique borrow of its pointee +pub(crate) struct Mut<'a, T> +where + T: ?Sized, +{ + pub(crate) ptr: NonNull, + lifetime: PhantomData<&'a mut T>, +} + +impl<'a, T> Copy for Mut<'a, T> where T: ?Sized {} + +impl<'a, T> Clone for Mut<'a, T> +where + T: ?Sized, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Mut<'a, T> +where + T: ?Sized, +{ + pub(crate) fn cast(self) -> Mut<'a, U::Target> { + Mut { + ptr: self.ptr.cast(), + lifetime: PhantomData, + } + } + + pub(crate) const fn by_ref(self) -> Ref<'a, T> { + Ref { + ptr: self.ptr, + lifetime: PhantomData, + } + } + + pub(crate) fn extend<'b>(self) -> Mut<'b, T> { + Mut { + ptr: self.ptr, + lifetime: PhantomData, + } + } + + pub(crate) unsafe fn deref_mut(self) -> &'a mut T { + &mut *self.ptr.as_ptr() + } +} + +impl<'a, T> Mut<'a, T> { + pub(crate) unsafe fn read(self) -> T { + self.ptr.as_ptr().read() + } +} + +pub(crate) trait CastTo { + type Target; +} + +impl CastTo for T { + type Target = T; +} diff --git a/vendor/miette/src/eyreish/wrapper.rs b/vendor/miette/src/eyreish/wrapper.rs new file mode 100644 index 0000000..91a5ef3 --- /dev/null +++ b/vendor/miette/src/eyreish/wrapper.rs @@ -0,0 +1,234 @@ +use core::fmt::{self, Debug, Display}; + +use std::error::Error as StdError; + +use crate::{Diagnostic, LabeledSpan, Report, SourceCode}; + +use crate as miette; + +#[repr(transparent)] +pub(crate) struct DisplayError(pub(crate) M); + +#[repr(transparent)] +pub(crate) struct MessageError(pub(crate) M); + +pub(crate) struct NoneError; + +impl Debug for DisplayError +where + M: Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl Display for DisplayError +where + M: Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl StdError for DisplayError where M: Display + 'static {} +impl Diagnostic for DisplayError where M: Display + 'static {} + +impl Debug for MessageError +where + M: Display + Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.0, f) + } +} + +impl Display for MessageError +where + M: Display + Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl StdError for MessageError where M: Display + Debug + 'static {} +impl Diagnostic for MessageError where M: Display + Debug + 'static {} + +impl Debug for NoneError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt("Option was None", f) + } +} + +impl Display for NoneError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt("Option was None", f) + } +} + +impl StdError for NoneError {} +impl Diagnostic for NoneError {} + +#[repr(transparent)] +pub(crate) struct BoxedError(pub(crate) Box); + +impl Diagnostic for BoxedError { + fn code<'a>(&'a self) -> Option> { + self.0.code() + } + + fn severity(&self) -> Option { + self.0.severity() + } + + fn help<'a>(&'a self) -> Option> { + self.0.help() + } + + fn url<'a>(&'a self) -> Option> { + self.0.url() + } + + fn labels<'a>(&'a self) -> Option + 'a>> { + self.0.labels() + } + + fn source_code(&self) -> Option<&dyn miette::SourceCode> { + self.0.source_code() + } + + fn related<'a>(&'a self) -> Option + 'a>> { + self.0.related() + } + + fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { + self.0.diagnostic_source() + } +} + +impl Debug for BoxedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.0, f) + } +} + +impl Display for BoxedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl StdError for BoxedError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + self.0.source() + } + + fn description(&self) -> &str { + #[allow(deprecated)] + self.0.description() + } + + fn cause(&self) -> Option<&dyn StdError> { + #[allow(deprecated)] + self.0.cause() + } +} + +pub(crate) struct WithSourceCode { + pub(crate) error: E, + pub(crate) source_code: C, +} + +impl Diagnostic for WithSourceCode { + fn code<'a>(&'a self) -> Option> { + self.error.code() + } + + fn severity(&self) -> Option { + self.error.severity() + } + + fn help<'a>(&'a self) -> Option> { + self.error.help() + } + + fn url<'a>(&'a self) -> Option> { + self.error.url() + } + + fn labels<'a>(&'a self) -> Option + 'a>> { + self.error.labels() + } + + fn source_code(&self) -> Option<&dyn miette::SourceCode> { + Some(&self.source_code) + } + + fn related<'a>(&'a self) -> Option + 'a>> { + self.error.related() + } + + fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { + self.error.diagnostic_source() + } +} + +impl Diagnostic for WithSourceCode { + fn code<'a>(&'a self) -> Option> { + self.error.code() + } + + fn severity(&self) -> Option { + self.error.severity() + } + + fn help<'a>(&'a self) -> Option> { + self.error.help() + } + + fn url<'a>(&'a self) -> Option> { + self.error.url() + } + + fn labels<'a>(&'a self) -> Option + 'a>> { + self.error.labels() + } + + fn source_code(&self) -> Option<&dyn miette::SourceCode> { + Some(&self.source_code) + } + + fn related<'a>(&'a self) -> Option + 'a>> { + self.error.related() + } + + fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { + self.error.diagnostic_source() + } +} + +impl Debug for WithSourceCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.error, f) + } +} + +impl Display for WithSourceCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.error, f) + } +} + +impl StdError for WithSourceCode { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + self.error.source() + } +} + +impl StdError for WithSourceCode { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + self.error.source() + } +} diff --git a/vendor/miette/src/handler.rs b/vendor/miette/src/handler.rs new file mode 100644 index 0000000..e983a55 --- /dev/null +++ b/vendor/miette/src/handler.rs @@ -0,0 +1,323 @@ +use std::fmt; + +use crate::protocol::Diagnostic; +use crate::GraphicalReportHandler; +use crate::GraphicalTheme; +use crate::NarratableReportHandler; +use crate::ReportHandler; +use crate::ThemeCharacters; +use crate::ThemeStyles; + +/// Settings to control the color format used for graphical rendering. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +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 + Never, +} + +impl Default for RgbColors { + fn default() -> RgbColors { + RgbColors::Never + } +} + +/** +Create a custom [`MietteHandler`] from options. + +## Example + +```no_run +miette::set_hook(Box::new(|_| { + Box::new(miette::MietteHandlerOpts::new() + .terminal_links(true) + .unicode(false) + .context_lines(3) + .build()) +})) +# .unwrap(); +``` +*/ +#[derive(Default, Debug, Clone)] +pub struct MietteHandlerOpts { + pub(crate) linkify: Option, + pub(crate) width: Option, + pub(crate) theme: Option, + pub(crate) force_graphical: Option, + pub(crate) force_narrated: Option, + pub(crate) rgb_colors: RgbColors, + pub(crate) color: Option, + pub(crate) unicode: Option, + pub(crate) footer: Option, + pub(crate) context_lines: Option, + pub(crate) tab_width: Option, + pub(crate) with_cause_chain: Option, +} + +impl MietteHandlerOpts { + /// Create a new `MietteHandlerOpts`. + pub fn new() -> Self { + Default::default() + } + + /// If true, specify whether the graphical handler will make codes be + /// clickable links in supported terminals. Defaults to auto-detection + /// based on known supported terminals. + pub fn terminal_links(mut self, linkify: bool) -> Self { + self.linkify = Some(linkify); + self + } + + /// Set a graphical theme for the handler when rendering in graphical mode. + /// Use [`force_graphical()`](`MietteHandlerOpts::force_graphical) to force + /// graphical mode. This option overrides + /// [`color()`](`MietteHandlerOpts::color). + pub fn graphical_theme(mut self, theme: GraphicalTheme) -> Self { + self.theme = Some(theme); + self + } + + /// Sets the width to wrap the report at. Defaults to 80. + pub fn width(mut self, width: usize) -> Self { + self.width = Some(width); + self + } + + /// Include the cause chain of the top-level error in the report. + pub fn with_cause_chain(mut self) -> Self { + self.with_cause_chain = Some(true); + self + } + + /// Do not include the cause chain of the top-level error in the report. + pub fn without_cause_chain(mut self) -> Self { + self.with_cause_chain = Some(false); + self + } + + /// If true, colors will be used during graphical rendering, regardless + /// of whether or not the terminal supports them. + /// + /// If false, colors will never be used. + /// + /// If unspecified, colors will be used only if the terminal supports them. + /// + /// The actual format depends on the value of + /// [`MietteHandlerOpts::rgb_colors`]. + pub fn color(mut self, color: bool) -> Self { + self.color = Some(color); + self + } + + /// Controls which color format to use if colors are used in graphical + /// rendering. + /// + /// The default is `Never`. + /// + /// This value does not control whether or not colors are being used in the + /// first place. That is handled by the [`MietteHandlerOpts::color`] + /// setting. If colors are not being used, the value of `rgb_colors` has + /// no effect. + pub fn rgb_colors(mut self, color: RgbColors) -> Self { + self.rgb_colors = color; + self + } + + /// If true, forces unicode display for graphical output. If set to false, + /// forces ASCII art display. + pub fn unicode(mut self, unicode: bool) -> Self { + self.unicode = Some(unicode); + self + } + + /// If true, graphical rendering will be used regardless of terminal + /// detection. + pub fn force_graphical(mut self, force: bool) -> Self { + self.force_graphical = Some(force); + self + } + + /// If true, forces use of the narrated renderer. + pub fn force_narrated(mut self, force: bool) -> Self { + self.force_narrated = Some(force); + self + } + + /// Set a footer to be displayed at the bottom of the report. + pub fn footer(mut self, footer: String) -> Self { + self.footer = Some(footer); + self + } + + /// Sets the number of context lines before and after a span to display. + pub fn context_lines(mut self, context_lines: usize) -> Self { + self.context_lines = Some(context_lines); + self + } + + /// Set the displayed tab width in spaces. + pub fn tab_width(mut self, width: usize) -> Self { + self.tab_width = Some(width); + self + } + + /// Builds a [`MietteHandler`] from this builder. + pub fn build(self) -> MietteHandler { + let graphical = self.is_graphical(); + let width = self.get_width(); + if !graphical { + let mut handler = NarratableReportHandler::new(); + if let Some(footer) = self.footer { + handler = handler.with_footer(footer); + } + if let Some(context_lines) = self.context_lines { + handler = handler.with_context_lines(context_lines); + } + if let Some(with_cause_chain) = self.with_cause_chain { + if with_cause_chain { + handler = handler.with_cause_chain(); + } else { + handler = handler.without_cause_chain(); + } + } + MietteHandler { + inner: Box::new(handler), + } + } else { + let linkify = self.use_links(); + let characters = match self.unicode { + Some(true) => ThemeCharacters::unicode(), + Some(false) => ThemeCharacters::ascii(), + None if supports_unicode::on(supports_unicode::Stream::Stderr) => { + ThemeCharacters::unicode() + } + None => ThemeCharacters::ascii(), + }; + let styles = if self.color == Some(false) { + ThemeStyles::none() + } else if let Some(color) = supports_color::on(supports_color::Stream::Stderr) { + match self.rgb_colors { + RgbColors::Always => ThemeStyles::rgb(), + RgbColors::Preferred if color.has_16m => ThemeStyles::rgb(), + _ => ThemeStyles::ansi(), + } + } else if self.color == Some(true) { + match self.rgb_colors { + RgbColors::Always => ThemeStyles::rgb(), + _ => ThemeStyles::ansi(), + } + } else { + ThemeStyles::none() + }; + let theme = self.theme.unwrap_or(GraphicalTheme { characters, styles }); + let mut handler = GraphicalReportHandler::new() + .with_width(width) + .with_links(linkify) + .with_theme(theme); + if let Some(with_cause_chain) = self.with_cause_chain { + if with_cause_chain { + handler = handler.with_cause_chain(); + } else { + handler = handler.without_cause_chain(); + } + } + if let Some(footer) = self.footer { + handler = handler.with_footer(footer); + } + if let Some(context_lines) = self.context_lines { + handler = handler.with_context_lines(context_lines); + } + if let Some(w) = self.tab_width { + handler = handler.tab_width(w); + } + MietteHandler { + inner: Box::new(handler), + } + } + } + + pub(crate) fn is_graphical(&self) -> bool { + if let Some(force_narrated) = self.force_narrated { + !force_narrated + } else if let Some(force_graphical) = self.force_graphical { + force_graphical + } else if let Ok(env) = std::env::var("NO_GRAPHICS") { + env == "0" + } else { + true + } + } + + // Detects known terminal apps based on env variables and returns true if + // they support rendering links. + pub(crate) fn use_links(&self) -> bool { + if let Some(linkify) = self.linkify { + linkify + } else { + supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr) + } + } + + #[cfg(not(miri))] + pub(crate) fn get_width(&self) -> usize { + self.width.unwrap_or_else(|| { + terminal_size::terminal_size() + .unwrap_or((terminal_size::Width(80), terminal_size::Height(0))) + .0 + .0 as usize + }) + } + + #[cfg(miri)] + // miri doesn't support a syscall (specifically ioctl) + // performed by terminal_size, which causes test execution to fail + // so when miri is running we'll just fallback to a constant + pub(crate) fn get_width(&self) -> usize { + self.width.unwrap_or(80) + } +} + +/** +A [`ReportHandler`] that displays a given [`Report`](crate::Report) in a +quasi-graphical way, using terminal colors, unicode drawing characters, and +other such things. + +This is the default reporter bundled with `miette`. + +This printer can be customized by using +[`GraphicalReportHandler::new_themed()`] and handing it a [`GraphicalTheme`] of +your own creation (or using one of its own defaults). + +See [`set_hook`](crate::set_hook) for more details on customizing your global +printer. +*/ +#[allow(missing_debug_implementations)] +pub struct MietteHandler { + inner: Box, +} + +impl MietteHandler { + /// Creates a new [`MietteHandler`] with default settings. + pub fn new() -> Self { + Default::default() + } +} + +impl Default for MietteHandler { + fn default() -> Self { + MietteHandlerOpts::new().build() + } +} + +impl ReportHandler for MietteHandler { + fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + return fmt::Debug::fmt(diagnostic, f); + } + + self.inner.debug(diagnostic, f) + } +} diff --git a/vendor/miette/src/handlers/debug.rs b/vendor/miette/src/handlers/debug.rs new file mode 100644 index 0000000..50450a4 --- /dev/null +++ b/vendor/miette/src/handlers/debug.rs @@ -0,0 +1,71 @@ +use std::fmt; + +use crate::{protocol::Diagnostic, ReportHandler}; + +/** +[`ReportHandler`] that renders plain text and avoids extraneous graphics. +It's optimized for screen readers and braille users, but is also used in any +non-graphical environments, such as non-TTY output. +*/ +#[derive(Debug, Clone)] +pub struct DebugReportHandler; + +impl DebugReportHandler { + /// Create a new [`NarratableReportHandler`](crate::NarratableReportHandler) + /// There are no customization options. + pub const fn new() -> Self { + Self + } +} + +impl Default for DebugReportHandler { + fn default() -> Self { + Self::new() + } +} + +impl DebugReportHandler { + /// Render a [`Diagnostic`]. This function is mostly internal and meant to + /// be called by the toplevel [`ReportHandler`] handler, but is made public + /// to make it easier (possible) to test in isolation from global state. + pub fn render_report( + &self, + f: &mut fmt::Formatter<'_>, + diagnostic: &(dyn Diagnostic), + ) -> fmt::Result { + let mut diag = f.debug_struct("Diagnostic"); + diag.field("message", &format!("{}", diagnostic)); + if let Some(code) = diagnostic.code() { + diag.field("code", &code.to_string()); + } + if let Some(severity) = diagnostic.severity() { + diag.field("severity", &format!("{:?}", severity)); + } + if let Some(url) = diagnostic.url() { + diag.field("url", &url.to_string()); + } + if let Some(help) = diagnostic.help() { + diag.field("help", &help.to_string()); + } + if let Some(labels) = diagnostic.labels() { + let labels: Vec<_> = labels.collect(); + diag.field("labels", &format!("{:?}", labels)); + } + if let Some(cause) = diagnostic.diagnostic_source() { + diag.field("caused by", &format!("{:?}", cause)); + } + diag.finish()?; + writeln!(f)?; + writeln!(f, "NOTE: If you're looking for the fancy error reports, install miette with the `fancy` feature, or write your own and hook it up with miette::set_hook().") + } +} + +impl ReportHandler for DebugReportHandler { + fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + return fmt::Debug::fmt(diagnostic, f); + } + + self.render_report(f, diagnostic) + } +} diff --git a/vendor/miette/src/handlers/graphical.rs b/vendor/miette/src/handlers/graphical.rs new file mode 100644 index 0000000..b5dd754 --- /dev/null +++ b/vendor/miette/src/handlers/graphical.rs @@ -0,0 +1,920 @@ +use std::fmt::{self, Write}; + +use owo_colors::{OwoColorize, Style}; +use unicode_width::UnicodeWidthChar; + +use crate::diagnostic_chain::{DiagnosticChain, ErrorKind}; +use crate::handlers::theme::*; +use crate::protocol::{Diagnostic, Severity}; +use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents}; + +/** +A [`ReportHandler`] that displays a given [`Report`](crate::Report) in a +quasi-graphical way, using terminal colors, unicode drawing characters, and +other such things. + +This is the default reporter bundled with `miette`. + +This printer can be customized by using [`new_themed()`](GraphicalReportHandler::new_themed) and handing it a +[`GraphicalTheme`] of your own creation (or using one of its own defaults!) + +See [`set_hook()`](crate::set_hook) for more details on customizing your global +printer. +*/ +#[derive(Debug, Clone)] +pub struct GraphicalReportHandler { + pub(crate) links: LinkStyle, + pub(crate) termwidth: usize, + pub(crate) theme: GraphicalTheme, + pub(crate) footer: Option, + pub(crate) context_lines: usize, + pub(crate) tab_width: usize, + pub(crate) with_cause_chain: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum LinkStyle { + None, + Link, + Text, +} + +impl GraphicalReportHandler { + /// Create a new `GraphicalReportHandler` with the default + /// [`GraphicalTheme`]. This will use both unicode characters and colors. + pub fn new() -> Self { + Self { + links: LinkStyle::Link, + termwidth: 200, + theme: GraphicalTheme::default(), + footer: None, + context_lines: 1, + tab_width: 4, + with_cause_chain: true, + } + } + + ///Create a new `GraphicalReportHandler` with a given [`GraphicalTheme`]. + pub fn new_themed(theme: GraphicalTheme) -> Self { + Self { + links: LinkStyle::Link, + termwidth: 200, + theme, + footer: None, + context_lines: 1, + tab_width: 4, + with_cause_chain: true, + } + } + + /// Set the displayed tab width in spaces. + pub fn tab_width(mut self, width: usize) -> Self { + self.tab_width = width; + self + } + + /// Whether to enable error code linkification using [`Diagnostic::url()`]. + pub fn with_links(mut self, links: bool) -> Self { + self.links = if links { + LinkStyle::Link + } else { + LinkStyle::Text + }; + self + } + + /// Include the cause chain of the top-level error in the graphical output, + /// if available. + pub fn with_cause_chain(mut self) -> Self { + self.with_cause_chain = true; + self + } + + /// Do not include the cause chain of the top-level error in the graphical + /// output. + pub fn without_cause_chain(mut self) -> Self { + self.with_cause_chain = false; + self + } + + /// Whether to include [`Diagnostic::url()`] in the output. + /// + /// Disabling this is not recommended, but can be useful for more easily + /// reproducible tests, as `url(docsrs)` links are version-dependent. + pub fn with_urls(mut self, urls: bool) -> Self { + self.links = match (self.links, urls) { + (_, false) => LinkStyle::None, + (LinkStyle::None, true) => LinkStyle::Link, + (links, true) => links, + }; + self + } + + /// Set a theme for this handler. + pub fn with_theme(mut self, theme: GraphicalTheme) -> Self { + self.theme = theme; + self + } + + /// Sets the width to wrap the report at. + pub fn with_width(mut self, width: usize) -> Self { + self.termwidth = width; + self + } + + /// Sets the 'global' footer for this handler. + pub fn with_footer(mut self, footer: String) -> Self { + self.footer = Some(footer); + self + } + + /// Sets the number of lines of context to show around each error. + pub fn with_context_lines(mut self, lines: usize) -> Self { + self.context_lines = lines; + self + } +} + +impl Default for GraphicalReportHandler { + fn default() -> Self { + Self::new() + } +} + +impl GraphicalReportHandler { + /// Render a [`Diagnostic`]. This function is mostly internal and meant to + /// be called by the toplevel [`ReportHandler`] handler, but is made public + /// to make it easier (possible) to test in isolation from global state. + pub fn render_report( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + ) -> fmt::Result { + self.render_header(f, diagnostic)?; + self.render_causes(f, diagnostic)?; + let src = diagnostic.source_code(); + self.render_snippets(f, diagnostic, src)?; + self.render_footer(f, diagnostic)?; + self.render_related(f, diagnostic, src)?; + if let Some(footer) = &self.footer { + writeln!(f)?; + let width = self.termwidth.saturating_sub(4); + let opts = textwrap::Options::new(width) + .initial_indent(" ") + .subsequent_indent(" "); + writeln!(f, "{}", textwrap::fill(footer, opts))?; + } + Ok(()) + } + + fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result { + let severity_style = match diagnostic.severity() { + Some(Severity::Error) | None => self.theme.styles.error, + Some(Severity::Warning) => self.theme.styles.warning, + Some(Severity::Advice) => self.theme.styles.advice, + }; + let mut header = String::new(); + if self.links == LinkStyle::Link && diagnostic.url().is_some() { + let url = diagnostic.url().unwrap(); // safe + let code = if let Some(code) = diagnostic.code() { + format!("{} ", code) + } else { + "".to_string() + }; + let link = format!( + "\u{1b}]8;;{}\u{1b}\\{}{}\u{1b}]8;;\u{1b}\\", + url, + code.style(severity_style), + "(link)".style(self.theme.styles.link) + ); + write!(header, "{}", link)?; + writeln!(f, "{}", header)?; + writeln!(f)?; + } else if let Some(code) = diagnostic.code() { + write!(header, "{}", code.style(severity_style),)?; + if self.links == LinkStyle::Text && diagnostic.url().is_some() { + let url = diagnostic.url().unwrap(); // safe + write!(header, " ({})", url.style(self.theme.styles.link))?; + } + writeln!(f, "{}", header)?; + writeln!(f)?; + } + Ok(()) + } + + fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result { + let (severity_style, severity_icon) = match diagnostic.severity() { + Some(Severity::Error) | None => (self.theme.styles.error, &self.theme.characters.error), + Some(Severity::Warning) => (self.theme.styles.warning, &self.theme.characters.warning), + Some(Severity::Advice) => (self.theme.styles.advice, &self.theme.characters.advice), + }; + + let initial_indent = format!(" {} ", severity_icon.style(severity_style)); + let rest_indent = format!(" {} ", self.theme.characters.vbar.style(severity_style)); + let width = self.termwidth.saturating_sub(2); + let opts = textwrap::Options::new(width) + .initial_indent(&initial_indent) + .subsequent_indent(&rest_indent); + + writeln!(f, "{}", textwrap::fill(&diagnostic.to_string(), opts))?; + + if !self.with_cause_chain { + return Ok(()); + } + + if let Some(mut cause_iter) = diagnostic + .diagnostic_source() + .map(DiagnosticChain::from_diagnostic) + .or_else(|| diagnostic.source().map(DiagnosticChain::from_stderror)) + .map(|it| it.peekable()) + { + while let Some(error) = cause_iter.next() { + let is_last = cause_iter.peek().is_none(); + let char = if !is_last { + self.theme.characters.lcross + } else { + self.theme.characters.lbot + }; + let initial_indent = format!( + " {}{}{} ", + char, self.theme.characters.hbar, self.theme.characters.rarrow + ) + .style(severity_style) + .to_string(); + let rest_indent = format!( + " {} ", + if is_last { + ' ' + } else { + self.theme.characters.vbar + } + ) + .style(severity_style) + .to_string(); + let opts = textwrap::Options::new(width) + .initial_indent(&initial_indent) + .subsequent_indent(&rest_indent); + match error { + ErrorKind::Diagnostic(diag) => { + let mut inner = String::new(); + + // Don't print footer for inner errors + let mut inner_renderer = self.clone(); + inner_renderer.footer = None; + inner_renderer.with_cause_chain = false; + inner_renderer.render_report(&mut inner, diag)?; + + writeln!(f, "{}", textwrap::fill(&inner, opts))?; + } + ErrorKind::StdError(err) => { + writeln!(f, "{}", textwrap::fill(&err.to_string(), opts))?; + } + } + } + } + + Ok(()) + } + + fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result { + if let Some(help) = diagnostic.help() { + let width = self.termwidth.saturating_sub(4); + let initial_indent = " help: ".style(self.theme.styles.help).to_string(); + let opts = textwrap::Options::new(width) + .initial_indent(&initial_indent) + .subsequent_indent(" "); + writeln!(f, "{}", textwrap::fill(&help.to_string(), opts))?; + } + Ok(()) + } + + fn render_related( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + parent_src: Option<&dyn SourceCode>, + ) -> fmt::Result { + if let Some(related) = diagnostic.related() { + writeln!(f)?; + for rel in related { + match rel.severity() { + Some(Severity::Error) | None => write!(f, "Error: ")?, + Some(Severity::Warning) => write!(f, "Warning: ")?, + Some(Severity::Advice) => write!(f, "Advice: ")?, + }; + self.render_header(f, rel)?; + self.render_causes(f, rel)?; + let src = rel.source_code().or(parent_src); + self.render_snippets(f, rel, src)?; + self.render_footer(f, rel)?; + self.render_related(f, rel, src)?; + } + } + Ok(()) + } + + fn render_snippets( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + opt_source: Option<&dyn SourceCode>, + ) -> fmt::Result { + if let Some(source) = opt_source { + if let Some(labels) = diagnostic.labels() { + let mut labels = labels.collect::>(); + labels.sort_unstable_by_key(|l| l.inner().offset()); + if !labels.is_empty() { + let contents = labels + .iter() + .map(|label| { + source.read_span(label.inner(), self.context_lines, self.context_lines) + }) + .collect::>>, MietteError>>() + .map_err(|_| fmt::Error)?; + let mut contexts = Vec::with_capacity(contents.len()); + for (right, right_conts) in labels.iter().cloned().zip(contents.iter()) { + if contexts.is_empty() { + contexts.push((right, right_conts)); + } else { + let (left, left_conts) = contexts.last().unwrap().clone(); + let left_end = left.offset() + left.len(); + let right_end = right.offset() + right.len(); + if left_conts.line() + left_conts.line_count() >= right_conts.line() { + // The snippets will overlap, so we create one Big Chunky Boi + let new_span = LabeledSpan::new( + left.label().map(String::from), + left.offset(), + if right_end >= left_end { + // Right end goes past left end + right_end - left.offset() + } else { + // right is contained inside left + left.len() + }, + ); + if source + .read_span( + new_span.inner(), + self.context_lines, + self.context_lines, + ) + .is_ok() + { + contexts.pop(); + contexts.push(( + // We'll throw this away later + new_span, left_conts, + )); + } else { + contexts.push((right, right_conts)); + } + } else { + contexts.push((right, right_conts)); + } + } + } + for (ctx, _) in contexts { + self.render_context(f, source, &ctx, &labels[..])?; + } + } + } + } + Ok(()) + } + + fn render_context<'a>( + &self, + f: &mut impl fmt::Write, + source: &'a dyn SourceCode, + context: &LabeledSpan, + labels: &[LabeledSpan], + ) -> fmt::Result { + let (contents, lines) = self.get_lines(source, context.inner())?; + + // sorting is your friend + let labels = labels + .iter() + .zip(self.theme.styles.highlights.iter().cloned().cycle()) + .map(|(label, st)| FancySpan::new(label.label().map(String::from), *label.inner(), st)) + .collect::>(); + + // The max number of gutter-lines that will be active at any given + // point. We need this to figure out indentation, so we do one loop + // over the lines to see what the damage is gonna be. + let mut max_gutter = 0usize; + for line in &lines { + let mut num_highlights = 0; + for hl in &labels { + if !line.span_line_only(hl) && line.span_applies(hl) { + num_highlights += 1; + } + } + max_gutter = std::cmp::max(max_gutter, num_highlights); + } + + // Oh and one more thing: We need to figure out how much room our line + // numbers need! + let linum_width = lines[..] + .last() + .map(|line| line.line_number) + // It's possible for the source to be an empty string. + .unwrap_or(0) + .to_string() + .len(); + + // Header + write!( + f, + "{}{}{}", + " ".repeat(linum_width + 2), + self.theme.characters.ltop, + self.theme.characters.hbar, + )?; + + if let Some(source_name) = contents.name() { + let source_name = source_name.style(self.theme.styles.link); + writeln!( + f, + "[{}:{}:{}]", + source_name, + contents.line() + 1, + contents.column() + 1 + )?; + } else if lines.len() <= 1 { + writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?; + } else { + writeln!(f, "[{}:{}]", contents.line() + 1, contents.column() + 1)?; + } + + // Now it's time for the fun part--actually rendering everything! + for line in &lines { + // Line number, appropriately padded. + self.write_linum(f, linum_width, line.line_number)?; + + // Then, we need to print the gutter, along with any fly-bys We + // have separate gutters depending on whether we're on the actual + // line, or on one of the "highlight lines" below it. + self.render_line_gutter(f, max_gutter, line, &labels)?; + + // And _now_ we can print out the line text itself! + self.render_line_text(f, &line.text)?; + + // Next, we write all the highlights that apply to this particular line. + let (single_line, multi_line): (Vec<_>, Vec<_>) = labels + .iter() + .filter(|hl| line.span_applies(hl)) + .partition(|hl| line.span_line_only(hl)); + if !single_line.is_empty() { + // no line number! + self.write_no_linum(f, linum_width)?; + // gutter _again_ + self.render_highlight_gutter(f, max_gutter, line, &labels)?; + self.render_single_line_highlights( + f, + line, + linum_width, + max_gutter, + &single_line, + &labels, + )?; + } + for hl in multi_line { + if hl.label().is_some() && line.span_ends(hl) && !line.span_starts(hl) { + // no line number! + self.write_no_linum(f, linum_width)?; + // gutter _again_ + self.render_highlight_gutter(f, max_gutter, line, &labels)?; + self.render_multi_line_end(f, hl)?; + } + } + } + writeln!( + f, + "{}{}{}", + " ".repeat(linum_width + 2), + self.theme.characters.lbot, + self.theme.characters.hbar.to_string().repeat(4), + )?; + Ok(()) + } + + fn render_line_gutter( + &self, + f: &mut impl fmt::Write, + max_gutter: usize, + line: &Line, + highlights: &[FancySpan], + ) -> fmt::Result { + if max_gutter == 0 { + return Ok(()); + } + let chars = &self.theme.characters; + let mut gutter = String::new(); + let applicable = highlights.iter().filter(|hl| line.span_applies(hl)); + let mut arrow = false; + for (i, hl) in applicable.enumerate() { + if line.span_starts(hl) { + gutter.push_str(&chars.ltop.style(hl.style).to_string()); + gutter.push_str( + &chars + .hbar + .to_string() + .repeat(max_gutter.saturating_sub(i)) + .style(hl.style) + .to_string(), + ); + gutter.push_str(&chars.rarrow.style(hl.style).to_string()); + arrow = true; + break; + } else if line.span_ends(hl) { + if hl.label().is_some() { + gutter.push_str(&chars.lcross.style(hl.style).to_string()); + } else { + gutter.push_str(&chars.lbot.style(hl.style).to_string()); + } + gutter.push_str( + &chars + .hbar + .to_string() + .repeat(max_gutter.saturating_sub(i)) + .style(hl.style) + .to_string(), + ); + gutter.push_str(&chars.rarrow.style(hl.style).to_string()); + arrow = true; + break; + } else if line.span_flyby(hl) { + gutter.push_str(&chars.vbar.style(hl.style).to_string()); + } else { + gutter.push(' '); + } + } + write!( + f, + "{}{}", + gutter, + " ".repeat( + if arrow { 1 } else { 3 } + max_gutter.saturating_sub(gutter.chars().count()) + ) + )?; + Ok(()) + } + + fn render_highlight_gutter( + &self, + f: &mut impl fmt::Write, + max_gutter: usize, + line: &Line, + highlights: &[FancySpan], + ) -> fmt::Result { + if max_gutter == 0 { + return Ok(()); + } + let chars = &self.theme.characters; + let mut gutter = String::new(); + let applicable = highlights.iter().filter(|hl| line.span_applies(hl)); + for (i, hl) in applicable.enumerate() { + if !line.span_line_only(hl) && line.span_ends(hl) { + gutter.push_str(&chars.lbot.style(hl.style).to_string()); + gutter.push_str( + &chars + .hbar + .to_string() + .repeat(max_gutter.saturating_sub(i) + 2) + .style(hl.style) + .to_string(), + ); + break; + } else { + gutter.push_str(&chars.vbar.style(hl.style).to_string()); + } + } + write!(f, "{:width$}", gutter, width = max_gutter + 1)?; + Ok(()) + } + + fn write_linum(&self, f: &mut impl fmt::Write, width: usize, linum: usize) -> fmt::Result { + write!( + f, + " {:width$} {} ", + linum.style(self.theme.styles.linum), + self.theme.characters.vbar, + width = width + )?; + Ok(()) + } + + fn write_no_linum(&self, f: &mut impl fmt::Write, width: usize) -> fmt::Result { + write!( + f, + " {:width$} {} ", + "", + self.theme.characters.vbar_break, + width = width + )?; + Ok(()) + } + + /// Returns an iterator over the visual width of each character in a line. + fn line_visual_char_width<'a>(&self, text: &'a str) -> impl Iterator + 'a { + let mut column = 0; + let tab_width = self.tab_width; + text.chars().map(move |c| { + let width = if c == '\t' { + // Round up to the next multiple of tab_width + tab_width - column % tab_width + } else { + c.width().unwrap_or(0) + }; + column += width; + width + }) + } + + /// Returns the visual column position of a byte offset on a specific line. + fn visual_offset(&self, line: &Line, offset: usize) -> usize { + let line_range = line.offset..=(line.offset + line.length); + assert!(line_range.contains(&offset)); + + let text_index = offset - line.offset; + let text = &line.text[..text_index.min(line.text.len())]; + let text_width = self.line_visual_char_width(text).sum(); + if text_index > line.text.len() { + // Spans extending past the end of the line are always rendered as + // one column past the end of the visible line. + // + // This doesn't necessarily correspond to a specific byte-offset, + // since a span extending past the end of the line could contain: + // - an actual \n character (1 byte) + // - a CRLF (2 bytes) + // - EOF (0 bytes) + text_width + 1 + } else { + text_width + } + } + + /// Renders a line to the output formatter, replacing tabs with spaces. + fn render_line_text(&self, f: &mut impl fmt::Write, text: &str) -> fmt::Result { + for (c, width) in text.chars().zip(self.line_visual_char_width(text)) { + if c == '\t' { + for _ in 0..width { + f.write_char(' ')? + } + } else { + f.write_char(c)? + } + } + f.write_char('\n')?; + Ok(()) + } + + fn render_single_line_highlights( + &self, + f: &mut impl fmt::Write, + line: &Line, + linum_width: usize, + max_gutter: usize, + single_liners: &[&FancySpan], + all_highlights: &[FancySpan], + ) -> fmt::Result { + let mut underlines = String::new(); + let mut highest = 0; + + let chars = &self.theme.characters; + let vbar_offsets: Vec<_> = single_liners + .iter() + .map(|hl| { + let byte_start = hl.offset(); + let byte_end = hl.offset() + hl.len(); + let start = self.visual_offset(line, byte_start).max(highest); + let end = self.visual_offset(line, byte_end).max(start + 1); + + let vbar_offset = (start + end) / 2; + let num_left = vbar_offset - start; + let num_right = end - vbar_offset - 1; + if start < end { + underlines.push_str( + &format!( + "{:width$}{}{}{}", + "", + chars.underline.to_string().repeat(num_left), + if hl.len() == 0 { + chars.uarrow + } else if hl.label().is_some() { + chars.underbar + } else { + chars.underline + }, + chars.underline.to_string().repeat(num_right), + width = start.saturating_sub(highest), + ) + .style(hl.style) + .to_string(), + ); + } + highest = std::cmp::max(highest, end); + + (hl, vbar_offset) + }) + .collect(); + writeln!(f, "{}", underlines)?; + + for hl in single_liners.iter().rev() { + if let Some(label) = hl.label() { + self.write_no_linum(f, linum_width)?; + self.render_highlight_gutter(f, max_gutter, line, all_highlights)?; + let mut curr_offset = 1usize; + for (offset_hl, vbar_offset) in &vbar_offsets { + while curr_offset < *vbar_offset + 1 { + write!(f, " ")?; + curr_offset += 1; + } + if *offset_hl != hl { + write!(f, "{}", chars.vbar.to_string().style(offset_hl.style))?; + curr_offset += 1; + } else { + let lines = format!( + "{}{} {}", + chars.lbot, + chars.hbar.to_string().repeat(2), + label, + ); + writeln!(f, "{}", lines.style(hl.style))?; + break; + } + } + } + } + Ok(()) + } + + fn render_multi_line_end(&self, f: &mut impl fmt::Write, hl: &FancySpan) -> fmt::Result { + writeln!( + f, + "{} {}", + self.theme.characters.hbar.style(hl.style), + hl.label().unwrap_or_else(|| "".into()), + )?; + Ok(()) + } + + fn get_lines<'a>( + &'a self, + source: &'a dyn SourceCode, + context_span: &'a SourceSpan, + ) -> Result<(Box + 'a>, Vec), fmt::Error> { + let context_data = source + .read_span(context_span, self.context_lines, self.context_lines) + .map_err(|_| fmt::Error)?; + let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected"); + let mut line = context_data.line(); + let mut column = context_data.column(); + let mut offset = context_data.span().offset(); + let mut line_offset = offset; + 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; + match char { + '\r' => { + if iter.next_if_eq(&'\n').is_some() { + offset += 1; + line += 1; + column = 0; + } else { + line_str.push(char); + column += 1; + } + at_end_of_file = iter.peek().is_none(); + } + '\n' => { + at_end_of_file = iter.peek().is_none(); + line += 1; + column = 0; + } + _ => { + line_str.push(char); + column += 1; + } + } + + if iter.peek().is_none() && !at_end_of_file { + line += 1; + } + + if column == 0 || iter.peek().is_none() { + lines.push(Line { + line_number: line, + offset: line_offset, + length: offset - line_offset, + text: line_str.clone(), + }); + line_str.clear(); + line_offset = offset; + } + } + Ok((context_data, lines)) + } +} + +impl ReportHandler for GraphicalReportHandler { + fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + return fmt::Debug::fmt(diagnostic, f); + } + + self.render_report(f, diagnostic) + } +} + +/* +Support types +*/ + +#[derive(Debug)] +struct Line { + line_number: usize, + offset: usize, + length: usize, + text: String, +} + +impl Line { + fn span_line_only(&self, span: &FancySpan) -> bool { + span.offset() >= self.offset && span.offset() + span.len() <= self.offset + self.length + } + + fn span_applies(&self, span: &FancySpan) -> bool { + let spanlen = if span.len() == 0 { 1 } else { span.len() }; + // Span starts in this line + (span.offset() >= self.offset && span.offset() < self.offset + self.length) + // Span passes through this line + || (span.offset() < self.offset && span.offset() + spanlen > self.offset + self.length) //todo + // Span ends on this line + || (span.offset() + spanlen > self.offset && span.offset() + spanlen <= self.offset + self.length) + } + + // A 'flyby' is a multi-line span that technically covers this line, but + // does not begin or end within the line itself. This method is used to + // calculate gutters. + fn span_flyby(&self, span: &FancySpan) -> bool { + // The span itself starts before this line's starting offset (so, in a + // prev line). + span.offset() < self.offset + // ...and it stops after this line's end. + && span.offset() + span.len() > self.offset + self.length + } + + // Does this line contain the *beginning* of this multiline span? + // This assumes self.span_applies() is true already. + fn span_starts(&self, span: &FancySpan) -> bool { + span.offset() >= self.offset + } + + // Does this line contain the *end* of this multiline span? + // This assumes self.span_applies() is true already. + fn span_ends(&self, span: &FancySpan) -> bool { + span.offset() + span.len() >= self.offset + && span.offset() + span.len() <= self.offset + self.length + } +} + +#[derive(Debug, Clone)] +struct FancySpan { + label: Option, + span: SourceSpan, + style: Style, +} + +impl PartialEq for FancySpan { + fn eq(&self, other: &Self) -> bool { + self.label == other.label && self.span == other.span + } +} + +impl FancySpan { + fn new(label: Option, span: SourceSpan, style: Style) -> Self { + FancySpan { label, span, style } + } + + fn style(&self) -> Style { + self.style + } + + fn label(&self) -> Option { + self.label + .as_ref() + .map(|l| l.style(self.style()).to_string()) + } + + fn offset(&self) -> usize { + self.span.offset() + } + + fn len(&self) -> usize { + self.span.len() + } +} diff --git a/vendor/miette/src/handlers/json.rs b/vendor/miette/src/handlers/json.rs new file mode 100644 index 0000000..29e21a0 --- /dev/null +++ b/vendor/miette/src/handlers/json.rs @@ -0,0 +1,182 @@ +use std::fmt::{self, Write}; + +use crate::{ + diagnostic_chain::DiagnosticChain, protocol::Diagnostic, ReportHandler, Severity, SourceCode, +}; + +/** +[`ReportHandler`] that renders JSON output. It's a machine-readable output. +*/ +#[derive(Debug, Clone)] +pub struct JSONReportHandler; + +impl JSONReportHandler { + /// Create a new [`JSONReportHandler`]. There are no customization + /// options. + pub const fn new() -> Self { + Self + } +} + +impl Default for JSONReportHandler { + fn default() -> Self { + Self::new() + } +} + +struct Escape<'a>(&'a str); + +impl fmt::Display for Escape<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for c in self.0.chars() { + let escape = match c { + '\\' => Some(r"\\"), + '"' => Some(r#"\""#), + '\r' => Some(r"\r"), + '\n' => Some(r"\n"), + '\t' => Some(r"\t"), + '\u{08}' => Some(r"\b"), + '\u{0c}' => Some(r"\f"), + _ => None, + }; + if let Some(escape) = escape { + f.write_str(escape)?; + } else { + f.write_char(c)?; + } + } + Ok(()) + } +} + +const fn escape(input: &'_ str) -> Escape<'_> { + Escape(input) +} + +impl JSONReportHandler { + /// Render a [`Diagnostic`]. This function is mostly internal and meant to + /// be called by the toplevel [`ReportHandler`] handler, but is made public + /// to make it easier (possible) to test in isolation from global state. + pub fn render_report( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + ) -> fmt::Result { + self._render_report(f, diagnostic, None) + } + + fn _render_report( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + parent_src: Option<&dyn SourceCode>, + ) -> fmt::Result { + write!(f, r#"{{"message": "{}","#, escape(&diagnostic.to_string()))?; + if let Some(code) = diagnostic.code() { + write!(f, r#""code": "{}","#, escape(&code.to_string()))?; + } + let severity = match diagnostic.severity() { + Some(Severity::Error) | None => "error", + Some(Severity::Warning) => "warning", + Some(Severity::Advice) => "advice", + }; + write!(f, r#""severity": "{:}","#, severity)?; + if let Some(cause_iter) = diagnostic + .diagnostic_source() + .map(DiagnosticChain::from_diagnostic) + .or_else(|| diagnostic.source().map(DiagnosticChain::from_stderror)) + { + write!(f, r#""causes": ["#)?; + let mut add_comma = false; + for error in cause_iter { + if add_comma { + write!(f, ",")?; + } else { + add_comma = true; + } + write!(f, r#""{}""#, escape(&error.to_string()))?; + } + write!(f, "],")? + } else { + write!(f, r#""causes": [],"#)?; + } + if let Some(url) = diagnostic.url() { + write!(f, r#""url": "{}","#, &url.to_string())?; + } + if let Some(help) = diagnostic.help() { + write!(f, r#""help": "{}","#, escape(&help.to_string()))?; + } + let src = diagnostic.source_code().or(parent_src); + if let Some(src) = src { + self.render_snippets(f, diagnostic, src)?; + } + if let Some(labels) = diagnostic.labels() { + write!(f, r#""labels": ["#)?; + let mut add_comma = false; + for label in labels { + if add_comma { + write!(f, ",")?; + } else { + add_comma = true; + } + write!(f, "{{")?; + if let Some(label_name) = label.label() { + write!(f, r#""label": "{}","#, escape(label_name))?; + } + write!(f, r#""span": {{"#)?; + write!(f, r#""offset": {},"#, label.offset())?; + write!(f, r#""length": {}"#, label.len())?; + + write!(f, "}}}}")?; + } + write!(f, "],")?; + } else { + write!(f, r#""labels": [],"#)?; + } + if let Some(relateds) = diagnostic.related() { + write!(f, r#""related": ["#)?; + let mut add_comma = false; + for related in relateds { + if add_comma { + write!(f, ",")?; + } else { + add_comma = true; + } + self._render_report(f, related, src)?; + } + write!(f, "]")?; + } else { + write!(f, r#""related": []"#)?; + } + write!(f, "}}") + } + + fn render_snippets( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + source: &dyn SourceCode, + ) -> fmt::Result { + if let Some(mut labels) = diagnostic.labels() { + if let Some(label) = labels.next() { + if let Ok(span_content) = source.read_span(label.inner(), 0, 0) { + let filename = span_content.name().unwrap_or_default(); + return write!(f, r#""filename": "{}","#, escape(filename)); + } + } + } + write!(f, r#""filename": "","#) + } +} + +impl ReportHandler for JSONReportHandler { + fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.render_report(f, diagnostic) + } +} + +#[test] +fn test_escape() { + assert_eq!(escape("a\nb").to_string(), r"a\nb"); + assert_eq!(escape("C:\\Miette").to_string(), r"C:\\Miette"); +} diff --git a/vendor/miette/src/handlers/mod.rs b/vendor/miette/src/handlers/mod.rs new file mode 100644 index 0000000..fde2dc9 --- /dev/null +++ b/vendor/miette/src/handlers/mod.rs @@ -0,0 +1,24 @@ +/*! +Reporters included with `miette`. +*/ + +#[allow(unreachable_pub)] +pub use debug::*; +#[allow(unreachable_pub)] +#[cfg(feature = "fancy-no-backtrace")] +pub use graphical::*; +#[allow(unreachable_pub)] +pub use json::*; +#[allow(unreachable_pub)] +pub use narratable::*; +#[allow(unreachable_pub)] +#[cfg(feature = "fancy-no-backtrace")] +pub use theme::*; + +mod debug; +#[cfg(feature = "fancy-no-backtrace")] +mod graphical; +mod json; +mod narratable; +#[cfg(feature = "fancy-no-backtrace")] +mod theme; diff --git a/vendor/miette/src/handlers/narratable.rs b/vendor/miette/src/handlers/narratable.rs new file mode 100644 index 0000000..c809124 --- /dev/null +++ b/vendor/miette/src/handlers/narratable.rs @@ -0,0 +1,423 @@ +use std::fmt; + +use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; + +use crate::diagnostic_chain::DiagnosticChain; +use crate::protocol::{Diagnostic, Severity}; +use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents}; + +/** +[`ReportHandler`] that renders plain text and avoids extraneous graphics. +It's optimized for screen readers and braille users, but is also used in any +non-graphical environments, such as non-TTY output. +*/ +#[derive(Debug, Clone)] +pub struct NarratableReportHandler { + context_lines: usize, + with_cause_chain: bool, + footer: Option, +} + +impl NarratableReportHandler { + /// Create a new [`NarratableReportHandler`]. There are no customization + /// options. + pub const fn new() -> Self { + Self { + footer: None, + context_lines: 1, + with_cause_chain: true, + } + } + + /// Include the cause chain of the top-level error in the report, if + /// available. + pub const fn with_cause_chain(mut self) -> Self { + self.with_cause_chain = true; + self + } + + /// Do not include the cause chain of the top-level error in the report. + pub const fn without_cause_chain(mut self) -> Self { + self.with_cause_chain = false; + self + } + + /// Set the footer to be displayed at the end of the report. + pub fn with_footer(mut self, footer: String) -> Self { + self.footer = Some(footer); + self + } + + /// Sets the number of lines of context to show around each error. + pub const fn with_context_lines(mut self, lines: usize) -> Self { + self.context_lines = lines; + self + } +} + +impl Default for NarratableReportHandler { + fn default() -> Self { + Self::new() + } +} + +impl NarratableReportHandler { + /// Render a [`Diagnostic`]. This function is mostly internal and meant to + /// be called by the toplevel [`ReportHandler`] handler, but is + /// made public to make it easier (possible) to test in isolation from + /// global state. + pub fn render_report( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + ) -> fmt::Result { + self.render_header(f, diagnostic)?; + if self.with_cause_chain { + self.render_causes(f, diagnostic)?; + } + let src = diagnostic.source_code(); + self.render_snippets(f, diagnostic, src)?; + self.render_footer(f, diagnostic)?; + self.render_related(f, diagnostic, src)?; + if let Some(footer) = &self.footer { + writeln!(f, "{}", footer)?; + } + Ok(()) + } + + fn render_header(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result { + writeln!(f, "{}", diagnostic)?; + let severity = match diagnostic.severity() { + Some(Severity::Error) | None => "error", + Some(Severity::Warning) => "warning", + Some(Severity::Advice) => "advice", + }; + writeln!(f, " Diagnostic severity: {}", severity)?; + Ok(()) + } + + fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result { + if let Some(cause_iter) = diagnostic + .diagnostic_source() + .map(DiagnosticChain::from_diagnostic) + .or_else(|| diagnostic.source().map(DiagnosticChain::from_stderror)) + { + for error in cause_iter { + writeln!(f, " Caused by: {}", error)?; + } + } + + Ok(()) + } + + fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result { + if let Some(help) = diagnostic.help() { + writeln!(f, "diagnostic help: {}", help)?; + } + if let Some(code) = diagnostic.code() { + writeln!(f, "diagnostic code: {}", code)?; + } + if let Some(url) = diagnostic.url() { + writeln!(f, "For more details, see:\n{}", url)?; + } + Ok(()) + } + + fn render_related( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + parent_src: Option<&dyn SourceCode>, + ) -> fmt::Result { + if let Some(related) = diagnostic.related() { + writeln!(f)?; + for rel in related { + match rel.severity() { + Some(Severity::Error) | None => write!(f, "Error: ")?, + Some(Severity::Warning) => write!(f, "Warning: ")?, + Some(Severity::Advice) => write!(f, "Advice: ")?, + }; + self.render_header(f, rel)?; + writeln!(f)?; + self.render_causes(f, rel)?; + let src = rel.source_code().or(parent_src); + self.render_snippets(f, rel, src)?; + self.render_footer(f, rel)?; + self.render_related(f, rel, src)?; + } + } + Ok(()) + } + + fn render_snippets( + &self, + f: &mut impl fmt::Write, + diagnostic: &(dyn Diagnostic), + source_code: Option<&dyn SourceCode>, + ) -> fmt::Result { + if let Some(source) = source_code { + if let Some(labels) = diagnostic.labels() { + let mut labels = labels.collect::>(); + labels.sort_unstable_by_key(|l| l.inner().offset()); + if !labels.is_empty() { + let contents = labels + .iter() + .map(|label| { + source.read_span(label.inner(), self.context_lines, self.context_lines) + }) + .collect::>>, MietteError>>() + .map_err(|_| fmt::Error)?; + let mut contexts = Vec::new(); + for (right, right_conts) in labels.iter().cloned().zip(contents.iter()) { + if contexts.is_empty() { + contexts.push((right, right_conts)); + } else { + let (left, left_conts) = contexts.last().unwrap().clone(); + let left_end = left.offset() + left.len(); + let right_end = right.offset() + right.len(); + if left_conts.line() + left_conts.line_count() >= right_conts.line() { + // The snippets will overlap, so we create one Big Chunky Boi + let new_span = LabeledSpan::new( + left.label().map(String::from), + left.offset(), + if right_end >= left_end { + // Right end goes past left end + right_end - left.offset() + } else { + // right is contained inside left + left.len() + }, + ); + if source + .read_span( + new_span.inner(), + self.context_lines, + self.context_lines, + ) + .is_ok() + { + contexts.pop(); + contexts.push(( + new_span, // We'll throw this away later + left_conts, + )); + } else { + contexts.push((right, right_conts)); + } + } else { + contexts.push((right, right_conts)); + } + } + } + for (ctx, _) in contexts { + self.render_context(f, source, &ctx, &labels[..])?; + } + } + } + } + Ok(()) + } + + fn render_context( + &self, + f: &mut impl fmt::Write, + source: &dyn SourceCode, + context: &LabeledSpan, + labels: &[LabeledSpan], + ) -> fmt::Result { + let (contents, lines) = self.get_lines(source, context.inner())?; + write!(f, "Begin snippet")?; + if let Some(filename) = contents.name() { + write!(f, " for {}", filename,)?; + } + writeln!( + f, + " starting at line {}, column {}", + contents.line() + 1, + contents.column() + 1 + )?; + writeln!(f)?; + for line in &lines { + writeln!(f, "snippet line {}: {}", line.line_number, line.text)?; + let relevant = labels + .iter() + .filter_map(|l| line.span_attach(l.inner()).map(|a| (a, l))); + for (attach, label) in relevant { + match attach { + SpanAttach::Contained { col_start, col_end } if col_start == col_end => { + write!( + f, + " label at line {}, column {}", + line.line_number, col_start, + )?; + } + SpanAttach::Contained { col_start, col_end } => { + write!( + f, + " label at line {}, columns {} to {}", + line.line_number, col_start, col_end, + )?; + } + SpanAttach::Starts { col_start } => { + write!( + f, + " label starting at line {}, column {}", + line.line_number, col_start, + )?; + } + SpanAttach::Ends { col_end } => { + write!( + f, + " label ending at line {}, column {}", + line.line_number, col_end, + )?; + } + } + if let Some(label) = label.label() { + write!(f, ": {}", label)?; + } + writeln!(f)?; + } + } + Ok(()) + } + + fn get_lines<'a>( + &'a self, + source: &'a dyn SourceCode, + context_span: &'a SourceSpan, + ) -> Result<(Box + 'a>, Vec), fmt::Error> { + let context_data = source + .read_span(context_span, self.context_lines, self.context_lines) + .map_err(|_| fmt::Error)?; + let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected"); + let mut line = context_data.line(); + let mut column = context_data.column(); + let mut offset = context_data.span().offset(); + let mut line_offset = offset; + 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; + match char { + '\r' => { + if iter.next_if_eq(&'\n').is_some() { + offset += 1; + line += 1; + column = 0; + } else { + line_str.push(char); + column += 1; + } + at_end_of_file = iter.peek().is_none(); + } + '\n' => { + at_end_of_file = iter.peek().is_none(); + line += 1; + column = 0; + } + _ => { + line_str.push(char); + column += 1; + } + } + + if iter.peek().is_none() && !at_end_of_file { + line += 1; + } + + if column == 0 || iter.peek().is_none() { + lines.push(Line { + line_number: line, + offset: line_offset, + text: line_str.clone(), + at_end_of_file, + }); + line_str.clear(); + line_offset = offset; + } + } + Ok((context_data, lines)) + } +} + +impl ReportHandler for NarratableReportHandler { + fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + return fmt::Debug::fmt(diagnostic, f); + } + + self.render_report(f, diagnostic) + } +} + +/* +Support types +*/ + +struct Line { + line_number: usize, + offset: usize, + text: String, + at_end_of_file: bool, +} + +enum SpanAttach { + Contained { col_start: usize, col_end: usize }, + Starts { col_start: usize }, + Ends { col_end: usize }, +} + +/// Returns column at offset, and nearest boundary if offset is in the middle of +/// the character +fn safe_get_column(text: &str, offset: usize, start: bool) -> usize { + let mut column = text.get(0..offset).map(|s| s.width()).unwrap_or_else(|| { + let mut column = 0; + for (idx, c) in text.char_indices() { + if offset <= idx { + break; + } + column += c.width().unwrap_or(0); + } + column + }); + if start { + // Offset are zero-based, so plus one + column += 1; + } // On the other hand for end span, offset refers for the next column + // So we should do -1. column+1-1 == column + column +} + +impl Line { + fn span_attach(&self, span: &SourceSpan) -> Option { + let span_end = span.offset() + span.len(); + let line_end = self.offset + self.text.len(); + + let start_after = span.offset() >= self.offset; + let end_before = self.at_end_of_file || span_end <= line_end; + + if start_after && end_before { + let col_start = safe_get_column(&self.text, span.offset() - self.offset, true); + let col_end = if span.is_empty() { + col_start + } else { + // span_end refers to the next character after token + // while col_end refers to the exact character, so -1 + safe_get_column(&self.text, span_end - self.offset, false) + }; + return Some(SpanAttach::Contained { col_start, col_end }); + } + if start_after && span.offset() <= line_end { + let col_start = safe_get_column(&self.text, span.offset() - self.offset, true); + return Some(SpanAttach::Starts { col_start }); + } + if end_before && span_end >= self.offset { + let col_end = safe_get_column(&self.text, span_end - self.offset, false); + return Some(SpanAttach::Ends { col_end }); + } + None + } +} diff --git a/vendor/miette/src/handlers/theme.rs b/vendor/miette/src/handlers/theme.rs new file mode 100644 index 0000000..1f5236a --- /dev/null +++ b/vendor/miette/src/handlers/theme.rs @@ -0,0 +1,275 @@ +use is_terminal::IsTerminal; +use owo_colors::Style; + +/** +Theme used by [`GraphicalReportHandler`](crate::GraphicalReportHandler) to +render fancy [`Diagnostic`](crate::Diagnostic) reports. + +A theme consists of two things: the set of characters to be used for drawing, +and the +[`owo_colors::Style`](https://docs.rs/owo-colors/latest/owo_colors/struct.Style.html)s to be used to paint various items. + +You can create your own custom graphical theme using this type, or you can use +one of the predefined ones using the methods below. +*/ +#[derive(Debug, Clone)] +pub struct GraphicalTheme { + /// Characters to be used for drawing. + pub characters: ThemeCharacters, + /// Styles to be used for painting. + pub styles: ThemeStyles, +} + +impl GraphicalTheme { + /// ASCII-art-based graphical drawing, with ANSI styling. + pub fn ascii() -> Self { + Self { + characters: ThemeCharacters::ascii(), + styles: ThemeStyles::ansi(), + } + } + + /// Graphical theme that draws using both ansi colors and unicode + /// characters. + /// + /// Note that full rgb colors aren't enabled by default because they're + /// an accessibility hazard, especially in the context of terminal themes + /// that can change the background color and make hardcoded colors illegible. + /// Such themes typically remap ansi codes properly, treating them more + /// like CSS classes than specific colors. + pub fn unicode() -> Self { + Self { + characters: ThemeCharacters::unicode(), + styles: ThemeStyles::ansi(), + } + } + + /// Graphical theme that draws in monochrome, while still using unicode + /// characters. + pub fn unicode_nocolor() -> Self { + Self { + characters: ThemeCharacters::unicode(), + styles: ThemeStyles::none(), + } + } + + /// A "basic" graphical theme that skips colors and unicode characters and + /// just does monochrome ascii art. If you want a completely non-graphical + /// rendering of your `Diagnostic`s, check out + /// [crate::NarratableReportHandler], or write your own + /// [crate::ReportHandler]! + pub fn none() -> Self { + Self { + characters: ThemeCharacters::ascii(), + styles: ThemeStyles::none(), + } + } +} + +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() + } + Ok(string) if string != "0" => Self::unicode_nocolor(), + _ => Self::unicode(), + } + } +} + +/** +Styles for various parts of graphical rendering for the [crate::GraphicalReportHandler]. +*/ +#[derive(Debug, Clone)] +pub struct ThemeStyles { + /// Style to apply to things highlighted as "error". + pub error: Style, + /// Style to apply to things highlighted as "warning". + pub warning: Style, + /// Style to apply to things highlighted as "advice". + pub advice: Style, + /// Style to apply to the help text. + pub help: Style, + /// Style to apply to filenames/links/URLs. + pub link: Style, + /// Style to apply to line numbers. + pub linum: Style, + /// Styles to cycle through (using `.iter().cycle()`), to render the lines + /// and text for diagnostic highlights. + pub highlights: Vec