Opened 13 months ago

Last modified 3 months ago

#8740 new bug

Deriving instance conditionally compiles

Reported by: thomaseding Owned by:
Priority: normal Milestone:
Component: Compiler (Type checker) Version: 7.6.3
Keywords: Cc:
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: GHC rejects valid program Test Case:
Blocked By: Blocking:
Related Tickets: #8128 Differential Revisions:

Description

{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}

data Abstract
data Reified
data Player

data Elect p a where
    ElectRefAsTypeOf :: Int -> Elect Abstract a -> Elect Abstract a
    ElectHandle :: a -> Elect Reified a
    Controller :: Elect Abstract Player
    Owner :: Elect Abstract Player
    You :: Elect Abstract Player

deriving instance (Eq a) => Eq (Elect p a)
deriving instance (Ord a) => Ord (Elect p a)

As is, the above code fails to compile. But if I move ElectRefAsTypeOf to be the last constructor for the GADT, the code does compile. If I remove one of the Elect Abstract Player constructors, the code still won't compile even if the ElectRefAsTypeOf is moved.

Change History (3)

comment:1 Changed 13 months ago by thomaseding

  • Summary changed from Code conditionally compiles to Deriving instance conditionally compiles

comment:2 Changed 3 months ago by thomie

  • Component changed from Compiler to Compiler (Type checker)
  • Operating System changed from MacOS X to Unknown/Multiple

comment:3 Changed 3 months ago by simonpj

I grant that this is extremely confusing. Here's a more concrete example:

{-# LANGUAGE MagicHash, GADTs #-}

module Foo1 where
import GHC.Types
import GHC.Prim

data Abstract
data Reified
data Player

data Elect p a where
     ElectHandle :: a -> Elect Reified a
     Controller :: Elect Abstract Player
     Owner :: Elect Abstract Player
     You :: Elect Abstract Player
     ElectRefAsTypeOf :: Int -> Elect Abstract a

con2TagE :: Elect p a -> Int
con2TagE (ElectHandle {}) = 1
con2TagE Controller = 2
con2TagE Owner = 3
con2TagE You = 4
con2TagE (ElectRefAsTypeOf {}) = 5

cmp :: Ord a => Elect p a -> Elect p a -> Ordering

-- Works; ElectRefAsTypeOf is the LAST constructor

cmp (ElectHandle a1) (ElectHandle b1) = a1 `compare` b1
cmp (ElectHandle a1) _                = LT

cmp (ElectRefAsTypeOf a1) (ElectRefAsTypeOf b1) = compare a1 b1
cmp (ElectRefAsTypeOf a1) _                     = GT

cmp a b = con2TagE a `compare` con2TagE b


{-
-- Fails; ElectRefAsTypeOf is the FIRST constructor
cmp1 (ElectRefAsTypeOf a1) (ElectRefAsTypeOf b1) = compare a1 b1
cmp1 (ElectRefAsTypeOf a1) _                     = LT

cmp1 (ElectHandle a) (ElectRefAsTypeOf b) = GT   -- This line fails
cmp1 (ElectHandle a) (ElectHandle b)      = a `compare` b

cmp1 a b = con2TagE a `compare` con2TagE b
-}

Here

  • The code for cmp is generated by deriving(Ord) when ElectRefAsTypeOf is the LAST constructor; i.e. as declared above
  • the code for cmp1 is generated by deriving(Ord) when ElectRefAsTypeOf is the FIRST constructor; i.e. in the data decl, move that constructor from last to first.

The code for each is not unreasonable.

If you compile these two definitions you'll see that cmp succeeds, but cmp1 fails, because the -- This line fails case is unreachable.

This is all very tiresome and confusing. The main possibility that occurs to me would be to switch off the "inaccessible code" error in derived code. We already do that for other errors. I'm sure it'd be do-able but it's not entirely straightforward because of course there are other type errors we'd like to catch.

Anyway I thought I would at least document what is going on here.

Simon

Last edited 3 months ago by simonpj (previous) (diff)
Note: See TracTickets for help on using tickets.