GithubHelp home page GithubHelp logo

error-messages's People

Contributors

aionescu avatar akrmn avatar alt-romes avatar david-christiansen avatar davidlaewen avatar elland avatar fisx avatar gillchristian avatar goldfirere avatar inverseintegral avatar isomorpheme avatar jaccokrijnen avatar ketzacoatl avatar maralorn avatar mknorps avatar morrowm avatar nomeata avatar ppkfs avatar raphaelmeyer avatar reasonable-solutions avatar serras avatar simonpj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

error-messages's Issues

Language Extensions vs. Plain Haskell 98/2010/...

After reading the first few issues, I think it would be nice if there were guidelines on when to recommend enabling language extensions in the error messages.

e.g.: I think the examples in #6 and #7 should be handled differently:

  • #6
    should recommend ScopedTypeVariables, since it's the obvious solution.
  • #7
    should only recommend TypeApplications if it is already enabled and stick to type signatures otherwise, since it doesn't need the extension.

wdyt?

Scoped type variables not suggested

Consider the following file:

module ErrMsg where

f :: a -> a
f x = y
  where
    y :: a
    y = x

When loaded up in GHCi we get the following error message:

ErrMsg.hs:7:9: error:
    • Couldn't match expected type ‘a1’ with actual type ‘a’
      ‘a1’ is a rigid type variable bound by
        the type signature for:
          y :: forall a1. a1
        at ErrMsg.hs:6:5-10
      ‘a’ is a rigid type variable bound by
        the type signature for:
          f :: forall a. a -> a
        at ErrMsg.hs:3:1-11
    • In the expression: x
      In an equation for ‘y’: y = x
      In an equation for ‘f’:
          f x
            = y
            where
                y :: a
                y = x
    • Relevant bindings include
        y :: a1 (bound at ErrMsg.hs:7:5)
        x :: a (bound at ErrMsg.hs:4:3)
        f :: a -> a (bound at ErrMsg.hs:4:1)
  |
7 |     y = x
  |

The fact that ScopedTypeVariables is never even mentioned is particularly egregious, in my opinion. Also, note that the same error message is produced with ScopedTypeVariables enabled, and doesn't mention using an explicit forall.

Suggested error message

When ScopedTypeVariables is off:

• Couldn't match expected type ‘a1’ with actual type ‘a’
  The type variable ‘a’ is not in scope in the type signature
  for ‘y’, therefore it gets renamed to ‘a1’ and is not the same
  as ‘a’
  Consider enabling ScopedTypeVariables and including an explicit
  forall in the type signature for ‘f’ in order to bring ‘a’ into scope
• Relevant type variables include
    ‘a1’ (bound at ErrMsg.hs:6:5-10, in the signature for ‘y’)
    ‘a’ (bound at ErrMsg.hs:3:1-11, in the signature for ‘f’)
• Relevant bindings include
    y :: a1 (bound at ErrMsg.hs:7:5)
    x :: a (bound at ErrMsg.hs:4:3)
    f :: a -> a (bound at ErrMsg.hs:4:1)
  |
7 |     y = x
  |         ^

When ScopedTypeVariables is on simply replace this line:

Consider enabling ScopedTypeVariables and including an explicit
forall in the type signature for ‘f’ in order to bring ‘a’ into scope

With this:

Consider including an explicit forall in the type signature for ‘f’ 
in order to bring ‘a’ into scope

This sort of error would also benefit greatly from having a further explanation linked. I've seen discussions of giving errors unique ids and having a collection of explanations that can be referenced. This seems like a good application for that.

Typed Holes, GADTs and residual constraints

The error messages when using typed holes on the RHS of a pattern match on GADT constructors are not optimal, since type equality constraints are left unresolved and not applied as substitutions. (Tested with GHC 8.6, 8.10 and 9.2.1).
Understanding the error message requires understanding both GADTs and the implementation of GADTs with the help of type equality constraints.

Example

Consider the following snippet:

{-# LANGUAGE GADTs #-}

data Expr a where
  ExprInt :: Int -> Expr Int
  ExprBool :: Bool -> Expr Bool

foo :: Expr a -> Bool
foo x = case x of
  ExprInt _ -> True
  ExprBool _ -> _

The error message I get (shortened):

example.hs:10:17: error:
    • Found hole: _ :: Bool
    • In the expression: _
      In a case alternative: ExprBool _ -> _
      In the expression:
        case x of
          ExprInt _ -> True
          ExprBool _ -> _
    • Relevant bindings include
        x :: Expr a (bound at example.hs:8:5)
        foo :: Expr a -> Bool (bound at example.hs:8:1)
      Constraints include a ~ Bool (from example.hs:10:3-12)

This error message correctly identifies the constraint a ~ Bool which came into context by pattern matching on the GADT constructor ExprBool. But understanding this error message requires understanding how GADT constructors are desugared internally (into ExprBool :: forall a. a ~ Bool => Bool -> Expr a)

The error message I would ideally expect would turn this constraint into a substitution and apply it:

example.hs:10:17: error:
    • Found hole: _ :: Bool
    • In the expression: _
      In a case alternative: ExprBool _ -> _
      In the expression:
        case x of
          ExprInt _ -> True
          ExprBool _ -> _
    • Relevant bindings include
        x :: Expr Bool (bound at example.hs:8:5)
        foo :: Expr a -> Bool (bound at example.hs:8:1)

I am not very familiar with GHC internals, in particular how closely GHC follows the implementation described in the OutsideIn paper, but the problem is probably that a is considered an untouchable unification variable within the implication constraint generated for the RHS of the pattern match. But for the typed hole, the unification variable a should probably be allowed to unify with Bool. (Except for the occurrence in foo, of course).

Illegal .. notation for constructor LogOutView. The constructor has no labeled field

Given this code:

data LogOutView = LogOutView

instance View LogOutView where
    html LogOutView { .. } = [hsx||]

GHC errors with:

Illegal .. notation for constructor LogOutView. The constructor has no labeled field

A better error message would be:

The 'data LogOutView' has no fields, so you cannot use 'LogOutView { .. }' here. Perhaps remove the '{ .. }' here, or add some fields to 'data LogOutView'? 

(Taken from https://github.com/digitallyinduced/haskell-ux)

Parse errors should include context

Currently, when a parse error occurs, the error message says parse error, but includes no information whatsoever about what caused the parse error. This should be unacceptable. For someone who doesn't know the exact syntax (and for everyone else too), this is a horrible error message, it says almost nothing other than "something is wrong". I know that there are already some issues regarding this (I've linked them below), but I want to create a meta-issue for them to highlight the general problem.

The parse errors should describe what exactly is expected (a certain keyword, an expression, ...). Some examples:

  • incomplete if expression (e.g. if, if True, if True then, if True then 42, ...): suggest something like "expected an expression" (for if, if True then, ...) or "expected 'then'" (for if True)
  • module, module Lib: suggest "expected a module name" (for module) or "expected 'where' or a list of exported items" (for module Lib)
  • f x = -- this happens **very** often, especially to beginners
        | True = x
    emit an error message suggesting to remove the first =
  • class A a where
        instance A Int
    say something like "unexpected instance delcaration"

Previous parse error issues / more examples:

Should suggest to turn on PatternSynonyms on pattern import

I would expect to receive the suggestion "perhaps you meant to enable PatternSynonyms" in the first case.

> import Control.Exception (pattern ErrorCall)
<interactive>:1:35: error: parse error on input ‘ErrorCall’
> :set -XPatternSynonyms 
> import Control.Exception (pattern ErrorCall)

(GHC 8.10.7)

Type variables versus unification variables

I've always gotten the sense that GHC is a little bit fast-and-loose about this, especially as polymorphic types can be inferred, whereby unification variables become type variables due to an implicit quantifier.

I am not sure exactly what I want different, especially implementation wise, but this just makes me feel like GHC is gaslighting me by virtue of me being unable to describe what the alternative looks like!

@xplat one remarked the same thing, after which a wave of relief and validation swept over me. Maybe he can describe the problem and potential solutions more eloquently.

Not a data constructor clarity

For a module containing only:

data Foo = bar

we get the error message:

error: Not a data constructor: ‘bar’
  |
1 | data Foo = bar
  |

when compiling or loading into ghci. It came up that this message does not explain why 'bar' is not a valid data constructor, that it does not start with a capital letter or ':'. It would be useful to have that information presented alongside this error message, perhaps with something like:

error: Not a data constructor: ‘bar’
Note: data constructors must begin with a capital letter or ':'
  |
1 | data Foo = bar
  |

or an equivalent that better matches the style of GHC's other error messages. If adding an additional note would not fit that style, simply changing the message to:

error: Not a valid data constructor: ‘bar’
  |
1 | data Foo = bar
  |

would help make it more clear that 'bar' cannot be a data constructor and that this is a syntactical issue. I believe there are no other methods of raising this error, but I could see either approach making such cases more confusing if they do exist.

Perhaps you want to use `pure`?

Given this code:

initModelContext :: FrameworkConfig -> IO ModelContext
initModelContext FrameworkConfig { environment, dbPoolIdleTime, dbPoolMaxConnections, databaseUrl } = do
    let isDevelopment = environment == Env.Development
    modelContext <- (\modelContext -> modelContext { queryDebuggingEnabled = isDevelopment }) <$> createModelContext dbPoolIdleTime dbPoolMaxConnections databaseUrl
    modelContext

GHC errors with:

IHP/Server.hs:133:5: error:
     Couldn't match expected type IO ModelContext
                  with actual type ModelContext
     In a stmt of a 'do' block: modelContext
      In the expression:
        do let isDevelopment = environment == Env.Development
           modelContext <- (\ modelContext
                              -> modelContext {queryDebuggingEnabled = isDevelopment})
                             <$>
                               createModelContext dbPoolIdleTime dbPoolMaxConnections databaseUrl
           modelContext
      In an equation for initModelContext’:
          initModelContext
            FrameworkConfig {environment, dbPoolIdleTime, dbPoolMaxConnections,
                             databaseUrl}
            = do let isDevelopment = ...
                 modelContext <- (\ modelContext
                                    -> modelContext {queryDebuggingEnabled = isDevelopment})
                                   <$>
                                     createModelContext
                                       dbPoolIdleTime dbPoolMaxConnections databaseUrl
                 modelContext
    |
133 |     modelContext

A better error message would be:

IHP/Server.hs:133:5: error:
     Perhaps you meant `pure modelContext`?
    
    Couldn't match expected type IO ModelContext
                  with actual type ModelContext
     In a stmt of a 'do' block: modelContext
      In the expression:
        do let isDevelopment = environment == Env.Development
           modelContext <- (\ modelContext
                              -> modelContext {queryDebuggingEnabled = isDevelopment})
                             <$>
                               createModelContext dbPoolIdleTime dbPoolMaxConnections databaseUrl
           modelContext
      In an equation for initModelContext’:
          initModelContext
            FrameworkConfig {environment, dbPoolIdleTime, dbPoolMaxConnections,
                             databaseUrl}
            = do let isDevelopment = ...
                 modelContext <- (\ modelContext
                                    -> modelContext {queryDebuggingEnabled = isDevelopment})
                                   <$>
                                     createModelContext
                                       dbPoolIdleTime dbPoolMaxConnections databaseUrl
                 modelContext
    |
133 |     modelContext

Better suggestion for ambiguous type variables in certain situations

In this recent stackoverflow question the asker presents the following piece of code (I have slightly adapted it) which gives an unhelpful error message:

module Test where

import Data.Aeson
import Data.Text (Text, unpack, pack)
import Text.Ginger
import Data.Functor.Identity
import Data.Function

mapLeft f (Left x) = Left (f x)
mapLeft _ (Right x) = Right x

tshow = pack . show

renderTemplate :: ToJSON c => Text -> c -> Either Text Text
renderTemplate template ctx = do
  tpl <- tplEither
  let ctxGVal = rawJSONToGVal $ toJSON ctx
  let r = easyRender ctxGVal tpl
  return r
  where
    tplEither :: Either Text (Template SourcePos)
    tplEither = parseGinger nullResolver Nothing (unpack template) & runIdentity & mapLeft tshow
    nullResolver :: IncludeResolver Identity
    nullResolver = const $ return Nothing

The error message is:

Ginger.hs:18:11: error:
    • Could not deduce (ToGVal
                          (Run SourcePos (Control.Monad.Trans.Writer.Lazy.Writer Text) Text)
                          (GVal m0))
        arising from a use of ‘easyRender’
      from the context: ToJSON c
        bound by the type signature for:
                   renderTemplate :: forall c.
                                     ToJSON c =>
                                     Text -> c -> Either Text Text
        at Ginger.hs:14:1-59
      The type variable ‘m0’ is ambiguous
      These potential instance exist:
        instance ToGVal m (GVal m) -- Defined in ‘Text.Ginger.GVal’
    • In the expression: easyRender ctxGVal tpl
      In an equation for ‘r’: r = easyRender ctxGVal tpl
      In the expression:
        do tpl <- tplEither
           let ctxGVal = rawJSONToGVal $ toJSON ctx
           let r = easyRender ctxGVal tpl
           return r
   |
18 |   let r = easyRender ctxGVal tpl
   |           ^^^^^^^^^^^^^^^^^^^^^^

It is large but it is fairly clear what the issue is: m0 is ambiguous. There is even a potential instance listed, but that is not really actionable unless the programmer understands that the m in that instance needs to be Run SourcePos (Control.Monad.Trans.Writer.Lazy.Writer Text) Text which can be hard to see for less experienced programmers.

In such ambiguous cases where there is only one potential instance then it might make sense to give a more elaborate explanation about how that instance could be used. In this case it could be:

These potential instance exist:
        instance ToGVal m (GVal m) -- Defined in ‘Text.Ginger.GVal’
To use this instance you can add a type signature to `ctxGVal`:
        ctxGVal :: GVal (Run SourcePos (Control.Monad.Trans.Writer.Lazy.Writer Text) Text)

Or perhaps it is easier (for GHC) to suggest TypeApplications:

To use this instance you can use `TypeApplications`:
        easyRender @_ @_ @(GVal (Run SourcePos (Control.Monad.Trans.Writer.Lazy.Writer Text) Text))

This brings up another issue which is that GHC sometimes shows names that are not in scope in its output. In this case you will get a second error that is probably understandable but not very nice:

Ginger.hs:21:34: error:
    Not in scope:
      type constructor or class ‘Control.Monad.Trans.Writer.Lazy.Writer’
    No module named ‘Control.Monad.Trans.Writer.Lazy’ is imported.
   |
21 |   let r = easyRender (ctxGVal :: Control.Monad.Trans.Writer.Lazy.Writer) tpl
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

It gets even worse if the transformers package is not exposed, then it will show transformers-0.5.6.2:Control.Monad.Trans.Writer.Lazy.Writer which is not valid Haskell syntax at all.

`Parse error in pattern` for operator-style data constructors is vague

Consider the following GHCi session:

ghci> f x:xs = print(x, xs)

<interactive>:162:1: error: Parse error in pattern: f

This is a function that a beginner I was talking to asked about. They (understandably) didn't understand what the issue was, or how to fix it. The error message doesn't help much here. It does mention something about a pattern, though.

The solution, of course, is to add parentheses around the pattern. f (x:xs) = print(x, xs). I think it would be an improvement to reflect this in the error message:

<interactive>:162:1: error: 
    Parse error in pattern: f x:xs
    Hint: Constructor patterns with arguments must be surrounded by parentheses

Note that this is not as bad in the case of non-operator constructors:

ghci> data Foo = Foo Int
ghci> f Foo x = x

<interactive>:2:3: error:
    • The constructor ‘Foo’ should have 1 argument, but has been given none
    • In the pattern: Foo
      In an equation for ‘f’: f Foo x = x

I think we'd still benefit from including the hint. I think it's worded loosely enough to be understood as a possible solution, not an absolute one, so even if they just forgot an argument it'd be fine. But I'm more concerned about the operator case.

I think good parse error messages are important for a smooth learning experience, and the state of parse error messages today is fairly poor in my opinion. It'd be nice to see more of these sorts of issues opened here, like #14 , for example.

Cannot construct infinite type

A stackoverflow question contains this code with a simple typo:

quicksort1 :: (Ord a) => [a] -> [a]
quicksort1 [] = []
quicksort1 (x:xs) =
  let smallerSorted = quicksort1 [a | a <- x, a <= xs]
      biggerSorted = quicksort1 [a | a <- x, a > xs]
  in  smallerSorted ++ [x] ++ biggerSorted

If you missed the error: the x and xs are swapped in the list comprehensions.

Now, the problem is the error message that this produces:

QS.hs:4:44: error:
    • Occurs check: cannot construct the infinite type: a ~ [[a]]
    • In the expression: x
      In a stmt of a list comprehension: a <- x
      In the first argument of ‘quicksort1’, namely
        ‘[a | a <- x, a <= xs]’
    • Relevant bindings include
        smallerSorted :: [[a]] (bound at QS.hs:4:7)
        biggerSorted :: [[a]] (bound at QS.hs:5:7)
        xs :: [a] (bound at QS.hs:3:15)
        x :: a (bound at QS.hs:3:13)
        quicksort1 :: [a] -> [a] (bound at QS.hs:2:1)
  |
4 |   let smallerSorted = quicksort1 [a | a <- x, a <= xs]
  |                                            ^

QS.hs:5:43: error:
    • Occurs check: cannot construct the infinite type: a ~ [[a]]
    • In the expression: x
      In a stmt of a list comprehension: a <- x
      In the first argument of ‘quicksort1’, namely
        ‘[a | a <- x, a > xs]’
    • Relevant bindings include
        biggerSorted :: [[a]] (bound at QS.hs:5:7)
        xs :: [a] (bound at QS.hs:3:15)
        x :: a (bound at QS.hs:3:13)
        quicksort1 :: [a] -> [a] (bound at QS.hs:2:1)
  |
5 |       biggerSorted = quicksort1 [a | a <- x, a > xs]
  |                                           ^

QS.hs:6:31: error:
    • Occurs check: cannot construct the infinite type: a ~ [a]
      Expected type: [a]
        Actual type: [[a]]
    • In the second argument of ‘(++)’, namely ‘biggerSorted’
      In the second argument of ‘(++)’, namely ‘[x] ++ biggerSorted’
      In the expression: smallerSorted ++ [x] ++ biggerSorted
    • Relevant bindings include
        smallerSorted :: [[a]] (bound at QS.hs:4:7)
        biggerSorted :: [[a]] (bound at QS.hs:5:7)
        xs :: [a] (bound at QS.hs:3:15)
        x :: a (bound at QS.hs:3:13)
        quicksort1 :: [a] -> [a] (bound at QS.hs:2:1)
  |
6 |   in  smallerSorted ++ [x] ++ biggerSorted
  |                               ^^^^^^^^^^^^

I think many beginners will not even try to start figuring out what these mean.

First of all, "occurs check" is compiler jargon that doesn't mean much to users.

The compiler seems to know the right types:

        xs :: [a] (bound at QS.hs:3:15)
        x :: a (bound at QS.hs:3:13)

So, I think it could say something like: "expecting x to be a list in a <- x". It could perhaps even detect that xs would work and suggest that the user perhaps made a typo.

Timeline for error message improvements?

I'd like to know when these error message improvements would be reflected on the upstream GHC.
Surely such a communication would help many others as well.
Could you prepare some timeline for this purpose?

Plus, how are the improvements to the error messages implemented?
Are they manually implemented through PRs by volunteers? Any dedicated team in GHC for this?

Error about wrong missing instance

Here's a puzzling one. I've made a mistake in the second clause of leftSkewed. The second argument should be Leaf n, not n. Therefore I really want the error "No instance for Num Tree", pointing to that mistake. Instead I get the error "No instance for Eq Tree" pointing to the 0 match in the first clause!

I think what has happened here is that the type checker has deduced that n :: Tree, and given that we're passing n - 1 to leftSkewed the argument to leftSkewed must be :: Tree. Then the 0 pattern requires an Eq Tree constraint. But I wish it would just bail out earlier and say that it can't find a Num instance for Tree!

data Tree = Branch Tree Tree | Leaf Int

leftSkewed 0 = Leaf 0
leftSkewed n = Branch (leftSkewed (n - 1)) n

Negative reasoning for type classes to make better errors

Rust has skirted the edge with various aspects of type classes. In particular, per Ixrec/rust-orphan-rules#1 there have been various explorations of "negative reasoning" and revises orphan rules. (Rust doesn't allow orphans, and uses its orphans to ensure there are no overlaps (except for "specialization" but let's ignore that for now).

One big idea here is that while regular contexts depending on "anti-constraints" is pretty bogus (except for perhaps not equal constraints), there is no reason while top level assertions, that are completely erased, cannot be negative. Indeed, orphan and overlap rules can be phrased in terms somewhat like that (if condition....something is not allowed).

Why do I bring this up? I am thinking it may be useful to make errors better without baking in ad-hoc logic into GHC or removing expressive power.

For example, inspired by the recent https://www.thecodedmessage.com/posts/haskell-gripe/, take

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedLists #-}
1 + [1]

this program is deeply unconstrained, and could perhaps be well typed in all sorts of stupid ways.
But if we could write a declaration like

assert !(Num a, IsList a)

to assert no type has both number and list literals, we can in fact rule out the program, with a nice error, e.g something like

No type 'a' satisfying 
  1 :: a
  [1] :: a
  (+) :: a -> a -> a
in expression
  1 + [1]
no types are both list-like and number-like

Besides trying to keep brittle ad-hoc logic out of GHC, I think it quite a subjective matter whether one should provide assertions like this---I would not put such assertions in base. Instead, I would advocate they be just in custom preludes, especially for teaching. This would be a small step towards "teaching languages".

Note that since assert !(Num a, IsList a) is erased / has no computation interpretation, there is no risk of programs "relying" on this in any ways. It simply makes more programs ill-typed. Instances are always exported, but assertions like this can safely not be. Teaching prelude modules would of course export them for downstream student use, but any user is free to write their own that just effects the local module and nothing else.

Provide a link in error messages a la https://ipxe.org/err/...

This might be an entirely separate project but the way that iPXE produces error messages has always seemed to me to be utterly ingenious, whether or not it originated there. I.e., error messages are tagged with error codes that it spits out in the form of URLs, like, e.g.,

https://ipxe.org/err/040ee1

(Edit: richer example here: https://ipxe.org/err/4c1060)

When a user pulls up the link, it produces a wiki page that points right to the location in the (current) source code of iPXE where the error was spit out (which is really useful for that project because iPXE itself usually can't provide a lot of detail when it croaks, so you sometimes have to read backwards through the code).

Often much more usefully, the page has an editable portion where other users are encouraged to contribute pointers, as in the case of that ipxe.org page, where they've got almost a diagnostic key (and you don't need to step backwards through the code to try to infer the cause).

Hence, instead of cutting-and-pasting the error message text and hoping an appropriate stackoverflow answer pops up and it's not one of the other 15 ways that, say, host was unreachable, you get something with more pinpoint accuracy, like, say, (just making stuff up) it's "no route to host", so check your netmask and your gateway.

I'm imagining that the type checker or whatever is producing GHC error messages might be able to leave breadcrumbs in a URL that might trace the way that it failed, so the particular pathology it's wended its way through can lead to a more friendly diagnosis in a related error gallery, if somebody's been generous enough to describe how it was resolved in that person's case. (And then you can mine the usage data to figure out stuff about where people are running into errors that stump them, i guess. Getting even further fantastical, maybe an IDE could request some version of the URL to tell it how to walk the user through a fix, although now maybe i'm regurgitating the AI Kool-Aid. Or maybe some haskell-version upgrader could do likewise to mechanically upgrade code bases that fail to compile. Or maybe i should just shut up now and stop conjuring up privacy nightmares).

Not that i don't think stackoverflow and web search aren't otherwise pretty awesome.

And not that GHC doesn't already do an awesome job suggesting potential fixes and doing all but writing your *{-# LANGUAGE #-}` pragmas into the source file for you ... But maybe some of the work it takes for GHC to dispense that wisdom could be handed off to such a reference instead, maybe leaving it so the developers can develop and not mollycoddle us 😛 . (Though, yeah, that makes it harder to work with GHC when you don't have an internet connection and/or don't want to let whoever's eavesdropping know that you're developing your killer app in Haskell, so, maybe, nah)

And thanks to anybody who's reading this who had a role in any of that. Hope this wasn't a distracting waste of the time you obviously know how to put to good use otherwise.

Binding representation-polymorphic values

Currently, binding a levity-polymorphic value (actually, a representation-polymorphic value) is not allowed. For example

($#) :: forall r1 r2 (a :: TYPE r1) (b :: TYPE r2). (a -> b) -> a -> b
f $# x = f x

produces the following error message:

    A levity-polymorphic type is not allowed here:
      Type: a
      Kind: TYPE r1
    In the type of binder ‘x’

I think it would be clearer to say that binding representation-polymorphic values in general isn't allowed, since that isn't exactly obvious from the error message.

Unhelpful error message with top-level let-bindings

From https://reddit.com/r/haskell/comments/pizmp7/haskell_beginner_what_am_i_doing_wrong_i/

The code in question is:

let triangles = [ (a,b,c) | c <- [1..10], b <- [1..10], a <- [1..10] ]
let rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2 ]

If you save this to a file (say Let.hs) and compile it with ghc, it will produce this error message:

Let.hs:2:1: error:
    parse error (possibly incorrect indentation or mismatched brackets)
  |
2 | let rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2 ]
  | ^

I think the problem is that it expects the in ... part of a let ... in ... expression which can occur at the top level if TemplateHaskell is enabled. Such expressions require indentation, so I think it complains about that.

It seems to me to be difficult to improve this without hard-coding a check for top-level let bindings. Maybe that is what we should do? Or perhaps someone can come up with a more general solution.

Potentially confusing "Pattern match is redundant" with single alternative case/pattern binding

Compiling the following program with GHC Version 9.2.1 gives a redundant match warning.

module Test (patBind, caseMatch) where

patBind :: a
Just patBind = Nothing

caseMatch :: a
caseMatch = case Nothing of 
  Just x -> x

This produces:

Test.hs:5:1: warning: [-Woverlapping-patterns]
    Pattern match is redundant
    In a pattern binding: Just patBind = ...
  |
5 | Just patBind = Nothing
  | ^^^^^^^^^^^^^^^^^^^^^^

Test.hs:9:3: warning: [-Woverlapping-patterns]
    Pattern match is redundant
    In a case alternative: Just x -> ...
  |
9 |   Just x -> x

While it is arguably correct to emit a warning for both definitions as they can only produce a pattern match error, I think it is misleading to call the pattern match redundant.
For me, redundant means I can just delete the pattern match entirely. I clearly cannot do that for the patBind example, as this is the only definition for it. In caseMatch, removing the match yields an empty case, which is not allowed without EmptyCase.

In my opinion, the definitions should produce something like the following.

Test.hs:5:1: warning: [-Woverlapping-patterns]
    Inaccessible pattern binding (?)
    The pattern can never match the left hand side expression. 
    In a pattern binding: Just patBind = ...
  |
5 | Just patBind = Nothing
  | ^^^^^^^^^^^^^^^^^^^^^^

Test.hs:9:3: warning: [-Woverlapping-patterns]
    Inaccessible right hand side. 
    The pattern can never match the scrutinized expression. 
    In a case expression: case Nothing of ...
    In a case alternative: Just x -> ...
  |
9 |   Just x -> x

On a side note, I did not realize that top-level pattern bindings are actually part of the Haskell standard. I've never encountered them anywhere and never had the urge to use them.

Parse error can never be module header or import declaration

This code just passed by the Monthly Hask Question thread on Reddit:

main :: IO ()
main = putStrLn "Hello World!"

type Addrs = (Char, Int)

copy :: Addrs -> Int
copy (x, y) = y + 1

copy ("Street X", 10)

GHC complains:

T.hs:9:1: error:
    Parse error: module header, import declaration
    or top-level declaration expected.
  |
9 | copy ("Street X", 10)
  | ^^^^^^^^^^^^^^^^^^^^^

But there can never be a module header or import declaration at this point of the program. I would propose changing the message to:

T.hs:9:1: error:
    Parse error: top-level declaration expected.
  |
9 | copy ("Street X", 10)
  | ^^^^^^^^^^^^^^^^^^^^^

Maybe it could even suggest binding it to a variable?

Display -Werror messages after warnings

Usually when compiling a module, GHC first displays all warnings and then all errors.
This is not the case when compiling with -Werror.

Example:

f x = case x of 0 -> Just {}

When compiled with -Wmissing-fields -Werror=incomplete-patterns, this example first displays the error message for -Werror=incomplete-patterns and then the warning for -Wmissing-fields.
This means that one has to scroll up to see the error message, and especially in GHCi, it is not even immediately obvious, that the module didn't compile if there are a few warnings.

Ideally, GHC should first display all warnings and after that display errors, including those produced by -Werror.

Bare top-level expressions

Given this module:

sum [1,2,3,4]

We get this error when loading it into GHCi:

Main.hs:1:1: error:
    Parse error: module header, import declaration
    or top-level declaration expected.
  |
1 | sum [1,2,3,4]
  | ^^^^^^^^^^^^^
Failed, no modules loaded.

Perhaps we can:

  1. Recognize that this is a bare top-level expression.
  2. Reflect that in the error message.
  3. Suggest that the user might want to bind this bare expression to a name.

Suggested error message:

Main.hs:1:1: error:
    Parse error: module header, import declaration
    or top-level declaration expected, but encountered
    a bare expression instead.
    Perhaps you meant to bind this expression to a name?
  |
1 | sum [1,2,3,4]
  | ^^^^^^^^^^^^^
Failed, no modules loaded.

Ideally, it would also be nice if the error message didn't mention module headers or import declarations in places where it doesn't apply, such as in this example:

import Data.List
xs = [3,5,1,2]
sort xs
Main.hs:3:1: error:
    Parse error: module header, import declaration
    or top-level declaration expected.
  |
3 | sort xs
  | ^^^^^

Better error messages for Num literals

I hope, this is the correct way to post this, since this repo seems to be pretty new.

In my experience, one of the most annoying error messages in GHC is when you try to assign a number literal to a type which does not implement Num.

x :: String
x = 5

What you would expect as a beginner is something like Could not match type Int with String, but because Num literals are overloaded in Haskell, you actually get the following.

<interactive>:2:18: error:
    • No instance for (Num String) arising from the literal ‘5’
    • In the expression: 5
      In an equation for ‘x’: x = 5

If the source of this No instance for (Num String) part is a literal, the compiler could provide a more useful message.
Something like Expected a String, but got a Num literal seems a lot more helpful to newbies.

Get this party started

To kick things off, we have a few initial first steps:

  • Get a basic README in place, so people know what this project is about (even if we're still figuring that out). The README should include goals and contributor expectations.
  • Add Issue Templates for the two main types of issues/tickets: a) error message to track, b) admin task
  • Identify and scope out a workflow that gets the ball moving. It won't be perfect, but that workflow should facilitate building an initial list of problematic error messages we can then triage and make progress on.
  • Possibly start with our own example to showcase what we are looking for, and to test the waters.
  • Add labels for each of the tools (GHC/Stack/Cabal/HLS?)
  • Add labels for different issue types (admin-task for this repo vs error-message vs local-improvement)
  • Put a call out to contributors, collaboration and contributions welcome!

Workflow Requirements

  • The workflow should collect information that is relevant and necessary to improving the tool error messaging.
  • The workflow should encourage and facilitate efficiency.
  • Code included in the discussion and resolution should be as easily reproducible as we can make (example: using a stack script with build dependencies inline, single file scripts, etc).
  • While code may come up in discussion in github issues, if it is relevant, the code should be copied into a directory structure that exists in the repository.

Better error messages by adding impossible instances

It was raised on Reddit that the error for 1 + [2, 3] is inscrutable:

Prelude> 1+[2,3]

<interactive>:1:1: error:
    • Non type-variable argument in the constraint: Num [a]
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        it :: forall a. (Num a, Num [a]) => [a]

I think the ultimate problem is, rather than any particular processing that the compiler does of error messages, that instance Num [a] should be explicitly forbidden rather the left unspecified. That is, the following seems to read much better

> 1 + [2,3]
<interactive>:7:1: error:
    • [a] is not an instance of Num
    • When checking the inferred type
        it :: forall a. ((TypeError ...), Num a) => [a]

which I achieve with

> :set -XUndecidableInstances -XTypeFamilies -XDataKinds -XTypeOperators
> import GHC.TypeLits
> import Data.Kind
> type family E t :: Constraint
> type instance E [a] = TypeError (ShowType [a] :<>: Text " is not an instance of Num")
> instance E [a] => Num [a] where
<interactive>:6:10: warning: [-Wmissing-methods]
...

Ultimately this would need to go through the CLC process, but I'm presenting it to the error-messages first to receive their expert opinion on whether this change would be worthwhile.

Language extensions don't work

/u/crmills_2000 posted the following code on Reddit:

module Lib ( someFunc) where

{-# LANGUAGE CPP, DeriveGeneric, OverloadedStrings, ScopedTypeVariables #-}
{-# LANGUAGE DeriveGeneric #-}

import Data.Text (Text)
import Data.Vector (Vector)
import GHC.Generics (Generic)
import qualified Data.ByteString.Lazy as BL
import Data.Csv

data Person = Person { name :: !Text , salary :: !Int }
  deriving ( Show)

instance FromNamedRecord Person where
  parseNamedRecord r = Person <$> r .: "name" <*> r .: "salary"

main :: IO ()
main = do
  csvData <- BL.readFile "salaries.csv"
  case decodeByName csvData of
    Left err -> putStrLn err
    Right v -> 0 -- V.forM_ v $ \ (name, salary :: Int) ->

  -- putStrLn $ name ++ " earns " ++ show salary ++ " dollars"

someFunc :: IO ()
someFunc = putStrLn "someFunc"

This produces the errors:

T.hs:16:40: error:
    • Couldn't match expected type ‘Data.ByteString.Internal.ByteString’
                  with actual type ‘[Char]’
    • In the second argument of ‘(.:)’, namely ‘"name"’
      In the second argument of ‘(<$>)’, namely ‘r .: "name"’
      In the first argument of ‘(<*>)’, namely ‘Person <$> r .: "name"’
   |
16 |   parseNamedRecord r = Person <$> r .: "name" <*> r .: "salary"
   |                                        ^^^^^^

T.hs:16:56: error:
    • Couldn't match expected type ‘Data.ByteString.Internal.ByteString’
                  with actual type ‘[Char]’
    • In the second argument of ‘(.:)’, namely ‘"salary"’
      In the second argument of ‘(<*>)’, namely ‘r .: "salary"’
      In the expression: Person <$> r .: "name" <*> r .: "salary"
   |
16 |   parseNamedRecord r = Person <$> r .: "name" <*> r .: "salary"
   |                                                        ^^^^^^^^

That is quite surprising because the OverloadedStrings extension seems to be enabled. However, the extensions are written below the module ... where declaration, so they are silently ignored.

I really don't know what the best way to solve this is. Maybe we should add an "important" warning about the misplaced language pragmas which is not overwritten by the errors? Or perhaps if OverloadedStrings would be suggested then the user at least knows that the extension is not detected by the compiler.

Edit: In this case the user reporting this issue mentions this is one of the first programs they have written in Haskell, so they have probably not heard of the OverloadedStrings extension before and just copied it from somewhere, so I don't know if it would be possible to have them fix the issue on their own with just a better error message. We can try and perhaps help some people who know slightly more, but maybe I'm just too trigger-happy reporting these.

Ambiguous occurrence ‘map’

A new user reports confusion about the error message below. I can understand the confusion. The error message is technically correct, of course, but wouldn't it be more helpful to say "To resolve the error change map to Prelude.map or Data.Map.map (if either of those is what you meant) or something else (if you meant something else.".

(Implicit import of Prelude is a confounding factor here.)

<interactive>:12:2: error:
    Ambiguous occurrence ‘map’
    It could refer to
       either ‘Prelude.map’,
              imported from ‘Prelude’ at modules.hs:1:1
              (and originally defined in ‘GHC.Base’)
           or ‘Data.Map.map’,
              imported from ‘Data.Map’ at modules.hs:5:1-15
              (and originally defined in ‘Data.Map.Internal’)

EDIT, the op's code which produced the error:

import Data.List (nub, sort)  --you can selectively import functions
{-import Data.List hiding (nub)  you're importing all functions accept the nub-}
-- import qualified Data.Map as M  
-- Now, to reference Data.Map's filter function, we just use M.filter
import Data.Map
import qualified Data.Set
  
numUniques :: (Eq a) => [a] -> Int  
numUniques = length . nub

Incorrect usage of the term "bang-pattern"

Given the following module:

module Bang where

z = let !(x,y) = (1,2) in x

We get the following error message:

Bang.hs:3:9: error:
    Illegal bang-pattern (use BangPatterns):
    ! (x, y)
  |
3 | z = let !(x,y) = (1,2) in x
  |         ^^^^^^

which is incorrect, this is a strict binding, not a bang pattern.

Suggested error message:

Bang.hs:3:9: error:
    Illegal strict binding (use BangPatterns):
    ! (x, y)
  |
3 | z = let !(x,y) = (1,2) in x
  |         ^^^^^^

How about "contributions THEN structure them"?

I just edited the README with my own whiny newbie complaint about one of the earliest (and probably easiest-to-understand) error messages, a message that nevertheless stopped me cold in my tracks. Why was I stumped? Because I'm already a panicked deer in the on-rushing headlights of all I have to learn, as a newbie, about Haskell. I'm a month in, and I'm still doing copypasta examples. The dopamine surge of actually making something that feels at least slightly original still hasn't arrived. A month. That's never happened to me before, with any programming language. I still feel very little confidence about writing code rather than just copying it and changing it a little. Puzzling error messages aren't exactly helping.

So, let's not get bogged down in process design. How about instead putting a notice out everywhere that's conceivably relevant and seeing if anything comes in, just on the README? THEN get bogged down in process design. At least you'll be bogged down in designing for actual customers. I use the word "customers" advisedly. I can say from 20 years in business (and 20 years of mostly working in businesses) that nothing happens until you make the sale. In this case, "the sales pitch" has to be, "we're listening, you matter to us, please just complain about anything and everything. Don't hold back. Tell us what you really think." This, for lack of ability to actually deliver easier error messages until the GHC maintainers see enough critical mass to take the initiative seriously.

Puzzling problem with constraint

I was trying to implement an example from the tagsoup package. (The example has typos but that's not relevant here.)

Specifically, there is a value

parseOptions :: StringLike str => ParseOptions str 

and I want to override some of its fields at str = String

parseOptionsPreserveEntities :: ParseOptions String
parseOptionsPreserveEntities = parseOptions
  { optEntityData = \(str,b) -> [TagText $ "&" ++ str ++ [';' | b]]
  , optEntityAttrib = \(str,b) -> ("&" ++ str ++ [';' | b], [])
  }

But this doesn't work

app/Main.hs:10:32: error:
    • Ambiguous type variable ‘str0’ arising from a use of ‘parseOptions’
      prevents the constraint ‘(Text.StringLike.StringLike
                                  str0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘str0’ should be.
      These potential instances exist:
        instance [safe] Text.StringLike.StringLike String
          -- Defined in ‘Text.StringLike’
        ...plus four instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the expression: parseOptions
      In the expression:
        parseOptions
          {optEntityData = \ (str, b) -> [TagText $ "&" ++ str ++ ...],
           optEntityAttrib = \ (str, b) -> ("&" ++ str ++ [';' | b], [])}
      In an equation for ‘parseOptionsPreserveEntities’:
          parseOptionsPreserveEntities
            = parseOptions
                {optEntityData = \ (str, b) -> [...],
                 optEntityAttrib = \ (str, b) -> ("&" ++ str ++ ..., [])}
   |
10 | parseOptionsPreserveEntities = parseOptions
   |                                ^^^^^^^^^^^^...

I don't actually understand why it doesn't work. A workaround is to convert it into two record updates:

parseOptionsPreserveEntities :: ParseOptions String
parseOptionsPreserveEntities = parseOptions
  { optEntityData = \(str,b) -> [TagText $ "&" ++ str ++ [';' | b]] }
  { optEntityAttrib = \(str,b) -> ("&" ++ str ++ [';' | b], []) }

I'm not sure whether I can suggest a candidate for a good error message because I don't actually understand what's going on. Perhaps multiple record updates desugar into something that breaks type inference. Perhaps this could be detected and a helpful suggestion to split the update into multiple single-field updates could be emitted.

(GHC 8.10.7)

(rigid, skolem) type variables escaping scope

This is an example of technical jargon in error messages which doesn't mean much to anyone but the most experienced Haskellers. Here is a simple example to reproduce this error:

module Foo where

h :: (b -> ()) -> Int
h = error "urk"

f = h (\x -> let g :: a -> Int
                 g y = length [x, y]
              in ())

The issue here is that x and y must have the same type because they are both elements of the same list, and y has type a which is bound in the type signature of g, but g is bound inside the lambda that binds the variable x, so the information about a would have to travel from inside the lambda to the outside, which is not allowed.

The error message that is produced with GHC 9.0.2 is:


Foo.hs:7:35: error:
    • Couldn't match expected type ‘b0’ with actual type ‘a’
        because type variable ‘a’ would escape its scope
      This (rigid, skolem) type variable is bound by
        the type signature for:
          g :: forall a. a -> Int
        at Foo.hs:6:18-30
    • In the expression: y
      In the first argument of ‘length’, namely ‘[x, y]’
      In the expression: length [x, y]
    • Relevant bindings include
        y :: a (bound at Foo.hs:7:20)
        g :: a -> Int (bound at Foo.hs:7:18)
        x :: b0 (bound at Foo.hs:6:9)
  |
7 |                  g y = length [x, y]
  |                                   ^

In my opinion there are a few strange things in this error message.

  1. The message mentions type variables escaping a "scope", but I have not enabled ScopedTypeVariables so which scope does the message mean?

  2. Rigid and skolem are very technical terms, could that simply be left out without upsetting advanced Haskell users?

  3. The signature for g is written as g :: forall a. a -> Int, but that is not legal syntax because I haven't enabled ExplicitForAll.

  4. Where does the variable b0 come from? Now it is relatively clear because there is only one b type variable in the whole program, but what about more complicated programs?

do desugaring yields confusing errors when the argument count is wrong

module BadError where

{-
I get:
    Couldn't match expected type ‘() -> Either String Int’
        with actual type ‘Either String Int’
    ...
  |
6 |     x <- do_thing arg2
  |     ^^^^^^^^^^^^^^^^^^

This makes it seem like "do_thing arg2" is short one argument, but it's not!
'confusing' has an undeclared argument ().
-}

confusing :: Int -> String -> () -> Either String Int
confusing arg1 arg2 = do
    x <- do_thing arg2
    -- ... much stuff
    return $ arg1 + 2
    where
    do_thing :: String -> Either String Int
    do_thing = undefined

{-
In this case I think I was misled by the highlighting, it highlights "do_thing
arg2".  But the "in the expression" is correct, it fingers the entire do
expression.  When desugared, it seems that this is an artifact of the error
highlighting, since the below highlights the entire expression:
-}

-- desugared :: Int -> String -> () -> Either String Int
-- desugared arg1 arg2 =
--     do_thing arg2 >>= \x -> return $ arg1 + 2
--     where
--     do_thing :: String -> Either String Int
--     do_thing = undefined

Unhelpful error with associated types

In this stackoverflow question the asker presents this code:

{-# LANGUAGE TypeFamilies #-}

class Reproductive a where

  -- | A sequence of genetic information for an agent.
  type Strand a

  -- | Full set (both strands) of genetic information for an organism.
  type Genome a = (Strand a, Strand a)

But this produces the unhelpful error message (I was confused for a minute):

Assoc.hs:20:8: error:
    ‘Genome’ is not a (visible) associated type of class ‘Reproductive’
   |
20 |   type Genome a = (Strand a, Strand a)
   |        ^^^^^^

As a user I'm thinking that I'm defining an associated type, but this error message says that it expects the type to already be defined.

I now understand that you can easily solve this by writing a separate "kind signature" where the signature part can be omitted because it defaults to *:

{-# LANGUAGE TypeFamilies #-}

class Reproductive a where

  -- | A sequence of genetic information for an agent.
  type Strand a

  -- | Full set (both strands) of genetic information for an organism.
  type Genome a
  type Genome a = (Strand a, Strand a)

I think this warning can be improved, but I'm not sure how, yet.

Rename this repository to reduce risk of confusion

As the readme notes, there is already a package named "error" and a name like "error-messages" would probably be clearer about the intent. It would also match closer to the name of the repo that inspired this one: https://github.com/elm/error-message-catalog. (As was mentioned in the thread for creating this repo: https://discourse.haskell.org/t/proposal-an-issue-tracker-for-better-error-messages/2498)

I regret not doing this bikeshed before the repo was created.

Better error messages for the monomorphism restriction

Today Competitive_Ad2539 on Reddit asks why type inference fails for this code:

showLine = (++ "\n") . concat

The error message is (with GHC 9.2.1 and 9.0.2):

T.hs:1:24: error:
    * Ambiguous type variable `t0' arising from a use of `concat'
      prevents the constraint `(Foldable t0)' from being solved.
      Relevant bindings include
        showLine :: t0 [Char] -> [Char] (bound at T.hs:1:1)
      Probable fix: use a type annotation to specify what `t0' should be.
      These potential instances exist:
        instance Foldable (Either a) -- Defined in `Data.Foldable'
        instance Foldable Maybe -- Defined in `Data.Foldable'
        instance Foldable ((,) a) -- Defined in `Data.Foldable'
        ...plus two others
        ...plus 26 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    * In the second argument of `(.)', namely `concat'
      In the expression: (++ "\n") . concat
      In an equation for `showLine': showLine = (++ "\n") . concat
  |
1 | showLine = (++ "\n") . concat
  |                        ^^^^^^

Experts might see at a glance that this is probably caused by the monomorphism restriction, but that is not at all obvious from this error message.

I would propose adding a note in the error message that the monomorphism restriction prevents it from inferring the most general type.

Parse erorr on input `->`

From stackoverflow:

concat [[a]] -> [a]
concat xss = [x | xs <- xss, x <- xs]

Produces the error:

Test.hs:1:14: error: parse error on input ‘->’
  |
1 | concat [[a]] -> [a]
  |              ^^

Which is not very descriptive. Perhaps GHC knows enough to be able to suggest adding ::? Maybe the -> token can be used to infer that the user probably meant to write a type signature?

Actually, I think this is very similar to #14 in the sense that the compiler starts parsing a top-level splice. But in this case the suggestion to use <variable-name> = <expr> is not right.

Custom error for tuple arg when multiple args are expected

A Titular_Hero on stack overflow reports the following code snippet:

import Data.List

encode [] = []
encode ls = zip((map head list), (map length list))
 where list = group ls 

This produces the error message:

T.hs:3:13: error:
    * Couldn't match expected type: [a1]
                  with actual type: [b0] -> [(a0, b0)]
    * Probable cause: `zip' is applied to too few arguments      
      In the expression: zip ((map head list), (map length list))
      In an equation for `encode':
          encode ls
            = zip ((map head list), (map length list))
            where
                list = group ls
    * Relevant bindings include
        encode :: [a] -> [a1] (bound at T.hs:2:1)
  |
3 | encode ls = zip((map head list), (map length list))
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

T.hs:3:16: error:
    * Couldn't match expected type: [a0]
                  with actual type: ([a], [Int])
    * In the first argument of `zip', namely
        `((map head list), (map length list))'
      In the expression: zip ((map head list), (map length list))
      In an equation for `encode':
          encode ls
            = zip ((map head list), (map length list))
            where
                list = group ls
    * Relevant bindings include
        list :: [[a]] (bound at T.hs:4:8)
        ls :: [a] (bound at T.hs:3:8)
        encode :: [a] -> [a1] (bound at T.hs:2:1)
  |
3 | encode ls = zip((map head list), (map length list))
  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

First of all, it is not ideal that this produces two (rather lengthy) error messages.

But more importantly, I think the error message for this code should mention that arguments should just be passed separately and not in a tuple. Although, a programmer making this mistake is probably not aware of the fact that they are using a tuple, so I would suggest to try to describe it in another way in an improved error message.

The accepted answer by chepner is worded like this:

You are trying to use C-style syntax to call zip, which is interpreted as zip getting a single tuple as its argument, rather than the two lists you intended.

Perhaps a slight modification/generalization of this could be a good start for an improved error message.

Non-exhaustive pattern match warning with guards

In this stackoverflow question the asker presents this code:

data Tree = Leaf | Node Int Tree Tree
  deriving (Eq, Show, Read, Ord)

insert :: Int -> Tree -> Tree
insert n Leaf = Node n Leaf Leaf
insert n tree@(Node num lt rt)
                    | n < num  = Node num (insert n lt) rt
                    | n > num  = Node num lt (insert n rt)
                    | n == num = tree

Which produces the following warning (with -Wall):

Test.hs:5:1: warning: [-Wincomplete-patterns]
    Pattern match(es) are non-exhaustive
    In an equation for ‘insert’:
        Patterns not matched:
            _ (Node _ Leaf Leaf)
            _ (Node _ Leaf (Node _ _ _))
            _ (Node _ (Node _ _ _) Leaf)
            _ (Node _ (Node _ _ _) (Node _ _ _))
  |
5 | insert n Leaf = Node n Leaf Leaf
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

The list of "patterns not matched" here is strange, it seems to me that the pattern not matched is just _ (Node _ _ _) and I think it should also mention something about the guards.

Possibly confusing "inaccessible right hand side" with nested `Void`-like type

(based on a true story)

{-# LANGUAGE NamedFieldPuns #-}

module Test where

data A

data B = B
  { stuff :: !Int,
    morestuff :: !Double,
    a :: !A
  }

data C = C
  { foo :: !Int,
    bar :: !Bool,
    baz :: !B
  }

and consider the function

fff :: C -> Int
fff c = if bar then foo else 0
  where
    C {foo, bar} = c

This results in a warning (since GHC 8.8, see 4th bullet point here)

Test.hs:22:5: warning: [-Woverlapping-patterns]
    Pattern match has inaccessible right hand side
    In a pattern binding: C {foo, bar} = ...
   |
22 |     C {foo, bar} = c
   |     ^^^^^^^^^^^^^^^^

Now imagine that B and A are defined in a completely different place (maybe A and the new field a in B were recently introduced as placeholders) than fff. Then this message gives no direct hints what went wrong, and the problematic nested field isn't even mentioned in fff.

(Also, it is a bit confusing that the right hand side is supposed to be inaccessible)

I find it very nice that GHC can warn about this, but maybe it could give a hint which field is Void-like in this case?

labels for tracking issues

As this project grows, it becomes more important to be able to filter the list of issues in various ways (in order to be able to review the different groups of issues and help push them along further). To that end, I'd like to suggest we come up with a list of labels for useful sorting/filtering of the issues.

Some obvious "states" I think we should probably have labels for:

  • new, needs review
  • needs more info or a better example, or some more discussion
  • ready for upstream ticket
  • has a ticket upstream and waiting on upstream action
  • upstream needs contributor to own the change
  • updates complete

What should our labels be, which states should have labels?

Leaking information about `GHC.GHCi.ghciStepIO` in GHCi

Here's a real example of an error message someone asked about.

ghci> thing = putStrLn -- defined higher up
ghci> :{
| thing <- getLine
| putStrLn thing
| :}
<interactive>:291:9: error:
    • Couldn't match expected type ‘(String -> IO ())
                                    -> (String -> IO ()) -> IO a0’
                  with actual type ‘IO String’
    • The function ‘getLine’ is applied to two arguments,
      but its type ‘IO String’ has none
      In the first argument of ‘GHC.GHCi.ghciStepIO ::
                                  forall a. IO a -> IO a’, namely
        ‘(getLine putStrLn thing)’
      In a stmt of an interactive GHCi command:
        thing <- GHC.GHCi.ghciStepIO :: forall a. IO a -> IO a
                 (getLine putStrLn thing)
  1. I don't think we should be leaking this detail about stepIO to end users.
  2. It obfuscates the real issue, that this is all being run as a single statement. The issue would be clearer if the message were:
    • The function ‘getLine’ is applied to two arguments,
      but its type ‘IO String’ has none
      In the expression:  getLine putStrLn thing
      In a stmt of an interactive GHCi command:
        thing <- getLine putStrLn thing
                 ```

Tested with GHC 8.10.7 and 9.0.1.

Better MonadFail

from Haskell GameDev discord

Given a failable ... <- ... pattern on sum type Request with two variants:

unknown

Most users probably don't want to implement a MonadFail instance for monads defined in a library. The following would be more helpful:

    • Failable pattern in non-failable do statement
      `(HaulRequest (resource, requiredAmount) amount)`
      This pattern fails on:
        `ConstructionRequest`
    • Note: the left pattern of `<-` must succeed on 
      all variants unless `MonadFail` is defined
    • Perhaps you intended to handle each variant with `case`:
        `r0 <- get building
         case r0 of
           HaulRequest ...
           ConstructionRequest ...`
    • Perhaps you intended to define a 
      `MonadFail (SystemT World IO)` instance to
      handle failable patterns

Wrong indentation: Parser error lacking description

Code in question:

let i = 1
  j = 2
  in ...

Error message:

site.hs:104:3: error: parse error on input ‘j’
    |
104 |   j = 2
    |   ^

(there are 103 lines above this let clause, but surely those parts are irrelevant)

Suggested Improvements:

site.hs:104:3: error: parser error on input 'j'
`let` clause require indenting at least 3 spaces
103 | let i = 1
104 |   j = 2
    |   ^

Or, at least
error: parser error on input 'j' (likely incorrect indentation)

There should be similar class of errors, but let-clause related ones were the most frequent and problematic.

Add to build-depends in your cabal file

A recent question on stackoverflow concerns adding dependencies to the cabal file. The error suggests adding a dependency to the build-depends field:

    src\Main.hs:3:2: error:
    Could not load module `Text.ParserCombinators.Parsec'
    It is a member of the hidden package `parsec-3.1.13.0'.
    Perhaps you need to add `parsec' to the build-depends in your .cabal file.
    Use -v to see a list of the files searched for.
  |
3 |  import Text.ParserCombinators.Parsec hiding (spaces)
  |  ^^

However, the .cabal file could contain multiple build-depends fields, so it would be useful if the error message could include slightly more information about the build component and perhaps even a line number in the .cabal file.

That does seem to require some coordination between GHC and Cabal. Maybe with structured error messages GHC could provide a generic "package not found" error and then cabal could intercept that and provide detailed information on how to solve that. That seems like quite a big change, so maybe there is an easier solution?

Errors from confusing whether to use `let` or `bind`

I am quite sure I've seen more cryptic error messages, but for now I can only found simpler ones.

Problem

I noticed that there are many errors coming from confusion between:

do
  foo <- bar baz
  useFoo foo

vs.

do
  let foo = bar baz
  useFoo foo

It would be great if we could give a suggestion.

Cases and Errors

StackOverflow search gave a few such cases.

Code:

parseDnsMessage :: BG.BitGet DnsMessage

recQuery :: BS.ByteString -> String -> IO BS.ByteString

resolveName :: [Word8] -> [Word8] -> BS.ByteString -> String
resolveName qname name bstr = do
  let newbstr = BSL.toStrict $ replace (BS.pack qname) (BS.pack name) bstr
  retbstr <- recQuery newbstr (head rootServers4)
  let msg = BG.runBitGet retbstr parseDnsMessage
  case msg of
    Right m -> (intercalate "." $ map show (rdata $ head $ answer $ m))

Error message:

Couldn't match expected type ‘[BSI.ByteString]’
            with actual type ‘IO BSI.ByteString’
In a stmt of a 'do' block:
  retbstr <- recQuery newbstr (head rootServers4)
In the expression:
  do { let newbstr
             = BSL.toStrict $ replace (BS.pack qname) (BS.pack name) bstr;
       retbstr <- recQuery newbstr (head rootServers4);
       let msg = BG.runBitGet retbstr parseDnsMessage;
       case msg of {
         Right m
           -> (intercalate "." $ map show (rdata $ head $ answer $ m)) } }

Code:

title :: IO Html
title = do
    y <- getCurrentYear
    return $ toHtml $ "Registration " ++ y

getRootR :: Handler RepHtml
getRootR = do
    (widget, enctype) <- generateFormPost personForm -- not important for the problem at hand, comes from the example in the yesod book
    defaultLayout $ do
        setTitle title -- this is where I get the type error
[...]

Error:

Couldn't match expected type `Html' with actual type `IO Html'
In the first argument of `setTitle', namely `title'
[...]

(You know there is no suggestion in ... part)

This one is special in that they are trying to use top-level IO action. still, likely a similar fix.

Code:

displayList :: [Int] -> IO()
displayList [] = putStrLn ""
displayList (firstUnit:theRest) =  putStrLn (show firstUnit ++ "\n" ++ 
                                   displayList theRest)

Error:

• Couldn't match expected type ‘[Char]’ with actual type ‘IO ()’
• In the second argument of ‘(++)’, namely ‘(displayList theRest)’
  In the first argument of ‘putStrLn’, namely
    ‘((show firstUnit) ++ (displayList theRest))’
  In the expression:
    putStrLn ((show firstUnit) ++ (displayList theRest))

Suggestion

Let me consider the last one.

Couldn't match expected type ‘[Char]’ with actual type ‘IO ()’
  In the second argument of ‘(++)’, namely ‘(displayList theRest)’
  In the first argument of ‘putStrLn’, namely
    ‘((show firstUnit) ++ (displayList theRest))’
  In the expression:
    putStrLn ((show firstUnit) ++ (displayList theRest))

Perhaps add suggestion like this:

Couldn't match expected type ‘[Char]’ with actual type ‘IO ()’
Perhaps you would like to bind `displayList theRest` in the do notation.
  In the second argument of ‘(++)’, namely ‘(displayList theRest)’
[...]

(Though, I do not like the word bind here)

or this:

Couldn't match expected type ‘[Char]’ with actual type ‘IO ()’
Suggested fix: use `<-` in do notation.
  In the second argument of ‘(++)’, namely ‘(displayList theRest)’
[...]
Perhaps you want to use:
  do
    d <- displayList theRest
    -- use d here
instead of:
  (displayList theRest)
in the expression.

Don't suggest turning on typeclass extensions that don't help

Returning to the example from: #42 , when FlexibleInstances is already on we get the not-that-bad error

ghci> 1 + [2,3]

<interactive>:6:1: error:
    * No instance for (Num [a0]) arising from a use of `it'
    * In a stmt of an interactive GHCi command: print it

But when FlexibleInstances is not yet on, we instead get an error suggesting we turn it on!

I think that errors which suggest some typeclass extension should be turned on (at call sites, not instance declarations) should first check if the extension would help -- i.e. if the code would typecheck with the extension. If it still would not typecheck with the extension, then the error message should be about the failure to typecheck, not the extension that requires it.

This seems like a reasonable-to-implement principle which could potentially improve a broad class of error messages.

Perhaps you meant a :: Maybe Int

Given this code:

a :: Just Int
a = Just 5

GHC errors with:

Not in scope: type constructor or class 'Just'A data constructor of that name is in scope; did you mean DataKinds?

A better error message would be:

Perhaps you meant a :: Maybe Int?
Not in scope: type constructor or class 'Just'
A data constructor of that name is in scope; did you mean DataKinds?

Suggested on reddit: https://www.reddit.com/r/haskell/comments/kgvdon/improving_haskell_ghc_error_messages/gghjajf/?utm_source=reddit&utm_medium=web2x&context=3

Suggest -XMagicHash where appropriate

Consider using a primitive such as spark#. Without -XMagicHash, on GHC 9.2.1, I get the following error:

src/Rtq/Internal.hs:172:31: error:
    Variable not in scope:
      (#) :: t0 -> t1 -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #)
    |
172 | par x = Par $ IO $ \s -> spark# x s
    |  

In almost every case when a haskell developer is writing the hash character (#), they mean to invoke the use of a primitive, or some identifier which is donning a hash to signal its involvement with unboxed things or primitives (e.g. I have implemented compareByteArray# :: ByteArray# -> ByteArray# -> Int#). There are some instances for which this is not the case, such as (#) in profunctors, but I can't see why it would hurt to say "Perhaps you intended to enable MagicHash?". I also think this is another case where allowing operators without spaces is suboptimal from a UX perspective (e.g. IMO using (.) without spaces is poor form), but disallowing that is not what I hope to accomplish from posting here, improving the error message is.

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.