Skip to content

Must perform family consistency check on non-imported identifiers

Currently, the family consistency check checks pairs of *imported* modules (and the modules they transitively import) for consistency. However, there are a number of mechanisms by which we can refer to an identifier from a module without explicitly importing it. Here is one example from Template Haskell:

-- A.hs
{-# LANGUAGE TypeFamilies #-}
module A where
type family F a

-- B.hs
{-# LANGUAGE TypeFamilies #-}
module B where
import A
type instance F Bool = String
g :: F Bool
g = "af"

-- C.hs
{-# LANGUAGE TypeFamilies #-}
module C where
import A
type instance F Bool = Int
h :: F Bool -> IO ()
h = print

-- D.hs
{-# LANGUAGE TemplateHaskell #-}
import C
import Language.Haskell.TH.Syntax

main = h $( return (VarE (Name (OccName "g") (NameG VarName (PkgName "main") (ModName "B")))) )

This does an unsafe coerce:

ezyang@sabre:~/Dev/labs/T13102$ ghc-head --make B.hs D.hs -fforce-recomp
[1 of 4] Compiling A                ( A.hs, A.o )
[2 of 4] Compiling B                ( B.hs, B.o )
[3 of 4] Compiling C                ( C.hs, C.o )
[4 of 4] Compiling Main             ( D.hs, D.o )
Linking D ...
ezyang@sabre:~/Dev/labs/T13102$ ./D
8070450533355229282

Clearly, checking consistency on imports is not enough: we must also check up on original names that come by other mechanisms. (Other ways we can end up with identifiers without imports include overloading, see ticket:13102#comment:131619.

A few things to note about how to fix this:

  • Currently, type family instances are checked for consistency as we process imports. Template Haskell splices can occur much later in a Haskell file, so we must correspondingly do these checks later.
  • If we refer to an identifier by synthesizing a name manually, it is as if we imported it. This also means that a reference of this sort implies an implicit import of the defining module (#13102) and we should consider instances from it visible (at the moment, it's not considered visible.) (Actually, with TH, this is a bit tricky, because if we take these semantics, an instance might be visible below a top-level splice, but invisible above it.)
  • It is probably simplest if the type family compatibility check happens at the end. So we should go ahead and revive idea (2) from #11062 (closed)##13251 ; if there are overlapping families we should just not reduce the type family.
  • For wired in things, it's pretty easy to find out if we have an implicit import: if we bang on checkWiredInTyCon, that means we intended for the instance to visible; so we should collect all of the TyCons we banged on this way. For TH, this isn't exactly going to work, but maybe we can just track when NameGs get synthesized.
Edited by rwbarton
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information