Opened 20 months ago

Last modified 2 weeks ago

#9173 new bug

Better type error messages

Reported by: simonpj Owned by:
Priority: normal Milestone:
Component: Compiler Version: 7.8.2
Keywords: Cc: hvr
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: Incorrect warning at compile-time Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

Generating better type-error messages is a hoary chestnut, but Herbert brought my attention to this thread on reddit, which has the following idea.

At the moment, from

module Foo where
  addThree = \x -> x + 3 :: Int
  y = addThree $ Just 5

we get this error:

Foo.hs:2:20
    Couldn't match expected type `Int' with actual type `Maybe a0'
    In the return type of a call of `Just'
    In the second argument of `($)', namely `Just 5'
    In the expression: addThree $ Just 5

Maybe we could generate this instead

Foo.hs:2:20
  inferred: "Just 5" has type "Maybe a0" 
  expected: second argument of "($)" must have type "Int"
  in the expression: addThree $ Just 5

Change History (19)

comment:1 Changed 20 months ago by thoughtpolice

I like this, and think the second set of output reads much, much better. Bonus points: use ANSI terminal colors to highlight some key words in the output, such as "Warning" or "Error" should they exist.

Lennart also makes an observation about the way the Mu Haskell compiler handles this: https://pay.reddit.com/r/haskell/comments/26tcrk/curious_with_a_bit_of_beginner_ranting_about_some/chuwwns

comment:2 Changed 20 months ago by hvr

  • Cc hvr added

comment:3 Changed 20 months ago by schyler

Here's an error that a noob making a subtle typo might get from GHC:

Prelude> map 1 [1..5]

<interactive>:2:1:
    Could not deduce (Num (a0 -> b))
      arising from the ambiguity check for ‛it’
    from the context (Num (a -> b), Num a, Enum a)
      bound by the inferred type for ‛it’:
                 (Num (a -> b), Num a, Enum a) => [b]
      at <interactive>:2:1-12
    The type variable ‛a0’ is ambiguous
    When checking that ‛it’
      has the inferred type ‛forall a b.
                             (Num (a -> b), Num a, Enum a) =>
                             [b]’
    Probable cause: the inferred type is ambiguous

I wholeheartedly agree that the information needs to be presented in a way that's easier to digest.

For what should be a really simple error, I really have to stare that down to understand what it's actually trying to say ...

Wanted: Num a => a -> b
Got: Num a => a
Last edited 20 months ago by schyler (previous) (diff)

comment:4 Changed 20 months ago by nomeata

Despite the very general ticket title, let’s not confalte multiple errors. The original ticket was a about a type mismatch (inferred vs. expected). The example by schlyer is about an ambiguous type in the GHCi prompt. Note that with some more instances added, map 1 [1..5] would type check!

comment:5 Changed 6 months ago by thomie

  • Keywords newcomer added

This seems like a nice and easy ticket to handle by a newcomer.

The function to change is misMatchMsg in compiler/typecheck/TcErrors.hs.

Don't forget to update the expected test output. Use 'make accept', see Building/RunningTests/Updating. Please make sure you get all of them (grep for the old error message).

comment:6 Changed 6 months ago by thomie

It seems this ticket is not as easy to fix as I thought it would be. See: https://mail.haskell.org/pipermail/ghc-devs/2015-August/009559.html

comment:7 Changed 6 months ago by goldfire

  • Keywords newcomer removed

Yes, I don't think this is easy, precisely for the reasons described in that post.

comment:8 Changed 5 months ago by thomie

Reddit user physicologist writes:

While I love the the proposal in the ticket, I feel that a purely textual change to the error message could be a great boon without changing a single line of code.

"Expected" versus "actual" don't really the nature of the problem. For example, what is the type of foo in the following

foo :: Int
foo = "Howdy!"

Is foo actually an Int, since that was declared? Did the compiler expect foo to be a String, since that's what we passed it? Or is foo actually a "String", since that's what it contained, but the compiler expected a Int, since that's what we told it would be contained?

Perhaps just changing

Couldn't match expected type 'Foo' with actual type 'Bar'

to

Couldn't match declared type 'Foo' against a value of type `Bar'

might make things clearer?

comment:9 Changed 5 months ago by chreekat

thomie, that gets to the root of my problem with the message precisely!

comment:10 Changed 5 months ago by rwbarton

The error there would not be about the type of foo, though. It would be about the type of "Howdy!".

Foo.hs:4:7:
    Couldn't match expected type ‘Int’ with actual type ‘[Char]’
    In the expression: "Howdy!"
    In an equation for ‘foo’: foo = "Howdy!"

The "actual type" is ... the actual type of the expression "Howdy!" that the compiler points out. The "expected type" is the type expected from the context, that is, the type the expression would have to have for the whole thing to type check. I know lots of people find this confusing but I have never been able to understand why...

comment:11 follow-up: Changed 5 months ago by rwbarton

I wonder if it would help at all to just reverse the order of the two, that is,

    Couldn't match actual type ‘[Char]’ with expected type ‘Int’
    In the expression: "Howdy!"

Presumably the programmer has a strong association between the expression and its (actual) type, and then can work out what "expected type" refers to by elimination.

I'm also not that attached to the phrase "expected type". We could be more explicit and say something like "type ‘Int’ expected from context". I think the phrase "actual type" is quite good, though.

comment:12 follow-up: Changed 5 months ago by simonpj

The trouble with "declared type" is that it often isn't declared. Consider

not 'c'

The actual type of 'c' is Char, but the type expected by the context (the call of not) is Bool. But it'd be confusing to say that Bool was the "declared" type!

Switching the order as Reid suggests in comment:11 would be easy, and I can see that it might help. Do others like that?

I'd also be ok with saying "type expected by the context" instead of just "expected". That longer phrase would also suggest putting it second.

Simon

comment:13 in reply to: ↑ 11 Changed 4 months ago by chreekat

Foo.hs:4:7:
    Couldn't match expected type ‘Int’ with actual type ‘[Char]’
    In the expression: "Howdy!"
    In an equation for ‘foo’: foo = "Howdy!"

This example reminded me that sometimes the phrase "In the expression" is confusing to me. It seems that the phrase *should* be "Of the expression". Using 'in' makes me expect that the error message is referring to some particular component of the expression, rather than the whole thing.

It's a small incongruence, but it's big enough to force me to stop thinking about code and start thinking about the possible meanings of the error message.

comment:14 in reply to: ↑ 12 Changed 4 months ago by chreekat

Replying to simonpj:

Switching the order as Reid suggests in comment:11 would be easy, and I can see that it might help. Do others like that?

I'd also be ok with saying "type expected by the context" instead of just "expected". That longer phrase would also suggest putting it second.

+1. E.g.:

foo.hs:1:11:
    Couldn't match actual type ‘Char’ with type expected by context, ‘Bool’
    In the first argument of ‘not’, namely ‘'c'’
    In the expression: not 'c'
    In an equation for ‘foo’: foo = not 'c'

(I recall now that "In the expression" is used because the message often *does* refer to a component of the whole expression. Still, I think it would be nice if it switched to "In the expression" when referring to the whole thing.)

comment:15 Changed 3 months ago by thomie

Again discussed in this thread: https://www.reddit.com/r/haskell/comments/3tm3lv/proposal_expectedactual_requiredfound/

Not much new. expected/found instead of expected/actual seems a popular choice.

I vote for switching the order as well, mentioning the word "context", and perhaps omitting the word actual. Something like this:

Couldn't match type ‘Char’ with type ‘Bool’ expected from context. 

comment:16 Changed 3 months ago by goldfire

Once Phab:D808 gets merged (could be today! but more likely in 2-3), printing out the expression that has the bad type will be much easier, as I've restructured how that information sloshes around internally. So that will be a viable improvement very soon.

comment:17 Changed 2 months ago by Richard Eisenberg <eir@…>

In 67465497/ghc:

Add kind equalities to GHC.

This implements the ideas originally put forward in
"System FC with Explicit Kind Equality" (ICFP'13).

There are several noteworthy changes with this patch:
 * We now have casts in types. These change the kind
   of a type. See new constructor `CastTy`.

 * All types and all constructors can be promoted.
   This includes GADT constructors. GADT pattern matches
   take place in type family equations. In Core,
   types can now be applied to coercions via the
   `CoercionTy` constructor.

 * Coercions can now be heterogeneous, relating types
   of different kinds. A coercion proving `t1 :: k1 ~ t2 :: k2`
   proves both that `t1` and `t2` are the same and also that
   `k1` and `k2` are the same.

 * The `Coercion` type has been significantly enhanced.
   The documentation in `docs/core-spec/core-spec.pdf` reflects
   the new reality.

 * The type of `*` is now `*`. No more `BOX`.

 * Users can write explicit kind variables in their code,
   anywhere they can write type variables. For backward compatibility,
   automatic inference of kind-variable binding is still permitted.

 * The new extension `TypeInType` turns on the new user-facing
   features.

 * Type families and synonyms are now promoted to kinds. This causes
   trouble with parsing `*`, leading to the somewhat awkward new
   `HsAppsTy` constructor for `HsType`. This is dispatched with in
   the renamer, where the kind `*` can be told apart from a
   type-level multiplication operator. Without `-XTypeInType` the
   old behavior persists. With `-XTypeInType`, you need to import
   `Data.Kind` to get `*`, also known as `Type`.

 * The kind-checking algorithms in TcHsType have been significantly
   rewritten to allow for enhanced kinds.

 * The new features are still quite experimental and may be in flux.

 * TODO: Several open tickets: #11195, #11196, #11197, #11198, #11203.

 * TODO: Update user manual.

Tickets addressed: #9017, #9173, #7961, #10524, #8566, #11142.
Updates Haddock submodule.

comment:18 Changed 2 months ago by goldfire

The information needed in TcErrors to address this ticket is now very much to hand, in the uo_thing field of a TypeEqOrigin. There is some refactoring to do to improve the plumbing, but it would now be easy for someone to take an honest stab at this ticket.

comment:19 Changed 2 weeks ago by thomie

  • Type of failure changed from None/Unknown to Incorrect warning at compile-time
Note: See TracTickets for help on using tickets.