Skip to content

Commit

Permalink
feat(protocol) overhauled entire protocol to be based on byte offsets (
Browse files Browse the repository at this point in the history
…#1)

Includes other improvements!

BREAKING CHANGE: Yeah this is pretty much a rewrite.
  • Loading branch information
zkat committed Aug 5, 2021
1 parent 3b3a07b commit 6b51694
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 126 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ edition = "2018"

[dependencies]
indenter = "0.3.3"
thiserror = "1.0.26"

[dev-dependencies]
thiserror = "1.0.26"
29 changes: 16 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ you FAIL miette? you fail her compiler like the unsafe C program? oh! oh! jail f

Here's an example of using something like `thisdiagnostic` to define Diagnostics declaratively.

```rust
```ignore
use thiserror::Error;
use thisdiagnostic::Diagnostic;
Expand Down Expand Up @@ -40,24 +40,27 @@ pub enum MyDiagnostic {
)]
#[error("Tried to add a {bad_type} to a {good_type}")]
BadArithmetic {
src: PathBuf,
other_src: PathBuf,

// Regular error metadata for programmatic use.
good_type: Type,
bad_type: Type,
bad_var: Var,
#[span_source(src)]
#[label("This is a {bad_type}")]
bad_var_span: SourceSpan,
// Anything implementing the Source trait can be used as a source.
src: PathBuf,
other_src: String,
// The context is the span of code that will be rendered.
// There can be multiple contexts in a Diagnostic.
#[context(src, "This region is where things went wrong.")]
ctx: SourceSpan,
#[span_source(src)]
#[label("This is a {good_type}")]
good_var_span: Option<SourceSpan>,
// Highlights underline and label specific subsections of the context.
#[highlight(ctx, "This is a {bad_type}")]
bad_var_span: SourceSpan, // These can span multiple lines!
#[span(other_src)]
#[label("{bad_var} is defined here")]
bad_var_definition_span: SourceSpan, // multiline span
// They can be optional!
#[highlight(ctx, "This is a {good_type}")]
good_var_span: Option<SourceSpan>,
},
#[error(transparent)]
Expand Down
2 changes: 1 addition & 1 deletion src/chain.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*!
Iterate over error `.source()` chains.
NOTE: This module is taken wholesale from https://crates.io/crates/eyre.
NOTE: This module is taken wholesale from <https://crates.io/crates/eyre>.
*/
use std::error::Error as StdError;
use std::vec;
Expand Down
14 changes: 14 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub use std::io;

pub use thiserror::Error;

/**
Error enum for miette. Used by certain operations in the protocol.
*/
#[derive(Debug, Error)]
pub enum MietteError {
#[error(transparent)]
IoError(#[from] io::Error),
#[error("The given offset is outside the bounds of its Source")]
OutOfBounds
}
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#![doc = include_str!("../README.md")]

pub use chain::*;
pub use error::*;
pub use protocol::*;
pub use reporter::*;

mod chain;
mod error;
mod source_impls;
mod protocol;
mod reporter;
150 changes: 121 additions & 29 deletions src/protocol.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
/*!
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::Display;
use std::io::{self, Read};

use crate::MietteError;

/**
Adds rich metadata to your Error that can be used by [DiagnosticReporter] to print
Expand All @@ -13,7 +20,7 @@ pub trait Diagnostic: std::error::Error + Send + Sync + 'static {
/// `E0123` or Enums will work just fine.
fn code(&self) -> &(dyn Display + 'static);

/// Diagnostic severity. This may be used by [Reporter]s to change the
/// Diagnostic severity. This may be used by [DiagnosticReporter]s to change the
/// display format of this diagnostic.
fn severity(&self) -> Severity;

Expand All @@ -23,9 +30,9 @@ pub trait Diagnostic: std::error::Error + Send + Sync + 'static {
None
}

/// Additional contextual details. This is typically used for adding
/// Additional contextual snippets. This is typically used for adding
/// marked-up source file output the way compilers often do.
fn details(&self) -> Option<&[DiagnosticDetail]> {
fn snippets(&self) -> Option<&[DiagnosticSnippet]> {
None
}
}
Expand Down Expand Up @@ -69,52 +76,137 @@ pub enum Severity {
}

/**
Represents a readable source of some sort: a source file, a String, etc.
Represents a readable source of some sort.
This trait is able to support simple Source 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 Sources which are gigabytes or larger in size.
*/
pub trait Source: std::fmt::Debug + Send + Sync + 'static {
/// Get a `Read`er from a given [Source].
fn open(&self) -> io::Result<Box<dyn Read>>;
/// Read the bytes for a specific span from this Source.
fn read_span<'a>(&'a self, span: &SourceSpan)
-> Result<Box<dyn SpanContents<'a> + '_>, MietteError>;
}

/**
Contents of a [Source] 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) -> &[u8];
/// The 0-indexed line in the associated [Source] where the data begins.
fn line(&self) -> usize;
/// The 0-indexed column in the associated [Source] where the data begins,
/// relative to `line`.
fn column(&self) -> usize;
}

/**
Details and additional context to be displayed.
Basic implementation of the [SpanContents] trait, for convenience.
*/
#[derive(Clone, Debug)]
pub struct MietteSpanContents<'a> {
/// Data from a [Source], in bytes.
data: &'a [u8],
// The 0-indexed line where the associated [SourceSpan] _starts_.
line: usize,
// The 0-indexed column where the associated [SourceSpan] _starts_.
column: usize,
}

impl<'a> MietteSpanContents<'a> {
/// Make a new [MietteSpanContents] object.
pub fn new(data: &'a [u8], line: usize, column: usize) -> MietteSpanContents<'a> {
MietteSpanContents { data, line, column }
}
}

impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
fn data(&self) -> &[u8] {
self.data
}
fn line(&self) -> usize {
self.line
}
fn column(&self) -> usize {
self.column
}
}

/**
A snippet from a [Source] to be displayed with a message and possibly some highlights.
*/
#[derive(Debug)]
pub struct DiagnosticDetail {
/// Explanation of this specific diagnostic detail.
pub struct DiagnosticSnippet {
/// Explanation of this specific diagnostic snippet.
pub message: Option<String>,
/// The "filename" for this diagnostic.
/// The "filename" for this snippet.
pub source_name: String,
/// A [Source] that can be used to read the actual text of a source.
pub source: Box<dyn Source>,
/// The primary [SourceSpan] where this diagnostic is located.
pub span: SourceSpan,
/// Additional [SourceSpan]s that can add secondary context.
pub other_spans: Option<Vec<SourceSpan>>,
pub context: SourceSpan,
/// Additional [SourceSpan]s that mark specific sections of the span, for
/// example, to underline specific text within the larger span. They're
/// paired with labels that should be applied to those sections.
pub highlights: Option<Vec<(String, SourceSpan)>>,
}

/**
Span within a [Source] with an associated message.
*/
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct SourceSpan {
/// A name for the thing this SourceSpan is actually pointing to.
pub label: String,
/// The start of the span.
pub start: SourceLocation,
/// The end of the span. Optional
pub end: Option<SourceLocation>,
pub start: SourceOffset,
/// The (exclusive) end of the span.
pub end: SourceOffset,
}

impl SourceSpan {
pub fn new(start: SourceOffset, end: SourceOffset) -> Self {
assert!(
start.offset() <= end.offset(),
"Starting offset must come before the end offset."
);
Self { start, end }
}

pub fn len(&self) -> usize {
self.end.offset() - self.start.offset() + 1
}

pub fn is_empty(&self) -> bool {
self.start.offset() == self.end.offset()
}
}

/**
Specific location in a [SourceSpan]
"Raw" type for the byte offset from the beginning of a [Source].
*/
#[derive(Debug)]
pub struct SourceLocation {
/// 0-indexed column of location.
pub column: usize,
/// 0-indexed line of location.
pub line: usize,
/// 0-indexed _character_ offset of location.
pub offset: usize,
pub type ByteOffset = usize;

/**
Newtype that represents the [ByteOffset] from the beginning of a [Source]
*/
#[derive(Clone, Copy, Debug)]
pub struct SourceOffset(ByteOffset);

impl SourceOffset {
/// Actual byte offset.
pub fn offset(&self) -> ByteOffset {
self.0
}
}

impl From<ByteOffset> for SourceOffset {
fn from(bytes: ByteOffset) -> Self {
SourceOffset(bytes)
}
}
Loading

0 comments on commit 6b51694

Please sign in to comment.