packages feed

intensional-datatys-0.2.0.0: src/Intensional/InferM.hs

{-# LANGUAGE FlexibleInstances #-}

module Intensional.InferM
  ( InferM,
    Context,
    InferEnv (..),
    Stats (..),
    runInferM,
    Intensional.InferM.saturate,
    branchAny,
    emitDD,
    emitDK,
    emitKD,
    fresh,
    putVar,
    putVars,
    setLoc,
    getExternalName,
    isTrivial,
    isIneligible,
    noteD,
    noteK,
    incrN,
    noteErrs,
    Intensional.InferM.cexs
  )
where

import Intensional.Constraints as Constraints
import Intensional.Constructors
import Control.Monad.RWS.Strict hiding (guard)
import qualified Data.IntSet as IntSet
import qualified Data.Map as M
import GhcPlugins hiding ((<>), singleton)
import Intensional.Scheme
import Intensional.Types
import Intensional.Ubiq
import Intensional.Guard

type InferM = RWS InferEnv ConstraintSet InferState

type Context = M.Map Name Scheme

data InferEnv
  = InferEnv
      { modName :: Module, -- The current module
        varEnv :: Context,
        inferLoc :: SrcSpan -- The current location in the source text
      }

data InferState = 
        InferState {
          maxK :: Int,
          maxD :: Int,
          maxI :: Int,
          cntN :: Int,
          rVar :: Int,
          errs :: ConstraintSet
        }

initState :: InferState
initState = InferState 0 0 0 0 0 mempty

noteK :: Int -> InferM ()
noteK x = modify (\s -> s { maxK = max x (maxK s) })

noteD :: Int -> InferM ()
noteD x = modify (\s -> s { maxD = max x (maxD s) })

noteI :: Int -> InferM ()
noteI x = modify (\s -> s { maxI = max x (maxI s) })

incrV :: InferM ()
incrV = modify (\s -> s { rVar = rVar s + 1 })

incrN :: InferM ()
incrN = modify (\s -> s { cntN = cntN s + 1 })

{-|
  Given a set of trivially unsatisfiable constraints @es@,
  @noteErrs es@ is the action that records them
  in the accumulating set in the inference state.
-}
noteErrs :: ConstraintSet -> InferM ()
noteErrs es = modify (\s -> s { errs = es <> errs s })

data Stats = 
        Stats {
          getK :: Int,
          getD :: Int,
          getV :: Int,
          getI :: Int,
          getN :: Int
        }

runInferM ::
  InferM a ->
  Module ->
  Context ->
  (a, [Atomic], Stats)
runInferM run mod_name init_env =
  let (a, s, _) = runRWS run (InferEnv mod_name init_env (UnhelpfulSpan (mkFastString "Nowhere"))) initState
  in (a, Constraints.toList (errs s), Stats (maxK s) (maxD s) (rVar s) (maxI s) (cntN s))

-- Transitively remove local constraints
saturate :: Refined a => InferM a -> InferM a
saturate ma = pass $
  do
    a <- ma
    env <- asks varEnv
    m <- asks modName
    src <- asks inferLoc
    let interface = domain a <> domain env
    noteI (IntSet.size interface)
    let fn cs =
          let ds = Constraints.saturate (CInfo m src) interface cs
           in if debugging then debugBracket a env src cs ds else ds
    return (a, fn)
  where
    debugBracket a env src cs ds =
      let asz = "type: " ++ show (IntSet.size $ domain a)
          esz = "env: " ++ show (IntSet.size $ domain env)
          csz = show (size cs)
          spn = traceSpan src
          tmsg = "#interface = (" ++ asz ++ " + " ++ esz ++ "), #constraints = " ++ csz
          ds' = trace ("[TRACE] BEGIN saturate at " ++ spn ++ ": " ++ tmsg) ds
       in ds' `seq` trace ("[TRACE] END saturate at " ++ spn ++ " saturated size: " ++ (show $ size ds)) ds

{-|
    Given a constraint set @cs@, @cexs cs@ is the inference action that
    attempts to build a model of @cs@ and returns the set of counterexamples.
-}
cexs :: ConstraintSet -> InferM ConstraintSet
cexs cs = 
  do  m <- asks modName 
      src <- asks inferLoc 
      return $ Constraints.cexs (CInfo m src) cs

-- Check if a core datatype is ineligible for refinement
isIneligible :: TyCon -> InferM Bool
isIneligible tc =  
  do  m <- asks modName
      return (not (homeOrBase m (getName tc)) || null (tyConDataCons tc))
  where
    homeOrBase m n =
      nameIsHomePackage m n 

        -- Previously we tried to include as much of base as possible by asking for specific modules
        -- but this is a little too coarse grain (e.g. GHC.Types will include Bool, but also Int):
        --
        -- vc|| ( not (nameIsFromExternalPackage baseUnitId n && nameIsFromExternalPackage primUnitId n)
        --        && case nameModule_maybe n of
        --          Nothing -> False
        --          Just (Module _ m) ->
        --            List.isPrefixOf "Prelude" (moduleNameString m)
        --              || List.isPrefixOf "Data" (moduleNameString m)
        --              || List.isPrefixOf "GHC.Base" (moduleNameString m)
        --              || List.isPrefixOf "GHC.Types" (moduleNameString m)
        --    )
        --
        -- a better approach would be, simply:
        --  strName = occNameString (getOccName tc)
        --  ...
        --  || strName == "Bool"
        --  || strName == "Maybe"

isTrivial :: TyCon -> Bool
isTrivial tc = (== 1) (length (tyConDataCons tc))



{- 
  Given a list of data constructors @ks@, a datatype @Inj x d@ and an 
  inference action @m@, @branchAny ks (Inj x d) m@ is the inference action
  that consists of doing @m@ then guarding all emitted constraints
  by the requirement that @ks in x(d)@.
-}
branchAny :: [DataCon] -> DataType TyCon -> InferM a -> InferM a
branchAny _ (Base _)  m = m
branchAny ks (Inj x d) m = 
    if (isTrivial d) then m else censor guardWithAll m
  where
    dn = getName d
    guardWithAll cs =
      foldMap (\k -> Constraints.guardWith (singleton [getName k] x dn) cs) ks

mkConFromCtx :: ConL -> ConR -> InferM Atomic
mkConFromCtx l r =
  do  m <- asks modName
      s <- asks inferLoc
      return (Constraint l r mempty (CInfo m s))

emitDD :: DataType TyCon -> DataType TyCon -> InferM ()
emitDD (Inj x d) (Inj y _) = 
  unless (isTrivial d) $ 
    do  a <- mkConFromCtx (Dom (Inj x dn)) (Dom (Inj y dn))
        tell (Constraints.fromList [a])
  where
    dn = getName d
emitDD _ _ = return ()

emitKD :: DataCon -> SrcSpan -> DataType TyCon -> InferM ()
emitKD k s (Inj x d) =
  unless (isTrivial d) $ 
    do  a <- mkConFromCtx (Con kn s) (Dom (Inj x dn))
        tell (Constraints.fromList [a])
  where
    dn  = getName d
    kn = getName k
emitKD _ _ _ = return ()

emitDK :: DataType TyCon -> [DataCon] -> SrcSpan -> InferM ()
emitDK (Inj x d) ks s =
  unless (isTrivial d || length (tyConDataCons d) == length ks) $ 
    do  a <- mkConFromCtx (Dom (Inj x dn)) (Set ksn s)
        tell (Constraints.fromList [a])
  where
    dn  = getName d
    ksn = mkUniqSet (map getName ks)
emitDK _ _ _ = return ()


-- A fresh refinement variable
fresh :: InferM RVar
fresh = do
  i <- gets rVar
  incrV
  return i

-- Insert variables into environment
putVar :: Name -> Scheme -> InferM a -> InferM a
putVar n s = local (\env -> env {varEnv = M.insert n s (varEnv env)})

putVars :: Context -> InferM a -> InferM a
putVars ctx = local (\env -> env {varEnv = M.union ctx (varEnv env)})

-- Add source text location tick
setLoc :: SrcSpan -> InferM a -> InferM a
setLoc l = local (\env -> env {inferLoc = l})

-- Prepare name for interface
-- Should be used before all type variables
getExternalName :: NamedThing a => a -> InferM Name
getExternalName a = do
  let n = getName a
  mn <- asks modName
  return $ mkExternalName (nameUnique n) mn (nameOccName n) (nameSrcSpan n)