Skip to content

RFC: Proposal for "modern" API #13

Closed as not planned
Closed as not planned
@CAD97

Description

@CAD97

In order to address concerns in #11, #12, wycats/language-reporting#6, and probably others.

I've been experimenting with merging the APIs of codespan/language-reporting/annotate-snippets, and the below API surface is what that I think makes the most sense.

NOTE: the suggested API has changed multiple times from feedback, see conversation starting at this comment for the most recent API and discussion.

Original Proposal

An experimental implementation of the API based on #12 is at CAD97/retort#1 (being pushed within 24 hours of posting, I've got one last bit to "port" but I've got to get to bed now but I wanted to get this posted first).

EDIT: I've reconsidered this API, though the linked PR does implement most of it. I'm sketching a new slightly lower-level design from this one, and the diagnostic layout of this current API will probably be a wrapper library around annotate-snippets. (I get to use the retort name!)

API
use termcolor::WriteColor;

trait Span: fmt::Debug + Copy {
    type Origin: ?Sized + fmt::Debug + Eq;

    fn start(&self) -> usize;
    fn end(&self) -> usize;
    fn new(&self, start: usize, end: usize) -> Self;
    fn origin(&self) -> &Self::Origin;
}

trait SpanResolver<Sp> {
    fn first_line_of(&mut self, span: Sp) -> Option<SpannedLine<Sp>>;
    fn next_line_of(&mut self, span: Sp, line: SpannedLine<Sp>) -> Option<SpannedLine<Sp>>;
    fn write_span(&mut self, w: &mut dyn WriteColor, span: Sp) -> io::Result<()>;
    fn write_origin(&mut self, w: &mut dyn WriteColor, origin: Sp) -> io::Result<()>;
}

#[derive(Debug, Copy, Clone)]
pub struct SpannedLine<Sp> {
    line_num: usize,
    char_count: usize,
    span: Sp,
}

impl Span for (usize, usize) {
    type Origin = ();
}

impl<Sp: Span<Origin=()>> Span for (&'_ str, Sp) {
    type Origin = str;
}

impl<Sp: Span> SpanResolver<Sp> for &str
where Sp::Origin: fmt::Display;

mod diagnostic {
    #[derive(Debug, Clone)]
    struct Diagnostic<'a, Sp: Span> {
        pub primary: Annotation<'a, Sp>,
        pub code: Option<Cow<'a, str>>,
        pub secondary: Cow<'a, [Annotation<'a, Sp>]>,
    }

    #[derive(Debug, Clone)]
    struct Annotation<'a, Sp: Span> {
        pub span: Sp,
        pub level: Level,
        pub message: Cow<'a, str>,
    }

    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
    enum Level {
        Err,
        Warn,
        Info,
        Hint,
    }

    impl<Sp: Span> Diagnostic<'_, Sp> {
        pub fn borrow(&self) -> Diagnostic<'_, Sp>;
        pub fn into_owned(self) -> Diagnostic<'static, Sp>;
    }

    impl<Sp: Span> Annotation<'_, Sp> {
        pub fn borrow(&self) -> Annotation<'_, Sp>;
        pub fn into_owned(self) -> Annotation<'static, Sp>;
    }

    impl fmt::Display for Level;
}

mod style {
    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
    enum Mark {
        None,
        Start,
        Continue,
        End,
    }

    #[non_exhaustive]
    #[derive(Debug, Copy, Clone)]
    pub enum Style {
        Base,
        Code,
        Diagnostic(Level),
        LineNum,
        TitleLine,
        OriginLine,
    }

    trait Stylesheet {
        fn set_style(&mut self, w: &mut dyn WriteColor, style: Style) -> io::Result<()>;
        fn write_marks(&mut self, w: &mut dyn WriteColor, marks: &[Mark]) -> io::Result<()>;
        fn write_divider(&mut self, w: &mut dyn WriteColor) -> io::Result<()>;
        fn write_underline(
            &mut self,
            w: &mut dyn WriteColor,
            level: Level,
            len: usize,
        ) -> io::Result<()>;
    }

    struct Rustc; impl Stylesheet for Rustc;
    // other styles in the future
}

mod renderer {
    fn render<'a, Sp: Span>(
        w: &mut dyn WriteColor,
        stylesheet: &dyn Stylesheet,
        span_resolver: &mut dyn SpanResolver<Sp>,
        diagnostic: &'a Diagnostic<'a, Sp>,
    ) -> io::Result<()>;

    fn lsp<'a, Sp: Span + 'a>(
        diagnostics: impl IntoIterator<Item = Diagnostic<'a, Sp>>,
        source: Option<&'_ str>,
        span_resolver: impl FnMut(Sp) -> lsp_types::Location,
    ) -> Vec<lsp_types::PublishDiagnosticsParams>;
}

Notes:

  • I've skipped imports and implementation bodies for clarity. All definitions are exported where I've written them.
  • I've liberally used dyn Trait, so the only monomorphization should be over the Span type.
  • I'm not particularly attached to any of the organization of exports, things can move around.
  • Span::new is only used for impl SpanResolver<impl Span> for &str; making that impl more specific can get rid of that trait method.
  • SpanResolver takes &mut for its methods primarily because it can, in order to allow use of a single-threaded DB that requires &mut access for caching as a span resolver.
  • Span resolution is passed through SpanResolver at the last moment such that a SpanResolver can supply syntax highlighting for errors.
  • SpanResolver::write_origin only gets io::Write because styling is done ahead of time by Stylesheet. Because WriteColor does not have an upcast method, this means we can't use dyn WriteColor anywhere that will end up calling SpanResolver::write_origin. This can be changed to take WriteColor if desired.
  • Diagnostic's layout is tuned to have similar layout to the language server protocol's Diagnostic.
  • Diagnostic is set up so that Diagnostic<'_, Sp> can be borrowed but also an owned Diagnostic<'static, Sp> can be produced by using Cows. This eases use with constructed diagnostics.
  • Potential style improvement: extend Style::Code to be an enum of general code token types (e.g. the list from pygments), SpanResolver::write_span just gets the ability to set the style to one of those, which goes through the StyleSheet for styling.

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-enhancementCategory: enhancementE-help-wantedCall for participation: Help is requested to fix this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions