Skip to content

Properly treat cross-stage type variables in typed quotes

Matthew Pickering requested to merge mpickering/ghc:t15437-new into master

The crux of the problem is that we didn't consider cross-stage persistence for type variables. With the advent of type applications this has become more pertinent but has been a problem for a long time in untyped splices.

The original program presented in the ticket was

foo :: forall a . Q (TExp Int)
foo = [|| get @a ||]

This should be rejected immediately as a is bound at stage 0 and used at stage 1 which is a stage violation.

However, we can fix this by implicitly lifting type variables like we do for term variables.

To do this, we define a new built-in class called LiftT with a single method which lifts a type to its representation.

class LiftT (t :: k) where
   liftTyCl :: Q Type

Instances for this class are solved automatically by the compiler for all types.

Now if we use a cross-stage variable, like for terms, we have to add a class constraint which says we can represent it.

foo :: forall a . LiftT a => Q (TExp Int)
foo = [|| get @a ||]

This is then implicitly desugared into

foo :: forall a . Typeable a => Q (TExp Int)
foo = [|| get @$$(liftTyCl @a) ||]

where liftTyCl turns a type into a Q Type.

The knowledgeable reader will know that this problem has already been solved in the same manner for Cloud Haskell but obviously the code paths are completely different.

Edited by Matthew Pickering

Merge request reports