Skip to content

Commit

Permalink
better generics
Browse files Browse the repository at this point in the history
  • Loading branch information
asmello committed Nov 18, 2024
1 parent 3c9ff88 commit a728f72
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 70 deletions.
110 changes: 41 additions & 69 deletions src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub trait Diagnostic<'s>: Sized + private::Sealed {
type Subject: private::IntoOwned;

/// Combine the error with its subject to generate a [`Report`].
fn into_report(self, subject: impl Into<Self::Subject>) -> Report<'s, Self> {
fn into_report(self, subject: impl Into<Self::Subject>) -> Report<Self, Self::Subject> {
Report::new(self, subject)
}

Expand Down Expand Up @@ -42,118 +42,96 @@ impl From<Label> for miette::LabeledSpan {
}

/// An error wrapper which includes the subject of the failure.
#[derive(Clone, PartialEq, Eq)]
pub struct Report<'s, S: Diagnostic<'s>> {
source: S,
subject: S::Subject,
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Report<SRC, SUB> {
source: SRC,
subject: SUB,
}

impl<'s, S: Diagnostic<'s>> Report<'s, S> {
impl<SRC, SUB> Report<SRC, SUB> {
/// Create a new `Report` with the given subject and error.
fn new(source: S, subject: impl Into<S::Subject>) -> Self {
fn new(source: SRC, subject: impl Into<SUB>) -> Self {
Self {
source,
subject: subject.into(),
}
}

/// The value which caused the error.
pub fn subject(&self) -> &S::Subject {
pub fn subject(&self) -> &SUB {
&self.subject
}

/// The error which occurred.
pub fn original(&self) -> &S {
pub fn original(&self) -> &SRC {
&self.source
}

/// The original parts of the [`Report`].
pub fn decompose(self) -> (S, S::Subject) {
pub fn decompose(self) -> (SRC, SUB) {
(self.source, self.subject)
}
}

impl<'s, S> Report<'s, S>
impl<'s, S> Report<S, <S as Diagnostic<'s>>::Subject>
where
S: Diagnostic<'s>,
{
/// Converts the Report into an owned instance (generally by cloning the subject).
pub fn into_owned<O>(self) -> Report<'static, O>
where
S: Into<O>,
O: Diagnostic<'static, Subject = <S::Subject as private::IntoOwned>::Owned>,
{
pub fn into_owned(self) -> Report<S, <S::Subject as private::IntoOwned>::Owned> {
use private::IntoOwned;

Report {
// TODO: is there a way to avoid the `Into` here? We really want `S == O`,
// but I'm strugggling to express that without a bound predicate cycle
source: self.source.into(),
source: self.source,
subject: self.subject.into_owned(),
}
}
}

impl<'s, S> core::ops::Deref for Report<'s, S>
where
S: Diagnostic<'s>,
{
type Target = S;
impl<SRC, SUB> core::ops::Deref for Report<SRC, SUB> {
type Target = SRC;

fn deref(&self) -> &Self::Target {
&self.source
}
}

impl<'s, S> fmt::Display for Report<'s, S>
impl<SRC, SUB> fmt::Display for Report<SRC, SUB>
where
S: Diagnostic<'s> + fmt::Display,
SRC: fmt::Display,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fmt::Display::fmt(&self.source, f)
}
}

impl<'s, S> fmt::Debug for Report<'s, S>
where
S: Diagnostic<'s> + fmt::Debug,
S::Subject: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Enriched")
.field("source", &self.source)
.field("subject", &self.subject)
.finish()
}
}

#[cfg(feature = "std")]
impl<'s, S> std::error::Error for Report<'s, S>
impl<SRC, SUB> std::error::Error for Report<SRC, SUB>
where
S: Diagnostic<'s> + fmt::Debug + std::error::Error + 'static,
S::Subject: fmt::Debug,
SRC: fmt::Debug + std::error::Error + 'static,
SUB: fmt::Debug,
{
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.source)
}
}

#[cfg(feature = "miette")]
impl<'s, S> miette::Diagnostic for Report<'s, S>
impl<'s, SRC> miette::Diagnostic for Report<SRC, SRC::Subject>
where
S: Diagnostic<'s> + fmt::Debug + std::error::Error + 'static,
S::Subject: fmt::Debug + miette::SourceCode,
SRC: Diagnostic<'s> + fmt::Debug + std::error::Error + 'static,
SRC::Subject: fmt::Debug + miette::SourceCode,
{
fn url<'a>(&'a self) -> Option<Box<dyn core::fmt::Display + 'a>> {
Some(Box::new(S::url()))
Some(Box::new(SRC::url()))
}

fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Some(&self.subject)
}

fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
Some(Box::new(S::labels(self, &self.subject)?.map(Into::into)))
Some(Box::new(SRC::labels(self, &self.subject)?.map(Into::into)))
}
}

Expand Down Expand Up @@ -187,31 +165,22 @@ macro_rules! impl_diagnostic_url {
pub(crate) use impl_diagnostic_url;

mod private {
use crate::Pointer;
use alloc::borrow::Cow;

pub trait Sealed {}
impl Sealed for crate::pointer::ParseError {}
impl Sealed for crate::assign::Error {}

pub trait IntoOwned {
type Owned: 'static;
type Owned;

fn into_owned(self) -> Self::Owned;
}

impl<'s> IntoOwned for Cow<'s, str> {
type Owned = Cow<'static, str>;

fn into_owned(self) -> Cow<'static, str> {
Cow::Owned(self.into_owned())
}
}

impl<'s> IntoOwned for Cow<'s, Pointer> {
type Owned = Cow<'static, Pointer>;
impl<'s, T: 'static + ToOwned + ?Sized> IntoOwned for Cow<'s, T> {
type Owned = Cow<'static, T>;

fn into_owned(self) -> Cow<'static, Pointer> {
fn into_owned(self) -> Cow<'static, T> {
Cow::Owned(self.into_owned())
}
}
Expand All @@ -224,10 +193,13 @@ pub trait Diagnose<'s, T> {
fn diagnose(

Check warning on line 193 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L193

warning: missing documentation for a method --> src/diagnostic.rs:193:5 | 193 | / fn diagnose( 194 | | self, 195 | | subject: impl Into<<Self::Error as Diagnostic<'s>>::Subject>, 196 | | ) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>>; | |__________________________________________________________________________________^
Raw output
src/diagnostic.rs:193:5:w:warning: missing documentation for a method
   --> src/diagnostic.rs:193:5
    |
193 | /     fn diagnose(
194 | |         self,
195 | |         subject: impl Into<<Self::Error as Diagnostic<'s>>::Subject>,
196 | |     ) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>>;
    | |__________________________________________________________________________________^


__END__
self,
subject: impl Into<<Self::Error as Diagnostic<'s>>::Subject>,
) -> Result<T, Report<'s, Self::Error>>;
) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>>;

#[allow(clippy::missing_errors_doc)]
fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<'s, Self::Error>>
fn diagnose_with<F, S>(

Check warning on line 199 in src/diagnostic.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] src/diagnostic.rs#L199

warning: missing documentation for a method --> src/diagnostic.rs:199:5 | 199 | / fn diagnose_with<F, S>( 200 | | self, 201 | | f: F, 202 | | ) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>> 203 | | where 204 | | F: FnOnce() -> S, 205 | | S: Into<<Self::Error as Diagnostic<'s>>::Subject>; | |__________________________________________________________^
Raw output
src/diagnostic.rs:199:5:w:warning: missing documentation for a method
   --> src/diagnostic.rs:199:5
    |
199 | /     fn diagnose_with<F, S>(
200 | |         self,
201 | |         f: F,
202 | |     ) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>>
203 | |     where
204 | |         F: FnOnce() -> S,
205 | |         S: Into<<Self::Error as Diagnostic<'s>>::Subject>;
    | |__________________________________________________________^


__END__
self,
f: F,
) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>>
where
F: FnOnce() -> S,
S: Into<<Self::Error as Diagnostic<'s>>::Subject>;
Expand All @@ -242,11 +214,14 @@ where
fn diagnose(
self,
subject: impl Into<<Self::Error as Diagnostic<'s>>::Subject>,
) -> Result<T, Report<'s, Self::Error>> {
) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>> {
self.map_err(|error| error.into_report(subject.into()))
}

fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<'s, Self::Error>>
fn diagnose_with<F, S>(
self,
f: F,
) -> Result<T, Report<Self::Error, <Self::Error as Diagnostic<'s>>::Subject>>
where
F: FnOnce() -> S,
S: Into<<Self::Error as Diagnostic<'s>>::Subject>,
Expand All @@ -258,7 +233,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::{ParseError, Pointer, PointerBuf};
use crate::{Pointer, PointerBuf};

#[test]
#[cfg(all(
Expand Down Expand Up @@ -294,10 +269,7 @@ mod tests {

#[test]
fn into_owned() {
// NOTE: the type has to be fully specified here because of the `S: Into<O>` bound
// in `Report::into_owned`. Ideally we'd express `S == O` so that the output can
// be automatically inferred.
let owned_report: Report<'static, ParseError> = {
let owned_report = {
// creating owned string to ensure its lifetime is local
// (could also coerce a static reference, but this is less brittle)
let invalid = "/foo/bar/invalid~3~encoding/cannot/reach".to_string();
Expand Down
2 changes: 1 addition & 1 deletion src/pointer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,7 @@ impl std::error::Error for ParseError {
}
}

pub type RichParseError<'s> = Report<'s, ParseError>;
pub type RichParseError<'s> = Report<ParseError, <ParseError as Diagnostic<'s>>::Subject>;

/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Expand Down

0 comments on commit a728f72

Please sign in to comment.