Refactor Hooks and Plugins
Several parts of the compiler support hooks, either via Hooks per-se or via Plugins (which provide stackable hooks).
Hooks and Plugin types are defined as:
data Hooks = Hooks
{ dsForeignsHook :: Maybe DsForeignsHook
-- ^ Actual type:
-- @Maybe ([LForeignDecl GhcTc] -> DsM (ForeignStubs, OrdList (Id, CoreExpr)))@
, tcForeignImportsHook :: Maybe ([LForeignDecl GhcRn]
-> TcM ([Id], [LForeignDecl GhcTc], Bag GlobalRdrElt))
, tcForeignExportsHook :: Maybe ([LForeignDecl GhcRn]
-> TcM (LHsBinds GhcTc, [LForeignDecl GhcTc], Bag GlobalRdrElt))
, hscFrontendHook :: Maybe (ModSummary -> Hsc FrontendResult)
, hscCompileCoreExprHook ::
Maybe (HscEnv -> SrcSpan -> CoreExpr -> IO ForeignHValue)
, ghcPrimIfaceHook :: Maybe ModIface
, runPhaseHook :: Maybe (PhasePlus -> FilePath -> DynFlags
-> CompPipeline (PhasePlus, FilePath))
, runMetaHook :: Maybe (MetaHook TcM)
, linkHook :: Maybe (GhcLink -> DynFlags -> Bool
-> HomePackageTable -> IO SuccessFlag)
, runRnSpliceHook :: Maybe (HsSplice GhcRn -> RnM (HsSplice GhcRn))
, getValueSafelyHook :: Maybe (HscEnv -> Name -> Type
-> IO (Maybe HValue))
, createIservProcessHook :: Maybe (CreateProcess -> IO ProcessHandle)
, stgToCmmHook :: Maybe (DynFlags -> Module -> [TyCon] -> CollectedCCs
-> [CgStgTopBinding] -> HpcInfo -> Stream IO CmmGroup ModuleLFInfos)
, cmmToRawCmmHook :: forall a . Maybe (DynFlags -> Maybe Module -> Stream IO CmmGroupSRTs a
-> IO (Stream IO RawCmmGroup a))
}
data Plugin = Plugin {
installCoreToDos :: CorePlugin
, tcPlugin :: TcPlugin
, holeFitPlugin :: HoleFitPlugin
, dynflagsPlugin :: [CommandLineOption] -> DynFlags -> IO DynFlags
, pluginRecompile :: [CommandLineOption] -> IO PluginRecompile
, parsedResultAction :: [CommandLineOption] -> ModSummary -> HsParsedModule -> Hsc HsParsedModule
, renamedResultAction :: [CommandLineOption] -> TcGblEnv -> HsGroup GhcRn -> TcM (TcGblEnv, HsGroup GhcRn)
, typeCheckResultAction :: [CommandLineOption] -> ModSummary -> TcGblEnv -> TcM TcGblEnv
, spliceRunAction :: [CommandLineOption] -> LHsExpr GhcTc -> TcM (LHsExpr GhcTc)
, interfaceLoadAction :: forall lcl . [CommandLineOption] -> ModIface -> IfM lcl ModIface
}
These definitions make the code not very modular because any user of these types depends on modules defining every type mentioned in the signatures of the fields.
Solution 0: accept the dependencies and use .hs-boot files to break import cycles
- Pros:
- Simpler to implement (nothing to do)
- Cons:
- Usual issues with import cycles and .hs-boot files
- Make the module graph more complex than strictly required. In particular we can't enforce invariants such as "the type-checker mustn't depend in any way on the backend and vice-versa" because any module using DynFlags/HscEnv depends on Plugin/Hooks which depend on TC stuff and backend stuff.
Solution 1: keep the types of the hooks abstract using type families
For each hook, a type family is defined type family MyHook :: Type
. Then an instance is defined in another module (e.g., in the module using the hook).
- Pros:
- only codes interested in using a hook have to import the module with the type instance.
- Cons:
- the list of hooks is fixed and can't be extended via ghc-api/plugins
DsForeignsHook
already uses this method to avoid making the parser depend on the desugarer (cf CountParserDeps test). !4047 (closed) generalizes this approach to all hooks.
Solution 2: store hooks as Dynamic values.
- Pros:
- only codes interested in using a hook have to import the module defining the hook type
- the list of hooks can be extended by plugins/ghc-api
- Cons:
- We rely on introspection to extract hooks
!4363 (closed) implements this (only for hooks for now)