Skip to content

Instantly share code, notes, and snippets.

@bgamari
Last active July 22, 2019 20:37
Show Gist options
  • Save bgamari/81106997ae2657159f2a45012d0bfea2 to your computer and use it in GitHub Desktop.
Save bgamari/81106997ae2657159f2a45012d0bfea2 to your computer and use it in GitHub Desktop.
title
Rich errors messages in GHC

Pretty-printer with point annotations

See the proposal.

Pros

  • Relatively low implementation and maintenance effort
  • Allows us to realize any number of design points on the spectrum from plain text errors (the status quo) to an explicit ADT-of-errors (discussed below)
  • Easily allows errors to be built up compositionally

Cons

  • Ambiguity regarding how to analyze documents
  • Still very presentation-centric

Pretty-printer with scoped annotations

This is the approach used by Idris (as described in A pretty-printer that says what it means).

Pros

  • Very low implementation and maintenance effort
  • Proven to be useful in Idris's rich REPL

Cons

  • Very presentation-centric
  • Not terribly conducive to analysis by external tools

Classic ADT-of-errors approach

Here we define a type defining all of the errors which can possibly be produced by GHC. To avoid separating the type definition and its rendering from the point where we emit it, we define a type for each error alongside the point where it's produced (e.g. in TcErrors). This avoids some of the maintenance overhead we worry this approach might otherwise incur.

-- We define a sum type of all errors GHC can throw
module GhcErrors where

import TcErrors
import ... -- other modules which can produce errors

data GhcError = Error1' Error1
              | ...
              | GenericError SDoc
                -- ^ enables us to gradually port the existing SDoc errors to
                -- the new scheme.


-- Where we throw the error we define its structure and textual presentation
module TcErrors where

import {-# SOURCE #-} GhcErrors

data Error1 = Error1 ...

instance Outputable Error1 where ...

mkError1 :: ... -> TcM GhcError
mkError1 = do
  ...
  return $ Error1' $ Error1 { ... }

Pros

  • It's clear what errors can be produced

Cons

  • Import cycles. Either GhcErrors or TcErrors et al. will require an hs-boot file due to the cyclic imports.

A more dynamic approach

I suspect the import cycles above are nearly a deal-breaker. To avoid them we can go for a slightly more dynamic approach, taking inspiration from our extensible exceptions mechanism:

module GhcErrors where

class (Typeable a, Outputable a) => IsError a

data GhcError where
  GhcError :: forall a. (IsError a) => a -> GhcError


--
module TcErrors where

data Error1 = Error1 ...

instance Outputable Error1 where ...
instance IsError Error1

mkError1 :: ... -> TcM GhcError

Pros

Cons

  • It's slightly less obvious what
@bgamari
Copy link
Author

bgamari commented Jul 22, 2019

It's one hs-boot file!

It is, but it must mirror the entire GhcError type, including all of its data constructors.

However, thinking through this again, I suppose you could largely mitigate this issue by defining a single TcError type in the TcErrors module. I'll modify the text to reflect this idea.

But it seems quite clunky to consumers.

I completely agree.

@bgamari
Copy link
Author

bgamari commented Jul 22, 2019

I think now is probably a good time to move this under proper version control. I have created ghc-pretty-errors for this purpose. You should have an invitation for collaborator status.

@bgamari
Copy link
Author

bgamari commented Jul 22, 2019

For the record, the version of this document in ghc-pretty-errors has been amended to account for the change described above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment