goldfirere / th-desugar

Desugars Template Haskell abstract syntax to a simpler format without changing semantics
BSD 3-Clause "New" or "Revised" License
20 stars 13 forks source link

Desugared data constructor has incorrect type variable specificity #199

Closed RyanGlScott closed 4 months ago

RyanGlScott commented 1 year ago

In this example:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TemplateHaskell #-}
module Bug where

import Data.Kind (Type)
import Language.Haskell.TH hiding (Type)
import Language.Haskell.TH.Desugar

type P :: forall {k}. k -> Type
data P a = MkP

$(pure [])

$(do TyConI dec <- reify ''P
     [ddec] <- dsDec dec
     runIO $ putStrLn $ pprint $ decToTH ddec
     pure [])

The type of MkP, as reported by GHCi's :type command, is:

λ> :type MkP
MkP :: forall {k} (a :: k). P a

But this is not the same type that is given to MkP after round-tripping through th-desugar:

λ> :load Bug.hs
[1 of 1] Compiling Bug              ( Bug.hs, interpreted )
data Bug.P (a_0 :: k_1) where
    Bug.MkP :: forall k_1 (a_0 :: k_1) . Bug.P (a_0 :: k_1)

Here, k is specified rather than inferred! What went wrong?

In some sense, the problem lies with reify. If you replace decToTH ddec with dec above, then it reveals what reify ''P returns:

λ> :load Bug.hs
[1 of 1] Compiling Bug              ( Bug.hs, interpreted )
data Bug.P (a_0 :: k_1) = Bug.MkP
Ok, one module loaded.

If this is to be believed, then P was declared as data P (a :: k) = MkP, which would suggest that k is indeed specified. We know this not to be the case, of course. And indeed, this reify quirk is a deliberate design choice: see the discussion at GHC#22828 for more details.

One way to solve this problem would be to call reifyType 'MkP, which tells us:

forall {k_0 :: *} (a_1 :: k_0) . Bug.P a_1

This preserves the fact that k is inferred. We could use the output of reifyType as a guide when determining whether type variables in a Haskell98-style data constructor are specified or inferred.

RyanGlScott commented 6 months ago

A similar bug exists when reifying a locally defined data constructor:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TemplateHaskell #-}
module Bug where

import Data.Kind (Type)
import Language.Haskell.TH hiding (Type)
import Language.Haskell.TH.Desugar

$(do decs <- [d| type P :: forall {k}. k -> Type
                 data P (a :: k) = MkP |]
     DataConI _ pTy _ <- withLocalDeclarations decs $ reifyWithLocals $ mkName "MkP"
     runIO $ putStrLn $ pprint pTy
     pure [])
[1 of 1] Compiling Bug              ( Bug.hs, interpreted )
forall k_0 (a_1 :: k_0) . P_2 a_1

Again, k_0 should be inferred, not specified.

See also #220, which is in similar territory.