Mesabloo / diagnose

A simple library for reporting compiler/interpreter errors
https://hackage.haskell.org/package/diagnose
BSD 3-Clause "New" or "Revised" License
258 stars 19 forks source link

Alternative `codespan-reporting` style formatting? #11

Open ruifengx opened 2 years ago

ruifengx commented 2 years ago

Thanks for your great library!

I use both Rust and Haskell, and have learned about error reporting libraries like ariadne and codespan-reporting for a while. Previously when working on a Rust project, I chose codespan-reporting because I preferred its formatting (formatting of ariadne is also beautiful, but a bit too fancy for me), given their interfaces are pretty similar. Is it possible to extend this library to have an alternative codespan-reporting-style formatting for the diagnostic messages? Or is there some design space to expose an API for custom report formatting?

FYI, here is a comparison of the said two styles (BTW it is amusing to see a more Haskell-like syntax for illustration in codespan-reporting the Rust library but a more Rust-like style here):

Name Illustration
(current) ariadne-style
codespan-reporting-style codespan_reporting_preview

If this is deemed non-trivial, I am willing to work on this, in that case would you please provide some guidance?

ruifengx commented 2 years ago

Because my instance is marked as {-# OVERLAPPABLE #-}, any {-# OVERLAPPING #-} instance should be privileged (or at least that's what I think it does). However, I am not quite sure and this needs some testing.

If my understanding of the GHC manual is correct, resolving the instance also requires the priviledged instance to be strictly more specific. For this case, my understanding is that the two instances are equally specific. I believe we actually need the default instance in the library to be marked {-# INCOHERENT #-}. Anyway, I agree that testing would be the only way to confirm this.


And regarding the review for the PR, please take your time. Also, I will be happy to address any readability/efficiency issues or code duplication.

Mesabloo commented 2 years ago

It turns out that you are definitely right, and that my understanding of overlapping instances was just off. I also tested with {-# INCOHERENT #-} but GHC also reported duplicated instances (see the MWE underneath). I guess I'll just have to separate the annotation types from the instance (so that if you don't want the default instance, you don't import the module it is contained in).

EDIT: and indeed, from the GHC manual you linked, the two conditions must hold:

  • Eliminate any candidate IX for which there is another candidate IY such that both of the following hold:
    • IY is strictly more specific than IX. That is, IY is a substitution instance of IX but not vice versa.
    • Either IX is overlappable, or IY is overlapping. (This “either/or” design, rather than a “both/and” design, allow a client to deliberately override an instance from a library, without requiring a change to the library.)

MWE:

And the error:

A.hs:6:29: error:
    Duplicate instance declarations:
      instance [incoherent] [safe] Something Int -- Defined at A.hs:6:29
      instance [overlapping] Something Int -- Defined at Main.hs:5:30
  |
6 | instance {-# INCOHERENT #-} Something Int where
  |                             ^^^^^^^^^^^^^
spacekitteh commented 1 year ago

So I've noticed that you're hard-coding a layout style as something which can be converted to an AnsiStyle. Why is that? Would it not make more sense to have it as a generic DiagnosticStyle which has actual diagnostic annotation information, rather than colour information? Not only would this mean that layouts could be user customisable, it also means that layouts could share more of a common vocabulary between them.

Further, prettyprinter says that annotations should be semantic in nature for as long as possible; it's still entirely plausible to separate layouts from colours, with the mapping of diagnostic components to colours delayed until the user desires it.

spacekitteh commented 1 year ago

For example, rather than something analogous to FileColor, layout algorithms would annotate it with, say, File <filename>; then renderers could choose both the colouring, AND whether to annotate it with a hyperlink.

Mesabloo commented 1 year ago

I'm not quite sure that I quite follow the suggestion here. Unfortunately, I don't think that it is possible to have a single annotation type for all renderers, given that each has their own quirks (e.g. Ariadne uses a rule color, whereas the GCC layout does not). We already discussed this with @ruifengx earlier in this issue, but new suggestions are obviously welcome! Do you mind expanding a bit on it?

spacekitteh commented 1 year ago

Sure, I'll elaborate in the morning!

spacekitteh commented 1 year ago

In the current approach (both 2.4.0 and the branch), there are two separate operations that are combined into one:

  1. Layout. This refers to taking a Diagnostic and producing a Doc which has a two-dimensional structure; with faulty code, contextual code, hints, error codes, rules, etc. This document should contain only semantic information: annotations which say "this text corresponds to this particular filename", "this text corresponds to a line of code", etc. The user may want to use that semantic annotation before it's modified further, such as to add hyperlinks on error codes linking to a help page for that specific error, etc.
  2. Styling. This refers to setting text properties: Bold, underline, different colours, etc. This should be applied at the latest time possible, and may not correspond to a terminal; it may be for rendering to HTML, for example.

These two things are both conceptually and semantically distinct; a given layout can have many styles, and a given style can apply to many layouts. As an example, say a user really likes the Ariadne layout, but hates the colours; then they could apply a different style to the Ariadne layout.

So, the idea is two have two passes: layout, then styling. The user should get to choose when to apply styling; and they should get to choose what style to apply to a given layout.


data DiagnosticAnnotation = File FilePath 
                          | SourceLocation Position 
                          | Severity SeverityLevel 
                          | DiagnosticCode 
                          | ...

-- | A prismatic class for diagnostic annotations, as they may be embedded
--  into a larger annotation type in a larger document.
class HasDiagnosticAnnotation diagAnn where
  injectDiagnosticAnnotation :: DiagnosticAnnotation -> diagAnn
  extractDiagnosticAnnotation :: diagAnn -> Maybe DiagnosticAnnotation 

-- | Layout the diagnostic in a given format (ariadne, gcc, codespan-reporting, etc)
layoutDiagnostic :: HasDiagnosticAnnotation diagAnn => Diagnostic -> Doc diagAnn

-- | User may wish to colourise the diagnostics in a larger document that 
-- still has other semantic annotations.
class HasAnsiStyling ann where
  injectStylingAnnotation :: AnsiStyle -> ann

-- | Apply bold/underlining/italic/colour/etc
renderAsANSI :: (HasDiagnosticAnnotation diagAnn, HasAnsiStyling stylingAnn) => 
                Doc  diagAnn -> 
                Doc stylingAnn
Mesabloo commented 1 year ago

These two things are both conceptually and semantically distinct; a given layout can have many styles, and a given style can apply to many layouts.

Reusing styles across multiple layouts is something that I would have initially liked to have. Unfortunately, your suggestion comes with a DiagnosticAnnotation which is the base for all layouts. As it is meant to semantically indicate what is which part of the layout, there are a few gotchas that I can think about:

I'd be happy to have restyling and reusing styles for multiple different layouts, but sadly I don't know if this can be done while remaining semantically correct (and avoiding dropping cases with undefined). Let me know what you think about some points. :)