Commit 526c3af1 authored by Simon Marlow's avatar Simon Marlow

Use MD5 checksums for recompilation checking (fixes #1372, #1959)

This is a much more robust way to do recompilation checking.  The idea
is to create a fingerprint of the ABI of an interface, and track
dependencies by recording the fingerprints of ABIs that a module
depends on.  If any of those ABIs have changed, then we need to
recompile.

In bug #1372 we weren't recording dependencies on package modules,
this patch fixes that by recording fingerprints of package modules
that we depend on.  Within a package there is still fine-grained
recompilation avoidance as before.

We currently use MD5 for fingerprints, being a good compromise between
efficiency and security.  We're not worried about attackers, but we
are worried about accidental collisions.

All the MD5 sums do make interface files a bit bigger, but compile
times on the whole are about the same as before.  Recompilation
avoidance should be a bit more accurate than in 6.8.2 due to fixing
#1959, especially when using -O.
parent 842e9d66
...@@ -572,7 +572,7 @@ SRC_MKDEPENDC_OPTS += -I$(GHC_INCLUDE_DIR) ...@@ -572,7 +572,7 @@ SRC_MKDEPENDC_OPTS += -I$(GHC_INCLUDE_DIR)
SRC_HC_OPTS += \ SRC_HC_OPTS += \
-cpp -fglasgow-exts -fno-generics -Rghc-timing \ -cpp -fglasgow-exts -fno-generics -Rghc-timing \
-I. -Iparser -I. -Iparser -Iutil
# Omitted: -I$(GHC_INCLUDE_DIR) # Omitted: -I$(GHC_INCLUDE_DIR)
# We should have -I$(GHC_INCLUDE_DIR) in SRC_HC_OPTS, # We should have -I$(GHC_INCLUDE_DIR) in SRC_HC_OPTS,
......
...@@ -42,6 +42,7 @@ module Module ...@@ -42,6 +42,7 @@ module Module
modulePackageId, moduleName, modulePackageId, moduleName,
pprModule, pprModule,
mkModule, mkModule,
stableModuleCmp,
-- * The ModuleLocation type -- * The ModuleLocation type
ModLocation(..), ModLocation(..),
...@@ -71,6 +72,7 @@ import FiniteMap ...@@ -71,6 +72,7 @@ import FiniteMap
import LazyUniqFM import LazyUniqFM
import FastString import FastString
import Binary import Binary
import Util
import System.FilePath import System.FilePath
\end{code} \end{code}
...@@ -182,6 +184,7 @@ mkModuleNameFS s = ModuleName s ...@@ -182,6 +184,7 @@ mkModuleNameFS s = ModuleName s
moduleNameSlashes :: ModuleName -> String moduleNameSlashes :: ModuleName -> String
moduleNameSlashes = dots_to_slashes . moduleNameString moduleNameSlashes = dots_to_slashes . moduleNameString
where dots_to_slashes = map (\c -> if c == '.' then pathSeparator else c) where dots_to_slashes = map (\c -> if c == '.' then pathSeparator else c)
\end{code} \end{code}
%************************************************************************ %************************************************************************
...@@ -205,8 +208,13 @@ instance Binary Module where ...@@ -205,8 +208,13 @@ instance Binary Module where
put_ bh (Module p n) = put_ bh p >> put_ bh n put_ bh (Module p n) = put_ bh p >> put_ bh n
get bh = do p <- get bh; n <- get bh; return (Module p n) get bh = do p <- get bh; n <- get bh; return (Module p n)
instance Uniquable PackageId where -- This gives a stable ordering, as opposed to the Ord instance which
getUnique pid = getUnique (packageIdFS pid) -- gives an ordering based on the Uniques of the components, which may
-- not be stable from run to run of the compiler.
stableModuleCmp :: Module -> Module -> Ordering
stableModuleCmp (Module p1 n1) (Module p2 n2)
= (packageIdFS p1 `compare` packageIdFS p2) `thenCmp`
(moduleNameFS n1 `compare` moduleNameFS n2)
mkModule :: PackageId -> ModuleName -> Module mkModule :: PackageId -> ModuleName -> Module
mkModule = Module mkModule = Module
...@@ -235,9 +243,17 @@ pprPackagePrefix p mod = getPprStyle doc ...@@ -235,9 +243,17 @@ pprPackagePrefix p mod = getPprStyle doc
%************************************************************************ %************************************************************************
\begin{code} \begin{code}
newtype PackageId = PId FastString deriving( Eq, Ord ) -- includes the version newtype PackageId = PId FastString deriving( Eq ) -- includes the version
-- here to avoid module loops with PackageConfig -- here to avoid module loops with PackageConfig
instance Uniquable PackageId where
getUnique pid = getUnique (packageIdFS pid)
-- Note: *not* a stable lexicographic ordering, a faster unique-based
-- ordering.
instance Ord PackageId where
nm1 `compare` nm2 = getUnique nm1 `compare` getUnique nm2
instance Outputable PackageId where instance Outputable PackageId where
ppr pid = text (packageIdString pid) ppr pid = text (packageIdString pid)
......
...@@ -40,16 +40,13 @@ import {-# SOURCE #-} TypeRep( TyThing ) ...@@ -40,16 +40,13 @@ import {-# SOURCE #-} TypeRep( TyThing )
import OccName import OccName
import Module import Module
import SrcLoc import SrcLoc
import UniqFM
import Unique import Unique
import Maybes import Maybes
import Binary import Binary
import FastMutInt
import FastTypes import FastTypes
import FastString import FastString
import Outputable import Outputable
import Data.IORef
import Data.Array import Data.Array
\end{code} \end{code}
...@@ -309,20 +306,9 @@ instance NamedThing Name where ...@@ -309,20 +306,9 @@ instance NamedThing Name where
\begin{code} \begin{code}
instance Binary Name where instance Binary Name where
put_ bh name = do put_ bh name =
case getUserData bh of { case getUserData bh of
UserData { ud_symtab_map = symtab_map_ref, UserData{ ud_put_name = put_name } -> put_name bh name
ud_symtab_next = symtab_next } -> do
symtab_map <- readIORef symtab_map_ref
case lookupUFM symtab_map name of
Just (off,_) -> put_ bh off
Nothing -> do
off <- readFastMutInt symtab_next
writeFastMutInt symtab_next (off+1)
writeIORef symtab_map_ref
$! addToUFM symtab_map name (off,name)
put_ bh off
}
get bh = do get bh = do
i <- get bh i <- get bh
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
\begin{code} \begin{code}
module OccName ( module OccName (
mk_deriv,
-- * The NameSpace type; abstact -- * The NameSpace type; abstact
NameSpace, tcName, clsName, tcClsName, dataName, varName, NameSpace, tcName, clsName, tcClsName, dataName, varName,
tvName, srcDataName, tvName, srcDataName,
......
...@@ -363,6 +363,17 @@ lintCoreExpr e@(Case scrut var alt_ty alts) = ...@@ -363,6 +363,17 @@ lintCoreExpr e@(Case scrut var alt_ty alts) =
do { scrut_ty <- lintCoreExpr scrut do { scrut_ty <- lintCoreExpr scrut
; alt_ty <- lintTy alt_ty ; alt_ty <- lintTy alt_ty
; var_ty <- lintTy (idType var) ; var_ty <- lintTy (idType var)
; let mb_tc_app = splitTyConApp_maybe (idType var)
; case mb_tc_app of
Just (tycon, _)
| debugIsOn &&
isAlgTyCon tycon &&
null (tyConDataCons tycon) ->
pprTrace "case binder's type has no constructors" (ppr e)
$ return ()
_otherwise -> return ()
-- Don't use lintIdBndr on var, because unboxed tuple is legitimate -- Don't use lintIdBndr on var, because unboxed tuple is legitimate
; subst <- getTvSubst ; subst <- getTvSubst
......
...@@ -1635,7 +1635,8 @@ showPackages = do ...@@ -1635,7 +1635,8 @@ showPackages = do
pkg_ids <- fmap (preloadPackages . pkgState) getDynFlags pkg_ids <- fmap (preloadPackages . pkgState) getDynFlags
io $ putStrLn $ showSDoc $ vcat $ io $ putStrLn $ showSDoc $ vcat $
text "packages currently loaded:" text "packages currently loaded:"
: map (nest 2 . text . packageIdString) (sort pkg_ids) : map (nest 2 . text . packageIdString)
(sortBy (compare `on` packageIdFS) pkg_ids)
where showFlag (ExposePackage p) = text $ " -package " ++ p where showFlag (ExposePackage p) = text $ " -package " ++ p
showFlag (HidePackage p) = text $ " -hide-package " ++ p showFlag (HidePackage p) = text $ " -hide-package " ++ p
showFlag (IgnorePackage p) = text $ " -ignore-package " ++ p showFlag (IgnorePackage p) = text $ " -ignore-package " ++ p
......
...@@ -32,7 +32,9 @@ import SrcLoc ...@@ -32,7 +32,9 @@ import SrcLoc
import ErrUtils import ErrUtils
import Config import Config
import FastMutInt import FastMutInt
import Unique
import Outputable import Outputable
import FastString
import Data.List import Data.List
import Data.Word import Data.Word
...@@ -149,7 +151,19 @@ writeBinIface dflags hi_path mod_iface = do ...@@ -149,7 +151,19 @@ writeBinIface dflags hi_path mod_iface = do
put_ bh symtab_p_p put_ bh symtab_p_p
-- Make some intial state -- Make some intial state
ud <- newWriteState symtab_next <- newFastMutInt
writeFastMutInt symtab_next 0
symtab_map <- newIORef emptyUFM
let bin_symtab = BinSymbolTable {
bin_symtab_next = symtab_next,
bin_symtab_map = symtab_map }
dict_next_ref <- newFastMutInt
writeFastMutInt dict_next_ref 0
dict_map_ref <- newIORef emptyUFM
let bin_dict = BinDictionary {
bin_dict_next = dict_next_ref,
bin_dict_map = dict_map_ref }
ud <- newWriteState (putName bin_symtab) (putFastString bin_dict)
-- Put the main thing, -- Put the main thing,
bh <- return $ setUserData bh ud bh <- return $ setUserData bh ud
...@@ -161,8 +175,8 @@ writeBinIface dflags hi_path mod_iface = do ...@@ -161,8 +175,8 @@ writeBinIface dflags hi_path mod_iface = do
seekBin bh symtab_p -- Seek back to the end of the file seekBin bh symtab_p -- Seek back to the end of the file
-- Write the symbol table itself -- Write the symbol table itself
symtab_next <- readFastMutInt (ud_symtab_next ud) symtab_next <- readFastMutInt symtab_next
symtab_map <- readIORef (ud_symtab_map ud) symtab_map <- readIORef symtab_map
putSymbolTable bh symtab_next symtab_map putSymbolTable bh symtab_next symtab_map
debugTraceMsg dflags 3 (text "writeBinIface:" <+> int symtab_next debugTraceMsg dflags 3 (text "writeBinIface:" <+> int symtab_next
<+> text "Names") <+> text "Names")
...@@ -176,8 +190,8 @@ writeBinIface dflags hi_path mod_iface = do ...@@ -176,8 +190,8 @@ writeBinIface dflags hi_path mod_iface = do
seekBin bh dict_p -- Seek back to the end of the file seekBin bh dict_p -- Seek back to the end of the file
-- Write the dictionary itself -- Write the dictionary itself
dict_next <- readFastMutInt (ud_dict_next ud) dict_next <- readFastMutInt dict_next_ref
dict_map <- readIORef (ud_dict_map ud) dict_map <- readIORef dict_map_ref
putDictionary bh dict_next dict_map putDictionary bh dict_next dict_map
debugTraceMsg dflags 3 (text "writeBinIface:" <+> int dict_next debugTraceMsg dflags 3 (text "writeBinIface:" <+> int dict_next
<+> text "dict entries") <+> text "dict entries")
...@@ -248,6 +262,51 @@ serialiseName bh name _ = do ...@@ -248,6 +262,51 @@ serialiseName bh name _ = do
let mod = nameModule name let mod = nameModule name
put_ bh (modulePackageId mod, moduleName mod, nameOccName name) put_ bh (modulePackageId mod, moduleName mod, nameOccName name)
putName :: BinSymbolTable -> BinHandle -> Name -> IO ()
putName BinSymbolTable{
bin_symtab_map = symtab_map_ref,
bin_symtab_next = symtab_next } bh name
= do
symtab_map <- readIORef symtab_map_ref
case lookupUFM symtab_map name of
Just (off,_) -> put_ bh off
Nothing -> do
off <- readFastMutInt symtab_next
writeFastMutInt symtab_next (off+1)
writeIORef symtab_map_ref
$! addToUFM symtab_map name (off,name)
put_ bh off
data BinSymbolTable = BinSymbolTable {
bin_symtab_next :: !FastMutInt, -- The next index to use
bin_symtab_map :: !(IORef (UniqFM (Int,Name)))
-- indexed by Name
}
putFastString :: BinDictionary -> BinHandle -> FastString -> IO ()
putFastString BinDictionary { bin_dict_next = j_r,
bin_dict_map = out_r} bh f
= do
out <- readIORef out_r
let uniq = getUnique f
case lookupUFM out uniq of
Just (j, _) -> put_ bh j
Nothing -> do
j <- readFastMutInt j_r
put_ bh j
writeFastMutInt j_r (j + 1)
writeIORef out_r $! addToUFM out uniq (j, f)
data BinDictionary = BinDictionary {
bin_dict_next :: !FastMutInt, -- The next index to use
bin_dict_map :: !(IORef (UniqFM (Int,FastString)))
-- indexed by FastString
}
-- ----------------------------------------------------------------------------- -- -----------------------------------------------------------------------------
-- All the binary instances -- All the binary instances
...@@ -300,70 +359,74 @@ instance Binary ModIface where ...@@ -300,70 +359,74 @@ instance Binary ModIface where
put_ bh (ModIface { put_ bh (ModIface {
mi_module = mod, mi_module = mod,
mi_boot = is_boot, mi_boot = is_boot,
mi_mod_vers = mod_vers, mi_iface_hash= iface_hash,
mi_mod_hash = mod_hash,
mi_orphan = orphan, mi_orphan = orphan,
mi_finsts = hasFamInsts, mi_finsts = hasFamInsts,
mi_deps = deps, mi_deps = deps,
mi_usages = usages, mi_usages = usages,
mi_exports = exports, mi_exports = exports,
mi_exp_vers = exp_vers, mi_exp_hash = exp_hash,
mi_fixities = fixities, mi_fixities = fixities,
mi_deprecs = deprecs, mi_deprecs = deprecs,
mi_decls = decls, mi_decls = decls,
mi_insts = insts, mi_insts = insts,
mi_fam_insts = fam_insts, mi_fam_insts = fam_insts,
mi_rules = rules, mi_rules = rules,
mi_rule_vers = rule_vers, mi_orphan_hash = orphan_hash,
mi_vect_info = vect_info, mi_vect_info = vect_info,
mi_hpc = hpc_info }) = do mi_hpc = hpc_info }) = do
put_ bh mod put_ bh mod
put_ bh is_boot put_ bh is_boot
put_ bh mod_vers put_ bh iface_hash
put_ bh mod_hash
put_ bh orphan put_ bh orphan
put_ bh hasFamInsts put_ bh hasFamInsts
lazyPut bh deps lazyPut bh deps
lazyPut bh usages lazyPut bh usages
put_ bh exports put_ bh exports
put_ bh exp_vers put_ bh exp_hash
put_ bh fixities put_ bh fixities
lazyPut bh deprecs lazyPut bh deprecs
put_ bh decls put_ bh decls
put_ bh insts put_ bh insts
put_ bh fam_insts put_ bh fam_insts
lazyPut bh rules lazyPut bh rules
put_ bh rule_vers put_ bh orphan_hash
put_ bh vect_info put_ bh vect_info
put_ bh hpc_info put_ bh hpc_info
get bh = do get bh = do
mod_name <- get bh mod_name <- get bh
is_boot <- get bh is_boot <- get bh
mod_vers <- get bh iface_hash <- get bh
mod_hash <- get bh
orphan <- get bh orphan <- get bh
hasFamInsts <- get bh hasFamInsts <- get bh
deps <- lazyGet bh deps <- lazyGet bh
usages <- {-# SCC "bin_usages" #-} lazyGet bh usages <- {-# SCC "bin_usages" #-} lazyGet bh
exports <- {-# SCC "bin_exports" #-} get bh exports <- {-# SCC "bin_exports" #-} get bh
exp_vers <- get bh exp_hash <- get bh
fixities <- {-# SCC "bin_fixities" #-} get bh fixities <- {-# SCC "bin_fixities" #-} get bh
deprecs <- {-# SCC "bin_deprecs" #-} lazyGet bh deprecs <- {-# SCC "bin_deprecs" #-} lazyGet bh
decls <- {-# SCC "bin_tycldecls" #-} get bh decls <- {-# SCC "bin_tycldecls" #-} get bh
insts <- {-# SCC "bin_insts" #-} get bh insts <- {-# SCC "bin_insts" #-} get bh
fam_insts <- {-# SCC "bin_fam_insts" #-} get bh fam_insts <- {-# SCC "bin_fam_insts" #-} get bh
rules <- {-# SCC "bin_rules" #-} lazyGet bh rules <- {-# SCC "bin_rules" #-} lazyGet bh
rule_vers <- get bh orphan_hash <- get bh
vect_info <- get bh vect_info <- get bh
hpc_info <- get bh hpc_info <- get bh
return (ModIface { return (ModIface {
mi_module = mod_name, mi_module = mod_name,
mi_boot = is_boot, mi_boot = is_boot,
mi_mod_vers = mod_vers, mi_iface_hash = iface_hash,
mi_mod_hash = mod_hash,
mi_orphan = orphan, mi_orphan = orphan,
mi_finsts = hasFamInsts, mi_finsts = hasFamInsts,
mi_deps = deps, mi_deps = deps,
mi_usages = usages, mi_usages = usages,
mi_exports = exports, mi_exports = exports,
mi_exp_vers = exp_vers, mi_exp_hash = exp_hash,
mi_fixities = fixities, mi_fixities = fixities,
mi_deprecs = deprecs, mi_deprecs = deprecs,
mi_decls = decls, mi_decls = decls,
...@@ -371,13 +434,13 @@ instance Binary ModIface where ...@@ -371,13 +434,13 @@ instance Binary ModIface where
mi_insts = insts, mi_insts = insts,
mi_fam_insts = fam_insts, mi_fam_insts = fam_insts,
mi_rules = rules, mi_rules = rules,
mi_rule_vers = rule_vers, mi_orphan_hash = orphan_hash,
mi_vect_info = vect_info, mi_vect_info = vect_info,
mi_hpc = hpc_info, mi_hpc = hpc_info,
-- And build the cached values -- And build the cached values
mi_dep_fn = mkIfaceDepCache deprecs, mi_dep_fn = mkIfaceDepCache deprecs,
mi_fix_fn = mkIfaceFixCache fixities, mi_fix_fn = mkIfaceFixCache fixities,
mi_ver_fn = mkIfaceVerCache decls }) mi_hash_fn = mkIfaceHashCache decls })
getWayDescr :: IO String getWayDescr :: IO String
getWayDescr = do getWayDescr = do
...@@ -421,22 +484,31 @@ instance (Binary name) => Binary (GenAvailInfo name) where ...@@ -421,22 +484,31 @@ instance (Binary name) => Binary (GenAvailInfo name) where
return (AvailTC ab ac) return (AvailTC ab ac)
instance Binary Usage where instance Binary Usage where
put_ bh usg = do put_ bh usg@UsagePackageModule{} = do
put_ bh (usg_name usg) putByte bh 0
put_ bh (usg_mod usg) put_ bh (usg_mod usg)
put_ bh (usg_mod_hash usg)
put_ bh usg@UsageHomeModule{} = do
putByte bh 1
put_ bh (usg_mod_name usg)
put_ bh (usg_mod_hash usg)
put_ bh (usg_exports usg) put_ bh (usg_exports usg)
put_ bh (usg_entities usg) put_ bh (usg_entities usg)
put_ bh (usg_rules usg)
get bh = do get bh = do
nm <- get bh h <- getByte bh
mod <- get bh case h of
exps <- get bh 0 -> do
ents <- get bh nm <- get bh
rules <- get bh mod <- get bh
return (Usage { usg_name = nm, usg_mod = mod, return UsagePackageModule { usg_mod = nm, usg_mod_hash = mod }
usg_exports = exps, usg_entities = ents, _ -> do
usg_rules = rules }) nm <- get bh
mod <- get bh
exps <- get bh
ents <- get bh
return UsageHomeModule { usg_mod_name = nm, usg_mod_hash = mod,
usg_exports = exps, usg_entities = ents }
instance Binary Deprecations where instance Binary Deprecations where
put_ bh NoDeprecs = putByte bh 0 put_ bh NoDeprecs = putByte bh 0
......
This diff is collapsed.
...@@ -51,6 +51,7 @@ import BinIface ...@@ -51,6 +51,7 @@ import BinIface
import Panic import Panic
import Util import Util
import FastString import FastString
import Fingerprint
import Control.Monad import Control.Monad
import Data.List import Data.List
...@@ -323,7 +324,7 @@ addDeclsToPTE :: PackageTypeEnv -> [(Name,TyThing)] -> PackageTypeEnv ...@@ -323,7 +324,7 @@ addDeclsToPTE :: PackageTypeEnv -> [(Name,TyThing)] -> PackageTypeEnv
addDeclsToPTE pte things = extendNameEnvList pte things addDeclsToPTE pte things = extendNameEnvList pte things
loadDecls :: Bool loadDecls :: Bool
-> [(Version, IfaceDecl)] -> [(Fingerprint, IfaceDecl)]
-> IfL [(Name,TyThing)] -> IfL [(Name,TyThing)]
loadDecls ignore_prags ver_decls loadDecls ignore_prags ver_decls
= do { mod <- getIfModule = do { mod <- getIfModule
...@@ -333,7 +334,7 @@ loadDecls ignore_prags ver_decls ...@@ -333,7 +334,7 @@ loadDecls ignore_prags ver_decls
loadDecl :: Bool -- Don't load pragmas into the decl pool loadDecl :: Bool -- Don't load pragmas into the decl pool
-> Module -> Module
-> (Version, IfaceDecl) -> (Fingerprint, IfaceDecl)
-> IfL [(Name,TyThing)] -- The list can be poked eagerly, but the -> IfL [(Name,TyThing)] -- The list can be poked eagerly, but the
-- TyThings are forkM'd thunks -- TyThings are forkM'd thunks
loadDecl ignore_prags mod (_version, decl) loadDecl ignore_prags mod (_version, decl)
...@@ -616,13 +617,16 @@ pprModIface :: ModIface -> SDoc ...@@ -616,13 +617,16 @@ pprModIface :: ModIface -> SDoc
-- Show a ModIface -- Show a ModIface
pprModIface iface pprModIface iface
= vcat [ ptext (sLit "interface") = vcat [ ptext (sLit "interface")
<+> ppr (mi_module iface) <+> pp_boot <+> ppr (mi_module iface) <+> pp_boot
<+> ppr (mi_mod_vers iface) <+> pp_sub_vers
<+> (if mi_orphan iface then ptext (sLit "[orphan module]") else empty) <+> (if mi_orphan iface then ptext (sLit "[orphan module]") else empty)
<+> (if mi_finsts iface then ptext (sLit "[family instance module]") else empty) <+> (if mi_finsts iface then ptext (sLit "[family instance module]") else empty)
<+> (if mi_hpc iface then ptext (sLit "[hpc]") else empty) <+> (if mi_hpc iface then ptext (sLit "[hpc]") else empty)
<+> integer opt_HiVersion <+> integer opt_HiVersion
<+> ptext (sLit "where") , nest 2 (text "interface hash:" <+> ppr (mi_iface_hash iface))
, nest 2 (text "ABI hash:" <+> ppr (mi_mod_hash iface))
, nest 2 (text "export-list hash:" <+> ppr (mi_exp_hash iface))
, nest 2 (text "orphan hash:" <+> ppr (mi_orphan_hash iface))
, nest 2 (ptext (sLit "where"))
, vcat (map pprExport (mi_exports iface)) , vcat (map pprExport (mi_exports iface))
, pprDeps (mi_deps iface) , pprDeps (mi_deps iface)
, vcat (map pprUsage (mi_usages iface)) , vcat (map pprUsage (mi_usages iface))
...@@ -637,12 +641,6 @@ pprModIface iface ...@@ -637,12 +641,6 @@ pprModIface iface
where where
pp_boot | mi_boot iface = ptext (sLit "[boot]") pp_boot | mi_boot iface = ptext (sLit "[boot]")
| otherwise = empty | otherwise = empty
exp_vers = mi_exp_vers iface
rule_vers = mi_rule_vers iface
pp_sub_vers | exp_vers == initialVersion && rule_vers == initialVersion = empty
| otherwise = brackets (ppr exp_vers <+> ppr rule_vers)
\end{code} \end{code}