packages feed

overloaded-0.3.1: src/Overloaded/Plugin/HasConstructor.hs

{-# LANGUAGE CPP             #-}
{-# LANGUAGE RecordWildCards #-}
module Overloaded.Plugin.HasConstructor where

import Control.Monad (forM)
import Data.List     (find)
import Data.Maybe    (mapMaybe)

import qualified GHC.Compat.All  as GHC

#if MIN_VERSION_ghc(9,0,0)
import qualified GHC.Tc.Plugin as Plugins
#else
import qualified TcPluginM as Plugins
#endif

import Overloaded.Plugin.Diagnostics
import Overloaded.Plugin.TcPlugin.Ctx
import Overloaded.Plugin.TcPlugin.Utils
import Overloaded.Plugin.V

ifDebug :: Monad m => m () -> m ()
ifDebug _ = return ()

solveHasConstructor
    :: PluginCtx
    -> GHC.DynFlags
    -> (GHC.FamInstEnv, GHC.FamInstEnv)
    -> GHC.GlobalRdrEnv
    -> [GHC.Ct]
    -> Plugins.TcPluginM [(Maybe (GHC.EvTerm, [GHC.Ct]), GHC.Ct)]
solveHasConstructor PluginCtx {..} dflags famInstEnvs rdrEnv wanteds =
    forM wantedsHasPolyCon $ \(ct, tys@(V4 _k _name _s a)) -> do
        -- Plugins.tcPluginIO $ warn dflags noSrcSpan $
        --     GHC.text "HasConstructor wanted" GHC.<+> GHC.ppr ct

        m <- GHC.unsafeTcPluginTcM $ matchHasConstructor dflags famInstEnvs rdrEnv tys
        fmap (\evTerm -> (evTerm, ct)) $ forM m $ \(tc, dc, args, xs) -> do
            -- get location
            let ctloc = GHC.ctLoc ct
            let l = GHC.RealSrcSpan (GHC.ctLocSpan ctloc)
#if MIN_VERSION_ghc(9,0,0)
                        Nothing
#endif

            ifDebug $ Plugins.tcPluginIO $ warn dflags l $
                GHC.text "DEBUG1"
                    GHC.$$ GHC.ppr tc
                    GHC.$$ GHC.ppr dc
                    GHC.$$ GHC.ppr args
                    GHC.$$ GHC.ppr xs

            -- "s"
            let s' = GHC.mkTyConApp tc args

            -- type of constructor fields:
            -- - for unary constructors we use the field
            -- - for nullary we use unit
            -- - for others we wrap them in tuple.
            let a' :: GHC.Type
                a' = case xs of
                    [x] -> x
                    _   -> GHC.mkBoxedTupleTy xs
                    -- TODO: nullary
                    -- TODO: multiple

            -- let b' = a'
            --     t' = s'

            let tupleDataCon :: GHC.DataCon
                tupleDataCon = GHC.tupleDataCon GHC.Boxed (length xs)

            ifDebug $ Plugins.tcPluginIO $ warn dflags l $
                GHC.text "DEBUG2"
                    GHC.$$ GHC.ppr s'
                    GHC.$$ GHC.ppr a'

            -- build
            exprBuild <- case xs of
                -- unary: \a -> DC a
                [_] -> do
                    x <- makeVar "x" a'
                    return $ GHC.mkCoreLams [x] $ GHC.mkConApp2 dc args [x]

                -- nullary: \ (_unused :: ()) -> DC
                [] -> do
                    unused <- makeVar "_unused" a'
                    return $ GHC.mkCoreLams [unused] $ GHC.mkConApp2 dc args []

                -- multi: \ (a :: a) -> case a of
                --    (x1, ..., xn) -> DC x1 ... xn
                _ -> do
                    aBndr <- makeVar "a" a'
                    xs' <- makeVars "x" xs
                    return $ GHC.mkCoreLams [aBndr] $ GHC.Case (GHC.Var aBndr) aBndr s'
                        [( GHC.DataAlt tupleDataCon  -- (,,,)
                        , xs'                        -- x1 ... xn
                        , GHC.mkConApp2 dc args xs'  -- DC x1 ... xn
                        )]

            ifDebug $ Plugins.tcPluginIO $ warn dflags l $
                GHC.text "DEBUG-build"
                    GHC.$$ GHC.ppr exprBuild

            -- match
            exprMatch <- case xs of
                -- unary: \s -> case s of
                --            DC a -> Just a
                --            _    -> Nothing
                [_] -> do
                    sBndr <- makeVar "s" s'
                    aBndr <- makeVar "a" a'
                    return $ GHC.mkCoreLams [sBndr] $ GHC.Case (GHC.Var sBndr) sBndr
                        (GHC.mkTyConApp GHC.maybeTyCon [a'])
                        -- default case have to be first.
                        [ (GHC.DEFAULT, [], GHC.mkConApp2 GHC.nothingDataCon [a'] [])
                        , (GHC.DataAlt dc, [aBndr], GHC.mkConApp2 GHC.justDataCon [a'] [aBndr])
                        ]

                -- nullary: \s -> case s of
                --              DC -> Just ()
                --              _  -> Nothing
                [] -> do
                    sBndr <- makeVar "s" s'
                    return $ GHC.mkCoreLams [sBndr] $ GHC.Case (GHC.Var sBndr) sBndr
                        (GHC.mkTyConApp GHC.maybeTyCon [a'])
                        [ (GHC.DEFAULT, [], GHC.mkConApp2 GHC.nothingDataCon [a'] [])
                        , (GHC.DataAlt dc, [], GHC.mkConApp2 GHC.justDataCon [a'] [GHC.unitDataConId])
                        ]

                -- multi: \s -> case s of
                --            DC x1 ... xn -> let a = (x1, ... xn) in Just a
                --            _            -> Nothing
                _ -> do
                    sBndr <- makeVar "s" s'
                    aBndr <- makeVar "a" a'
                    xs' <- makeVars "x" xs

                    return $ GHC.mkCoreLams [sBndr] $ GHC.Case (GHC.Var sBndr) sBndr
                        (GHC.mkTyConApp GHC.maybeTyCon [a'])
                        [ (GHC.DEFAULT, [], GHC.mkConApp2 GHC.nothingDataCon [a'] [])
                        , (GHC.DataAlt dc, xs',
                          GHC.Let (GHC.NonRec aBndr $ GHC.mkConApp2 tupleDataCon xs xs') $
                          GHC.mkConApp2 GHC.justDataCon [a'] [aBndr])
                        ]

            ifDebug $ Plugins.tcPluginIO $ warn dflags l $
                GHC.text "DEBUG-match"
                    GHC.$$ GHC.ppr exprMatch

            -- wanteds
            let evterm = makeEvidence4_2 hasPolyConCls exprBuild exprMatch tys
            ctEvidence <- Plugins.newWanted ctloc $ GHC.mkPrimEqPred a a'

            return (evterm, [ GHC.mkNonCanonical ctEvidence -- a ~ a'
                            ])

  where
    wantedsHasPolyCon = mapMaybe (findClassConstraint4 hasPolyConCls) wanteds

matchHasConstructor
    :: GHC.DynFlags
    -> (GHC.FamInstEnv, GHC.FamInstEnv)
    -> GHC.GlobalRdrEnv
    -> V4 GHC.Type
    -> GHC.TcM (Maybe (GHC.TyCon, GHC.DataCon, [GHC.Type], [GHC.Type]))
matchHasConstructor _dflags famInstEnvs _rdrEnv (V4 _k x s _a)
    -- x should be a literal string
    | Just xStr <- GHC.isStrLitTy x
    -- s should be an applied type constructor
    , Just (tc, args) <- GHC.tcSplitTyConApp_maybe s
    -- use representation tycon (if data family); it has the fields
    , let s_tc = fstOf3 (GHC.tcLookupDataFamInst famInstEnvs tc args)
    -- x should be constructor of r
    , Just dcs <- GHC.tyConDataCons_maybe s_tc
    , Just dc  <- find (\dc -> GHC.getOccFS (GHC.dataConName dc) == xStr) dcs
    -- TODO: check that data con is in scope

    -- check that exist and theta are empty, this makes things simpler!
    , ([], [], xs) <- GHC.dataConInstSig dc args

    = return $ Just (tc, dc, args, xs)

matchHasConstructor _ _ _ _ = return Nothing