[GHC] #11700: pattern match bug

#11700: pattern match bug -------------------------------------+------------------------------------- Reporter: TobyGoodwin | Owner: Type: bug | Status: new Priority: normal | Milestone: Component: Compiler | Version: 7.10.3 Keywords: | Operating System: Unknown/Multiple Architecture: | Type of failure: None/Unknown Unknown/Multiple | Test Case: | Blocked By: https://github.com/TobyGoodwin | /odd-ghc-pattern-bug | Blocking: | Related Tickets: Differential Rev(s): | Wiki Page: -------------------------------------+------------------------------------- With some types built by persistent-template, I'm running into very odd behaviour. Attempting to pull apart a tuple in one go... {{{#!hs let (Entity museKey muse, Entity msgKey msg, Entity fldrKey fldr) = cluster }}} fails (or rather, attempting to use msg or fldr fails) with errors like {{{ Couldn't match expected type ‘Folder’ with actual type ‘Folder’ }}} But splitting the pattern up: {{{#!hs let (a, b, c) = cluster Entity museKey muse = a Entity msgKey msg = b Entity fldrKey fldr = c }}} everything works normally Complete test case here: https://github.com/TobyGoodwin/odd-ghc-pattern- bug -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/11700 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#11700: pattern match bug -------------------------------------+------------------------------------- Reporter: TobyGoodwin | Owner: Type: bug | Status: new Priority: normal | Milestone: Component: Compiler | Version: 7.10.3 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: | https://github.com/TobyGoodwin/odd- | ghc-pattern-bug Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by osa1): Hm, this is a weird one. I still don't understand the typechecker so can't fix this myself, but I did some debugging. Here's a simpler function that fails: {{{#!haskell fn2 :: Key User -> (Entity Message, Entity Folder) -> IO () fn2 usr cluster = let (Entity msgKey msg, Entity fldrKey fldr) = cluster in do print $ messageName msg print $ folderName fldr }}} Interestingly, the error message is about {{{Folder}}}, but if I replace {{{Message}}} with an empty tuple. {{{#!haskell fn2 :: Key User -> ((), Entity Folder) -> IO () fn2 usr cluster = let ((), Entity fldrKey fldr) = cluster in do print $ folderName fldr }}} It works. I also tried swapping the tuple elements: {{{#!haskell fn2 :: Key User -> (Entity Folder, Entity Message) -> IO () fn2 usr cluster = let (Entity fldrKey fldr, Entity msgKey msg) = cluster in do print $ messageName msg print $ folderName fldr }}} and it failed with an error message about {{{Message}}}: {{{ Message.hs:45:25: error: • Couldn't match expected type ‘Message’ with actual type ‘Message’ • In the first argument of ‘messageName’, namely ‘msg’ In the second argument of ‘($)’, namely ‘messageName msg’ In a stmt of a 'do' block: print $ messageName msg }}} It seems like some kind of side-effect is happening when type checking the first pattern. Oh, I also tried replacing the {{{in}}} part with {{{return ()}}}, and it worked: {{{#!haskell fn2 :: Key User -> (Entity Folder, Entity Message) -> IO () fn2 usr cluster = let (Entity fldrKey fldr, Entity msgKey msg) = cluster in do return () }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/11700#comment:1 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#11700: pattern match bug -------------------------------------+------------------------------------- Reporter: TobyGoodwin | Owner: Type: bug | Status: new Priority: normal | Milestone: Component: Compiler | Version: 7.10.3 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: | https://github.com/TobyGoodwin/odd- | ghc-pattern-bug Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by simonpj): Can you explain how to reproduce, preferably without installing all of Yesod? (eg can you cut it down somehow?) The readme on the odd-ghc-pattern-bug github doesn't give repro instructions. Thanks Simon -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/11700#comment:2 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#11700: pattern match bug -------------------------------------+------------------------------------- Reporter: TobyGoodwin | Owner: Type: bug | Status: new Priority: normal | Milestone: Component: Compiler | Version: 7.10.3 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: | https://github.com/TobyGoodwin/odd- | ghc-pattern-bug Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by TobyGoodwin): Commands to run are in RUNME (I just updated README to make this clearer). I whittled it down as far as I could in the time I had: it's not quite ''all'' of yesod... just persistent :) My hunch is that TH is implicated. When I get the chance, I'll see if I can build up a test case from nothing that mimics the way persistent defines the types. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/11700#comment:3 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#11700: pattern match bug -------------------------------------+------------------------------------- Reporter: TobyGoodwin | Owner: Type: bug | Status: new Priority: normal | Milestone: Component: Compiler | Version: 7.10.3 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: | https://github.com/TobyGoodwin/odd- | ghc-pattern-bug Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by thomie): Here is a reproducer: {{{#!hs {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE TypeFamilies #-} -- Remove this line and the code compiles. module T11700 where data Muse data Message data Folder class PersistEntity record data Entity record = PersistEntity record => Entity record fn1 :: (Entity Muse, Entity Message, Entity Folder) -> (Message, Folder) fn1 cluster = let (Entity muse, Entity msg, Entity fldr) = cluster in (msg, fldr) }}} {{{ [1 of 1] Compiling T11700 ( T11700.hs, /tmp/T11700.o ) T11700.hs:17:12: error: • Couldn't match expected type ‘Folder’ with actual type ‘Folder’ • In the expression: fldr In the expression: (msg, fldr) In the expression: let (Entity muse, Entity msg, Entity fldr) = cluster in (msg, fldr) T11700.hs:17:7: error: • Couldn't match expected type ‘Message’ with actual type ‘Message’ • In the expression: msg In the expression: (msg, fldr) In the expression: let (Entity muse, Entity msg, Entity fldr) = cluster in (msg, fldr) }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/11700#comment:4 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#11700: pattern match bug -------------------------------------+------------------------------------- Reporter: TobyGoodwin | Owner: Type: bug | Status: new Priority: normal | Milestone: Component: Compiler | Version: 7.10.3 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: | https://github.com/TobyGoodwin/odd- | ghc-pattern-bug Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by simonpj): Thomie, thanks. But when you say "here is a reproducer" you are assuming that I've installed some package defining "Muse" etc. Which ones, precisely? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/11700#comment:5 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#11700: pattern match bug -------------------------------------+------------------------------------- Reporter: TobyGoodwin | Owner: Type: bug | Status: new Priority: normal | Milestone: Component: Compiler | Version: 7.10.3 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: | https://github.com/TobyGoodwin/odd- | ghc-pattern-bug Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by TobyGoodwin): Fantastic work thomie! Thanks so much for this. That appears to be standalone to me, no extra packages needed. Copy and paste that code into `T11700.hs`, run `ghc T11700.hs` and you get the bogus error. Works for me with ghc-7.10.3 and no `~/.ghc` or `~/.cabal`. And as thomie says, removing the `TypeFamilies` extension makes the problem go away. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/11700#comment:6 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#11700: pattern match bug -------------------------------------+------------------------------------- Reporter: TobyGoodwin | Owner: Type: bug | Status: new Priority: normal | Milestone: Component: Compiler (Type | Version: 7.10.3 checker) | Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Changes (by thomie): * testcase: https://github.com/TobyGoodwin/odd-ghc-pattern-bug => * component: Compiler => Compiler (Type checker) Comment: @simonpj: like TobyGoodwin said, the example from comment:4 doesn't have any dependencies. This bug goes back to at least ghc-7.8. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/11700#comment:7 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#11700: pattern match bug
-------------------------------------+-------------------------------------
Reporter: TobyGoodwin | Owner:
Type: bug | Status: new
Priority: normal | Milestone:
Component: Compiler (Type | Version: 7.10.3
checker) |
Resolution: | Keywords:
Operating System: Unknown/Multiple | Architecture:
| Unknown/Multiple
Type of failure: None/Unknown | Test Case:
Blocked By: | Blocking:
Related Tickets: | Differential Rev(s):
Wiki Page: |
-------------------------------------+-------------------------------------
Comment (by Simon Peyton Jones

#11700: pattern match bug -------------------------------------+------------------------------------- Reporter: TobyGoodwin | Owner: Type: bug | Status: closed Priority: normal | Milestone: Component: Compiler (Type | Version: 7.10.3 checker) | Resolution: fixed | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: | typecheck/should_compile/T11700, | ExPat, ExPatFail Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Changes (by simonpj): * testcase: => typecheck/should_compile/T11700, ExPat, ExPatFail * status: new => closed * resolution: => fixed Comment: Well that has been lurking a long time. It was caused by the fact that when the pattern binds dictionaries (which this one does), we make an implication constraint, and bump the `TcLevel`. Normally that doesn't happen because GHC complains about a pattern binding that binds existential variables. But this one doesn't! It only binds constraints! Anyway it demonstrated to me that the way we were dealing with existentials in pattern bindings was all wrong... better to ''use'' the machinery of implication constraints, rather than cut across it. Here's the new method (a Note in `TcBinds`). Much nicer! And it actually allows more programs too. {{{ {- Note [Existentials in pattern bindings] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Consider (typecheck/should_compile/ExPat): data T where MkT :: Integral a => a -> Int -> T and suppose t :: T. Which of these pattern bindings are ok? E1. let { MkT p _ = t } in <body> E2. let { MkT _ q = t } in <body> E3. let { MkT (toInteger -> r) _ = t } in <body> Well (E1) is clearly wrong becuase the existential 'a' escapes. What type could 'p' possibly have? But (E2) is fine, despite the existential pattern, because q::Int, and nothing escapes. Even (E3) is fine. The existential pattern binds a dictionary for (Integral a) which the view pattern can use to convert the a-valued field to an Integer, so r :: Integer. An easy way to see all three is to imagine the desugaring. For (2) it would look like let q = case t of MkT _ q' -> q' in <body> We typecheck pattern bindings as follows: 1. In tcLhs we bind q'::alpha, for each varibable q bound by the pattern, where q' is a fresh name, and alpha is a fresh unification variable; it will be the monomorphic verion of q that we later generalise It's very important that these fresh unification variables alpha are born here, not deep under implications as would happen if we allocated them when we encountered q during tcPat. 2. Still in tcLhs, we build a little environment mappting "q" -> q':alpha, and pass that to tcLetPet. 3. Then tcLhs invokes tcLetPat to typecheck the patter as usual: - When tcLetPat finds an existential constructor, it binds fresh type variables and dictionaries as usual, and emits an implication constraint. - When tcLetPat finds a variable (TcPat.tcPatBndr) it looks it up in the little environment, which should always succeed. And uses tcSubTypeET to connect the type of that variable with the expected type of the pattern. And that's it! The implication constraints check for the skolem escape. It's quite simple and neat, and more exressive than before e.g. GHC 8.0 rejects (E2) and (E3). }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/11700#comment:9 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler
participants (1)
-
GHC