packages feed

codet-plugin-0.1: src/Language/Haskell/TH/CodeT/Plugin.hs

{-# LANGUAGE CPP             #-}
{-# LANGUAGE RecordWildCards #-}
module Language.Haskell.TH.CodeT.Plugin (
    plugin,
) where

import Control.Monad (forM)
import Data.Maybe    (catMaybes)
import Data.String   (fromString)

import qualified GHC.Plugins as Plugins

import Plugin.GHC

-- | A GHC type-checker plugin which solves 'Language.Haskell.TH.CodeT.LiftT' instances.
--
-- At the moment plugin solves only type constructor instances,
-- e.g. for a data type
--
-- @
-- data Foo a = MkFoo a
-- @
--
-- the plugin will solve needed instances in @'Language.Hsakell.TH.CodeT.codeT' \@Foo@ and @'Language.Hsakell.TH.CodeT.codeT' \@('MkFoo)@.
-- (There is @(LiftT f, LiftT x) => LiftT (f x)@ existing instance, so plugin doesn't need to).
--
-- Noteably, the plugin solves only for algebraic type constructors (@data@, @newtype@, @class@) and
-- promoted data constructors. Specifically it doesn't solve for type-family type constructors.
--
-- Enable plugin with:
--
-- @
-- \{-# OPTIONS_GHC -fplugin=Language.Haskell.TH.CodeT.Plugin #-\}
-- @
--
plugin :: Plugins.Plugin
plugin = Plugins.defaultPlugin
    { Plugins.tcPlugin = Just . tcPlugin
    }

tcPlugin :: a -> TcPlugin
tcPlugin _ = TcPlugin
    { tcPluginInit    = tcPluginInit_
    , tcPluginSolve   = tcPluginSolve_
    , tcPluginStop    = const (return ())
#if __GLASGOW_HASKELL__ >=904
    , tcPluginRewrite = \_ -> Plugins.emptyUFM
#endif
    }

data PluginCtx = PluginCtx
    { liftTClass        :: Class
    , unsafeCodeTNameD  :: Id
    , unsafeCodeTNameTC :: Id
    }

tcPluginInit_ :: TcPluginM PluginCtx
tcPluginInit_ = do
    let codet :: FastString
        codet = fromString "codet"

    let codeTModuleName :: ModuleName
        codeTModuleName =  mkModuleName "Language.Haskell.TH.CodeT.Unsafe"

    liftTClass <- do
        md <- findModulePluginM codeTModuleName codet
        tcLookupClass =<< lookupOrig md (mkTcOcc "LiftT")

    unsafeCodeTNameD <- do
        md <- findModulePluginM codeTModuleName codet
        lookupOrig md (mkVarOcc "unsafeCodeTNameD")  >>= tcLookupId

    unsafeCodeTNameTC <- do
        md <- findModulePluginM codeTModuleName codet
        lookupOrig md (mkVarOcc "unsafeCodeTNameTC")  >>= tcLookupId

    return PluginCtx {..}

tcPluginSolve_ :: PluginCtx -> TcPluginSolver
tcPluginSolve_ ctx _evBindsVar _given wanteds = do
    solved' <- unsafeTcPluginTcM $ forM wanteds $ \wanted -> solveLiftT ctx wanted

    let solved :: [(EvTerm, Ct)]
        solved = catMaybes solved'

    let new :: [Ct]
        new = []
    return $ TcPluginOk solved new

solveLiftT :: PluginCtx -> Ct -> TcM (Maybe (EvTerm, Ct))
solveLiftT ctx wanted
    | Just (ct, [k, x]) <- findClassConstraint (liftTClass ctx) wanted
    , Just (xTyCon, _args) <- splitTyConApp_maybe x
    , isAlgTyCon xTyCon || isPromotedDataCon xTyCon
    -- , let ki = tyConKind xTyCon
    -- in 9.0 splitPiTysInvisible
    -- , (_invPis, _) <- splitInvisPiTys ki
    , let xTyConName = getName xTyCon
    , Just tcMod <- nameModule_maybe xTyConName
    -- TODO: check that 'args' count matches 'invPis'?

    = do
        let occ = nameOccName xTyConName

        -- tcPluginIO $ logOutput logger $ text "wanted:" <+> ppr x
        let pkg_str    = unitString (moduleUnit tcMod)
            mod_str    = moduleNameString (moduleName tcMod)
            occ_str    = occNameString occ

        pkg_str' <- mkStringExpr pkg_str
        mod_str' <- mkStringExpr mod_str
        occ_str' <- mkStringExpr occ_str

        let fun | isDataOcc occ = unsafeCodeTNameD ctx
                | isTcOcc occ   = unsafeCodeTNameTC ctx
                | otherwise     = Plugins.pprPanic "solveLiftT" (ppr xTyConName)

        let ev = mkCoreApps (Var fun) [Type k, Type x, pkg_str', mod_str', occ_str']
        let evidence = makeClassEvidence (liftTClass ctx) [k, x] ev

        return (Just (evidence, ct))

    | otherwise
    = return Nothing