diff options
Diffstat (limited to 'vendor/miette/src/protocol.rs')
-rw-r--r-- | vendor/miette/src/protocol.rs | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/vendor/miette/src/protocol.rs b/vendor/miette/src/protocol.rs new file mode 100644 index 0000000..f516984 --- /dev/null +++ b/vendor/miette/src/protocol.rs @@ -0,0 +1,697 @@ +/*! +This module defines the core of the miette protocol: a series of types and +traits that you can implement to get access to miette's (and related library's) +full reporting and such features. +*/ +use std::{ + fmt::{self, Display}, + fs, + panic::Location, +}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::MietteError; + +/// Adds rich metadata to your Error that can be used by +/// [`Report`](crate::Report) to print really nice and human-friendly error +/// messages. +pub trait Diagnostic: std::error::Error { + /// Unique diagnostic code that can be used to look up more information + /// about this `Diagnostic`. Ideally also globally unique, and documented + /// in the toplevel crate's documentation for easy searching. Rust path + /// format (`foo::bar::baz`) is recommended, but more classic codes like + /// `E0123` or enums will work just fine. + fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { + None + } + + /// Diagnostic severity. This may be used by + /// [`ReportHandler`](crate::ReportHandler)s to change the display format + /// of this diagnostic. + /// + /// If `None`, reporters should treat this as [`Severity::Error`]. + fn severity(&self) -> Option<Severity> { + None + } + + /// Additional help text related to this `Diagnostic`. Do you have any + /// advice for the poor soul who's just run into this issue? + fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { + None + } + + /// URL to visit for a more detailed explanation/help about this + /// `Diagnostic`. + fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { + None + } + + /// Source code to apply this `Diagnostic`'s [`Diagnostic::labels`] to. + fn source_code(&self) -> Option<&dyn SourceCode> { + None + } + + /// Labels to apply to this `Diagnostic`'s [`Diagnostic::source_code`] + fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> { + None + } + + /// Additional related `Diagnostic`s. + fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> { + None + } + + /// The cause of the error. + fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { + None + } +} + +macro_rules! box_impls { + ($($box_type:ty),*) => { + $( + impl std::error::Error for $box_type { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + (**self).source() + } + + fn cause(&self) -> Option<&dyn std::error::Error> { + self.source() + } + } + )* + } +} + +box_impls! { + Box<dyn Diagnostic>, + Box<dyn Diagnostic + Send>, + Box<dyn Diagnostic + Send + Sync> +} + +impl<T: Diagnostic + Send + Sync + 'static> From<T> + for Box<dyn Diagnostic + Send + Sync + 'static> +{ + fn from(diag: T) -> Self { + Box::new(diag) + } +} + +impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + Send + 'static> { + fn from(diag: T) -> Self { + Box::<dyn Diagnostic + Send + Sync>::from(diag) + } +} + +impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + 'static> { + fn from(diag: T) -> Self { + Box::<dyn Diagnostic + Send + Sync>::from(diag) + } +} + +impl From<&str> for Box<dyn Diagnostic> { + fn from(s: &str) -> Self { + From::from(String::from(s)) + } +} + +impl<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> { + fn from(s: &str) -> Self { + From::from(String::from(s)) + } +} + +impl From<String> for Box<dyn Diagnostic> { + fn from(s: String) -> Self { + let err1: Box<dyn Diagnostic + Send + Sync> = From::from(s); + let err2: Box<dyn Diagnostic> = err1; + err2 + } +} + +impl From<String> for Box<dyn Diagnostic + Send + Sync> { + fn from(s: String) -> Self { + struct StringError(String); + + impl std::error::Error for StringError {} + impl Diagnostic for StringError {} + + impl Display for StringError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } + } + + // Purposefully skip printing "StringError(..)" + impl fmt::Debug for StringError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } + } + + Box::new(StringError(s)) + } +} + +impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> { + fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self { + #[derive(thiserror::Error)] + #[error(transparent)] + struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>); + impl fmt::Debug for BoxedDiagnostic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } + } + + impl Diagnostic for BoxedDiagnostic {} + + Box::new(BoxedDiagnostic(s)) + } +} + +/** +[`Diagnostic`] severity. Intended to be used by +[`ReportHandler`](crate::ReportHandler)s to change the way different +[`Diagnostic`]s are displayed. Defaults to [`Severity::Error`]. +*/ +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Severity { + /// Just some help. Here's how you could be doing it better. + Advice, + /// Warning. Please take note. + Warning, + /// Critical failure. The program cannot continue. + /// This is the default severity, if you don't specify another one. + Error, +} + +impl Default for Severity { + fn default() -> Self { + Severity::Error + } +} + +#[cfg(feature = "serde")] +#[test] +fn test_serialize_severity() { + use serde_json::json; + + assert_eq!(json!(Severity::Advice), json!("Advice")); + assert_eq!(json!(Severity::Warning), json!("Warning")); + assert_eq!(json!(Severity::Error), json!("Error")); +} + +#[cfg(feature = "serde")] +#[test] +fn test_deserialize_severity() { + use serde_json::json; + + let severity: Severity = serde_json::from_value(json!("Advice")).unwrap(); + assert_eq!(severity, Severity::Advice); + + let severity: Severity = serde_json::from_value(json!("Warning")).unwrap(); + assert_eq!(severity, Severity::Warning); + + let severity: Severity = serde_json::from_value(json!("Error")).unwrap(); + assert_eq!(severity, Severity::Error); +} + +/** +Represents readable source code of some sort. + +This trait is able to support simple `SourceCode` types like [`String`]s, as +well as more involved types like indexes into centralized `SourceMap`-like +types, file handles, and even network streams. + +If you can read it, you can source it, and it's not necessary to read the +whole thing--meaning you should be able to support `SourceCode`s which are +gigabytes or larger in size. +*/ +pub trait SourceCode: Send + Sync { + /// Read the bytes for a specific span from this SourceCode, keeping a + /// certain number of lines before and after the span as context. + fn read_span<'a>( + &'a self, + span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, + ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>; +} + +/// A labeled [`SourceSpan`]. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct LabeledSpan { + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + label: Option<String>, + span: SourceSpan, +} + +impl LabeledSpan { + /// Makes a new labeled span. + pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self { + Self { + label, + span: SourceSpan::new(SourceOffset(offset), SourceOffset(len)), + } + } + + /// Makes a new labeled span using an existing span. + pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self { + Self { + label, + span: span.into(), + } + } + + /// Makes a new label at specified span + /// + /// # Examples + /// ``` + /// use miette::LabeledSpan; + /// + /// let source = "Cpp is the best"; + /// let label = LabeledSpan::at(0..3, "should be Rust"); + /// assert_eq!( + /// label, + /// LabeledSpan::new(Some("should be Rust".to_string()), 0, 3) + /// ) + /// ``` + pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self { + Self::new_with_span(Some(label.into()), span) + } + + /// Makes a new label that points at a specific offset. + /// + /// # Examples + /// ``` + /// use miette::LabeledSpan; + /// + /// let source = "(2 + 2"; + /// let label = LabeledSpan::at_offset(4, "expected a closing parenthesis"); + /// assert_eq!( + /// label, + /// LabeledSpan::new(Some("expected a closing parenthesis".to_string()), 4, 0) + /// ) + /// ``` + pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self { + Self::new(Some(label.into()), offset, 0) + } + + /// Makes a new label without text, that underlines a specific span. + /// + /// # Examples + /// ``` + /// use miette::LabeledSpan; + /// + /// let source = "You have an eror here"; + /// let label = LabeledSpan::underline(12..16); + /// assert_eq!(label, LabeledSpan::new(None, 12, 4)) + /// ``` + pub fn underline(span: impl Into<SourceSpan>) -> Self { + Self::new_with_span(None, span) + } + + /// Gets the (optional) label string for this `LabeledSpan`. + pub fn label(&self) -> Option<&str> { + self.label.as_deref() + } + + /// Returns a reference to the inner [`SourceSpan`]. + pub const fn inner(&self) -> &SourceSpan { + &self.span + } + + /// Returns the 0-based starting byte offset. + pub const fn offset(&self) -> usize { + self.span.offset() + } + + /// Returns the number of bytes this `LabeledSpan` spans. + pub const fn len(&self) -> usize { + self.span.len() + } + + /// True if this `LabeledSpan` is empty. + pub const fn is_empty(&self) -> bool { + self.span.is_empty() + } +} + +#[cfg(feature = "serde")] +#[test] +fn test_serialize_labeled_span() { + use serde_json::json; + + assert_eq!( + json!(LabeledSpan::new(None, 0, 0)), + json!({ + "span": { "offset": 0, "length": 0 } + }) + ); + + assert_eq!( + json!(LabeledSpan::new(Some("label".to_string()), 0, 0)), + json!({ + "label": "label", + "span": { "offset": 0, "length": 0 } + }) + ) +} + +#[cfg(feature = "serde")] +#[test] +fn test_deserialize_labeled_span() { + use serde_json::json; + + let span: LabeledSpan = serde_json::from_value(json!({ + "label": null, + "span": { "offset": 0, "length": 0 } + })) + .unwrap(); + assert_eq!(span, LabeledSpan::new(None, 0, 0)); + + let span: LabeledSpan = serde_json::from_value(json!({ + "span": { "offset": 0, "length": 0 } + })) + .unwrap(); + assert_eq!(span, LabeledSpan::new(None, 0, 0)); + + let span: LabeledSpan = serde_json::from_value(json!({ + "label": "label", + "span": { "offset": 0, "length": 0 } + })) + .unwrap(); + assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0)) +} + +/** +Contents of a [`SourceCode`] covered by [`SourceSpan`]. + +Includes line and column information to optimize highlight calculations. +*/ +pub trait SpanContents<'a> { + /// Reference to the data inside the associated span, in bytes. + fn data(&self) -> &'a [u8]; + /// [`SourceSpan`] representing the span covered by this `SpanContents`. + fn span(&self) -> &SourceSpan; + /// An optional (file?) name for the container of this `SpanContents`. + fn name(&self) -> Option<&str> { + None + } + /// The 0-indexed line in the associated [`SourceCode`] where the data + /// begins. + fn line(&self) -> usize; + /// The 0-indexed column in the associated [`SourceCode`] where the data + /// begins, relative to `line`. + fn column(&self) -> usize; + /// Total number of lines covered by this `SpanContents`. + fn line_count(&self) -> usize; +} + +/** +Basic implementation of the [`SpanContents`] trait, for convenience. +*/ +#[derive(Clone, Debug)] +pub struct MietteSpanContents<'a> { + // Data from a [`SourceCode`], in bytes. + data: &'a [u8], + // span actually covered by this SpanContents. + span: SourceSpan, + // The 0-indexed line where the associated [`SourceSpan`] _starts_. + line: usize, + // The 0-indexed column where the associated [`SourceSpan`] _starts_. + column: usize, + // Number of line in this snippet. + line_count: usize, + // Optional filename + name: Option<String>, +} + +impl<'a> MietteSpanContents<'a> { + /// Make a new [`MietteSpanContents`] object. + pub const fn new( + data: &'a [u8], + span: SourceSpan, + line: usize, + column: usize, + line_count: usize, + ) -> MietteSpanContents<'a> { + MietteSpanContents { + data, + span, + line, + column, + line_count, + name: None, + } + } + + /// Make a new [`MietteSpanContents`] object, with a name for its 'file'. + pub const fn new_named( + name: String, + data: &'a [u8], + span: SourceSpan, + line: usize, + column: usize, + line_count: usize, + ) -> MietteSpanContents<'a> { + MietteSpanContents { + data, + span, + line, + column, + line_count, + name: Some(name), + } + } +} + +impl<'a> SpanContents<'a> for MietteSpanContents<'a> { + fn data(&self) -> &'a [u8] { + self.data + } + fn span(&self) -> &SourceSpan { + &self.span + } + fn line(&self) -> usize { + self.line + } + fn column(&self) -> usize { + self.column + } + fn line_count(&self) -> usize { + self.line_count + } + fn name(&self) -> Option<&str> { + self.name.as_deref() + } +} + +/// Span within a [`SourceCode`] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct SourceSpan { + /// The start of the span. + offset: SourceOffset, + /// The total length of the span + length: usize, +} + +impl SourceSpan { + /// Create a new [`SourceSpan`]. + pub const fn new(start: SourceOffset, length: SourceOffset) -> Self { + Self { + offset: start, + length: length.offset(), + } + } + + /// The absolute offset, in bytes, from the beginning of a [`SourceCode`]. + pub const fn offset(&self) -> usize { + self.offset.offset() + } + + /// Total length of the [`SourceSpan`], in bytes. + pub const fn len(&self) -> usize { + self.length + } + + /// Whether this [`SourceSpan`] has a length of zero. It may still be useful + /// to point to a specific point. + pub const fn is_empty(&self) -> bool { + self.length == 0 + } +} + +impl From<(ByteOffset, usize)> for SourceSpan { + fn from((start, len): (ByteOffset, usize)) -> Self { + Self { + offset: start.into(), + length: len, + } + } +} + +impl From<(SourceOffset, SourceOffset)> for SourceSpan { + fn from((start, len): (SourceOffset, SourceOffset)) -> Self { + Self::new(start, len) + } +} + +impl From<std::ops::Range<ByteOffset>> for SourceSpan { + fn from(range: std::ops::Range<ByteOffset>) -> Self { + Self { + offset: range.start.into(), + length: range.len(), + } + } +} + +impl From<SourceOffset> for SourceSpan { + fn from(offset: SourceOffset) -> Self { + Self { offset, length: 0 } + } +} + +impl From<ByteOffset> for SourceSpan { + fn from(offset: ByteOffset) -> Self { + Self { + offset: offset.into(), + length: 0, + } + } +} + +#[cfg(feature = "serde")] +#[test] +fn test_serialize_source_span() { + use serde_json::json; + + assert_eq!( + json!(SourceSpan::from(0)), + json!({ "offset": 0, "length": 0}) + ) +} + +#[cfg(feature = "serde")] +#[test] +fn test_deserialize_source_span() { + use serde_json::json; + + let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap(); + assert_eq!(span, SourceSpan::from(0)) +} + +/** +"Raw" type for the byte offset from the beginning of a [`SourceCode`]. +*/ +pub type ByteOffset = usize; + +/** +Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`] +*/ +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct SourceOffset(ByteOffset); + +impl SourceOffset { + /// Actual byte offset. + pub const fn offset(&self) -> ByteOffset { + self.0 + } + + /// Little utility to help convert 1-based line/column locations into + /// miette-compatible Spans + /// + /// This function is infallible: Giving an out-of-range line/column pair + /// will return the offset of the last byte in the source. + pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self { + let mut line = 0usize; + let mut col = 0usize; + let mut offset = 0usize; + for char in source.as_ref().chars() { + if line + 1 >= loc_line && col + 1 >= loc_col { + break; + } + if char == '\n' { + col = 0; + line += 1; + } else { + col += 1; + } + offset += char.len_utf8(); + } + + SourceOffset(offset) + } + + /// Returns an offset for the _file_ location of wherever this function is + /// called. If you want to get _that_ caller's location, mark this + /// function's caller with `#[track_caller]` (and so on and so forth). + /// + /// Returns both the filename that was given and the offset of the caller + /// as a [`SourceOffset`]. + /// + /// Keep in mind that this fill only work if the file your Rust source + /// file was compiled from is actually available at that location. If + /// you're shipping binaries for your application, you'll want to ignore + /// the Err case or otherwise report it. + #[track_caller] + pub fn from_current_location() -> Result<(String, Self), MietteError> { + let loc = Location::caller(); + Ok(( + loc.file().into(), + fs::read_to_string(loc.file()) + .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?, + )) + } +} + +impl From<ByteOffset> for SourceOffset { + fn from(bytes: ByteOffset) -> Self { + SourceOffset(bytes) + } +} + +#[test] +fn test_source_offset_from_location() { + let source = "f\n\noo\r\nbar"; + + assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0); + assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1); + assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2); + assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3); + assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4); + assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5); + assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6); + assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7); + assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8); + assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9); + assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10); + + // Out-of-range + assert_eq!( + SourceOffset::from_location(source, 5, 1).offset(), + source.len() + ); +} + +#[cfg(feature = "serde")] +#[test] +fn test_serialize_source_offset() { + use serde_json::json; + + assert_eq!(json!(SourceOffset::from(0)), 0) +} + +#[cfg(feature = "serde")] +#[test] +fn test_deserialize_source_offset() { + let offset: SourceOffset = serde_json::from_str("0").unwrap(); + assert_eq!(offset, SourceOffset::from(0)) +} |