packages feed

grisette-0.13.0.1: src/Grisette/Internal/TH/Derivation/Common.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}

-- |
-- Module      :   Grisette.Internal.TH.Derivation.Common
-- Copyright   :   (c) Sirui Lu 2024
-- License     :   BSD-3-Clause (see the LICENSE file)
--
-- Maintainer  :   siruilu@cs.washington.edu
-- Stability   :   Experimental
-- Portability :   GHC only
module Grisette.Internal.TH.Derivation.Common
  ( CheckArgsResult (..),
    checkArgs,
    ctxForVar,
    EvalModeConfig (..),
    DeriveConfig (..),
    extraEvalModeConstraint,
    extraBitSizeConstraint,
    extraFpBitSizeConstraint,
    extraExtraMergeableConstraint,
    extraConstraint,
    specializeResult,
    evalModeSpecializeList,
    isVarUsedInFields,
    freshenCheckArgsResult,
  )
where

import Control.Monad (foldM, unless, when, zipWithM)
import Data.Bifunctor (first)
import qualified Data.Map as M
import Data.Maybe (catMaybes, mapMaybe)
import qualified Data.Set as S
import GHC.TypeLits (KnownNat, Nat, type (<=))
import Grisette.Internal.Internal.Decl.Core.Data.Class.Mergeable
  ( Mergeable,
    Mergeable1,
    Mergeable2,
  )
import Grisette.Internal.SymPrim.FP (ValidFP)
import Grisette.Internal.Unified.EvalModeTag (EvalModeTag (C, S))
import Grisette.Internal.Unified.Util (DecideEvalMode)
import Language.Haskell.TH
  ( Kind,
    Name,
    Pred,
    Q,
    Type (AppT, ArrowT, ConT, PromotedT, StarT, VarT),
    conT,
    nameBase,
    newName,
  )
import Language.Haskell.TH.Datatype
  ( ConstructorInfo (constructorFields, constructorName, constructorVars),
    DatatypeInfo (datatypeCons, datatypeVars),
    TypeSubstitution (applySubstitution, freeVariables),
    reifyDatatype,
    tvName,
  )
import Language.Haskell.TH.Datatype.TyVarBndr (mapTVName, tvKind)

-- | Result of 'checkArgs' for a data type.
data CheckArgsResult = CheckArgsResult
  { constructors :: [ConstructorInfo],
    keptVars :: [(Type, Kind)],
    argVars :: [(Type, Kind)]
  }

-- | Specialize the evaluation mode tags for the t'CheckArgsResult'.
specializeResult :: [(Int, EvalModeTag)] -> CheckArgsResult -> Q CheckArgsResult
specializeResult evalModeConfigs result = do
  let modeToName C = 'C
      modeToName S = 'S
  map <-
    foldM
      ( \lst (n, tag) -> do
          let (_, knd) = lst !! n
          return $
            take n lst
              ++ [(PromotedT $ modeToName tag, knd)]
              ++ drop (n + 1) lst
      )
      (keptVars result)
      evalModeConfigs
  return $ result {keptVars = map}

freshenConstructorInfo :: ConstructorInfo -> Q ConstructorInfo
freshenConstructorInfo conInfo = do
  let vars = constructorVars conInfo
  newNames <- traverse (newName . nameBase . tvName) vars
  let newVars = zipWith (mapTVName . const) newNames vars
  let substMap = M.fromList $ zip (tvName <$> vars) $ VarT <$> newNames
  return $ applySubstitution substMap conInfo {constructorVars = newVars}

-- | Freshen the type variables in the t'CheckArgsResult'.
freshenCheckArgsResult :: Bool -> CheckArgsResult -> Q CheckArgsResult
freshenCheckArgsResult freshenNats result = do
  let genNewName :: (Type, Kind) -> Q (Maybe Name)
      genNewName (VarT _, knd) =
        if not freshenNats && knd == ConT ''Nat
          then return Nothing
          else Just <$> newName "a"
      genNewName _ = return Nothing
  keptNewNames <- traverse genNewName (keptVars result)
  argNewNames <- traverse genNewName (argVars result)

  let substMap =
        M.fromList
          $ mapMaybe
            ( \(newName, oldVar) ->
                case (newName, oldVar) of
                  (Just newName, (VarT oldName, _)) ->
                    Just (oldName, VarT newName)
                  _ -> Nothing
            )
          $ zip
            (keptNewNames ++ argNewNames)
            (keptVars result ++ argVars result)
  constructors <-
    mapM freshenConstructorInfo $
      applySubstitution substMap $
        constructors result
  let newKeptVars = first (applySubstitution substMap) <$> (keptVars result)
  let newArgVars = first (applySubstitution substMap) <$> (argVars result)
  return $
    result
      { constructors = constructors,
        keptVars = newKeptVars,
        argVars = newArgVars
      }

-- | Check if the number of type parameters is valid for a data type, and return
-- new names for the type variables, split into kept and arg parts.
checkArgs ::
  String ->
  Int ->
  Name ->
  Bool ->
  Int ->
  Q CheckArgsResult
checkArgs clsName maxArgNum typName allowExistential n = do
  when (n < 0) $
    fail $
      unlines
        [ "Cannot derive "
            ++ clsName
            ++ " instance with negative type parameters",
          "\tRequested: " ++ show n,
          "\tHint: Use a non-negative number of type parameters"
        ]
  when (n > maxArgNum) $
    fail $
      unlines
        [ "Cannot derive "
            <> clsName
            <> " instance with more than "
            <> show maxArgNum
            <> " type parameters",
          "\tRequested: " <> show n
        ]
  d <- reifyDatatype typName
  let dvars = datatypeVars d
  when (length dvars < n) $
    fail $
      unlines
        [ "Cannot derive "
            <> clsName
            <> show n
            <> " instance for the type "
            <> show typName,
          "\tReason: The type "
            <> show typName
            <> " has only "
            <> show (length dvars)
            <> " type variables."
        ]
  let keptVars =
        (\bndr -> (VarT $ tvName bndr, tvKind bndr))
          <$> take (length dvars - n) dvars
  let argVars =
        (\bndr -> (VarT $ tvName bndr, tvKind bndr))
          <$> drop (length dvars - n) dvars
  let constructors = datatypeCons d
  unless allowExistential $
    mapM_
      ( \c ->
          when (constructorVars c /= []) $
            fail $
              unlines
                [ "Cannot derive "
                    <> clsName
                    <> show n
                    <> " instance for the type "
                    <> show typName,
                  "\tReason: The constructor "
                    <> nameBase (constructorName c)
                    <> " has existential variables"
                ]
      )
      constructors
  mapM_
    ( \c -> do
        let fields = constructorFields c
        let existentialVars = tvName <$> constructorVars c
        let fieldReferencedVars = freeVariables fields
        let notReferencedVars =
              S.fromList existentialVars S.\\ S.fromList fieldReferencedVars
        unless (null notReferencedVars) $
          fail $
            unlines
              [ "Cannot derive "
                  <> clsName
                  <> show n
                  <> " instance for the type "
                  <> show typName,
                "Reason: Ambiguous existential variable in the constructor: "
                  <> nameBase (constructorName c)
                  <> ", this is not supported. Please consider binding the "
                  <> "existential variable to a field. You can use Proxy type to "
                  <> "do this."
              ]
    )
    constructors
  return $ CheckArgsResult {..}

isVarUsedInConstructorFields :: [ConstructorInfo] -> Name -> Bool
isVarUsedInConstructorFields constructors var =
  let allFields = concatMap constructorFields constructors
      allFieldsFreeVars = S.fromList $ freeVariables allFields
   in S.member var allFieldsFreeVars

-- | Check if a variable is used in the fields of a constructor.
isVarUsedInFields :: CheckArgsResult -> Name -> Bool
isVarUsedInFields CheckArgsResult {..} =
  isVarUsedInConstructorFields constructors

-- | Generate a context for a variable in a GADT.
ctxForVar :: [Type] -> Type -> Kind -> Q (Maybe Pred)
ctxForVar instanceExps ty knd = case knd of
  StarT ->
    Just
      <$> [t|$(return $ head instanceExps) $(return ty)|]
  AppT (AppT ArrowT StarT) StarT ->
    Just
      <$> [t|$(return $ instanceExps !! 1) $(return ty)|]
  AppT (AppT (AppT ArrowT StarT) StarT) StarT ->
    Just
      <$> [t|$(return $ instanceExps !! 2) $(return ty)|]
  AppT (AppT (AppT (AppT ArrowT StarT) StarT) StarT) StarT ->
    Just
      <$> [t|$(return $ instanceExps !! 3) $(return ty)|]
  AppT (AppT (AppT (AppT ArrowT StarT) StarT) StarT) _ ->
    fail $ "Unsupported kind: " <> show knd
  _ -> return Nothing

-- | Configuration for constraints for evaluation modes tag.
--
-- * 'EvalModeConstraints' specifies a list of constraints for the tag, for
--   example, we may use 'Grisette.Unified.EvalModeBase' and
--   'Grisette.Unified.EvalModeBV' to specify that the evaluation mode must
--   support both base (boolean and data types) and bit vectors. This should be
--   used when the data type uses bit vectors.
--
-- * 'EvalModeSpecified' specifies a that an evaluation mode tag should be
--   specialized to a specific tag for all the instances.
data EvalModeConfig
  = EvalModeConstraints [Name]
  | EvalModeSpecified EvalModeTag

-- | Configuration for deriving instances for a data type.
data DeriveConfig = DeriveConfig
  { evalModeConfig :: [(Int, EvalModeConfig)],
    bitSizePositions :: [Int],
    fpBitSizePositions :: [(Int, Int)],
    unconstrainedPositions :: [Int],
    needExtraMergeableUnderEvalMode :: Bool,
    needExtraMergeableWithConcretizedEvalMode :: Bool,
    useNoStrategy :: Bool,
    useSerialForCerealAndBinary :: Bool
  }

-- | Get all the evaluation modes to specialize in the t'DeriveConfig'.
evalModeSpecializeList :: DeriveConfig -> [(Int, EvalModeTag)]
evalModeSpecializeList DeriveConfig {..} =
  mapMaybe
    ( \(n, cfg) ->
        case cfg of
          EvalModeConstraints _ -> Nothing
          EvalModeSpecified tag -> Just (n, tag)
    )
    evalModeConfig

instance Semigroup DeriveConfig where
  l <> r =
    DeriveConfig
      { evalModeConfig = evalModeConfig l <> evalModeConfig r,
        bitSizePositions = bitSizePositions l <> bitSizePositions r,
        fpBitSizePositions = fpBitSizePositions l <> fpBitSizePositions r,
        unconstrainedPositions = unconstrainedPositions l <> unconstrainedPositions r,
        needExtraMergeableUnderEvalMode =
          needExtraMergeableUnderEvalMode l
            || needExtraMergeableUnderEvalMode r,
        needExtraMergeableWithConcretizedEvalMode =
          needExtraMergeableWithConcretizedEvalMode l
            || needExtraMergeableWithConcretizedEvalMode r,
        useNoStrategy = useNoStrategy l || useNoStrategy r,
        useSerialForCerealAndBinary =
          useSerialForCerealAndBinary l && useSerialForCerealAndBinary r
      }

instance Monoid DeriveConfig where
  mempty = DeriveConfig [] [] [] [] False False False True
  mappend = (<>)

-- | Generate extra constraints for evaluation modes.
extraEvalModeConstraint ::
  Name -> Name -> [(Type, Kind)] -> (Int, EvalModeConfig) -> Q [Pred]
extraEvalModeConstraint
  tyName
  instanceName
  args
  (n, EvalModeConstraints names)
    | n >= length args = return []
    | otherwise = do
        let (arg, argKind) = args !! n
        when (argKind /= ConT ''EvalModeTag) $
          fail $
            "Cannot introduce EvalMode constraint for the "
              <> show n
              <> "th argument of "
              <> show tyName
              <> " when deriving the "
              <> show instanceName
              <> " instance because it is not an EvalModeTag."
        traverse (\nm -> [t|$(conT nm) $(return arg)|]) names
extraEvalModeConstraint _ _ _ (_, EvalModeSpecified _) = return []

-- | Generate extra constraints for bit vectors.
extraBitSizeConstraint :: Name -> Name -> [(Type, Kind)] -> Int -> Q [Pred]
extraBitSizeConstraint tyName instanceName args n
  | n >= length args = return []
  | otherwise = do
      let (arg, argKind) = args !! n
      when (argKind /= ConT ''Nat) $
        fail $
          "Cannot introduce BitSize constraint for the "
            <> show n
            <> "th argument of "
            <> show tyName
            <> " when deriving the "
            <> show instanceName
            <> " instance because it is not a Nat."
      predKnown <- [t|KnownNat $(return arg)|]
      predPositive <- [t|1 <= $(return arg)|]
      return [predKnown, predPositive]

-- | Generate extra constraints for floating point exponents and significands.
extraFpBitSizeConstraint ::
  Name -> Name -> [(Type, Kind)] -> (Int, Int) -> Q [Pred]
extraFpBitSizeConstraint tyName instanceName args (eb, sb)
  | eb >= length args || sb >= length args = return []
  | otherwise = do
      let (argEb, argEbKind) = args !! eb
      let (argSb, argSbKind) = args !! sb
      when (argEbKind /= ConT ''Nat || argSbKind /= ConT ''Nat) $
        fail $
          "Cannot introduce ValidFP constraint for the "
            <> show eb
            <> "th and "
            <> show sb
            <> "th arguments of "
            <> show tyName
            <> " when deriving the "
            <> show instanceName
            <> " instance because they are not Nats."
      pred <- [t|ValidFP $(return argEb) $(return argSb)|]
      return [pred]

-- | Generate extra constraints for 'Mergeable' instances.
extraExtraMergeableConstraint ::
  DeriveConfig -> [ConstructorInfo] -> [(Type, Kind)] -> Q [Pred]
extraExtraMergeableConstraint deriveConfig constructors args = do
  let isTypeUsedInFields' (VarT nm) =
        isVarUsedInConstructorFields constructors nm
      isTypeUsedInFields' _ = False
  catMaybes
    <$> zipWithM
      ( \position (arg, knd) ->
          if isTypeUsedInFields' arg
            && notElem position (unconstrainedPositions deriveConfig)
            then
              ctxForVar
                [ ConT ''Mergeable,
                  ConT ''Mergeable1,
                  ConT ''Mergeable2
                ]
                arg
                knd
            else return Nothing
      )
      [0 ..]
      args

-- | Generate extra constraints for a data type.
extraConstraint ::
  DeriveConfig ->
  Name ->
  Name ->
  [(Type, Kind)] ->
  [(Type, Kind)] ->
  [ConstructorInfo] ->
  Q [Pred]
extraConstraint
  deriveConfig@DeriveConfig {..}
  tyName
  instanceName
  extraArgs
  keptArgs
  constructors = do
    evalModePreds <-
      traverse
        (extraEvalModeConstraint tyName instanceName keptArgs)
        evalModeConfig
    extraArgEvalModePreds <-
      if null evalModeConfig
        then
          traverse
            ( \(arg, kind) ->
                if kind == ConT ''EvalModeTag
                  then (: []) <$> [t|DecideEvalMode $(return arg)|]
                  else return []
            )
            extraArgs
        else return []
    bitSizePreds <-
      traverse
        (extraBitSizeConstraint tyName instanceName keptArgs)
        bitSizePositions
    fpBitSizePreds <-
      traverse
        (extraFpBitSizeConstraint tyName instanceName keptArgs)
        fpBitSizePositions
    extraMergeablePreds <-
      if needExtraMergeableUnderEvalMode
        && ( any
               ( \case
                   (_, EvalModeConstraints _) -> True
                   (_, EvalModeSpecified _) -> False
               )
               evalModeConfig
               || needExtraMergeableWithConcretizedEvalMode
           )
        then extraExtraMergeableConstraint deriveConfig constructors keptArgs
        else return []
    return $
      concat (extraArgEvalModePreds ++ evalModePreds)
        ++ if null constructors
          then []
          else extraMergeablePreds ++ concat (bitSizePreds ++ fpBitSizePreds)