resultats 0.1.0

A result facility to handle multiple errors.
Documentation
use std::{
    iter::FromIterator,
    ops::{ControlFlow, FromResidual, Try},
    panic::Location,
    // Standard Result
    result::Result::{self as SResult, Err as SErr, Ok as SOk},
};

use crate::{ast, format_error, source_code};

/// A static Location usually generated by rustc
type LocationStatic = &'static Location<'static>;

/// Use to get a Range of the source code
pub enum Loc {
    /// A simple location used to get a Span's range
    Location(LocationStatic),
    /// Use to get the Span's range of the first parent of Loc's span that match the Genre.
    Parent {
        location: LocationStatic,
        genre: ast::Genre,
    },
}
impl Loc {
    pub fn location(&self) -> LocationStatic {
        match self {
            Loc::Location(location) => location,
            Loc::Parent { location, .. } => location,
        }
    }
    pub fn related_code(&self) -> source_code::LineSpan {
        match self {
            Loc::Location(location) => source_code::code_from_location(location, None),
            Loc::Parent { location, genre } => source_code::code_from_location(location, Some(genre)),
        }
    }
}
impl std::fmt::Debug for Loc {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let location = match self {
            Self::Location(location) => location,
            Self::Parent { location, .. } => location,
        };
        writeln!(f, "{}", location)
    }
}

#[derive(Default, PartialEq, Eq)]
pub enum Severity {
    Trace,
    Debug,
    Info,
    Warn,
    #[default]
    Error,
    Off,
}

impl std::fmt::Display for Severity {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Severity::Trace => write!(f, "trace"),
            Severity::Debug => write!(f, "debug"),
            Severity::Info => write!(f, "info"),
            Severity::Warn => write!(f, "warning"),
            Severity::Error => write!(f, "error"),
            Severity::Off => write!(f, "off"),
        }
    }
}

/// A label to add information under code pointed by its span.
pub struct Label {
    // The span where to show the label.
    pub loc: Loc,
    // The content to show.
    pub label: String,
}
/// A group of errors that are linked together.
pub struct Group {
    /// Where the grouping has been asked.
    pub loc: Loc,
    /// The related errors.
    pub errors: Errors,
}
pub struct Error {
    /// Unique Id for the error type.
    pub code: &'static str,
    /// An url to provide more explanations.
    pub url: Option<String>,
    /// The severity of this error.
    pub severity: Severity,
    /// The display of the Error.
    pub display: String,
    /// Where the error has been raised.
    pub loc: Loc,
    /// A group of errors.
    pub groupe: Option<Group>,
    /// Labels that give more details on the error.
    pub labels: Vec<Label>,
    /// The error that has caused this one.
    pub source: Option<Box<Error>>,
}

impl std::fmt::Debug for Error {
    fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let stderr = &mut std::io::stderr();
        format_error::SimpleReportHandler::new()
            .render_error(stderr, self)
            .unwrap();
        std::fmt::Result::Ok(())
    }
}

#[derive(Default)]
pub struct Errors(pub Vec<Error>);

impl std::fmt::Debug for Errors {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for error in &self.0 {
            write!(f, "{error:?}")?;
        }
        SOk(())
    }
}

pub enum Result<T> {
    Ok(T),
    Err(Errors),
}

use Result::{Err, Ok};

impl<T> Result<T> {
    #[allow(dead_code)]
    fn into_result(self) -> SResult<T, Errors> {
        match self {
            Ok(value) => SOk(value),
            Err(errors) => SErr(errors),
        }
    }
}

// impl<E: std::error::Error + 'static> From<E> for Errors {
//     #[track_caller]
//     fn from(value: E) -> Self {
//         Self(vec![Error {
//             code: std::any::type_name::<E>(),
//             display: value.to_string(),
//             loc: Loc::Location(Location::caller()),
//             url: None,
//             severity: Severity::default(),
//             labels: Default::default(),
//             groupe: None,
//             source: None,
//         }])
//     }
// }

impl<E: ToString + 'static> From<E> for Errors {
    #[track_caller]
    fn from(value: E) -> Self {
        Self(vec![Error {
            code: std::any::type_name::<E>(),
            display: value.to_string(),
            loc: Loc::Location(Location::caller()),
            url: None,
            severity: Severity::default(),
            labels: Default::default(),
            groupe: None,
            source: None,
        }])
    }
}

impl<T> Try for Result<T> {
    type Output = T;
    type Residual = Result<T>;

    fn from_output(output: Self::Output) -> Self {
        Ok(output)
    }

    fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
        match self {
            Ok(value) => ControlFlow::Continue(value),
            Err(errors) => ControlFlow::Break(Err(errors)),
        }
    }
}

impl<T, E: Into<Errors>, R> FromResidual<SResult<T, E>> for Result<R> {
    #[track_caller]
    fn from_residual(residual: SResult<T, E>) -> Self {
        match residual {
            SErr(errors) => Err(errors.into()),
            _ => unreachable!("FromResidual got Ok.."),
        }
    }
}

impl<T, R> FromResidual<Result<T>> for Result<R> {
    fn from_residual(residual: Result<T>) -> Self {
        match residual {
            Err(errors) => Err(errors),
            _ => unreachable!("FromResidual got Ok.."),
        }
    }
}

impl<T> FromIterator<Result<T>> for Result<Vec<T>> {
    fn from_iter<I>(iter: I) -> Self
    where
        I: IntoIterator<Item = Result<T>>,
    {
        let mut errors = Errors::default();
        let mut results = Vec::new();

        for item in iter {
            match item {
                Ok(value) => results.push(value),
                Err(e) => errors.0.extend(e.0),
            }
        }

        if errors.0.is_empty() { Ok(results) } else { Err(errors) }
    }
}