content: {-# LANGUAGE AllowAmbiguousTypes #-}
  {-# LANGUAGE BangPatterns #-}
  {-# LANGUAGE CPP #-}
  {-# LANGUAGE ConstraintKinds #-}
  {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE DefaultSignatures #-}
  {-# LANGUAGE DeriveAnyClass #-}
  {-# LANGUAGE DeriveGeneric #-}
  {-# LANGUAGE DeriveLift #-}
  {-# HLINT ignore "Eta reduce" #-}
  {-# HLINT ignore "Unused LANGUAGE pragma" #-}
  {-# LANGUAGE DerivingStrategies #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE FlexibleInstances #-}
  {-# LANGUAGE FunctionalDependencies #-}
  {-# LANGUAGE GADTs #-}
  {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  {-# LANGUAGE InstanceSigs #-}
  {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE PatternSynonyms #-}
  {-# LANGUAGE QuantifiedConstraints #-}
  {-# LANGUAGE RankNTypes #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE StandaloneDeriving #-}
  {-# LANGUAGE Strict #-}
  {-# LANGUAGE TemplateHaskell #-}
  {-# LANGUAGE TypeApplications #-}
  {-# LANGUAGE TypeFamilies #-}
  {-# LANGUAGE TypeOperators #-}
  {-# LANGUAGE UndecidableInstances #-}
  {-# LANGUAGE ViewPatterns #-}
  {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
  {-# OPTIONS_GHC -funbox-strict-fields #-}
  
  -- |
  -- Module      :   Grisette.Internal.SymPrim.Prim.Internal.Term
  -- Copyright   :   (c) Sirui Lu 2021-2024
  -- License     :   BSD-3-Clause (see the LICENSE file)
  --
  -- Maintainer  :   siruilu@cs.washington.edu
  -- Stability   :   Experimental
  -- Portability :   GHC only
  module Grisette.Internal.SymPrim.Prim.Internal.Term
    ( -- * Supported primitive types
      SupportedPrimConstraint (..),
      SupportedPrim (..),
      SymRep (..),
      ConRep (..),
      LinkedRep (..),
  
      -- * Partial evaluation for the terms
      PEvalApplyTerm (..),
      PEvalBitwiseTerm (..),
      PEvalShiftTerm (..),
      PEvalRotateTerm (..),
      PEvalNumTerm (..),
      pevalSubNumTerm,
      PEvalOrdTerm (..),
      pevalGtOrdTerm,
      pevalGeOrdTerm,
      pevalNEqTerm,
      PEvalDivModIntegralTerm (..),
      PEvalBitCastTerm (..),
      PEvalBitCastOrTerm (..),
      PEvalBVTerm (..),
      PEvalFractionalTerm (..),
      PEvalFPTerm (..),
      PEvalFloatingTerm (..),
      PEvalFromIntegralTerm (..),
      PEvalIEEEFPConvertibleTerm (..),
  
      -- * Typed symbols
      SymbolKind (..),
      TypedSymbol (TypedSymbol, unTypedSymbol),
      typedConstantSymbol,
      typedAnySymbol,
      TypedConstantSymbol,
      TypedAnySymbol,
      SomeTypedSymbol (..),
      SomeTypedConstantSymbol,
      SomeTypedAnySymbol,
      IsSymbolKind (..),
      showUntyped,
      someTypedSymbol,
      eqHeteroSymbol,
      castSomeTypedSymbol,
  
      -- * Terms
      FPTrait (..),
      FPUnaryOp (..),
      FPBinaryOp (..),
      FPRoundingUnaryOp (..),
      FPRoundingBinaryOp (..),
      FloatingUnaryOp (..),
      Term (..),
      defaultValueDynamic,
      pattern DynTerm,
      toCurThread,
      CachedInfo (..),
      termInfo,
      termThreadId,
      termDigest,
      termId,
      termStableIdent,
      pformatTerm,
      ModelValue (..),
      toModelValue,
      unsafeFromModelValue,
  
      -- * Interning
      UTerm (..),
      prettyPrintTerm,
  
      -- * Interned constructors
      conTerm,
      symTerm,
      ssymTerm,
      isymTerm,
      forallTerm,
      existsTerm,
      notTerm,
      orTerm,
      andTerm,
      eqTerm,
      distinctTerm,
      iteTerm,
      addNumTerm,
      negNumTerm,
      mulNumTerm,
      absNumTerm,
      signumNumTerm,
      ltOrdTerm,
      leOrdTerm,
      andBitsTerm,
      orBitsTerm,
      xorBitsTerm,
      complementBitsTerm,
      shiftLeftTerm,
      rotateLeftTerm,
      shiftRightTerm,
      rotateRightTerm,
      bitCastTerm,
      bitCastOrTerm,
      bvConcatTerm,
      bvSelectTerm,
      bvExtendTerm,
      bvsignExtendTerm,
      bvzeroExtendTerm,
      applyTerm,
      divIntegralTerm,
      modIntegralTerm,
      quotIntegralTerm,
      remIntegralTerm,
      fpTraitTerm,
      fdivTerm,
      recipTerm,
      floatingUnaryTerm,
      powerTerm,
      fpUnaryTerm,
      fpBinaryTerm,
      fpRoundingUnaryTerm,
      fpRoundingBinaryTerm,
      fpFMATerm,
      fromIntegralTerm,
      fromFPOrTerm,
      toFPTerm,
  
      -- * Patterns
      pattern SupportedTerm,
      pattern SupportedTypedSymbol,
      pattern SupportedConstantTypedSymbol,
      pattern ConTerm,
      pattern SymTerm,
      pattern ForallTerm,
      pattern ExistsTerm,
      pattern NotTerm,
      pattern OrTerm,
      pattern AndTerm,
      pattern OrTermAll,
      pattern AndTermAll,
      pattern EqTerm,
      pattern DistinctTerm,
      pattern ITETerm,
      pattern AddNumTerm,
      pattern NegNumTerm,
      pattern MulNumTerm,
      pattern AbsNumTerm,
      pattern SignumNumTerm,
      pattern LtOrdTerm,
      pattern LeOrdTerm,
      pattern AndBitsTerm,
      pattern OrBitsTerm,
      pattern XorBitsTerm,
      pattern ComplementBitsTerm,
      pattern ShiftLeftTerm,
      pattern RotateLeftTerm,
      pattern ShiftRightTerm,
      pattern RotateRightTerm,
      pattern BitCastTerm,
      pattern BitCastOrTerm,
      pattern BVConcatTerm,
      pattern BVSelectTerm,
      pattern BVExtendTerm,
      pattern ApplyTerm,
      pattern DivIntegralTerm,
      pattern ModIntegralTerm,
      pattern QuotIntegralTerm,
      pattern RemIntegralTerm,
      pattern FPTraitTerm,
      pattern FdivTerm,
      pattern RecipTerm,
      pattern FloatingUnaryTerm,
      pattern PowerTerm,
      pattern FPUnaryTerm,
      pattern FPBinaryTerm,
      pattern FPRoundingUnaryTerm,
      pattern FPRoundingBinaryTerm,
      pattern FPFMATerm,
      pattern FromIntegralTerm,
      pattern FromFPOrTerm,
      pattern ToFPTerm,
  
      -- * Support for boolean type
      trueTerm,
      falseTerm,
      pattern BoolConTerm,
      pattern TrueTerm,
      pattern FalseTerm,
      pattern BoolTerm,
      pevalNotTerm,
      pevalOrTerm,
      pevalAndTerm,
      pevalImplyTerm,
      pevalXorTerm,
      pevalITEBasic,
      pevalITEBasicTerm,
      pevalDefaultEqTerm,
      NonFuncPrimConstraint,
      NonFuncSBVRep (..),
      SupportedNonFuncPrim (..),
      SBVRep (..),
      SBVFreshMonad (..),
      translateTypeError,
      parseSMTModelResultError,
      partitionCVArg,
      parseScalarSMTModelResult,
      bvIsNonZeroFromGEq1,
  
      -- * Partial evaluation
      PartialFun,
      PartialRuleUnary,
      TotalRuleUnary,
      PartialRuleBinary,
      TotalRuleBinary,
      totalize,
      totalize2,
      UnaryPartialStrategy (..),
      unaryPartial,
      BinaryCommPartialStrategy (..),
      BinaryPartialStrategy (..),
      binaryPartial,
  
      -- * Unfold
      unaryUnfoldOnce,
      binaryUnfoldOnce,
      generalUnaryUnfolded,
      generalBinaryUnfolded,
  
      -- * bv
      unsafePevalBVConcatTerm,
      unsafePevalBVExtendTerm,
      unsafePevalBVSelectTerm,
      boolToBVTerm,
  
      -- * num
      pevalDefaultAddNumTerm,
      pevalDefaultNegNumTerm,
      pevalDefaultMulNumTerm,
      pevalBitsAbsNumTerm,
      doPevalNoOverflowAbsNumTerm,
      pevalGeneralSignumNumTerm,
      doPevalNoOverflowSignumNumTerm,
    )
  where
  
  #if MIN_VERSION_prettyprinter(1,7,0)
  import Prettyprinter
    ( column,
      pageWidth,
      Doc,
      PageWidth(Unbounded, AvailablePerLine),
      Pretty(pretty),
    )
  #else
  import Data.Text.Prettyprint.Doc
    ( column,
      pageWidth,
      Doc,
      PageWidth(Unbounded, AvailablePerLine),
      Pretty(pretty),
    )
  #endif
  
  #if !MIN_VERSION_sbv(10,0,0)
  #define SMTDefinable Uninterpreted
  #endif
  
  #if MIN_VERSION_sbv(11,0,0)
  import qualified Data.SBV as SBVTC
  #endif
  
  #if MIN_VERSION_base(4,15,0)
  import Language.Haskell.TH (Code, Quote)
  #else
  import Language.Haskell.TH (TExpQ)
  #endif
  
  import Control.DeepSeq (NFData (rnf))
  import Control.Monad (msum)
  import Control.Monad.Except (MonadError (catchError))
  import Control.Monad.IO.Class (MonadIO)
  import qualified Control.Monad.RWS.Lazy as Lazy
  import qualified Control.Monad.RWS.Strict as Strict
  import Control.Monad.Reader (MonadTrans (lift), ReaderT)
  import qualified Control.Monad.State.Lazy as Lazy
  import qualified Control.Monad.State.Strict as Strict
  import qualified Control.Monad.Writer.Lazy as Lazy
  import qualified Control.Monad.Writer.Strict as Strict
  import Data.Atomics (atomicModifyIORefCAS_)
  import qualified Data.Binary as Binary
  import Data.Bits
    ( Bits (complement, isSigned, xor, zeroBits, (.&.), (.|.)),
      FiniteBits (countLeadingZeros),
    )
  import Data.Bytes.Serial (Serial (deserialize, serialize))
  import Data.Coerce (coerce)
  import qualified Data.HashMap.Strict as HM
  import qualified Data.HashSet as HS
  import Data.Hashable (Hashable (hashWithSalt))
  import Data.IORef (IORef, newIORef, readIORef)
  import Data.Kind (Constraint, Type)
  import Data.List.NonEmpty (NonEmpty ((:|)), toList)
  import Data.Maybe (fromMaybe, isJust)
  import Data.Proxy (Proxy (Proxy))
  import Data.SBV (BVIsNonZero)
  import qualified Data.SBV as SBV
  import qualified Data.SBV.Dynamic as SBVD
  import qualified Data.SBV.Trans as SBVT
  import qualified Data.SBV.Trans.Control as SBVTC
  import qualified Data.Serialize as Cereal
  import Data.String (IsString (fromString))
  import Data.Type.Equality ((:~:) (Refl), type (:~~:) (HRefl))
  import Data.Typeable (Typeable, cast, typeRepFingerprint)
  import GHC.Exts (Any, sortWith)
  import GHC.Fingerprint (Fingerprint)
  import GHC.Generics (Generic)
  import GHC.IO (unsafePerformIO)
  import GHC.Stack (HasCallStack)
  import GHC.TypeNats (KnownNat, Nat, natVal, sameNat, type (+), type (-), type (<=))
  import Grisette.Internal.Core.Data.Class.BitCast (BitCast (bitCast), BitCastOr)
  import Grisette.Internal.Core.Data.Class.BitVector
    ( SizedBV
        ( sizedBVConcat,
          sizedBVFromIntegral,
          sizedBVSelect,
          sizedBVSext,
          sizedBVZext
        ),
    )
  import Grisette.Internal.Core.Data.Class.IEEEFP
    ( fpIsNegativeZero,
      fpIsPositiveZero,
    )
  import Grisette.Internal.Core.Data.Symbol
    ( Identifier,
      Symbol (IndexedSymbol, SimpleSymbol),
    )
  import Grisette.Internal.SymPrim.AlgReal (AlgReal, fromSBVAlgReal, toSBVAlgReal)
  import Grisette.Internal.SymPrim.BV (IntN, WordN)
  import Grisette.Internal.SymPrim.FP
    ( FP (FP),
      FPRoundingMode (RNA, RNE, RTN, RTP, RTZ),
      ValidFP,
    )
  import Grisette.Internal.SymPrim.Prim.Internal.Caches
    ( CachedInfo
        ( CachedInfo,
          cachedDigest,
          cachedId,
          cachedStableIdent,
          cachedThreadId
        ),
      Digest,
      Id,
      Interned
        ( Description,
          Uninterned,
          describe,
          descriptionDigest,
          identify,
          threadId
        ),
      StableIdent,
      intern,
    )
  import Grisette.Internal.SymPrim.Prim.Internal.Utils
    ( WeakThreadId,
      myWeakThreadId,
    )
  import Grisette.Internal.Utils.Parameterized
    ( LeqProof (LeqProof),
      NatRepr,
      SomeNatRepr (SomeNatRepr),
      SomePositiveNatRepr (SomePositiveNatRepr),
      addNat,
      mkNatRepr,
      mkPositiveNatRepr,
      natRepr,
      unsafeAxiom,
      unsafeKnownProof,
      unsafeLeqProof,
      withKnownNat,
      withKnownProof,
    )
  import Language.Haskell.TH.Syntax (Lift (liftTyped))
  import Type.Reflection
    ( SomeTypeRep (SomeTypeRep),
      TypeRep,
      eqTypeRep,
      someTypeRep,
      typeRep,
    )
  import qualified Type.Reflection as R
  import Unsafe.Coerce (unsafeCoerce)
  
  -- $setup
  -- >>> import Grisette.Core
  -- >>> import Grisette.SymPrim
  
  -- | Monads that supports generating sbv fresh variables.
  class (MonadIO m) => SBVFreshMonad m where
    sbvFresh :: (SBV.SymVal a) => String -> m (SBV.SBV a)
  
  instance (MonadIO m) => SBVFreshMonad (SBVT.SymbolicT m) where
    sbvFresh = SBVT.free
    {-# INLINE sbvFresh #-}
  
  instance (MonadIO m) => SBVFreshMonad (SBVTC.QueryT m) where
    sbvFresh = SBVTC.freshVar
    {-# INLINE sbvFresh #-}
  
  instance (SBVFreshMonad m) => SBVFreshMonad (ReaderT r m) where
    sbvFresh = lift . sbvFresh
    {-# INLINE sbvFresh #-}
  
  instance (SBVFreshMonad m, Monoid w) => SBVFreshMonad (Lazy.WriterT w m) where
    sbvFresh = lift . sbvFresh
    {-# INLINE sbvFresh #-}
  
  instance (SBVFreshMonad m, Monoid w) => SBVFreshMonad (Lazy.RWST r w s m) where
    sbvFresh = lift . sbvFresh
    {-# INLINE sbvFresh #-}
  
  instance (SBVFreshMonad m) => SBVFreshMonad (Lazy.StateT s m) where
    sbvFresh = lift . sbvFresh
    {-# INLINE sbvFresh #-}
  
  instance (SBVFreshMonad m, Monoid w) => SBVFreshMonad (Strict.WriterT w m) where
    sbvFresh = lift . sbvFresh
    {-# INLINE sbvFresh #-}
  
  instance (SBVFreshMonad m, Monoid w) => SBVFreshMonad (Strict.RWST r w s m) where
    sbvFresh = lift . sbvFresh
    {-# INLINE sbvFresh #-}
  
  instance (SBVFreshMonad m) => SBVFreshMonad (Strict.StateT s m) where
    sbvFresh = lift . sbvFresh
    {-# INLINE sbvFresh #-}
  
  -- | Error message for unsupported types.
  translateTypeError :: (HasCallStack) => Maybe String -> TypeRep a -> b
  translateTypeError Nothing ta =
    error $
      "Don't know how to translate the type " ++ show ta ++ " to SMT"
  translateTypeError (Just reason) ta =
    error $
      "Don't know how to translate the type " ++ show ta ++ " to SMT: " <> reason
  
  -- | Type class for resolving the base type for the SBV type for the primitive
  -- type.
  class
    ( SupportedPrim a,
      Ord a,
      Eq a,
      Show a,
      Hashable a,
      Typeable a
    ) =>
    NonFuncSBVRep a
    where
    type NonFuncSBVBaseType a
  
  -- | Type class for resolving the constraint for a supported non-function
  -- primitive type.
  type NonFuncPrimConstraint a =
    ( SBV.SymVal (NonFuncSBVBaseType a),
      SBV.EqSymbolic (SBVType a),
      SBV.Mergeable (SBVType a),
      SBV.SMTDefinable (SBVType a),
      SBV.Mergeable (SBVType a),
      SBVType a ~ SBV.SBV (NonFuncSBVBaseType a),
      PrimConstraint a
    )
  
  -- | Indicates that a type is supported, can be represented as a symbolic term,
  -- is not a function type, and can be lowered to an SBV term.
  class (NonFuncSBVRep a) => SupportedNonFuncPrim a where
    conNonFuncSBVTerm :: a -> SBV.SBV (NonFuncSBVBaseType a)
    symNonFuncSBVTerm ::
      (SBVFreshMonad m) => String -> m (SBV.SBV (NonFuncSBVBaseType a))
    withNonFuncPrim :: ((NonFuncPrimConstraint a) => r) -> r
  
  -- | Partition the list of CVs for models for functions.
  partitionCVArg ::
    forall a.
    (SupportedNonFuncPrim a) =>
    [([SBVD.CV], SBVD.CV)] ->
    [(a, [([SBVD.CV], SBVD.CV)])]
  partitionCVArg cv =
    partitionOrdCVArg $
      parseFirstCVArg cv
    where
      parseFirstCVArg ::
        forall a.
        (SupportedNonFuncPrim a) =>
        [([SBVD.CV], SBVD.CV)] ->
        [(a, [([SBVD.CV], SBVD.CV)])]
      parseFirstCVArg =
        fmap
          ( \case
              (x : xs, v) ->
                (parseSMTModelResult 0 ([], x), [(xs, v)])
              _ -> error "impossible"
          )
      partitionOrdCVArg ::
        forall a.
        (SupportedNonFuncPrim a) =>
        [(a, [([SBVD.CV], SBVD.CV)])] ->
        [(a, [([SBVD.CV], SBVD.CV)])]
      partitionOrdCVArg v = go sorted
        where
          sorted = sortWith fst v :: [(a, [([SBVD.CV], SBVD.CV)])]
          go (x : x1 : xs) =
            if fst x == fst x1
              then go $ (fst x, snd x ++ snd x1) : xs
              else x : go (x1 : xs)
          go x = x
  
  -- | Parse the scalar model result.
  parseScalarSMTModelResult ::
    forall v r.
    (SBVT.SatModel r, Typeable v) =>
    (r -> v) ->
    ([([SBVD.CV], SBVD.CV)], SBVD.CV) ->
    v
  parseScalarSMTModelResult convert cvs@([], v) = case SBVT.parseCVs [v] of
    Just (x, _) -> convert x
    Nothing -> parseSMTModelResultError (typeRep @v) cvs
  parseScalarSMTModelResult _ cv = parseSMTModelResultError (typeRep @v) cv
  
  -- | Type class for resolving the SBV type for the primitive type.
  class SBVRep t where
    type SBVType t
  
  -- | Type class for resolving the constraint for a supported primitive type.
  class SupportedPrimConstraint t where
    type PrimConstraint t :: Constraint
    type PrimConstraint _ = ()
  
  -- | Indicates that a type is supported, can be represented as a symbolic term,
  -- and can be lowered to an SBV term.
  class
    ( Lift t,
      NFData t,
      Typeable t,
      SupportedPrimConstraint t,
      SBVRep t
    ) =>
    SupportedPrim t
    where
    primTypeRep :: TypeRep t
    default primTypeRep :: (Typeable t) => TypeRep t
    primTypeRep = typeRep
    sameCon :: t -> t -> Bool
    default sameCon :: (Eq t) => t -> t -> Bool
    sameCon = (==)
    hashConWithSalt :: Int -> t -> Int
    default hashConWithSalt :: (Hashable t) => Int -> t -> Int
    hashConWithSalt = hashWithSalt
    pformatCon :: t -> String
    default pformatCon :: (Show t) => t -> String
    pformatCon = show
    defaultValue :: t
    pevalITETerm :: Term Bool -> Term t -> Term t -> Term t
    pevalEqTerm :: Term t -> Term t -> Term Bool
    pevalDistinctTerm :: NonEmpty (Term t) -> Term Bool
    conSBVTerm :: t -> SBVType t
    symSBVName :: TypedSymbol 'AnyKind t -> Int -> String
    symSBVTerm :: (SBVFreshMonad m) => String -> m (SBVType t)
    default withPrim ::
      ( PrimConstraint t,
        SBV.SMTDefinable (SBVType t),
        SBV.Mergeable (SBVType t),
        Typeable (SBVType t)
      ) =>
      ( ( PrimConstraint t,
          SBV.SMTDefinable (SBVType t),
          SBV.Mergeable (SBVType t),
          Typeable (SBVType t)
        ) =>
        a
      ) ->
      a
    withPrim ::
      ( ( PrimConstraint t,
          SBV.SMTDefinable (SBVType t),
          SBV.Mergeable (SBVType t),
          Typeable (SBVType t)
        ) =>
        a
      ) ->
      a
    withPrim i = i
    {-# INLINE withPrim #-}
    sbvIte :: SBV.SBV Bool -> SBVType t -> SBVType t -> SBVType t
    sbvIte = withPrim @t SBV.ite
    sbvEq :: SBVType t -> SBVType t -> SBV.SBV Bool
    default sbvEq ::
      (SBVT.EqSymbolic (SBVType t)) => SBVType t -> SBVType t -> SBV.SBV Bool
    sbvEq = (SBV..==)
    sbvDistinct :: NonEmpty (SBVType t) -> SBV.SBV Bool
    default sbvDistinct ::
      (SBVT.EqSymbolic (SBVType t)) => NonEmpty (SBVType t) -> SBV.SBV Bool
    sbvDistinct = SBV.distinct . toList
    parseSMTModelResult :: Int -> ([([SBVD.CV], SBVD.CV)], SBVD.CV) -> t
    castTypedSymbol ::
      (IsSymbolKind knd') => TypedSymbol knd t -> Maybe (TypedSymbol knd' t)
    funcDummyConstraint :: SBVType t -> SBV.SBV Bool
  
  -- | The default value in a dynamic t'ModelValue'.
  defaultValueDynamic ::
    forall t proxy. (SupportedPrim t) => proxy t -> ModelValue
  defaultValueDynamic _ = toModelValue (defaultValue @t)
  
  -- | A value with its type information.
  data ModelValue where
    ModelValue :: forall v. (SupportedPrim v) => v -> ModelValue
  
  instance NFData ModelValue where
    rnf (ModelValue v) = rnf v
  
  instance Lift ModelValue where
    liftTyped (ModelValue v) = [||ModelValue v||]
  
  instance Show ModelValue where
    show (ModelValue (v :: v)) = pformatCon v ++ " :: " ++ show (primTypeRep @v)
  
  instance Eq ModelValue where
    (ModelValue (v1 :: v1)) == (ModelValue (v2 :: v2)) =
      case eqTypeRep (primTypeRep @v1) (primTypeRep @v2) of
        Just HRefl -> sameCon v1 v2
        _ -> False
  
  instance Hashable ModelValue where
    s `hashWithSalt` (ModelValue (v :: v)) =
      (s `hashWithSalt` (primTypeRep @v)) `hashConWithSalt` v
  
  -- | Convert from a model value. Crashes if the types does not match.
  unsafeFromModelValue :: forall a. (Typeable a) => ModelValue -> a
  unsafeFromModelValue (ModelValue (v :: v)) =
    case eqTypeRep (primTypeRep @v) (typeRep @a) of
      Just HRefl -> v
      _ ->
        error $
          "Bad model value type, expected type: "
            ++ show (typeRep @a)
            ++ ", but got: "
            ++ show (primTypeRep @v)
  
  -- | Convert to a model value.
  toModelValue :: forall a. (SupportedPrim a) => a -> ModelValue
  toModelValue = ModelValue
  
  -- | Cast a typed symbol to a different kind. Check if the kind is compatible.
  castSomeTypedSymbol ::
    (IsSymbolKind knd') => SomeTypedSymbol knd -> Maybe (SomeTypedSymbol knd')
  castSomeTypedSymbol (SomeTypedSymbol s@TypedSymbol {}) =
    SomeTypedSymbol <$> castTypedSymbol s
  {-# INLINE castSomeTypedSymbol #-}
  
  -- | Error message for failure to parse the SBV model result.
  parseSMTModelResultError ::
    (HasCallStack) => TypeRep a -> ([([SBVD.CV], SBVD.CV)], SBVD.CV) -> a
  parseSMTModelResultError ty cv =
    error $
      "BUG: cannot parse SBV model value \""
        <> show cv
        <> "\" to Grisette model value with the type "
        <> show ty
  
  -- | Partial evaluation for inequality terms.
  pevalNEqTerm :: (SupportedPrim a) => Term a -> Term a -> Term Bool
  pevalNEqTerm l r = pevalNotTerm $ pevalEqTerm l r
  {-# INLINE pevalNEqTerm #-}
  
  -- | Type family to resolve the concrete type associated with a symbolic type.
  class ConRep sym where
    type ConType sym
  
  -- | Type family to resolve the symbolic type associated with a concrete type.
  class (SupportedPrim con) => SymRep con where
    type SymType con
  
  -- | One-to-one mapping between symbolic types and concrete types.
  class
    (ConRep sym, SymRep con, sym ~ SymType con, con ~ ConType sym) =>
    LinkedRep con sym
      | con -> sym,
        sym -> con
    where
    underlyingTerm :: sym -> Term con
    wrapTerm :: Term con -> sym
  
  -- | Partial evaluation and lowering for function application terms.
  class PEvalApplyTerm f a b | f -> a b where
    pevalApplyTerm :: Term f -> Term a -> Term b
    sbvApplyTerm :: SBVType f -> SBVType a -> SBVType b
  
  -- | Partial evaluation and lowering for bitwise operation terms.
  class PEvalBitwiseTerm t where
    pevalAndBitsTerm :: Term t -> Term t -> Term t
    pevalOrBitsTerm :: Term t -> Term t -> Term t
    pevalXorBitsTerm :: Term t -> Term t -> Term t
    pevalComplementBitsTerm :: Term t -> Term t
    withSbvBitwiseTermConstraint :: (((Bits (SBVType t)) => r)) -> r
    sbvAndBitsTerm :: SBVType t -> SBVType t -> SBVType t
    sbvAndBitsTerm = withSbvBitwiseTermConstraint @t (SBV..&.)
    sbvOrBitsTerm :: SBVType t -> SBVType t -> SBVType t
    sbvOrBitsTerm = withSbvBitwiseTermConstraint @t (SBV..|.)
    sbvXorBitsTerm :: SBVType t -> SBVType t -> SBVType t
    sbvXorBitsTerm = withSbvBitwiseTermConstraint @t SBV.xor
    sbvComplementBitsTerm :: SBVType t -> SBVType t
    sbvComplementBitsTerm = withSbvBitwiseTermConstraint @t SBV.complement
  
  -- | Partial evaluation and lowering for symbolic shifting terms.
  class PEvalShiftTerm t where
    pevalShiftLeftTerm :: Term t -> Term t -> Term t
    pevalShiftRightTerm :: Term t -> Term t -> Term t
    withSbvShiftTermConstraint ::
      (((SBV.SIntegral (NonFuncSBVBaseType t)) => r)) -> r
    sbvShiftLeftTerm :: SBVType t -> SBVType t -> SBVType t
    default sbvShiftLeftTerm ::
      (SupportedNonFuncPrim t) => SBVType t -> SBVType t -> SBVType t
    sbvShiftLeftTerm l r =
      withNonFuncPrim @t $ withSbvShiftTermConstraint @t $ SBV.sShiftLeft l r
    default sbvShiftRightTerm ::
      (SupportedNonFuncPrim t) => SBVType t -> SBVType t -> SBVType t
    sbvShiftRightTerm :: SBVType t -> SBVType t -> SBVType t
    sbvShiftRightTerm l r =
      withNonFuncPrim @t $ withSbvShiftTermConstraint @t $ SBV.sShiftRight l r
  
  -- | Partial evaluation and lowering for symbolic rotate terms.
  class PEvalRotateTerm t where
    pevalRotateLeftTerm :: Term t -> Term t -> Term t
    pevalRotateRightTerm :: Term t -> Term t -> Term t
    withSbvRotateTermConstraint ::
      (((SBV.SIntegral (NonFuncSBVBaseType t)) => r)) -> r
    sbvRotateLeftTerm :: SBVType t -> SBVType t -> SBVType t
    default sbvRotateLeftTerm ::
      (SupportedNonFuncPrim t) => SBVType t -> SBVType t -> SBVType t
    sbvRotateLeftTerm l r =
      withNonFuncPrim @t $ withSbvRotateTermConstraint @t $ SBV.sRotateLeft l r
    sbvRotateRightTerm :: SBVType t -> SBVType t -> SBVType t
    default sbvRotateRightTerm ::
      (SupportedNonFuncPrim t) => SBVType t -> SBVType t -> SBVType t
    sbvRotateRightTerm l r =
      withNonFuncPrim @t $ withSbvRotateTermConstraint @t $ SBV.sRotateRight l r
  
  -- | Partial evaluation and lowering for number terms.
  class (Num t) => PEvalNumTerm t where
    pevalAddNumTerm :: Term t -> Term t -> Term t
    pevalNegNumTerm :: Term t -> Term t
    pevalMulNumTerm :: Term t -> Term t -> Term t
    pevalAbsNumTerm :: Term t -> Term t
    pevalSignumNumTerm :: Term t -> Term t
    withSbvNumTermConstraint :: (((Num (SBVType t)) => r)) -> r
    sbvAddNumTerm ::
      SBVType t ->
      SBVType t ->
      SBVType t
    sbvAddNumTerm l r = withSbvNumTermConstraint @t $ l + r
    sbvNegNumTerm ::
      SBVType t ->
      SBVType t
    sbvNegNumTerm l = withSbvNumTermConstraint @t $ -l
    sbvMulNumTerm ::
      SBVType t ->
      SBVType t ->
      SBVType t
    sbvMulNumTerm l r = withSbvNumTermConstraint @t $ l * r
    sbvAbsNumTerm ::
      SBVType t ->
      SBVType t
    sbvAbsNumTerm l = withSbvNumTermConstraint @t $ abs l
    sbvSignumNumTerm ::
      SBVType t ->
      SBVType t
    sbvSignumNumTerm l = withSbvNumTermConstraint @t $ signum l
  
  -- | Partial evaluation for subtraction terms.
  pevalSubNumTerm :: (PEvalNumTerm a) => Term a -> Term a -> Term a
  pevalSubNumTerm l r = pevalAddNumTerm l (pevalNegNumTerm r)
  
  -- | Partial evaluation and lowering for comparison terms.
  class PEvalOrdTerm t where
    pevalLtOrdTerm :: Term t -> Term t -> Term Bool
    pevalLeOrdTerm :: Term t -> Term t -> Term Bool
    withSbvOrdTermConstraint :: (((SBV.OrdSymbolic (SBVType t)) => r)) -> r
    sbvLtOrdTerm ::
      SBVType t ->
      SBVType t ->
      SBV.SBV Bool
    sbvLtOrdTerm l r = withSbvOrdTermConstraint @t $ l SBV..< r
    sbvLeOrdTerm :: SBVType t -> SBVType t -> SBV.SBV Bool
    sbvLeOrdTerm l r = withSbvOrdTermConstraint @t $ l SBV..<= r
  
  -- | Partial evaluation for greater than terms.
  pevalGtOrdTerm :: (PEvalOrdTerm a) => Term a -> Term a -> Term Bool
  pevalGtOrdTerm = flip pevalLtOrdTerm
  {-# INLINE pevalGtOrdTerm #-}
  
  -- | Partial evaluation for greater than or equal to terms.
  pevalGeOrdTerm :: (PEvalOrdTerm a) => Term a -> Term a -> Term Bool
  pevalGeOrdTerm = flip pevalLeOrdTerm
  {-# INLINE pevalGeOrdTerm #-}
  
  -- | Partial evaluation and lowering for integer division and modulo terms.
  class PEvalDivModIntegralTerm t where
    pevalDivIntegralTerm :: Term t -> Term t -> Term t
    pevalModIntegralTerm :: Term t -> Term t -> Term t
    pevalQuotIntegralTerm :: Term t -> Term t -> Term t
    pevalRemIntegralTerm :: Term t -> Term t -> Term t
    withSbvDivModIntegralTermConstraint ::
      (((SBV.SDivisible (SBVType t)) => r)) -> r
    sbvDivIntegralTerm :: SBVType t -> SBVType t -> SBVType t
    sbvDivIntegralTerm l r =
      withSbvDivModIntegralTermConstraint @t $ l `SBV.sDiv` r
    sbvModIntegralTerm :: SBVType t -> SBVType t -> SBVType t
    sbvModIntegralTerm l r =
      withSbvDivModIntegralTermConstraint @t $ l `SBV.sMod` r
    sbvQuotIntegralTerm :: SBVType t -> SBVType t -> SBVType t
    sbvQuotIntegralTerm l r =
      withSbvDivModIntegralTermConstraint @t $ l `SBV.sQuot` r
    sbvRemIntegralTerm :: SBVType t -> SBVType t -> SBVType t
    sbvRemIntegralTerm l r =
      withSbvDivModIntegralTermConstraint @t $ l `SBV.sRem` r
  
  -- | Partial evaluation and lowering for bitcast terms.
  class (BitCast a b) => PEvalBitCastTerm a b where
    pevalBitCastTerm :: Term a -> Term b
    sbvBitCast :: SBVType a -> SBVType b
  
  -- | Partial evaluation and lowering for bitcast or default value terms.
  class
    (BitCastOr a b) =>
    PEvalBitCastOrTerm a b
    where
    pevalBitCastOrTerm :: Term b -> Term a -> Term b
    sbvBitCastOr :: SBVType b -> SBVType a -> SBVType b
  
  -- | Partial evaluation and lowering for bit-vector terms.
  class
    ( SizedBV bv,
      forall n. (KnownNat n, 1 <= n) => PEvalNumTerm (bv n),
      forall n. (KnownNat n, 1 <= n) => PEvalBitwiseTerm (bv n),
      forall n. (KnownNat n, 1 <= n) => FiniteBits (bv n),
      forall n. (KnownNat n, 1 <= n) => Num (bv n)
    ) =>
    PEvalBVTerm bv
    where
    pevalBVConcatTerm ::
      (KnownNat l, KnownNat r, 1 <= l, 1 <= r) =>
      Term (bv l) ->
      Term (bv r) ->
      Term (bv (l + r))
    pevalBVExtendTerm ::
      (KnownNat l, KnownNat r, 1 <= l, 1 <= r, l <= r) =>
      Bool ->
      proxy r ->
      Term (bv l) ->
      Term (bv r)
    pevalBVSelectTerm ::
      (KnownNat n, KnownNat ix, KnownNat w, 1 <= n, 1 <= w, ix + w <= n) =>
      p ix ->
      q w ->
      Term (bv n) ->
      Term (bv w)
    sbvBVConcatTerm ::
      (KnownNat l, KnownNat r, 1 <= l, 1 <= r) =>
      p1 l ->
      p2 r ->
      SBVType (bv l) ->
      SBVType (bv r) ->
      SBVType (bv (l + r))
    sbvBVExtendTerm ::
      (KnownNat l, KnownNat r, 1 <= l, 1 <= r, l <= r) =>
      p1 l ->
      p2 r ->
      Bool ->
      SBVType (bv l) ->
      SBVType (bv r)
    sbvBVSelectTerm ::
      ( KnownNat ix,
        KnownNat w,
        KnownNat n,
        1 <= n,
        1 <= w,
        ix + w <= n
      ) =>
      p1 ix ->
      p2 w ->
      p3 n ->
      SBVType (bv n) ->
      SBVType (bv w)
  
  -- | Partial evaluation and lowering for fractional terms.
  class (Fractional t) => PEvalFractionalTerm t where
    pevalFdivTerm :: Term t -> Term t -> Term t
    pevalRecipTerm :: Term t -> Term t
    withSbvFractionalTermConstraint ::
      (((Fractional (SBVType t)) => r)) ->
      r
    sbvFdivTerm ::
      SBVType t ->
      SBVType t ->
      SBVType t
    sbvFdivTerm l r = withSbvFractionalTermConstraint @t $ l / r
    sbvRecipTerm ::
      SBVType t ->
      SBVType t
    sbvRecipTerm l = withSbvFractionalTermConstraint @t $ recip l
  
  -- | Unary floating point operations.
  data FloatingUnaryOp
    = FloatingExp
    | FloatingLog
    | FloatingSqrt
    | FloatingSin
    | FloatingCos
    | FloatingTan
    | FloatingAsin
    | FloatingAcos
    | FloatingAtan
    | FloatingSinh
    | FloatingCosh
    | FloatingTanh
    | FloatingAsinh
    | FloatingAcosh
    | FloatingAtanh
    deriving (Eq, Ord, Generic, Hashable, Lift, NFData, Serial)
  
  instance Cereal.Serialize FloatingUnaryOp where
    put = serialize
    get = deserialize
  
  instance Binary.Binary FloatingUnaryOp where
    put = serialize
    get = deserialize
  
  instance Show FloatingUnaryOp where
    show FloatingExp = "exp"
    show FloatingLog = "log"
    show FloatingSqrt = "sqrt"
    show FloatingSin = "sin"
    show FloatingCos = "cos"
    show FloatingTan = "tan"
    show FloatingAsin = "asin"
    show FloatingAcos = "acos"
    show FloatingAtan = "atan"
    show FloatingSinh = "sinh"
    show FloatingCosh = "cosh"
    show FloatingTanh = "tanh"
    show FloatingAsinh = "asinh"
    show FloatingAcosh = "acosh"
    show FloatingAtanh = "atanh"
  
  -- | Partial evaluation and lowering for floating point terms.
  class PEvalFPTerm fp where
    pevalFPTraitTerm :: (ValidFP eb sb) => FPTrait -> Term (fp eb sb) -> Term Bool
    pevalFPUnaryTerm ::
      (ValidFP eb sb) =>
      FPUnaryOp ->
      Term (fp eb sb) ->
      Term (fp eb sb)
    pevalFPBinaryTerm ::
      (ValidFP eb sb) =>
      FPBinaryOp ->
      Term (fp eb sb) ->
      Term (fp eb sb) ->
      Term (fp eb sb)
    pevalFPRoundingUnaryTerm ::
      (ValidFP eb sb) =>
      FPRoundingUnaryOp ->
      Term FPRoundingMode ->
      Term (fp eb sb) ->
      Term (fp eb sb)
    pevalFPRoundingBinaryTerm ::
      (ValidFP eb sb) =>
      FPRoundingBinaryOp ->
      Term FPRoundingMode ->
      Term (fp eb sb) ->
      Term (fp eb sb) ->
      Term (fp eb sb)
    pevalFPFMATerm ::
      (ValidFP eb sb) =>
      Term FPRoundingMode ->
      Term (fp eb sb) ->
      Term (fp eb sb) ->
      Term (fp eb sb) ->
      Term (fp eb sb)
    sbvFPTraitTerm ::
      (ValidFP eb sb) => FPTrait -> SBVType (fp eb sb) -> SBVType Bool
    sbvFPUnaryTerm ::
      (ValidFP eb sb) =>
      FPUnaryOp ->
      SBVType (fp eb sb) ->
      SBVType (fp eb sb)
    sbvFPBinaryTerm ::
      (ValidFP eb sb) =>
      FPBinaryOp ->
      SBVType (fp eb sb) ->
      SBVType (fp eb sb) ->
      SBVType (fp eb sb)
    sbvFPRoundingUnaryTerm ::
      (ValidFP eb sb) =>
      FPRoundingUnaryOp ->
      SBVType FPRoundingMode ->
      SBVType (fp eb sb) ->
      SBVType (fp eb sb)
    sbvFPRoundingBinaryTerm ::
      (ValidFP eb sb) =>
      FPRoundingBinaryOp ->
      SBVType FPRoundingMode ->
      SBVType (fp eb sb) ->
      SBVType (fp eb sb) ->
      SBVType (fp eb sb)
    sbvFPFMATerm ::
      (ValidFP eb sb) =>
      SBVType FPRoundingMode ->
      SBVType (fp eb sb) ->
      SBVType (fp eb sb) ->
      SBVType (fp eb sb) ->
      SBVType (fp eb sb)
  
  -- | Partial evaluation and lowering for floating point terms.
  class PEvalFloatingTerm t where
    pevalFloatingUnaryTerm :: FloatingUnaryOp -> Term t -> Term t
    pevalPowerTerm :: Term t -> Term t -> Term t
    withSbvFloatingTermConstraint ::
      (((Floating (SBVType t)) => r)) ->
      r
    sbvPowerTerm ::
      SBVType t ->
      SBVType t ->
      SBVType t
    sbvPowerTerm = withSbvFloatingTermConstraint @t (**)
    sbvFloatingUnaryTerm ::
      FloatingUnaryOp ->
      SBVType t ->
      SBVType t
    sbvFloatingUnaryTerm op l =
      withSbvFloatingTermConstraint @t $
        case op of
          FloatingExp -> exp l
          FloatingLog -> log l
          FloatingSqrt -> sqrt l
          FloatingSin -> sin l
          FloatingCos -> cos l
          FloatingTan -> tan l
          FloatingAsin -> asin l
          FloatingAcos -> acos l
          FloatingAtan -> atan l
          FloatingSinh -> sinh l
          FloatingCosh -> cosh l
          FloatingTanh -> tanh l
          FloatingAsinh -> asinh l
          FloatingAcosh -> acosh l
          FloatingAtanh -> atanh l
  
  -- | Partial evaluation and lowering for integral terms.
  class (Integral a, Num b) => PEvalFromIntegralTerm a b where
    pevalFromIntegralTerm :: Term a -> Term b
    sbvFromIntegralTerm :: SBVType a -> SBVType b
  
  -- | Partial evaluation and lowering for converting from and to IEEE floating
  -- point terms.
  class PEvalIEEEFPConvertibleTerm a where
    pevalFromFPOrTerm ::
      (ValidFP eb sb) =>
      Term a ->
      Term FPRoundingMode ->
      Term (FP eb sb) ->
      Term a
    pevalToFPTerm ::
      (ValidFP eb sb) => Term FPRoundingMode -> Term a -> Term (FP eb sb)
    sbvFromFPOrTerm ::
      (ValidFP eb sb) =>
      SBVType a ->
      SBVType FPRoundingMode ->
      SBVType (FP eb sb) ->
      SBVType a
    sbvToFPTerm ::
      (ValidFP eb sb) =>
      SBVType FPRoundingMode ->
      SBVType a ->
      SBVType (FP eb sb)
  
  -- Typed Symbols
  
  -- | The kind of a symbol.
  --
  -- All symbols are 'AnyKind', and all symbols other than general/tabular
  -- functions are 'ConstantKind'.
  data SymbolKind = ConstantKind | AnyKind
  
  -- | Decision procedure for symbol kinds.
  class IsSymbolKind (knd :: SymbolKind) where
    type SymbolKindConstraint knd :: Type -> Constraint
    decideSymbolKind :: Either (knd :~~: 'ConstantKind) (knd :~~: 'AnyKind)
    withSymbolKindConstraint ::
      TypedSymbol knd t ->
      ((SymbolKindConstraint knd t) => a) ->
      a
  
  instance IsSymbolKind 'ConstantKind where
    type SymbolKindConstraint 'ConstantKind = SupportedNonFuncPrim
    decideSymbolKind = Left HRefl
    withSymbolKindConstraint r = withConstantSymbolSupported r
  
  instance IsSymbolKind 'AnyKind where
    type SymbolKindConstraint 'AnyKind = SupportedPrim
    decideSymbolKind = Right HRefl
    withSymbolKindConstraint r = withSymbolSupported r
  
  -- | A typed symbol is a symbol that is associated with a type. Note that the
  -- same symbol bodies with different types are considered different symbols
  -- and can coexist in a term.
  --
  -- Simple symbols can be created with the @OverloadedStrings@ extension:
  --
  -- >>> "a" :: TypedSymbol 'AnyKind Bool
  -- a :: Bool
  data TypedSymbol (knd :: SymbolKind) t where
    TypedSymbol ::
      ( SupportedPrim t,
        SymbolKindConstraint knd t,
        IsSymbolKind knd
      ) =>
      {unTypedSymbol :: Symbol} ->
      TypedSymbol knd t
  
  -- | Create a typed symbol with constant kinds.
  typedConstantSymbol ::
    forall t. (SupportedNonFuncPrim t) => Symbol -> TypedSymbol 'ConstantKind t
  typedConstantSymbol = typedConstantSymbol' getPhantomNonFuncDict
  {-# INLINE typedConstantSymbol #-}
  
  {-# NOINLINE typedConstantSymbol' #-}
  typedConstantSymbol' ::
    forall t. PhantomNonFuncDict t -> Symbol -> TypedSymbol 'ConstantKind t
  typedConstantSymbol' PhantomNonFuncDict symbol = TypedSymbol symbol
  
  -- | Create a typed symbol with any kinds.
  typedAnySymbol ::
    forall t. (SupportedPrim t) => Symbol -> TypedSymbol 'AnyKind t
  typedAnySymbol = typedAnySymbol' getPhantomDict
  {-# INLINE typedAnySymbol #-}
  
  {-# NOINLINE typedAnySymbol' #-}
  typedAnySymbol' ::
    forall t. PhantomDict t -> Symbol -> TypedSymbol 'AnyKind t
  typedAnySymbol' PhantomDict symbol = TypedSymbol symbol
  
  -- | Constant symbol
  type TypedConstantSymbol = TypedSymbol 'ConstantKind
  
  -- | Any symbol
  type TypedAnySymbol = TypedSymbol 'AnyKind
  
  instance Eq (TypedSymbol knd t) where
    TypedSymbol x == TypedSymbol y = x == y
  
  instance Ord (TypedSymbol knd t) where
    TypedSymbol x <= TypedSymbol y = x <= y
  
  instance Lift (TypedSymbol knd t) where
    liftTyped (TypedSymbol x) = [||TypedSymbol x||]
  
  instance Show (TypedSymbol knd t) where
    show (TypedSymbol symbol) = show symbol ++ " :: " ++ show (primTypeRep @t)
  
  -- | Show a typed symbol without the type information.
  showUntyped :: TypedSymbol knd t -> String
  showUntyped (TypedSymbol symbol) = show symbol
  
  instance Hashable (TypedSymbol knd t) where
    s `hashWithSalt` TypedSymbol x = s `hashWithSalt` x
  
  instance NFData (TypedSymbol knd t) where
    rnf (TypedSymbol str) = rnf str
  
  instance
    ( SupportedPrim t,
      SymbolKindConstraint knd t,
      IsSymbolKind knd
    ) =>
    IsString (TypedSymbol knd t)
    where
    fromString = TypedSymbol . fromString
  
  -- | Introduce the 'SupportedPrim' constraint from the t'TypedSymbol'.
  withSymbolSupported ::
    forall knd t a.
    TypedSymbol knd t ->
    ((SupportedPrim t) => a) ->
    a
  withSymbolSupported (TypedSymbol _) a = a
  {-# INLINE withSymbolSupported #-}
  
  -- | Introduce the 'SupportedPrim' constraint from the t'TypedSymbol'.
  withConstantSymbolSupported ::
    forall t a.
    TypedSymbol 'ConstantKind t ->
    ((SupportedNonFuncPrim t) => a) ->
    a
  withConstantSymbolSupported (TypedSymbol _) a = a
  {-# INLINE withConstantSymbolSupported #-}
  
  -- | A non-indexed symbol. Type information are checked at runtime.
  data SomeTypedSymbol knd where
    SomeTypedSymbol ::
      forall knd t.
      TypedSymbol knd t ->
      SomeTypedSymbol knd
  
  -- | Non-indexed constant symbol
  type SomeTypedConstantSymbol = SomeTypedSymbol 'ConstantKind
  
  -- | Non-indexed any symbol
  type SomeTypedAnySymbol = SomeTypedSymbol 'AnyKind
  
  instance NFData (SomeTypedSymbol knd) where
    rnf (SomeTypedSymbol s) = rnf s
    {-# INLINE rnf #-}
  
  instance Lift (SomeTypedSymbol knd) where
    liftTyped (SomeTypedSymbol s) = [||SomeTypedSymbol s||]
  
  instance Eq (SomeTypedSymbol knd) where
    (SomeTypedSymbol (s1 :: TypedSymbol knd a))
      == (SomeTypedSymbol (s2 :: TypedSymbol knd b)) =
        withSymbolSupported s1 $
          withSymbolSupported s2 $
            case eqTypeRep (primTypeRep @a) (primTypeRep @b) of
              Just HRefl -> s1 == s2
              _ -> False
    {-# INLINE (==) #-}
  
  instance Ord (SomeTypedSymbol knd) where
    (SomeTypedSymbol (s1 :: TypedSymbol knd a))
      <= (SomeTypedSymbol (s2 :: TypedSymbol knd b)) =
        withSymbolSupported s1 $
          withSymbolSupported s2 $
            let t1 = primTypeRep @a
                t2 = primTypeRep @b
             in SomeTypeRep t1 < SomeTypeRep t2
                  || ( case eqTypeRep t1 t2 of
                         Just HRefl -> s1 <= s2
                         _ -> False
                     )
  
  instance Hashable (SomeTypedSymbol knd) where
    hashWithSalt s (SomeTypedSymbol s1) = s `hashWithSalt` s1
    {-# INLINE hashWithSalt #-}
  
  instance Show (SomeTypedSymbol knd) where
    show (SomeTypedSymbol s) = show s
  
  -- | Construct a t'SomeTypedSymbol' from a t'TypedSymbol'.
  someTypedSymbol :: forall knd t. TypedSymbol knd t -> SomeTypedSymbol knd
  someTypedSymbol s@(TypedSymbol _) = SomeTypedSymbol s
  {-# INLINE someTypedSymbol #-}
  
  -- Terms
  
  -- | Traits for IEEE floating point numbers.
  data FPTrait
    = FPIsNaN
    | FPIsPositive
    | FPIsNegative
    | FPIsPositiveInfinite
    | FPIsNegativeInfinite
    | FPIsInfinite
    | FPIsPositiveZero
    | FPIsNegativeZero
    | FPIsZero
    | FPIsNormal
    | FPIsSubnormal
    | FPIsPoint
    deriving (Eq, Ord, Generic, Hashable, Lift, NFData, Serial)
  
  instance Cereal.Serialize FPTrait where
    put = serialize
    get = deserialize
  
  instance Binary.Binary FPTrait where
    put = serialize
    get = deserialize
  
  instance Show FPTrait where
    show FPIsNaN = "is_nan"
    show FPIsPositive = "is_pos"
    show FPIsNegative = "is_neg"
    show FPIsPositiveInfinite = "is_pos_inf"
    show FPIsNegativeInfinite = "is_neg_inf"
    show FPIsInfinite = "is_inf"
    show FPIsPositiveZero = "is_pos_zero"
    show FPIsNegativeZero = "is_neg_zero"
    show FPIsZero = "is_zero"
    show FPIsNormal = "is_normal"
    show FPIsSubnormal = "is_subnormal"
    show FPIsPoint = "is_point"
  
  -- | Unary floating point operations.
  data FPUnaryOp = FPAbs | FPNeg
    deriving (Eq, Ord, Generic, Hashable, Lift, NFData, Serial)
  
  instance Cereal.Serialize FPUnaryOp where
    put = serialize
    get = deserialize
  
  instance Binary.Binary FPUnaryOp where
    put = serialize
    get = deserialize
  
  instance Show FPUnaryOp where
    show FPAbs = "fp.abs"
    show FPNeg = "fp.neg"
  
  -- | Binary floating point operations.
  data FPBinaryOp
    = FPRem
    | FPMinimum
    | FPMinimumNumber
    | FPMaximum
    | FPMaximumNumber
    deriving (Eq, Ord, Generic, Hashable, Lift, NFData, Serial)
  
  instance Cereal.Serialize FPBinaryOp where
    put = serialize
    get = deserialize
  
  instance Binary.Binary FPBinaryOp where
    put = serialize
    get = deserialize
  
  instance Show FPBinaryOp where
    show FPRem = "fp.rem"
    show FPMinimum = "fp.minimum"
    show FPMinimumNumber = "fp.minimumNumber"
    show FPMaximum = "fp.maximum"
    show FPMaximumNumber = "fp.maximumNumber"
  
  -- | Unary floating point operations with rounding modes.
  data FPRoundingUnaryOp = FPSqrt | FPRoundToIntegral
    deriving (Eq, Ord, Generic, Hashable, Lift, NFData, Serial)
  
  instance Cereal.Serialize FPRoundingUnaryOp where
    put = serialize
    get = deserialize
  
  instance Binary.Binary FPRoundingUnaryOp where
    put = serialize
    get = deserialize
  
  instance Show FPRoundingUnaryOp where
    show FPSqrt = "fp.sqrt"
    show FPRoundToIntegral = "fp.roundToIntegral"
  
  -- | Binary floating point operations with rounding modes.
  data FPRoundingBinaryOp = FPAdd | FPSub | FPMul | FPDiv
    deriving (Eq, Ord, Generic, Hashable, Lift, NFData, Serial)
  
  instance Cereal.Serialize FPRoundingBinaryOp where
    put = serialize
    get = deserialize
  
  instance Binary.Binary FPRoundingBinaryOp where
    put = serialize
    get = deserialize
  
  instance Show FPRoundingBinaryOp where
    show FPAdd = "fp.add"
    show FPSub = "fp.sub"
    show FPMul = "fp.mul"
    show FPDiv = "fp.div"
  
  instance NFData CachedInfo where
    rnf (CachedInfo tid digest id stableIdent) =
      rnf tid `seq` rnf digest `seq` rnf id `seq` rnf stableIdent
  
  -- | Internal representation for Grisette symbolic terms.
  data Term t where
    ConTerm' ::
      (SupportedPrim t) =>
      {-# UNPACK #-} !CachedInfo ->
      !t ->
      Term t
    SymTerm' ::
      {-# UNPACK #-} !CachedInfo ->
      !(TypedSymbol 'AnyKind t) ->
      Term t
    ForallTerm' ::
      {-# UNPACK #-} !CachedInfo ->
      !(TypedSymbol 'ConstantKind t) ->
      !(Term Bool) ->
      Term Bool
    ExistsTerm' ::
      {-# UNPACK #-} !CachedInfo ->
      !(TypedSymbol 'ConstantKind t) ->
      !(Term Bool) ->
      Term Bool
    NotTerm' ::
      {-# UNPACK #-} !CachedInfo ->
      !(Term Bool) ->
      Term Bool
    OrTerm' ::
      {-# UNPACK #-} !CachedInfo ->
      !(Term Bool) ->
      !(Term Bool) ->
      !(HS.HashSet (Term Bool)) -> -- All or'ed terms
      Term Bool
    AndTerm' ::
      {-# UNPACK #-} !CachedInfo ->
      !(Term Bool) ->
      !(Term Bool) ->
      !(HS.HashSet (Term Bool)) -> -- All and'ed terms
      Term Bool
    EqTerm' ::
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term Bool
    DistinctTerm' ::
      {-# UNPACK #-} !CachedInfo ->
      !(NonEmpty (Term t)) ->
      Term Bool
    ITETerm' ::
      (SupportedPrim t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term Bool) ->
      !(Term t) ->
      !(Term t) ->
      Term t
    AddNumTerm' ::
      (SupportedPrim t, PEvalNumTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    NegNumTerm' ::
      (SupportedPrim t, PEvalNumTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      Term t
    MulNumTerm' ::
      (SupportedPrim t, PEvalNumTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    AbsNumTerm' ::
      (SupportedPrim t, PEvalNumTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      Term t
    SignumNumTerm' ::
      (SupportedPrim t, PEvalNumTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      Term t
    LtOrdTerm' ::
      (SupportedPrim t, PEvalOrdTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term Bool
    LeOrdTerm' ::
      (SupportedPrim t, PEvalOrdTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term Bool
    AndBitsTerm' ::
      (SupportedPrim t, PEvalBitwiseTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    OrBitsTerm' ::
      (SupportedPrim t, PEvalBitwiseTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    XorBitsTerm' ::
      (SupportedPrim t, PEvalBitwiseTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    ComplementBitsTerm' ::
      (SupportedPrim t, PEvalBitwiseTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      Term t
    ShiftLeftTerm' ::
      (SupportedPrim t, PEvalShiftTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    ShiftRightTerm' ::
      (SupportedPrim t, PEvalShiftTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    RotateLeftTerm' ::
      (SupportedPrim t, PEvalRotateTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    RotateRightTerm' ::
      (SupportedPrim t, PEvalRotateTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    BitCastTerm' ::
      (SupportedPrim b, PEvalBitCastTerm a b) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term a) ->
      Term b
    BitCastOrTerm' ::
      (SupportedPrim b, PEvalBitCastOrTerm a b) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term b) ->
      !(Term a) ->
      Term b
    BVConcatTerm' ::
      ( PEvalBVTerm bv,
        KnownNat l,
        KnownNat r,
        KnownNat (l + r),
        1 <= l,
        1 <= r,
        1 <= l + r,
        SupportedPrim (bv (l + r))
      ) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term (bv l)) ->
      !(Term (bv r)) ->
      Term (bv (l + r))
    BVSelectTerm' ::
      ( PEvalBVTerm bv,
        KnownNat n,
        KnownNat ix,
        KnownNat w,
        1 <= n,
        1 <= w,
        ix + w <= n,
        SupportedPrim (bv w)
      ) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Proxy ix) ->
      !(Proxy w) ->
      !(Term (bv n)) ->
      Term (bv w)
    BVExtendTerm' ::
      ( PEvalBVTerm bv,
        KnownNat l,
        KnownNat r,
        1 <= l,
        1 <= r,
        l <= r,
        SupportedPrim (bv r)
      ) =>
      {-# UNPACK #-} !CachedInfo ->
      !Bool ->
      !(Proxy r) ->
      !(Term (bv l)) ->
      Term (bv r)
    ApplyTerm' ::
      (PEvalApplyTerm f a b, SupportedPrim b) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term f) ->
      !(Term a) ->
      Term b
    DivIntegralTerm' ::
      (SupportedPrim t, PEvalDivModIntegralTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    ModIntegralTerm' ::
      (SupportedPrim t, PEvalDivModIntegralTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    QuotIntegralTerm' ::
      (SupportedPrim t, PEvalDivModIntegralTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    RemIntegralTerm' ::
      (SupportedPrim t, PEvalDivModIntegralTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    FPTraitTerm' ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      {-# UNPACK #-} !CachedInfo ->
      !FPTrait ->
      !(Term (fp eb sb)) ->
      Term Bool
    FdivTerm' ::
      (SupportedPrim t, PEvalFractionalTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    RecipTerm' ::
      (SupportedPrim t, PEvalFractionalTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      Term t
    FloatingUnaryTerm' ::
      (SupportedPrim t, PEvalFloatingTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !FloatingUnaryOp ->
      !(Term t) ->
      Term t
    PowerTerm' ::
      (SupportedPrim t, PEvalFloatingTerm t) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term t) ->
      !(Term t) ->
      Term t
    FPUnaryTerm' ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      {-# UNPACK #-} !CachedInfo ->
      !FPUnaryOp ->
      !(Term (fp eb sb)) ->
      Term (fp eb sb)
    FPBinaryTerm' ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      {-# UNPACK #-} !CachedInfo ->
      !FPBinaryOp ->
      !(Term (fp eb sb)) ->
      !(Term (fp eb sb)) ->
      Term (fp eb sb)
    FPRoundingUnaryTerm' ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      {-# UNPACK #-} !CachedInfo ->
      !FPRoundingUnaryOp ->
      !(Term FPRoundingMode) ->
      !(Term (fp eb sb)) ->
      Term (fp eb sb)
    FPRoundingBinaryTerm' ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      {-# UNPACK #-} !CachedInfo ->
      !FPRoundingBinaryOp ->
      !(Term FPRoundingMode) ->
      !(Term (fp eb sb)) ->
      !(Term (fp eb sb)) ->
      Term (fp eb sb)
    FPFMATerm' ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term FPRoundingMode) ->
      !(Term (fp eb sb)) ->
      !(Term (fp eb sb)) ->
      !(Term (fp eb sb)) ->
      Term (fp eb sb)
    FromIntegralTerm' ::
      (PEvalFromIntegralTerm a b, SupportedPrim b) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term a) ->
      Term b
    FromFPOrTerm' ::
      ( PEvalIEEEFPConvertibleTerm a,
        ValidFP eb sb,
        SupportedPrim a
      ) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term a) ->
      !(Term FPRoundingMode) ->
      !(Term (FP eb sb)) ->
      Term a
    ToFPTerm' ::
      ( PEvalIEEEFPConvertibleTerm a,
        ValidFP eb sb,
        SupportedPrim (FP eb sb)
      ) =>
      {-# UNPACK #-} !CachedInfo ->
      !(Term FPRoundingMode) ->
      !(Term a) ->
      Proxy eb ->
      Proxy sb ->
      Term (FP eb sb)
  
  data SupportedPrimEvidence t where
    SupportedPrimEvidence :: (SupportedPrim t) => SupportedPrimEvidence t
  
  -- | Pattern synonym to introduce the SupportedPrim constraint.
  pattern SupportedTerm :: forall t. () => (SupportedPrim t) => Term t
  pattern SupportedTerm <-
    ( ( \v ->
          introSupportedPrimConstraint v $
            Just (SupportedPrimEvidence @t)
      ) ->
        Just SupportedPrimEvidence
      )
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# COMPLETE SupportedTerm #-}
  {-# INLINE SupportedTerm #-}
  #endif
  
  -- | Cast a term to another type.
  castTerm :: forall a b. (Typeable b) => Term a -> Maybe (Term b)
  castTerm t@SupportedTerm = cast t
  {-# INLINE castTerm #-}
  
  data SupportedTypedSymbolEvidence (k :: SymbolKind) t where
    SupportedTypedSymbolEvidence ::
      forall k t.
      (SupportedPrim t, SymbolKindConstraint k t, IsSymbolKind k) =>
      SupportedTypedSymbolEvidence k t
  
  supportedTypedSymbolViewPat ::
    TypedSymbol k t -> Maybe (SupportedTypedSymbolEvidence k t)
  supportedTypedSymbolViewPat (TypedSymbol _) = Just SupportedTypedSymbolEvidence
  
  -- | Pattern synonym to introduce constraints from a t'TypedSymbol'.
  pattern SupportedTypedSymbol ::
    forall (k :: SymbolKind) t.
    () =>
    (SupportedPrim t, SymbolKindConstraint k t, IsSymbolKind k) =>
    TypedSymbol k t
  pattern SupportedTypedSymbol <-
    (supportedTypedSymbolViewPat -> Just SupportedTypedSymbolEvidence)
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# COMPLETE SupportedTypedSymbol #-}
  {-# INLINE SupportedTypedSymbol #-}
  #endif
  
  data SupportedConstantTypedSymbolEvidence k t where
    SupportedConstantTypedSymbolEvidence ::
      forall k t.
      ( SupportedPrim t,
        SymbolKindConstraint k t,
        IsSymbolKind k,
        k ~ 'ConstantKind
      ) =>
      SupportedConstantTypedSymbolEvidence k t
  
  supportedConstantTypedSymbolViewPat ::
    forall k t.
    TypedSymbol k t ->
    Maybe (SupportedConstantTypedSymbolEvidence k t)
  supportedConstantTypedSymbolViewPat (TypedSymbol _) =
    case decideSymbolKind @k of
      Left HRefl -> Just SupportedConstantTypedSymbolEvidence
      Right _ -> Nothing
  
  -- | Pattern synonym to introduce constraints from a t'TypedSymbol'. Also checks
  -- that the symbol kind is 'ConstantKind'.
  pattern SupportedConstantTypedSymbol ::
    forall k t.
    () =>
    ( SupportedPrim t,
      SymbolKindConstraint k t,
      IsSymbolKind k,
      k ~ 'ConstantKind
    ) =>
    TypedSymbol k t
  pattern SupportedConstantTypedSymbol <-
    ( supportedConstantTypedSymbolViewPat ->
        Just SupportedConstantTypedSymbolEvidence
      )
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE SupportedConstantTypedSymbol #-}
  #endif
  
  -- | Pattern synonym for 'ConTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern ConTerm :: forall t. () => (SupportedPrim t) => t -> Term t
  pattern ConTerm t <- (ConTerm' _ t)
    where
      ConTerm = conTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE ConTerm #-}
  #endif
  
  -- | Pattern synonym for 'SymTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern SymTerm ::
    forall t. () => (SupportedPrim t) => TypedSymbol 'AnyKind t -> Term t
  pattern SymTerm t <- (SymTerm' _ t@SupportedTypedSymbol)
    where
      SymTerm = symTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE SymTerm #-}
  #endif
  
  -- | Pattern synonym for 'ForallTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern ForallTerm ::
    forall r.
    () =>
    forall t.
    (r ~ Bool, SupportedNonFuncPrim t) =>
    TypedSymbol 'ConstantKind t ->
    Term Bool ->
    Term r
  pattern ForallTerm sym body <-
    (ForallTerm' _ sym@SupportedConstantTypedSymbol body)
    where
      ForallTerm = forallTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE ForallTerm #-}
  #endif
  
  -- | Pattern synonym for 'ExistsTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern ExistsTerm ::
    forall r.
    () =>
    forall t.
    (r ~ Bool, SupportedNonFuncPrim t) =>
    TypedSymbol 'ConstantKind t ->
    Term Bool ->
    Term r
  pattern ExistsTerm sym body <-
    (ExistsTerm' _ sym@SupportedConstantTypedSymbol body)
    where
      ExistsTerm = existsTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE ExistsTerm #-}
  #endif
  
  -- | Pattern synonym for 'NotTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern NotTerm :: forall r. () => (r ~ Bool) => Term Bool -> Term r
  pattern NotTerm body <- (NotTerm' _ body)
    where
      NotTerm = pevalNotTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE NotTerm #-}
  #endif
  
  -- | Pattern synonym for 'OrTerm''. Note that using this pattern to construct a
  -- 'Term' will do term simplification.
  pattern OrTerm :: forall r. () => (r ~ Bool) => Term Bool -> Term Bool -> Term r
  pattern OrTerm l r <- (OrTerm' _ l r _)
    where
      OrTerm = pevalOrTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE OrTerm #-}
  #endif
  
  -- | Pattern synonym for 'OrTerm''. Note that using this pattern to construct a
  -- 'Term' will do term simplification.
  pattern OrTermAll ::
    forall r.
    () =>
    (r ~ Bool) =>
    Term Bool -> Term Bool -> HS.HashSet (Term Bool) -> Term r
  pattern OrTermAll l r s <- (OrTerm' _ l r s)
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE OrTermAll #-}
  #endif
  
  -- | Pattern synonym for 'AndTerm''. Note that using this pattern to construct a
  -- 'Term' will do term simplification.
  pattern AndTerm :: forall r. () => (r ~ Bool) => Term Bool -> Term Bool -> Term r
  pattern AndTerm l r <- (AndTerm' _ l r _)
    where
      AndTerm = pevalAndTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE AndTerm #-}
  #endif
  
  -- | Pattern synonym for 'AndTerm''. Note that using this pattern to construct a
  -- 'Term' will do term simplification.
  pattern AndTermAll ::
    forall r.
    () =>
    (r ~ Bool) =>
    Term Bool -> Term Bool -> HS.HashSet (Term Bool) -> Term r
  pattern AndTermAll l r s <- (AndTerm' _ l r s)
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE AndTermAll #-}
  #endif
  
  -- | Pattern synonym for 'EqTerm''. Note that using this pattern to construct a
  -- 'Term' will do term simplification.
  pattern EqTerm ::
    forall r.
    () =>
    forall t.
    (r ~ Bool, SupportedPrim t) =>
    Term t ->
    Term t ->
    Term r
  pattern EqTerm l r <- (EqTerm' _ l r@SupportedTerm)
    where
      EqTerm = pevalEqTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE EqTerm #-}
  #endif
  
  -- | Pattern synonym for 'DistinctTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern DistinctTerm ::
    forall r.
    () =>
    forall t.
    (r ~ Bool, SupportedPrim t) =>
    NonEmpty (Term t) ->
    Term r
  pattern DistinctTerm ts <- (DistinctTerm' _ ts@(SupportedTerm :| _))
    where
      DistinctTerm = pevalDistinctTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE DistinctTerm #-}
  #endif
  
  -- | Pattern synonym for 'ITETerm''. Note that using this pattern to construct a
  -- 'Term' will do term simplification.
  pattern ITETerm ::
    forall t.
    () =>
    (SupportedPrim t) =>
    Term Bool ->
    Term t ->
    Term t ->
    Term t
  pattern ITETerm cond t1 t2 <- (ITETerm' _ cond t1 t2)
    where
      ITETerm = pevalITETerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE ITETerm #-}
  #endif
  
  -- | Pattern synonym for 'AddNumTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern AddNumTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalNumTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern AddNumTerm l r <- (AddNumTerm' _ l r)
    where
      AddNumTerm = pevalAddNumTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE AddNumTerm #-}
  #endif
  
  -- | Pattern synonym for 'NegNumTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern NegNumTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalNumTerm t) =>
    Term t ->
    Term t
  pattern NegNumTerm t <- (NegNumTerm' _ t)
    where
      NegNumTerm = pevalNegNumTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE NegNumTerm #-}
  #endif
  
  -- | Pattern synonym for 'MulNumTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern MulNumTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalNumTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern MulNumTerm l r <- (MulNumTerm' _ l r)
    where
      MulNumTerm = pevalMulNumTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE MulNumTerm #-}
  #endif
  
  -- | Pattern synonym for 'AbsNumTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern AbsNumTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalNumTerm t) =>
    Term t ->
    Term t
  pattern AbsNumTerm t <- (AbsNumTerm' _ t)
    where
      AbsNumTerm = pevalAbsNumTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE AbsNumTerm #-}
  #endif
  
  -- | Pattern synonym for 'SignumNumTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern SignumNumTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalNumTerm t) =>
    Term t ->
    Term t
  pattern SignumNumTerm t <- (SignumNumTerm' _ t)
    where
      SignumNumTerm = pevalSignumNumTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE SignumNumTerm #-}
  #endif
  
  -- | Pattern synonym for 'LtOrdTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern LtOrdTerm ::
    forall r.
    () =>
    forall t.
    (r ~ Bool, SupportedPrim t, PEvalOrdTerm t) =>
    Term t ->
    Term t ->
    Term r
  pattern LtOrdTerm l r <- (LtOrdTerm' _ l r@SupportedTerm)
    where
      LtOrdTerm = pevalLtOrdTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE LtOrdTerm #-}
  #endif
  
  -- | Pattern synonym for 'LeOrdTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern LeOrdTerm ::
    forall r.
    () =>
    forall t.
    (r ~ Bool, SupportedPrim t, PEvalOrdTerm t) =>
    Term t ->
    Term t ->
    Term r
  pattern LeOrdTerm l r <- (LeOrdTerm' _ l r@SupportedTerm)
    where
      LeOrdTerm = pevalLeOrdTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE LeOrdTerm #-}
  #endif
  
  -- | Pattern synonym for 'AndBitsTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern AndBitsTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalBitwiseTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern AndBitsTerm l r <- (AndBitsTerm' _ l r)
    where
      AndBitsTerm = pevalAndBitsTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE AndBitsTerm #-}
  #endif
  
  -- | Pattern synonym for 'OrBitsTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern OrBitsTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalBitwiseTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern OrBitsTerm l r <- (OrBitsTerm' _ l r)
    where
      OrBitsTerm = pevalOrBitsTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE OrBitsTerm #-}
  #endif
  
  -- | Pattern synonym for 'XorBitsTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern XorBitsTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalBitwiseTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern XorBitsTerm l r <- (XorBitsTerm' _ l r)
    where
      XorBitsTerm = pevalXorBitsTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE XorBitsTerm #-}
  #endif
  
  -- | Pattern synonym for 'ComplementBitsTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern ComplementBitsTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalBitwiseTerm t) =>
    Term t ->
    Term t
  pattern ComplementBitsTerm t <- (ComplementBitsTerm' _ t)
    where
      ComplementBitsTerm = pevalComplementBitsTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE ComplementBitsTerm #-}
  #endif
  
  -- | Pattern synonym for 'ShiftLeftTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern ShiftLeftTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalShiftTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern ShiftLeftTerm l r <- (ShiftLeftTerm' _ l r)
    where
      ShiftLeftTerm = pevalShiftLeftTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE ShiftLeftTerm #-}
  #endif
  
  -- | Pattern synonym for 'ShiftRightTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern ShiftRightTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalShiftTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern ShiftRightTerm l r <- (ShiftRightTerm' _ l r)
    where
      ShiftRightTerm = pevalShiftRightTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE ShiftRightTerm #-}
  #endif
  
  -- | Pattern synonym for 'RotateLeftTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern RotateLeftTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalRotateTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern RotateLeftTerm l r <- (RotateLeftTerm' _ l r)
    where
      RotateLeftTerm = pevalRotateLeftTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE RotateLeftTerm #-}
  #endif
  
  -- | Pattern synonym for 'RotateRightTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern RotateRightTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalRotateTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern RotateRightTerm l r <- (RotateRightTerm' _ l r)
    where
      RotateRightTerm = pevalRotateRightTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE RotateRightTerm #-}
  #endif
  
  -- | Pattern synonym for 'BitCastTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern BitCastTerm ::
    forall b.
    () =>
    forall a.
    (SupportedPrim a, SupportedPrim b, PEvalBitCastTerm a b) =>
    Term a ->
    Term b
  pattern BitCastTerm t <- (BitCastTerm' _ t@SupportedTerm)
    where
      BitCastTerm = pevalBitCastTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE BitCastTerm #-}
  #endif
  
  -- | Pattern synonym for 'BitCastOrTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern BitCastOrTerm ::
    forall b.
    () =>
    forall a.
    (SupportedPrim a, SupportedPrim b, PEvalBitCastOrTerm a b) =>
    Term b ->
    Term a ->
    Term b
  pattern BitCastOrTerm t1 t2 <- (BitCastOrTerm' _ t1 t2@SupportedTerm)
    where
      BitCastOrTerm = pevalBitCastOrTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE BitCastOrTerm #-}
  #endif
  
  -- | Pattern synonym for 'BVConcatTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern BVConcatTerm ::
    forall ret.
    () =>
    forall bv l r.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      KnownNat (l + r),
      1 <= l,
      1 <= r,
      1 <= l + r,
      SupportedPrim (bv l),
      SupportedPrim (bv r),
      SupportedPrim (bv (l + r)),
      ret ~ bv (l + r)
    ) =>
    Term (bv l) ->
    Term (bv r) ->
    Term ret
  pattern BVConcatTerm l r <- (BVConcatTerm' _ l@SupportedTerm r@SupportedTerm)
    where
      BVConcatTerm = pevalBVConcatTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE BVConcatTerm #-}
  #endif
  
  -- | Pattern synonym for 'BVSelectTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern BVSelectTerm ::
    forall ret.
    () =>
    forall bv w n ix.
    ( PEvalBVTerm bv,
      KnownNat n,
      KnownNat ix,
      KnownNat w,
      1 <= n,
      1 <= w,
      ix + w <= n,
      SupportedPrim (bv n),
      SupportedPrim (bv w),
      ret ~ bv w
    ) =>
    Proxy ix ->
    Proxy w ->
    Term (bv n) ->
    Term ret
  pattern BVSelectTerm ix w t <- (BVSelectTerm' _ ix w t@SupportedTerm)
    where
      BVSelectTerm = pevalBVSelectTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE BVSelectTerm #-}
  #endif
  
  -- | Pattern synonym for 'BVExtendTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern BVExtendTerm ::
    forall ret.
    () =>
    forall bv l r.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      1 <= l,
      1 <= r,
      l <= r,
      SupportedPrim (bv l),
      SupportedPrim (bv r),
      ret ~ bv r
    ) =>
    Bool ->
    Proxy r ->
    Term (bv l) ->
    Term ret
  pattern BVExtendTerm signed p t <- (BVExtendTerm' _ signed p t@SupportedTerm)
    where
      BVExtendTerm = pevalBVExtendTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE BVExtendTerm #-}
  #endif
  
  -- | Pattern synonym for 'ApplyTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern ApplyTerm ::
    forall b.
    () =>
    forall f a.
    (PEvalApplyTerm f a b, SupportedPrim f, SupportedPrim a, SupportedPrim b) =>
    Term f ->
    Term a ->
    Term b
  pattern ApplyTerm f x <- (ApplyTerm' _ f@SupportedTerm x@SupportedTerm)
    where
      ApplyTerm = pevalApplyTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE ApplyTerm #-}
  #endif
  
  -- | Pattern synonym for 'DivIntegralTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern DivIntegralTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalDivModIntegralTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern DivIntegralTerm l r <- (DivIntegralTerm' _ l r)
    where
      DivIntegralTerm = pevalDivIntegralTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE DivIntegralTerm #-}
  #endif
  
  -- | Pattern synonym for 'ModIntegralTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern ModIntegralTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalDivModIntegralTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern ModIntegralTerm l r <- (ModIntegralTerm' _ l r)
    where
      ModIntegralTerm = pevalModIntegralTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE ModIntegralTerm #-}
  #endif
  
  -- | Pattern synonym for 'QuotIntegralTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern QuotIntegralTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalDivModIntegralTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern QuotIntegralTerm l r <- (QuotIntegralTerm' _ l r)
    where
      QuotIntegralTerm = pevalQuotIntegralTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE QuotIntegralTerm #-}
  #endif
  
  -- | Pattern synonym for 'RemIntegralTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern RemIntegralTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalDivModIntegralTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern RemIntegralTerm l r <- (RemIntegralTerm' _ l r)
    where
      RemIntegralTerm = pevalRemIntegralTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE RemIntegralTerm #-}
  #endif
  
  -- | Pattern synonym for 'FPTraitTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern FPTraitTerm ::
    forall r.
    () =>
    forall eb sb fp.
    (r ~ Bool, ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPTrait ->
    Term (fp eb sb) ->
    Term r
  pattern FPTraitTerm trait t <- (FPTraitTerm' _ trait t)
    where
      FPTraitTerm = pevalFPTraitTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FPTraitTerm #-}
  #endif
  
  -- | Pattern synonym for 'FdivTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern FdivTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalFractionalTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern FdivTerm l r <- (FdivTerm' _ l r)
    where
      FdivTerm = pevalFdivTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FdivTerm #-}
  #endif
  
  -- | Pattern synonym for 'RecipTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern RecipTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalFractionalTerm t) =>
    Term t ->
    Term t
  pattern RecipTerm t <- (RecipTerm' _ t)
    where
      RecipTerm = pevalRecipTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE RecipTerm #-}
  #endif
  
  -- | Pattern synonym for 'FloatingUnaryTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern FloatingUnaryTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalFloatingTerm t) =>
    FloatingUnaryOp ->
    Term t ->
    Term t
  pattern FloatingUnaryTerm op t <- (FloatingUnaryTerm' _ op t)
    where
      FloatingUnaryTerm = pevalFloatingUnaryTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FloatingUnaryTerm #-}
  #endif
  
  -- | Pattern synonym for 'PowerTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern PowerTerm ::
    forall t.
    () =>
    (SupportedPrim t, PEvalFloatingTerm t) =>
    Term t ->
    Term t ->
    Term t
  pattern PowerTerm l r <- (PowerTerm' _ l r)
    where
      PowerTerm = pevalPowerTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE PowerTerm #-}
  #endif
  
  -- | Pattern synonym for 'FPUnaryTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern FPUnaryTerm ::
    forall ret.
    () =>
    forall fp eb sb.
    (ret ~ fp eb sb, ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPUnaryOp ->
    Term (fp eb sb) ->
    Term ret
  pattern FPUnaryTerm op t <- (FPUnaryTerm' _ op t)
    where
      FPUnaryTerm = pevalFPUnaryTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FPUnaryTerm #-}
  #endif
  
  -- | Pattern synonym for 'FPBinaryTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern FPBinaryTerm ::
    forall ret.
    () =>
    forall fp eb sb.
    (ret ~ fp eb sb, ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPBinaryOp ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term ret
  pattern FPBinaryTerm op l r <- (FPBinaryTerm' _ op l r)
    where
      FPBinaryTerm = pevalFPBinaryTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FPBinaryTerm #-}
  #endif
  
  -- | Pattern synonym for 'FPRoundingUnaryTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern FPRoundingUnaryTerm ::
    forall ret.
    () =>
    forall fp eb sb.
    (ret ~ fp eb sb, ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPRoundingUnaryOp ->
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term ret
  pattern FPRoundingUnaryTerm op rm t <- (FPRoundingUnaryTerm' _ op rm t)
    where
      FPRoundingUnaryTerm = pevalFPRoundingUnaryTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FPRoundingUnaryTerm #-}
  #endif
  
  -- | Pattern synonym for 'FPRoundingBinaryTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern FPRoundingBinaryTerm ::
    forall ret.
    () =>
    forall fp eb sb.
    (ret ~ fp eb sb, ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPRoundingBinaryOp ->
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term ret
  pattern FPRoundingBinaryTerm op rm l r <- (FPRoundingBinaryTerm' _ op rm l r)
    where
      FPRoundingBinaryTerm = pevalFPRoundingBinaryTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FPRoundingBinaryTerm #-}
  #endif
  
  -- | Pattern synonym for 'FPFMATerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern FPFMATerm ::
    forall ret.
    () =>
    forall fp eb sb.
    (ret ~ fp eb sb, ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term ret
  pattern FPFMATerm rm t1 t2 t3 <- (FPFMATerm' _ rm t1 t2 t3)
    where
      FPFMATerm = pevalFPFMATerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FPFMATerm #-}
  #endif
  
  -- | Pattern synonym for 'FromIntegralTerm''. Note that using this pattern to
  -- construct a 'Term' will do term simplification.
  pattern FromIntegralTerm ::
    forall b.
    () =>
    forall a.
    (PEvalFromIntegralTerm a b, SupportedPrim a, SupportedPrim b) =>
    Term a ->
    Term b
  pattern FromIntegralTerm t <- (FromIntegralTerm' _ t@SupportedTerm)
    where
      FromIntegralTerm = pevalFromIntegralTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FromIntegralTerm #-}
  #endif
  
  -- | Pattern synonym for 'FromFPOrTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern FromFPOrTerm ::
    forall a.
    () =>
    forall eb sb.
    ( PEvalIEEEFPConvertibleTerm a,
      ValidFP eb sb,
      SupportedPrim a
    ) =>
    Term a ->
    Term FPRoundingMode ->
    Term (FP eb sb) ->
    Term a
  pattern FromFPOrTerm t1 rm t2 <- (FromFPOrTerm' _ t1 rm t2)
    where
      FromFPOrTerm = pevalFromFPOrTerm
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FromFPOrTerm #-}
  #endif
  
  -- | Pattern synonym for 'ToFPTerm''. Note that using this pattern to construct
  -- a 'Term' will do term simplification.
  pattern ToFPTerm ::
    forall ret.
    () =>
    forall eb sb a.
    ( PEvalIEEEFPConvertibleTerm a,
      ValidFP eb sb,
      SupportedPrim (FP eb sb),
      SupportedPrim a,
      ret ~ FP eb sb
    ) =>
    Term FPRoundingMode ->
    Term a ->
    Proxy eb ->
    Proxy sb ->
    Term ret
  pattern ToFPTerm rm t eb sb <- (ToFPTerm' _ rm t@SupportedTerm eb sb)
    where
      ToFPTerm rm t _ _ = pevalToFPTerm rm t
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE ToFPTerm #-}
  #endif
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# COMPLETE
    ConTerm,
    SymTerm,
    ForallTerm,
    ExistsTerm,
    NotTerm,
    OrTerm,
    AndTerm,
    EqTerm,
    DistinctTerm,
    ITETerm,
    AddNumTerm,
    NegNumTerm,
    MulNumTerm,
    AbsNumTerm,
    SignumNumTerm,
    LtOrdTerm,
    LeOrdTerm,
    AndBitsTerm,
    OrBitsTerm,
    XorBitsTerm,
    ComplementBitsTerm,
    ShiftLeftTerm,
    ShiftRightTerm,
    RotateLeftTerm,
    RotateRightTerm,
    BitCastTerm,
    BitCastOrTerm,
    BVConcatTerm,
    BVSelectTerm,
    BVExtendTerm,
    ApplyTerm,
    DivIntegralTerm,
    ModIntegralTerm,
    QuotIntegralTerm,
    RemIntegralTerm,
    FPTraitTerm,
    FdivTerm,
    RecipTerm,
    FloatingUnaryTerm,
    PowerTerm,
    FPUnaryTerm,
    FPBinaryTerm,
    FPRoundingUnaryTerm,
    FPRoundingBinaryTerm,
    FPFMATerm,
    FromIntegralTerm,
    FromFPOrTerm,
    ToFPTerm
    #-}
  #endif
  
  -- | Get the cached information for a term.
  termInfo :: Term t -> CachedInfo
  termInfo (ConTerm' i _) = i
  termInfo (SymTerm' i _) = i
  termInfo (ForallTerm' i _ _) = i
  termInfo (ExistsTerm' i _ _) = i
  termInfo (NotTerm' i _) = i
  termInfo (OrTerm' i _ _ _) = i
  termInfo (AndTerm' i _ _ _) = i
  termInfo (EqTerm' i _ _) = i
  termInfo (DistinctTerm' i _) = i
  termInfo (ITETerm' i _ _ _) = i
  termInfo (AddNumTerm' i _ _) = i
  termInfo (NegNumTerm' i _) = i
  termInfo (MulNumTerm' i _ _) = i
  termInfo (AbsNumTerm' i _) = i
  termInfo (SignumNumTerm' i _) = i
  termInfo (LtOrdTerm' i _ _) = i
  termInfo (LeOrdTerm' i _ _) = i
  termInfo (AndBitsTerm' i _ _) = i
  termInfo (OrBitsTerm' i _ _) = i
  termInfo (XorBitsTerm' i _ _) = i
  termInfo (ComplementBitsTerm' i _) = i
  termInfo (ShiftLeftTerm' i _ _) = i
  termInfo (ShiftRightTerm' i _ _) = i
  termInfo (RotateLeftTerm' i _ _) = i
  termInfo (RotateRightTerm' i _ _) = i
  termInfo (BitCastTerm' i _) = i
  termInfo (BitCastOrTerm' i _ _) = i
  termInfo (BVConcatTerm' i _ _) = i
  termInfo (BVSelectTerm' i _ _ _) = i
  termInfo (BVExtendTerm' i _ _ _) = i
  termInfo (ApplyTerm' i _ _) = i
  termInfo (DivIntegralTerm' i _ _) = i
  termInfo (ModIntegralTerm' i _ _) = i
  termInfo (QuotIntegralTerm' i _ _) = i
  termInfo (RemIntegralTerm' i _ _) = i
  termInfo (FPTraitTerm' i _ _) = i
  termInfo (FdivTerm' i _ _) = i
  termInfo (RecipTerm' i _) = i
  termInfo (FloatingUnaryTerm' i _ _) = i
  termInfo (PowerTerm' i _ _) = i
  termInfo (FPUnaryTerm' i _ _) = i
  termInfo (FPBinaryTerm' i _ _ _) = i
  termInfo (FPRoundingUnaryTerm' i _ _ _) = i
  termInfo (FPRoundingBinaryTerm' i _ _ _ _) = i
  termInfo (FPFMATerm' i _ _ _ _) = i
  termInfo (FromIntegralTerm' i _) = i
  termInfo (FromFPOrTerm' i _ _ _) = i
  termInfo (ToFPTerm' i _ _ _ _) = i
  
  -- | Get the thread ID for a term.
  {-# INLINE termThreadId #-}
  termThreadId :: Term t -> WeakThreadId
  termThreadId = cachedThreadId . termInfo
  
  -- | Get the digest for a term.
  {-# INLINE termDigest #-}
  termDigest :: Term t -> Digest
  termDigest = cachedDigest . termInfo
  
  -- | Get the ID for a term.
  {-# INLINE termId #-}
  termId :: Term t -> Id
  termId = cachedId . termInfo
  
  -- | Get the stable identifier for a term.
  {-# INLINE termStableIdent #-}
  termStableIdent :: Term t -> StableIdent
  termStableIdent = cachedStableIdent . termInfo
  
  -- | Pattern for term with dynamic typing.
  pattern DynTerm :: forall a b. (SupportedPrim a) => Term a -> Term b
  pattern DynTerm x <- ((\v@SupportedTerm -> cast v) -> Just x)
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE DynTerm #-}
  #endif
  
  -- baseHash :: Term t -> Digest
  -- baseHash t = case hashId t of
  --   HashId h _ -> h
  -- {-# INLINE baseHash #-}
  
  data HashId = HashId {-# UNPACK #-} !Digest Id deriving (Show)
  
  instance Eq HashId where
    HashId _ l == HashId _ r = l == r
    {-# INLINE (==) #-}
  
  instance Hashable HashId where
    hashWithSalt s (HashId i _) = hashWithSalt s i
    {-# INLINE hashWithSalt #-}
  
  eqHashId :: HashId -> HashId -> Bool
  eqHashId = (==)
  {-# INLINE eqHashId #-}
  
  data TypeHashId = TypeHashId {-# UNPACK #-} !Fingerprint {-# UNPACK #-} !HashId
    deriving (Show)
  
  instance Eq TypeHashId where
    TypeHashId l li == TypeHashId r ri = l == r && li == ri
    {-# INLINE (==) #-}
  
  instance Hashable TypeHashId where
    hashWithSalt s (TypeHashId tp i) = s `hashWithSalt` tp `hashWithSalt` i
    {-# INLINE hashWithSalt #-}
  
  {-# INLINE termHashId #-}
  termHashId :: Term t -> HashId
  termHashId t = HashId (termDigest t) (termId t)
  
  typeFingerprint :: forall t. (SupportedPrim t) => Fingerprint
  typeFingerprint = typeRepFingerprint $ SomeTypeRep $ primTypeRep @t
  {-# INLINE typeFingerprint #-}
  
  {-# INLINE termTypeHashId #-}
  termTypeHashId :: forall t. Term t -> TypeHashId
  termTypeHashId t@SupportedTerm = TypeHashId (typeFingerprint @t) (termHashId t)
  
  -- {-# NOINLINE typeHashId #-}
  introSupportedPrimConstraint0 :: forall t a. Term t -> ((SupportedPrim t) => a) -> a
  introSupportedPrimConstraint0 ConTerm' {} x = x
  introSupportedPrimConstraint0 (SymTerm' _ t) x = withSymbolSupported t x
  introSupportedPrimConstraint0 ForallTerm' {} x = x
  introSupportedPrimConstraint0 ExistsTerm' {} x = x
  introSupportedPrimConstraint0 NotTerm' {} x = x
  introSupportedPrimConstraint0 OrTerm' {} x = x
  introSupportedPrimConstraint0 AndTerm' {} x = x
  introSupportedPrimConstraint0 EqTerm' {} x = x
  introSupportedPrimConstraint0 DistinctTerm' {} x = x
  introSupportedPrimConstraint0 ITETerm' {} x = x
  introSupportedPrimConstraint0 AddNumTerm' {} x = x
  introSupportedPrimConstraint0 NegNumTerm' {} x = x
  introSupportedPrimConstraint0 MulNumTerm' {} x = x
  introSupportedPrimConstraint0 AbsNumTerm' {} x = x
  introSupportedPrimConstraint0 SignumNumTerm' {} x = x
  introSupportedPrimConstraint0 LtOrdTerm' {} x = x
  introSupportedPrimConstraint0 LeOrdTerm' {} x = x
  introSupportedPrimConstraint0 AndBitsTerm' {} x = x
  introSupportedPrimConstraint0 OrBitsTerm' {} x = x
  introSupportedPrimConstraint0 XorBitsTerm' {} x = x
  introSupportedPrimConstraint0 ComplementBitsTerm' {} x = x
  introSupportedPrimConstraint0 ShiftLeftTerm' {} x = x
  introSupportedPrimConstraint0 RotateLeftTerm' {} x = x
  introSupportedPrimConstraint0 ShiftRightTerm' {} x = x
  introSupportedPrimConstraint0 RotateRightTerm' {} x = x
  introSupportedPrimConstraint0 BitCastTerm' {} x = x
  introSupportedPrimConstraint0 BitCastOrTerm' {} x = x
  introSupportedPrimConstraint0 BVConcatTerm' {} x = x
  introSupportedPrimConstraint0 BVSelectTerm' {} x = x
  introSupportedPrimConstraint0 BVExtendTerm' {} x = x
  introSupportedPrimConstraint0 ApplyTerm' {} x = x
  introSupportedPrimConstraint0 DivIntegralTerm' {} x = x
  introSupportedPrimConstraint0 ModIntegralTerm' {} x = x
  introSupportedPrimConstraint0 QuotIntegralTerm' {} x = x
  introSupportedPrimConstraint0 RemIntegralTerm' {} x = x
  introSupportedPrimConstraint0 FPTraitTerm' {} x = x
  introSupportedPrimConstraint0 FdivTerm' {} x = x
  introSupportedPrimConstraint0 RecipTerm' {} x = x
  introSupportedPrimConstraint0 FloatingUnaryTerm' {} x = x
  introSupportedPrimConstraint0 PowerTerm' {} x = x
  introSupportedPrimConstraint0 FPUnaryTerm' {} x = x
  introSupportedPrimConstraint0 FPBinaryTerm' {} x = x
  introSupportedPrimConstraint0 FPRoundingUnaryTerm' {} x = x
  introSupportedPrimConstraint0 FPRoundingBinaryTerm' {} x = x
  introSupportedPrimConstraint0 FPFMATerm' {} x = x
  introSupportedPrimConstraint0 FromIntegralTerm' {} x = x
  introSupportedPrimConstraint0 FromFPOrTerm' {} x = x
  introSupportedPrimConstraint0 ToFPTerm' {} x = x
  
  -- | Introduce the 'SupportedPrim' constraint from a term.
  introSupportedPrimConstraint ::
    forall t a. Term t -> ((SupportedPrim t, Typeable t) => a) -> a
  introSupportedPrimConstraint t a = introSupportedPrimConstraint0 t a
  {-# INLINE introSupportedPrimConstraint #-}
  
  -- | Pretty-print a term.
  pformatTerm :: forall t. Term t -> String
  pformatTerm (ConTerm t) = pformatCon t
  pformatTerm (SymTerm sym) = showUntyped sym
  pformatTerm (ForallTerm sym arg) = "(forall " ++ show sym ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (ExistsTerm sym arg) = "(exists " ++ show sym ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (NotTerm arg) = "(! " ++ pformatTerm arg ++ ")"
  pformatTerm (OrTerm arg1 arg2) = "(|| " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (AndTerm arg1 arg2) = "(&& " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (EqTerm arg1 arg2) = "(= " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (DistinctTerm args) = "(distinct " ++ unwords (map pformatTerm $ toList args) ++ ")"
  pformatTerm (ITETerm cond arg1 arg2) = "(ite " ++ pformatTerm cond ++ " " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (AddNumTerm arg1 arg2) = "(+ " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (NegNumTerm arg) = "(- " ++ pformatTerm arg ++ ")"
  pformatTerm (MulNumTerm arg1 arg2) = "(* " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (AbsNumTerm arg) = "(abs " ++ pformatTerm arg ++ ")"
  pformatTerm (SignumNumTerm arg) = "(signum " ++ pformatTerm arg ++ ")"
  pformatTerm (LtOrdTerm arg1 arg2) = "(< " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (LeOrdTerm arg1 arg2) = "(<= " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (AndBitsTerm arg1 arg2) = "(& " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (OrBitsTerm arg1 arg2) = "(| " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (XorBitsTerm arg1 arg2) = "(^ " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (ComplementBitsTerm arg) = "(~ " ++ pformatTerm arg ++ ")"
  pformatTerm (ShiftLeftTerm arg n) = "(shl " ++ pformatTerm arg ++ " " ++ pformatTerm n ++ ")"
  pformatTerm (ShiftRightTerm arg n) = "(shr " ++ pformatTerm arg ++ " " ++ pformatTerm n ++ ")"
  pformatTerm (RotateLeftTerm arg n) = "(rotl " ++ pformatTerm arg ++ " " ++ pformatTerm n ++ ")"
  pformatTerm (RotateRightTerm arg n) = "(rotr " ++ pformatTerm arg ++ " " ++ pformatTerm n ++ ")"
  pformatTerm (BitCastTerm arg) = "(bitcast " ++ pformatTerm arg ++ ")"
  pformatTerm (BitCastOrTerm d arg) = "(bitcast_or " ++ pformatTerm d ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (BVConcatTerm arg1 arg2) = "(bvconcat " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (BVSelectTerm (_ :: Proxy ix) (_ :: Proxy w) arg) =
    "(bvselect " ++ show (typeRep @ix) ++ " " ++ show (typeRep @w) ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (BVExtendTerm signed (_ :: Proxy n) arg) =
    (if signed then "(bvsext " else "(bvzext ") ++ show (typeRep @n) ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (ApplyTerm func arg) = "(apply " ++ pformatTerm func ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (DivIntegralTerm arg1 arg2) = "(div " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (ModIntegralTerm arg1 arg2) = "(mod " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (QuotIntegralTerm arg1 arg2) = "(quot " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (RemIntegralTerm arg1 arg2) = "(rem " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (FPTraitTerm trait arg) = "(" ++ show trait ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (FdivTerm arg1 arg2) = "(fdiv " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (RecipTerm arg) = "(recip " ++ pformatTerm arg ++ ")"
  pformatTerm (FloatingUnaryTerm op arg) = "(" ++ show op ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (PowerTerm arg1 arg2) = "(** " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (FPUnaryTerm op arg) = "(" ++ show op ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (FPBinaryTerm op arg1 arg2) = "(" ++ show op ++ " " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (FPRoundingUnaryTerm op mode arg) = "(" ++ show op ++ " " ++ pformatTerm mode ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (FPRoundingBinaryTerm op mode arg1 arg2) =
    "(" ++ show op ++ " " ++ pformatTerm mode ++ " " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ ")"
  pformatTerm (FPFMATerm mode arg1 arg2 arg3) =
    "(fp.fma " ++ pformatTerm mode ++ " " ++ pformatTerm arg1 ++ " " ++ pformatTerm arg2 ++ " " ++ pformatTerm arg3 ++ ")"
  pformatTerm (FromIntegralTerm arg) = "(from_integral " ++ pformatTerm arg ++ ")"
  pformatTerm (FromFPOrTerm d r arg) = "(from_fp_or " ++ pformatTerm d ++ " " ++ pformatTerm r ++ " " ++ pformatTerm arg ++ ")"
  pformatTerm (ToFPTerm r arg _ _) = "(to_fp " ++ pformatTerm r ++ " " ++ pformatTerm arg ++ ")"
  
  -- {-# INLINE pformatTerm #-}
  
  instance NFData (Term a) where
    rnf i = rnf (termInfo i)
    {-# INLINE rnf #-}
  
  #if MIN_VERSION_base(4,15,0)
  type CODE x = forall qq. Quote qq => Code qq (x)
  #else
  type CODE x = TExpQ x
  #endif
  
  instance Lift (Term t) where
    liftTyped (ConTerm v) = [||conTerm v||]
    liftTyped (SymTerm t) = [||symTerm t||]
    liftTyped (ForallTerm t1 t2) = [||forallTerm t1 t2||]
    liftTyped (ExistsTerm t1 t2) = [||existsTerm t1 t2||]
    liftTyped (NotTerm t) = [||notTerm t||]
    liftTyped (OrTerm t1 t2) = [||orTerm t1 t2||]
    liftTyped (AndTerm t1 t2) = [||andTerm t1 t2||]
    liftTyped (EqTerm t1 t2) = [||eqTerm t1 t2||]
    liftTyped (DistinctTerm t) = [||distinctTerm t||]
    liftTyped (ITETerm t1 t2 t3) = [||iteTerm t1 t2 t3||]
    liftTyped (AddNumTerm t1 t2) = [||addNumTerm t1 t2||]
    liftTyped (NegNumTerm t) = [||negNumTerm t||]
    liftTyped (MulNumTerm t1 t2) = [||mulNumTerm t1 t2||]
    liftTyped (AbsNumTerm t) = [||absNumTerm t||]
    liftTyped (SignumNumTerm t) = [||signumNumTerm t||]
    liftTyped (LtOrdTerm t1 t2) = [||ltOrdTerm t1 t2||]
    liftTyped (LeOrdTerm t1 t2) = [||leOrdTerm t1 t2||]
    liftTyped (AndBitsTerm t1 t2) = [||andBitsTerm t1 t2||]
    liftTyped (OrBitsTerm t1 t2) = [||orBitsTerm t1 t2||]
    liftTyped (XorBitsTerm t1 t2) = [||xorBitsTerm t1 t2||]
    liftTyped (ComplementBitsTerm t) = [||complementBitsTerm t||]
    liftTyped (ShiftLeftTerm t1 t2) = [||shiftLeftTerm t1 t2||]
    liftTyped (ShiftRightTerm t1 t2) = [||shiftRightTerm t1 t2||]
    liftTyped (RotateLeftTerm t1 t2) = [||rotateLeftTerm t1 t2||]
    liftTyped (RotateRightTerm t1 t2) = [||rotateRightTerm t1 t2||]
    liftTyped (BitCastTerm t) = [||bitCastTerm t||]
    liftTyped (BitCastOrTerm t1 t2) = [||bitCastOrTerm t1 t2||]
    liftTyped (BVConcatTerm t1 t2) = [||bvConcatTerm t1 t2||]
    liftTyped (BVSelectTerm (_ :: p ix) (_ :: q w) t3) =
      let pix = [||Proxy||] :: CODE (Proxy ix)
          pw = [||Proxy||] :: CODE (Proxy w)
       in [||bvSelectTerm $$pix $$pw t3||]
    liftTyped (BVExtendTerm b (_ :: p r) t2) =
      let pr = [||Proxy||] :: CODE (Proxy r)
       in [||bvExtendTerm b $$pr t2||]
    liftTyped (ApplyTerm t1 t2) = [||applyTerm t1 t2||]
    liftTyped (DivIntegralTerm t1 t2) = [||divIntegralTerm t1 t2||]
    liftTyped (ModIntegralTerm t1 t2) = [||modIntegralTerm t1 t2||]
    liftTyped (QuotIntegralTerm t1 t2) = [||quotIntegralTerm t1 t2||]
    liftTyped (RemIntegralTerm t1 t2) = [||remIntegralTerm t1 t2||]
    liftTyped (FPTraitTerm t1 t2) = [||fpTraitTerm t1 t2||]
    liftTyped (FdivTerm t1 t2) = [||fdivTerm t1 t2||]
    liftTyped (RecipTerm t) = [||recipTerm t||]
    liftTyped (FloatingUnaryTerm t1 t2) = [||floatingUnaryTerm t1 t2||]
    liftTyped (PowerTerm t1 t2) = [||powerTerm t1 t2||]
    liftTyped (FPUnaryTerm t1 t2) = [||fpUnaryTerm t1 t2||]
    liftTyped (FPBinaryTerm t1 t2 t3) = [||fpBinaryTerm t1 t2 t3||]
    liftTyped (FPRoundingUnaryTerm t1 t2 t3) =
      [||fpRoundingUnaryTerm t1 t2 t3||]
    liftTyped (FPRoundingBinaryTerm t1 t2 t3 t4) =
      [||fpRoundingBinaryTerm t1 t2 t3 t4||]
    liftTyped (FPFMATerm t1 t2 t3 t4) = [||fpFMATerm t1 t2 t3 t4||]
    liftTyped (FromIntegralTerm t) = [||fromIntegralTerm t||]
    liftTyped (FromFPOrTerm t1 t2 t3) = [||fromFPOrTerm t1 t2 t3||]
    liftTyped (ToFPTerm t1 t2 _ _) =
      [||toFPTerm t1 t2||]
  
  instance Show (Term ty) where
    show t@(ConTerm v) =
      "ConTerm{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", v="
        ++ pformatCon v
        ++ "}"
    show t@(SymTerm name@TypedSymbol {}) =
      "SymTerm{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", name="
        ++ show name
        ++ ", type="
        ++ show (primTypeRep @ty)
        ++ "}"
    show t@(ForallTerm sym arg) =
      "Forall{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", sym="
        ++ show sym
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(ExistsTerm sym arg) =
      "Exists{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", sym="
        ++ show sym
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(NotTerm arg) =
      "Not{tid=" ++ show (termThreadId t) ++ ", id=" ++ show (termId t) ++ ", arg=" ++ show arg ++ "}"
    show t@(OrTerm arg1 arg2) =
      "Or{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(AndTerm arg1 arg2) =
      "And{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(EqTerm arg1 arg2) =
      "Eqv{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(DistinctTerm args) =
      "Distinct{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", args="
        ++ show args
        ++ "}"
    show t@(ITETerm cond l r) =
      "ITE{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", cond="
        ++ show cond
        ++ ", then="
        ++ show l
        ++ ", else="
        ++ show r
        ++ "}"
    show t@(AddNumTerm arg1 arg2) =
      "AddNum{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(NegNumTerm arg) =
      "NegNum{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(MulNumTerm arg1 arg2) =
      "MulNum{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(AbsNumTerm arg) =
      "AbsNum{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(SignumNumTerm arg) =
      "SignumNum{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(LtOrdTerm arg1 arg2) =
      "LTNum{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(LeOrdTerm arg1 arg2) =
      "LENum{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(AndBitsTerm arg1 arg2) =
      "AndBits{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(OrBitsTerm arg1 arg2) =
      "OrBits{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(XorBitsTerm arg1 arg2) =
      "XorBits{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(ComplementBitsTerm arg) =
      "ComplementBits{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(ShiftLeftTerm arg n) =
      "ShiftLeft{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ ", n="
        ++ show n
        ++ "}"
    show t@(ShiftRightTerm arg n) =
      "ShiftRight{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ ", n="
        ++ show n
        ++ "}"
    show t@(RotateLeftTerm arg n) =
      "RotateLeft{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ ", n="
        ++ show n
        ++ "}"
    show t@(RotateRightTerm arg n) =
      "RotateRight{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ ", n="
        ++ show n
        ++ "}"
    show t@(BitCastTerm arg) =
      "BitCast{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(BitCastOrTerm arg d) =
      "BitCastOr{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", default="
        ++ show d
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(BVConcatTerm arg1 arg2) =
      "BVConcat{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(BVSelectTerm ix w arg) =
      "BVSelect{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", ix="
        ++ show (natVal ix)
        ++ ", w="
        ++ show (natVal w)
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(BVExtendTerm signed n arg) =
      "BVExtend{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", signed="
        ++ show signed
        ++ ", n="
        ++ show (natVal n)
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(ApplyTerm func arg) =
      "Apply{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", f="
        ++ show func
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(DivIntegralTerm arg1 arg2) =
      "DivIntegral{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(ModIntegralTerm arg1 arg2) =
      "ModIntegral{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(QuotIntegralTerm arg1 arg2) =
      "QuotIntegral{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(RemIntegralTerm arg1 arg2) =
      "RemIntegral{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(FPTraitTerm trait arg) =
      "FPTrait{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", trait="
        ++ show trait
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(FdivTerm arg1 arg2) =
      "Fdiv{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(RecipTerm arg) =
      "Recip{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(FloatingUnaryTerm op arg) =
      "FloatingUnary{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", op="
        ++ show op
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(PowerTerm arg1 arg2) =
      "Power{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(FPUnaryTerm op arg) =
      "FPUnary{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", op="
        ++ show op
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(FPBinaryTerm op arg1 arg2) =
      "FPBinary{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", op="
        ++ show op
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(FPRoundingUnaryTerm op mode arg) =
      "FPRoundingUnary{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", op="
        ++ show op
        ++ ", mode="
        ++ show mode
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(FPRoundingBinaryTerm op mode arg1 arg2) =
      "FPRoundingBinary{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", op="
        ++ show op
        ++ ", mode="
        ++ show mode
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ "}"
    show t@(FPFMATerm mode arg1 arg2 arg3) =
      "FPFMA{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", mode="
        ++ show mode
        ++ ", arg1="
        ++ show arg1
        ++ ", arg2="
        ++ show arg2
        ++ ", arg3="
        ++ show arg3
        ++ "}"
    show t@(FromIntegralTerm arg) =
      "FromIntegral{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(FromFPOrTerm arg d mode) =
      "FromFPTerm{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", default="
        ++ show d
        ++ ", mode="
        ++ show mode
        ++ ", arg="
        ++ show arg
        ++ "}"
    show t@(ToFPTerm arg mode _ _) =
      "ToFPTerm{tid="
        ++ show (termThreadId t)
        ++ ", id="
        ++ show (termId t)
        ++ ", mode="
        ++ show mode
        ++ ", arg="
        ++ show arg
        ++ "}"
  
  -- {-# INLINE show #-}
  
  -- | Pretty-print a term, possibly eliding parts of it.
  prettyPrintTerm :: Term t -> Doc ann
  prettyPrintTerm v@SupportedTerm =
    column
      ( \c ->
          pageWidth $ \case
            AvailablePerLine i r ->
              if fromIntegral (c + len) > fromIntegral i * r
                then "..."
                else pretty formatted
            Unbounded -> pretty formatted
      )
    where
      formatted = pformatTerm v
      len = length formatted
  
  instance (SupportedPrim t) => Eq (Term t) where
    a == b =
      if threadId a == threadId b
        then termId a == termId b
        else unsafePerformIO $ do
          tid <- myWeakThreadId
          a' <- toCurThreadImpl tid a
          b' <- toCurThreadImpl tid b
          return $ a' == b'
  
  instance (SupportedPrim t) => Hashable (Term t) where
    hashWithSalt s t = hashWithSalt s $ termHashId t
    {-# INLINE hashWithSalt #-}
  
  -- | Term without identity (before internalizing).
  data UTerm t where
    UConTerm :: (SupportedPrim t) => !t -> UTerm t
    USymTerm :: !(TypedSymbol 'AnyKind t) -> UTerm t
    UForallTerm ::
      !(TypedSymbol 'ConstantKind t) ->
      !(Term Bool) ->
      UTerm Bool
    UExistsTerm ::
      !(TypedSymbol 'ConstantKind t) ->
      !(Term Bool) ->
      UTerm Bool
    UNotTerm :: !(Term Bool) -> UTerm Bool
    UOrTerm :: !(Term Bool) -> !(Term Bool) -> !(HS.HashSet (Term Bool)) -> UTerm Bool
    UAndTerm :: !(Term Bool) -> !(Term Bool) -> !(HS.HashSet (Term Bool)) -> UTerm Bool
    UEqTerm :: !(Term t) -> !(Term t) -> UTerm Bool
    UDistinctTerm :: !(NonEmpty (Term t)) -> UTerm Bool
    UITETerm ::
      (SupportedPrim t) =>
      !(Term Bool) ->
      !(Term t) ->
      !(Term t) ->
      UTerm t
    UAddNumTerm :: (SupportedPrim t, PEvalNumTerm t) => !(Term t) -> !(Term t) -> UTerm t
    UNegNumTerm :: (SupportedPrim t, PEvalNumTerm t) => !(Term t) -> UTerm t
    UMulNumTerm :: (SupportedPrim t, PEvalNumTerm t) => !(Term t) -> !(Term t) -> UTerm t
    UAbsNumTerm :: (SupportedPrim t, PEvalNumTerm t) => !(Term t) -> UTerm t
    USignumNumTerm :: (SupportedPrim t, PEvalNumTerm t) => !(Term t) -> UTerm t
    ULtOrdTerm :: (SupportedPrim t, PEvalOrdTerm t) => !(Term t) -> !(Term t) -> UTerm Bool
    ULeOrdTerm :: (SupportedPrim t, PEvalOrdTerm t) => !(Term t) -> !(Term t) -> UTerm Bool
    UAndBitsTerm :: (SupportedPrim t, PEvalBitwiseTerm t) => !(Term t) -> !(Term t) -> UTerm t
    UOrBitsTerm :: (SupportedPrim t, PEvalBitwiseTerm t) => !(Term t) -> !(Term t) -> UTerm t
    UXorBitsTerm :: (SupportedPrim t, PEvalBitwiseTerm t) => !(Term t) -> !(Term t) -> UTerm t
    UComplementBitsTerm :: (SupportedPrim t, PEvalBitwiseTerm t) => !(Term t) -> UTerm t
    UShiftLeftTerm ::
      (SupportedPrim t, PEvalShiftTerm t) => !(Term t) -> !(Term t) -> UTerm t
    UShiftRightTerm ::
      (SupportedPrim t, PEvalShiftTerm t) => !(Term t) -> !(Term t) -> UTerm t
    URotateLeftTerm ::
      (SupportedPrim t, PEvalRotateTerm t) => !(Term t) -> !(Term t) -> UTerm t
    URotateRightTerm ::
      (SupportedPrim t, PEvalRotateTerm t) => !(Term t) -> !(Term t) -> UTerm t
    UBitCastTerm ::
      (SupportedPrim b, PEvalBitCastTerm a b) =>
      !(Term a) ->
      UTerm b
    UBitCastOrTerm ::
      (SupportedPrim b, PEvalBitCastOrTerm a b) =>
      !(Term b) ->
      !(Term a) ->
      UTerm b
    UBVConcatTerm ::
      ( PEvalBVTerm bv,
        KnownNat l,
        KnownNat r,
        KnownNat (l + r),
        1 <= l,
        1 <= r,
        1 <= l + r,
        SupportedPrim (bv (l + r))
      ) =>
      !(Term (bv l)) ->
      !(Term (bv r)) ->
      UTerm (bv (l + r))
    UBVSelectTerm ::
      ( PEvalBVTerm bv,
        KnownNat n,
        KnownNat ix,
        KnownNat w,
        1 <= n,
        1 <= w,
        ix + w <= n,
        SupportedPrim (bv w)
      ) =>
      !(Proxy ix) ->
      !(Proxy w) ->
      !(Term (bv n)) ->
      UTerm (bv w)
    UBVExtendTerm ::
      ( PEvalBVTerm bv,
        KnownNat l,
        KnownNat r,
        1 <= l,
        1 <= r,
        l <= r,
        SupportedPrim (bv r)
      ) =>
      !Bool ->
      !(Proxy r) ->
      !(Term (bv l)) ->
      UTerm (bv r)
    UApplyTerm ::
      (PEvalApplyTerm f a b, SupportedPrim b) =>
      Term f ->
      Term a ->
      UTerm b
    UDivIntegralTerm ::
      (SupportedPrim t, PEvalDivModIntegralTerm t) =>
      !(Term t) ->
      !(Term t) ->
      UTerm t
    UModIntegralTerm ::
      (SupportedPrim t, PEvalDivModIntegralTerm t) =>
      !(Term t) ->
      !(Term t) ->
      UTerm t
    UQuotIntegralTerm ::
      (SupportedPrim t, PEvalDivModIntegralTerm t) =>
      !(Term t) ->
      !(Term t) ->
      UTerm t
    URemIntegralTerm ::
      (SupportedPrim t, PEvalDivModIntegralTerm t) =>
      !(Term t) ->
      !(Term t) ->
      UTerm t
    UFPTraitTerm ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      !FPTrait ->
      !(Term (fp eb sb)) ->
      UTerm Bool
    UFdivTerm ::
      (SupportedPrim t, PEvalFractionalTerm t) =>
      !(Term t) ->
      !(Term t) ->
      UTerm t
    URecipTerm :: (SupportedPrim t, PEvalFractionalTerm t) => !(Term t) -> UTerm t
    UFloatingUnaryTerm ::
      (SupportedPrim t, PEvalFloatingTerm t) =>
      !FloatingUnaryOp ->
      !(Term t) ->
      UTerm t
    UPowerTerm ::
      (SupportedPrim t, PEvalFloatingTerm t) => !(Term t) -> !(Term t) -> UTerm t
    UFPUnaryTerm ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      !FPUnaryOp ->
      !(Term (fp eb sb)) ->
      UTerm (fp eb sb)
    UFPBinaryTerm ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      !FPBinaryOp ->
      !(Term (fp eb sb)) ->
      !(Term (fp eb sb)) ->
      UTerm (fp eb sb)
    UFPRoundingUnaryTerm ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      !FPRoundingUnaryOp ->
      !(Term FPRoundingMode) ->
      !(Term (fp eb sb)) ->
      UTerm (fp eb sb)
    UFPRoundingBinaryTerm ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      !FPRoundingBinaryOp ->
      !(Term FPRoundingMode) ->
      !(Term (fp eb sb)) ->
      !(Term (fp eb sb)) ->
      UTerm (fp eb sb)
    UFPFMATerm ::
      (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
      !(Term FPRoundingMode) ->
      !(Term (fp eb sb)) ->
      !(Term (fp eb sb)) ->
      !(Term (fp eb sb)) ->
      UTerm (fp eb sb)
    UFromIntegralTerm ::
      (PEvalFromIntegralTerm a b, SupportedPrim b) =>
      !(Term a) ->
      UTerm b
    UFromFPOrTerm ::
      ( PEvalIEEEFPConvertibleTerm a,
        SupportedPrim a,
        ValidFP eb sb
      ) =>
      Term a ->
      !(Term FPRoundingMode) ->
      !(Term (FP eb sb)) ->
      UTerm a
    UToFPTerm ::
      ( PEvalIEEEFPConvertibleTerm a,
        ValidFP eb sb,
        SupportedPrim (FP eb sb)
      ) =>
      !(Term FPRoundingMode) ->
      !(Term a) ->
      Proxy eb ->
      Proxy sb ->
      UTerm (FP eb sb)
  
  -- | Compare two t'TypedSymbol's for equality.
  eqHeteroSymbol :: forall ta a tb b. TypedSymbol ta a -> TypedSymbol tb b -> Bool
  eqHeteroSymbol (TypedSymbol taga) (TypedSymbol tagb) =
    case eqTypeRep (primTypeRep @a) (primTypeRep @b) of
      Just HRefl -> taga == tagb
      Nothing -> False
  {-# INLINE eqHeteroSymbol #-}
  
  preHashConDescription :: (SupportedPrim t) => t -> Digest
  preHashConDescription = fromIntegral . hashConWithSalt 0
  {-# INLINE preHashConDescription #-}
  
  preHashSymDescription :: TypedSymbol 'AnyKind t -> Digest
  preHashSymDescription = fromIntegral . hashWithSalt 1
  {-# INLINE preHashSymDescription #-}
  
  preHashForallDescription ::
    TypedSymbol 'ConstantKind t -> HashId -> Digest
  preHashForallDescription sym h =
    fromIntegral
      ( 2
          `hashWithSalt` sym
          `hashWithSalt` h
      )
  {-# INLINE preHashForallDescription #-}
  
  preHashExistsDescription ::
    TypedSymbol 'ConstantKind t -> HashId -> Digest
  preHashExistsDescription sym h =
    fromIntegral
      ( 3
          `hashWithSalt` sym
          `hashWithSalt` h
      )
  {-# INLINE preHashExistsDescription #-}
  
  preHashNotDescription :: HashId -> Digest
  preHashNotDescription = fromIntegral . hashWithSalt 7
  {-# INLINE preHashNotDescription #-}
  
  preHashOrDescription :: HashId -> HashId -> Digest
  preHashOrDescription h1 h2 =
    fromIntegral (8 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashOrDescription #-}
  
  preHashAndDescription :: HashId -> HashId -> Digest
  preHashAndDescription h1 h2 =
    fromIntegral (9 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashAndDescription #-}
  
  preHashEqDescription :: Fingerprint -> HashId -> HashId -> Digest
  preHashEqDescription tp h1 h2 =
    fromIntegral (10 `hashWithSalt` tp `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashEqDescription #-}
  
  preHashDistinctDescription :: Fingerprint -> NonEmpty HashId -> Digest
  preHashDistinctDescription tp hs =
    fromIntegral (11 `hashWithSalt` tp `hashWithSalt` hs)
  {-# INLINE preHashDistinctDescription #-}
  
  preHashITEDescription :: HashId -> HashId -> HashId -> Digest
  preHashITEDescription h1 h2 h3 =
    fromIntegral (12 `hashWithSalt` h1 `hashWithSalt` h2 `hashWithSalt` h3)
  {-# INLINE preHashITEDescription #-}
  
  preHashAddNumDescription :: HashId -> HashId -> Digest
  preHashAddNumDescription h1 h2 =
    fromIntegral (13 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashAddNumDescription #-}
  
  preHashNegNumDescription :: HashId -> Digest
  preHashNegNumDescription =
    fromIntegral . hashWithSalt 14
  {-# INLINE preHashNegNumDescription #-}
  
  preHashMulNumDescription :: HashId -> HashId -> Digest
  preHashMulNumDescription h1 h2 =
    fromIntegral (15 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashMulNumDescription #-}
  
  preHashAbsNumDescription :: HashId -> Digest
  preHashAbsNumDescription = fromIntegral . hashWithSalt 16
  {-# INLINE preHashAbsNumDescription #-}
  
  preHashSignumNumDescription :: HashId -> Digest
  preHashSignumNumDescription = fromIntegral . hashWithSalt 17
  {-# INLINE preHashSignumNumDescription #-}
  
  preHashLtOrdDescription :: Fingerprint -> HashId -> HashId -> Digest
  preHashLtOrdDescription tp h1 h2 =
    fromIntegral (18 `hashWithSalt` tp `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashLtOrdDescription #-}
  
  preHashLeOrdDescription :: Fingerprint -> HashId -> HashId -> Digest
  preHashLeOrdDescription tp h1 h2 =
    fromIntegral (19 `hashWithSalt` tp `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashLeOrdDescription #-}
  
  preHashAndBitsDescription :: HashId -> HashId -> Digest
  preHashAndBitsDescription h1 h2 =
    fromIntegral (20 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashAndBitsDescription #-}
  
  preHashOrBitsDescription :: HashId -> HashId -> Digest
  preHashOrBitsDescription h1 h2 =
    fromIntegral (21 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashOrBitsDescription #-}
  
  preHashXorBitsDescription :: HashId -> HashId -> Digest
  preHashXorBitsDescription h1 h2 =
    fromIntegral (22 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashXorBitsDescription #-}
  
  preHashComplementBitsDescription :: HashId -> Digest
  preHashComplementBitsDescription = fromIntegral . hashWithSalt 23
  {-# INLINE preHashComplementBitsDescription #-}
  
  preHashShiftLeftDescription :: HashId -> HashId -> Digest
  preHashShiftLeftDescription h1 h2 =
    fromIntegral (24 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashShiftLeftDescription #-}
  
  preHashShiftRightDescription :: HashId -> HashId -> Digest
  preHashShiftRightDescription h1 h2 =
    fromIntegral (25 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashShiftRightDescription #-}
  
  preHashRotateLeftDescription :: HashId -> HashId -> Digest
  preHashRotateLeftDescription h1 h2 =
    fromIntegral (26 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashRotateLeftDescription #-}
  
  preHashRotateRightDescription :: HashId -> HashId -> Digest
  preHashRotateRightDescription h1 h2 =
    fromIntegral (27 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashRotateRightDescription #-}
  
  preHashBVConcatDescription :: TypeHashId -> TypeHashId -> Digest
  preHashBVConcatDescription h1 h2 =
    fromIntegral
      ( 28
          `hashWithSalt` h1
          `hashWithSalt` h2
      )
  
  preHashBVSelectDescription :: Fingerprint -> TypeHashId -> Digest
  preHashBVSelectDescription tp h =
    fromIntegral (29 `hashWithSalt` tp `hashWithSalt` h)
  
  preHashBVExtendDescription :: Bool -> TypeHashId -> Digest
  preHashBVExtendDescription signed h =
    fromIntegral (30 `hashWithSalt` signed `hashWithSalt` h)
  
  preHashBitCastDescription :: TypeHashId -> Digest
  preHashBitCastDescription = fromIntegral . hashWithSalt 31
  {-# INLINE preHashBitCastDescription #-}
  
  preHashBitCastOrDescription :: HashId -> TypeHashId -> Digest
  preHashBitCastOrDescription h1 h2 =
    fromIntegral (32 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashBitCastOrDescription #-}
  
  preHashApplyDescription :: TypeHashId -> TypeHashId -> Digest
  preHashApplyDescription h1 h2 =
    fromIntegral (33 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashApplyDescription #-}
  
  preHashDivIntegralDescription :: HashId -> HashId -> Digest
  preHashDivIntegralDescription h1 h2 =
    fromIntegral (34 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashDivIntegralDescription #-}
  
  preHashModIntegralDescription :: HashId -> HashId -> Digest
  preHashModIntegralDescription h1 h2 =
    fromIntegral (35 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashModIntegralDescription #-}
  
  preHashQuotIntegralDescription :: HashId -> HashId -> Digest
  preHashQuotIntegralDescription h1 h2 =
    fromIntegral (36 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashQuotIntegralDescription #-}
  
  preHashRemIntegralDescription :: HashId -> HashId -> Digest
  preHashRemIntegralDescription h1 h2 =
    fromIntegral (37 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashRemIntegralDescription #-}
  
  preHashFPTraitDescription :: FPTrait -> TypeHashId -> Digest
  preHashFPTraitDescription trait h =
    fromIntegral (38 `hashWithSalt` trait `hashWithSalt` h)
  {-# INLINE preHashFPTraitDescription #-}
  
  preHashFdivDescription :: HashId -> HashId -> Digest
  preHashFdivDescription h1 h2 =
    fromIntegral (39 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashFdivDescription #-}
  
  preHashRecipDescription :: HashId -> Digest
  preHashRecipDescription = fromIntegral . hashWithSalt 40
  {-# INLINE preHashRecipDescription #-}
  
  preHashFloatingUnaryDescription :: FloatingUnaryOp -> HashId -> Digest
  preHashFloatingUnaryDescription op h =
    fromIntegral (41 `hashWithSalt` op `hashWithSalt` h)
  {-# INLINE preHashFloatingUnaryDescription #-}
  
  preHashPowerDescription :: HashId -> HashId -> Digest
  preHashPowerDescription h1 h2 =
    fromIntegral (42 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashPowerDescription #-}
  
  preHashFPUnaryDescription :: FPUnaryOp -> HashId -> Digest
  preHashFPUnaryDescription op h =
    fromIntegral (43 `hashWithSalt` op `hashWithSalt` h)
  {-# INLINE preHashFPUnaryDescription #-}
  
  preHashFPBinaryDescription :: FPBinaryOp -> HashId -> HashId -> Digest
  preHashFPBinaryDescription op h1 h2 =
    fromIntegral (44 `hashWithSalt` op `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashFPBinaryDescription #-}
  
  preHashFPRoundingUnaryDescription ::
    FPRoundingUnaryOp -> HashId -> HashId -> Digest
  preHashFPRoundingUnaryDescription op mode h =
    fromIntegral (45 `hashWithSalt` op `hashWithSalt` mode `hashWithSalt` h)
  {-# INLINE preHashFPRoundingUnaryDescription #-}
  
  preHashFPRoundingBinaryDescription ::
    FPRoundingBinaryOp -> HashId -> HashId -> HashId -> Digest
  preHashFPRoundingBinaryDescription op mode h1 h2 =
    fromIntegral
      ( 46
          `hashWithSalt` op
          `hashWithSalt` mode
          `hashWithSalt` h1
          `hashWithSalt` h2
      )
  
  preHashFPFMADescription ::
    HashId -> HashId -> HashId -> HashId -> Digest
  preHashFPFMADescription mode h1 h2 h3 =
    fromIntegral
      ( 47
          `hashWithSalt` mode
          `hashWithSalt` h1
          `hashWithSalt` h2
          `hashWithSalt` h3
      )
  {-# INLINE preHashFPFMADescription #-}
  
  preHashFromIntegralDescription :: TypeHashId -> Digest
  preHashFromIntegralDescription = fromIntegral . hashWithSalt 48
  {-# INLINE preHashFromIntegralDescription #-}
  
  preHashFromFPOrDescription ::
    HashId -> HashId -> TypeHashId -> Digest
  preHashFromFPOrDescription h1 h2 h3 =
    fromIntegral (49 `hashWithSalt` h1 `hashWithSalt` h2 `hashWithSalt` h3)
  {-# INLINE preHashFromFPOrDescription #-}
  
  preHashToFPTermDescription :: HashId -> TypeHashId -> Digest
  preHashToFPTermDescription h1 h2 =
    fromIntegral (50 `hashWithSalt` h1 `hashWithSalt` h2)
  {-# INLINE preHashToFPTermDescription #-}
  
  instance Interned (Term t) where
    type Uninterned (Term t) = UTerm t
    data Description (Term t) where
      DConTerm ::
        (t -> t -> Bool) -> {-# UNPACK #-} !Digest -> t -> Description (Term t)
      DSymTerm ::
        {-# UNPACK #-} !Digest ->
        TypedSymbol 'AnyKind t ->
        Description (Term t)
      DForallTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !(TypedSymbol 'ConstantKind t) ->
        {-# UNPACK #-} !HashId ->
        Description (Term Bool)
      DExistsTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !(TypedSymbol 'ConstantKind t) ->
        {-# UNPACK #-} !HashId ->
        Description (Term Bool)
      DNotTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        Description (Term Bool)
      DOrTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        !(HS.HashSet (Term Bool)) ->
        Description (Term Bool)
      DAndTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        !(HS.HashSet (Term Bool)) ->
        Description (Term Bool)
      DEqTerm ::
        {-# UNPACK #-} !Digest ->
        Fingerprint ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term Bool)
      DDistinctTerm ::
        {-# UNPACK #-} !Digest ->
        Fingerprint ->
        !(NonEmpty HashId) ->
        Description (Term Bool)
      DITETerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DAddNumTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DNegNumTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DMulNumTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DAbsNumTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DSignumNumTerm ::
        {-# UNPACK #-} !Digest -> {-# UNPACK #-} !HashId -> Description (Term t)
      DLtOrdTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !Fingerprint ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term Bool)
      DLeOrdTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !Fingerprint ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term Bool)
      DAndBitsTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DOrBitsTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DXorBitsTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DComplementBitsTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DShiftLeftTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DShiftRightTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DRotateLeftTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DRotateRightTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term t)
      DBVConcatTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !TypeHashId ->
        {-# UNPACK #-} !TypeHashId ->
        Description (Term t)
      DBitCastTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !TypeHashId ->
        Description (Term b)
      DBitCastOrTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !TypeHashId ->
        Description (Term b)
      DBVSelectTerm ::
        forall bv (w :: Nat).
        {-# UNPACK #-} !Digest ->
        !Fingerprint ->
        {-# UNPACK #-} !TypeHashId ->
        Description (Term (bv w))
      DBVExtendTerm ::
        forall bv (r :: Nat).
        {-# UNPACK #-} !Digest ->
        !Bool ->
        !(Proxy r) ->
        {-# UNPACK #-} !TypeHashId ->
        Description (Term (bv r))
      DApplyTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !TypeHashId ->
        {-# UNPACK #-} !TypeHashId ->
        Description (Term b)
      DDivIntegralTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term a)
      DModIntegralTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term a)
      DQuotIntegralTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term a)
      DRemIntegralTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term a)
      DFPTraitTerm ::
        {-# UNPACK #-} !Digest ->
        FPTrait ->
        {-# UNPACK #-} !TypeHashId ->
        Description (Term Bool)
      DFdivTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term a)
      DRecipTerm ::
        {-# UNPACK #-} !Digest -> {-# UNPACK #-} !HashId -> Description (Term a)
      DFloatingUnaryTerm ::
        {-# UNPACK #-} !Digest ->
        FloatingUnaryOp ->
        {-# UNPACK #-} !HashId ->
        Description (Term a)
      DPowerTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term a)
      DFPUnaryTerm ::
        forall fp (eb :: Nat) (sb :: Nat).
        {-# UNPACK #-} !Digest ->
        FPUnaryOp ->
        {-# UNPACK #-} !HashId ->
        Description (Term (fp eb sb))
      DFPBinaryTerm ::
        forall fp (eb :: Nat) (sb :: Nat).
        {-# UNPACK #-} !Digest ->
        FPBinaryOp ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term (fp eb sb))
      DFPRoundingUnaryTerm ::
        forall fp (eb :: Nat) (sb :: Nat).
        {-# UNPACK #-} !Digest ->
        FPRoundingUnaryOp ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term (fp eb sb))
      DFPRoundingBinaryTerm ::
        forall fp (eb :: Nat) (sb :: Nat).
        {-# UNPACK #-} !Digest ->
        FPRoundingBinaryOp ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term (fp eb sb))
      DFPFMATerm ::
        forall fp (eb :: Nat) (sb :: Nat).
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        Description (Term (fp eb sb))
      DFromIntegralTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !TypeHashId ->
        Description (Term b)
      DFromFPOrTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !TypeHashId ->
        Description (Term a)
      DToFPTerm ::
        {-# UNPACK #-} !Digest ->
        {-# UNPACK #-} !HashId ->
        {-# UNPACK #-} !TypeHashId ->
        Description (Term (FP eb sb))
  
    describe (UConTerm v) = DConTerm sameCon (preHashConDescription v) v
    describe ((USymTerm name) :: UTerm t) =
      DSymTerm @t (preHashSymDescription name) name
    describe (UForallTerm (sym :: TypedSymbol 'ConstantKind arg) arg) =
      let argHashId = termHashId arg
       in DForallTerm (preHashForallDescription sym argHashId) sym argHashId
    describe (UExistsTerm (sym :: TypedSymbol 'ConstantKind arg) arg) =
      let argHashId = termHashId arg
       in DExistsTerm (preHashExistsDescription sym argHashId) sym argHashId
    describe (UNotTerm arg) =
      let argHashId = termHashId arg
       in DNotTerm (preHashNotDescription argHashId) argHashId
    describe (UOrTerm arg1 arg2 s) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DOrTerm
            (preHashOrDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
            s
    describe (UAndTerm arg1 arg2 s) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DAndTerm
            (preHashAndDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
            s
    describe (UEqTerm (arg1@SupportedTerm :: Term arg) arg2) = do
      let fingerprint = typeFingerprint @arg
          arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DEqTerm
            (preHashEqDescription fingerprint arg1HashId arg2HashId)
            fingerprint
            arg1HashId
            arg2HashId
    describe (UDistinctTerm args@((SupportedTerm :: Term arg) :| _)) =
      let fingerprint = typeFingerprint @arg
          argsHashId = termHashId <$> args
       in DDistinctTerm
            (preHashDistinctDescription fingerprint argsHashId)
            fingerprint
            argsHashId
    describe (UITETerm cond (l :: Term arg) r) =
      let condHashId = termHashId cond
          lHashId = termHashId l
          rHashId = termHashId r
       in DITETerm
            (preHashITEDescription condHashId lHashId rHashId)
            condHashId
            lHashId
            rHashId
    describe (UAddNumTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DAddNumTerm
            (preHashAddNumDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (UNegNumTerm arg) =
      let argHashId = termHashId arg
       in DNegNumTerm (preHashNegNumDescription argHashId) argHashId
    describe (UMulNumTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DMulNumTerm
            (preHashMulNumDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (UAbsNumTerm arg) =
      let argHashId = termHashId arg
       in DAbsNumTerm (preHashAbsNumDescription argHashId) argHashId
    describe (USignumNumTerm arg) =
      let argHashId = termHashId arg
       in DSignumNumTerm (preHashSignumNumDescription argHashId) argHashId
    describe (ULtOrdTerm (arg1 :: Term arg) arg2) =
      let tr = typeFingerprint @arg
          arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DLtOrdTerm
            (preHashLtOrdDescription tr arg1HashId arg2HashId)
            tr
            arg1HashId
            arg2HashId
    describe (ULeOrdTerm (arg1 :: Term arg) arg2) =
      let tr = typeFingerprint @arg
          arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DLeOrdTerm
            (preHashLeOrdDescription tr arg1HashId arg2HashId)
            tr
            arg1HashId
            arg2HashId
    describe (UAndBitsTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DAndBitsTerm
            (preHashAndBitsDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (UOrBitsTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DOrBitsTerm
            (preHashOrBitsDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (UXorBitsTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DXorBitsTerm
            (preHashXorBitsDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (UComplementBitsTerm arg) =
      let argHashId = termHashId arg
       in DComplementBitsTerm
            (preHashComplementBitsDescription argHashId)
            argHashId
    describe (UShiftLeftTerm arg n) =
      let argHashId = termHashId arg
          nHashId = termHashId n
       in DShiftLeftTerm
            (preHashShiftLeftDescription argHashId nHashId)
            argHashId
            nHashId
    describe (UShiftRightTerm arg n) =
      let argHashId = termHashId arg
          nHashId = termHashId n
       in DShiftRightTerm
            (preHashShiftRightDescription argHashId nHashId)
            argHashId
            nHashId
    describe (URotateLeftTerm arg n) =
      let argHashId = termHashId arg
          nHashId = termHashId n
       in DRotateLeftTerm
            (preHashRotateLeftDescription argHashId nHashId)
            argHashId
            nHashId
    describe (URotateRightTerm arg n) =
      let argHashId = termHashId arg
          nHashId = termHashId n
       in DRotateRightTerm
            (preHashRotateRightDescription argHashId nHashId)
            argHashId
            nHashId
    describe (UBitCastTerm (arg :: Term a)) =
      let argHashId = termTypeHashId arg
       in DBitCastTerm (preHashBitCastDescription argHashId) argHashId
    describe (UBitCastOrTerm d (arg :: Term a)) =
      let dHashId = termHashId d
          argHashId = termTypeHashId arg
       in DBitCastOrTerm
            (preHashBitCastOrDescription dHashId argHashId)
            dHashId
            argHashId
    describe (UBVConcatTerm (arg1 :: Term bv1) (arg2 :: Term bv2)) =
      let arg1HashId = termTypeHashId arg1
          arg2HashId = termTypeHashId arg2
       in DBVConcatTerm
            (preHashBVConcatDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (UBVSelectTerm (ix :: Proxy ix) _ (arg :: Term arg)) =
      let ixFingerprint = typeRepFingerprint $ someTypeRep ix
          argHashId = termTypeHashId arg
       in DBVSelectTerm
            (preHashBVSelectDescription ixFingerprint argHashId)
            ixFingerprint
            argHashId
    describe (UBVExtendTerm signed (n :: Proxy n) (arg :: Term arg)) =
      let argHashId = termTypeHashId arg
       in DBVExtendTerm
            (preHashBVExtendDescription signed argHashId)
            signed
            n
            argHashId
    describe (UApplyTerm (f :: Term f) (arg :: Term a)) =
      let fHashId = termTypeHashId f
          argHashId = termTypeHashId arg
       in DApplyTerm
            (preHashApplyDescription fHashId argHashId)
            fHashId
            argHashId
    describe (UDivIntegralTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DDivIntegralTerm
            (preHashDivIntegralDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (UModIntegralTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DModIntegralTerm
            (preHashModIntegralDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (UQuotIntegralTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DQuotIntegralTerm
            (preHashQuotIntegralDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (URemIntegralTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DRemIntegralTerm
            (preHashRemIntegralDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (UFPTraitTerm trait (arg :: Term arg)) =
      let argHashId = termTypeHashId arg
       in DFPTraitTerm
            (preHashFPTraitDescription trait argHashId)
            trait
            argHashId
    describe (UFdivTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DFdivTerm
            (preHashFdivDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (URecipTerm arg) =
      let argHashId = termHashId arg
       in DRecipTerm (preHashRecipDescription argHashId) argHashId
    describe (UFloatingUnaryTerm op arg) =
      let argHashId = termHashId arg
       in DFloatingUnaryTerm
            (preHashFloatingUnaryDescription op argHashId)
            op
            argHashId
    describe (UPowerTerm arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DPowerTerm
            (preHashPowerDescription arg1HashId arg2HashId)
            arg1HashId
            arg2HashId
    describe (UFPUnaryTerm op arg) =
      let argHashId = termHashId arg
       in DFPUnaryTerm
            (preHashFPUnaryDescription op argHashId)
            op
            argHashId
    describe (UFPBinaryTerm op arg1 arg2) =
      let arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DFPBinaryTerm
            (preHashFPBinaryDescription op arg1HashId arg2HashId)
            op
            arg1HashId
            arg2HashId
    describe (UFPRoundingUnaryTerm op mode arg) =
      let modeHashId = termHashId mode
          argHashId = termHashId arg
       in DFPRoundingUnaryTerm
            (preHashFPRoundingUnaryDescription op modeHashId argHashId)
            op
            modeHashId
            argHashId
    describe (UFPRoundingBinaryTerm op mode arg1 arg2) =
      let modeHashId = termHashId mode
          arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
       in DFPRoundingBinaryTerm
            ( preHashFPRoundingBinaryDescription
                op
                modeHashId
                arg1HashId
                arg2HashId
            )
            op
            modeHashId
            arg1HashId
            arg2HashId
    describe (UFPFMATerm mode arg1 arg2 arg3) =
      let modeHashId = termHashId mode
          arg1HashId = termHashId arg1
          arg2HashId = termHashId arg2
          arg3HashId = termHashId arg3
       in DFPFMATerm
            (preHashFPFMADescription modeHashId arg1HashId arg2HashId arg3HashId)
            modeHashId
            arg1HashId
            arg2HashId
            arg3HashId
    describe (UFromIntegralTerm (arg :: Term a)) =
      let argHashId = termTypeHashId arg
       in DFromIntegralTerm (preHashFromIntegralDescription argHashId) argHashId
    describe (UFromFPOrTerm d mode (arg :: Term a)) =
      let dHashId = termHashId d
          modeHashId = termHashId mode
          argHashId = termTypeHashId arg
       in DFromFPOrTerm
            (preHashFromFPOrDescription dHashId modeHashId argHashId)
            dHashId
            modeHashId
            argHashId
    describe (UToFPTerm mode (arg :: Term a) _ _) =
      let modeHashId = termHashId mode
          argHashId = termTypeHashId arg
       in DToFPTerm
            (preHashToFPTermDescription modeHashId argHashId)
            modeHashId
            argHashId
  
    -- {-# INLINE describe #-}
  
    identify info = go
      where
        go (UConTerm v) = goPhantomCon info getPhantomDict v
        go (USymTerm v) = SymTerm' info v
        go (UForallTerm sym arg) = ForallTerm' info sym arg
        go (UExistsTerm sym arg) = ExistsTerm' info sym arg
        go (UNotTerm arg) = NotTerm' info arg
        go (UOrTerm arg1 arg2 s) = OrTerm' info arg1 arg2 s
        go (UAndTerm arg1 arg2 s) = AndTerm' info arg1 arg2 s
        go (UEqTerm arg1 arg2) = EqTerm' info arg1 arg2
        go (UDistinctTerm args) = DistinctTerm' info args
        -- ITE is propagated
        go (UITETerm cond l r) = ITETerm' info cond l r
        go (UAddNumTerm arg1 arg2) = AddNumTerm' info arg1 arg2
        go (UNegNumTerm arg) = NegNumTerm' info arg
        go (UMulNumTerm arg1 arg2) = MulNumTerm' info arg1 arg2
        go (UAbsNumTerm arg) = AbsNumTerm' info arg
        go (USignumNumTerm arg) = SignumNumTerm' info arg
        go (ULtOrdTerm arg1 arg2) = LtOrdTerm' info arg1 arg2
        go (ULeOrdTerm arg1 arg2) = LeOrdTerm' info arg1 arg2
        go (UAndBitsTerm arg1 arg2) = AndBitsTerm' info arg1 arg2
        go (UOrBitsTerm arg1 arg2) = OrBitsTerm' info arg1 arg2
        go (UXorBitsTerm arg1 arg2) = XorBitsTerm' info arg1 arg2
        go (UComplementBitsTerm arg) = ComplementBitsTerm' info arg
        go (UShiftLeftTerm arg n) = ShiftLeftTerm' info arg n
        go (UShiftRightTerm arg n) = ShiftRightTerm' info arg n
        go (URotateLeftTerm arg n) = RotateLeftTerm' info arg n
        go (URotateRightTerm arg n) = RotateRightTerm' info arg n
        go (UBitCastTerm arg) = goPhantomBitCast info getPhantomDict arg
        go (UBitCastOrTerm d arg) = BitCastOrTerm' info d arg
        go (UBVConcatTerm arg1 arg2) =
          goPhantomBVConcat info getPhantomDict arg1 arg2
        go (UBVSelectTerm ix w arg) =
          goPhantomBVSelect info getPhantomDict ix w arg
        go (UBVExtendTerm signed n arg) =
          goPhantomBVExtend info getPhantomDict signed n arg
        go (UApplyTerm f arg) = goPhantomApply info getPhantomDict f arg
        go (UDivIntegralTerm arg1 arg2) = DivIntegralTerm' info arg1 arg2
        go (UModIntegralTerm arg1 arg2) = ModIntegralTerm' info arg1 arg2
        go (UQuotIntegralTerm arg1 arg2) = QuotIntegralTerm' info arg1 arg2
        go (URemIntegralTerm arg1 arg2) = RemIntegralTerm' info arg1 arg2
        go (UFPTraitTerm trait arg) =
          goPhantomFPTrait info getPhantomDict trait arg
        go (UFdivTerm arg1 arg2) = FdivTerm' info arg1 arg2
        go (URecipTerm arg) = RecipTerm' info arg
        go (UFloatingUnaryTerm op arg) = FloatingUnaryTerm' info op arg
        go (UPowerTerm arg1 arg2) = PowerTerm' info arg1 arg2
        go (UFPUnaryTerm op arg) = goPhantomFPUnary info getPhantomDict op arg
        go (UFPBinaryTerm op arg1 arg2) =
          goPhantomFPBinary info getPhantomDict op arg1 arg2
        go (UFPRoundingUnaryTerm op mode arg) =
          goPhantomFPRoundingUnary info getPhantomDict op mode arg
        go (UFPRoundingBinaryTerm op mode arg1 arg2) =
          goPhantomFPRoundingBinary info getPhantomDict op mode arg1 arg2
        go (UFPFMATerm mode arg1 arg2 arg3) =
          goPhantomFPFMA info getPhantomDict mode arg1 arg2 arg3
        go (UFromIntegralTerm arg) =
          goPhantomFromIntegral info getPhantomDict arg
        go (UFromFPOrTerm d mode arg) = FromFPOrTerm' info d mode arg
        go (UToFPTerm mode (arg :: Term a) _ _) =
          goPhantomToFP info getPhantomDict mode arg
        {-# INLINE go #-}
  
    -- {-# INLINE identify #-}
    threadId = termThreadId
    {-# INLINE threadId #-}
  
    descriptionDigest (DConTerm _ h _) = h
    descriptionDigest (DSymTerm h _) = h
    descriptionDigest (DForallTerm h _ _) = h
    descriptionDigest (DExistsTerm h _ _) = h
    descriptionDigest (DNotTerm h _) = h
    descriptionDigest (DOrTerm h _ _ _) = h
    descriptionDigest (DAndTerm h _ _ _) = h
    descriptionDigest (DEqTerm h _ _ _) = h
    descriptionDigest (DDistinctTerm h _ _) = h
    descriptionDigest (DITETerm h _ _ _) = h
    descriptionDigest (DAddNumTerm h _ _) = h
    descriptionDigest (DNegNumTerm h _) = h
    descriptionDigest (DMulNumTerm h _ _) = h
    descriptionDigest (DAbsNumTerm h _) = h
    descriptionDigest (DSignumNumTerm h _) = h
    descriptionDigest (DLtOrdTerm h _ _ _) = h
    descriptionDigest (DLeOrdTerm h _ _ _) = h
    descriptionDigest (DAndBitsTerm h _ _) = h
    descriptionDigest (DOrBitsTerm h _ _) = h
    descriptionDigest (DXorBitsTerm h _ _) = h
    descriptionDigest (DComplementBitsTerm h _) = h
    descriptionDigest (DShiftLeftTerm h _ _) = h
    descriptionDigest (DShiftRightTerm h _ _) = h
    descriptionDigest (DRotateLeftTerm h _ _) = h
    descriptionDigest (DRotateRightTerm h _ _) = h
    descriptionDigest (DBitCastTerm h _) = h
    descriptionDigest (DBitCastOrTerm h _ _) = h
    descriptionDigest (DBVConcatTerm h _ _) = h
    descriptionDigest (DBVSelectTerm h _ _) = h
    descriptionDigest (DBVExtendTerm h _ _ _) = h
    descriptionDigest (DDivIntegralTerm h _ _) = h
    descriptionDigest (DModIntegralTerm h _ _) = h
    descriptionDigest (DQuotIntegralTerm h _ _) = h
    descriptionDigest (DRemIntegralTerm h _ _) = h
    descriptionDigest (DApplyTerm h _ _) = h
    descriptionDigest (DFPTraitTerm h _ _) = h
    descriptionDigest (DFdivTerm h _ _) = h
    descriptionDigest (DRecipTerm h _) = h
    descriptionDigest (DFloatingUnaryTerm h _ _) = h
    descriptionDigest (DPowerTerm h _ _) = h
    descriptionDigest (DFPUnaryTerm h _ _) = h
    descriptionDigest (DFPBinaryTerm h _ _ _) = h
    descriptionDigest (DFPRoundingUnaryTerm h _ _ _) = h
    descriptionDigest (DFPRoundingBinaryTerm h _ _ _ _) = h
    descriptionDigest (DFPFMATerm h _ _ _ _) = h
    descriptionDigest (DFromIntegralTerm h _) = h
    descriptionDigest (DFromFPOrTerm h _ _ _) = h
    descriptionDigest (DToFPTerm h _ _) = h
  
  -- {-# INLINE descriptionDigest #-}
  {-# NOINLINE goPhantomCon #-}
  goPhantomCon ::
    CachedInfo ->
    PhantomDict t ->
    t ->
    Term t
  goPhantomCon info PhantomDict v = ConTerm' info v
  
  {-# NOINLINE goPhantomBitCast #-}
  goPhantomBitCast ::
    (PEvalBitCastTerm a t) =>
    CachedInfo ->
    PhantomDict t ->
    Term a ->
    Term t
  goPhantomBitCast info PhantomDict arg = BitCastTerm' info arg
  
  {-# NOINLINE goPhantomBVConcat #-}
  goPhantomBVConcat ::
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      KnownNat (l + r),
      1 <= l,
      1 <= r,
      1 <= l + r
    ) =>
    CachedInfo ->
    PhantomDict (bv (l + r)) ->
    Term (bv l) ->
    Term (bv r) ->
    Term (bv (l + r))
  goPhantomBVConcat info PhantomDict arg1 arg2 =
    BVConcatTerm' info arg1 arg2
  
  {-# NOINLINE goPhantomBVSelect #-}
  goPhantomBVSelect ::
    ( PEvalBVTerm bv,
      KnownNat n,
      KnownNat ix,
      KnownNat w,
      1 <= n,
      1 <= w,
      ix + w <= n
    ) =>
    CachedInfo ->
    PhantomDict (bv w) ->
    Proxy ix ->
    Proxy w ->
    Term (bv n) ->
    Term (bv w)
  goPhantomBVSelect info PhantomDict ix w arg =
    BVSelectTerm' info ix w arg
  
  {-# NOINLINE goPhantomBVExtend #-}
  goPhantomBVExtend ::
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      1 <= l,
      1 <= r,
      l <= r
    ) =>
    CachedInfo ->
    PhantomDict (bv r) ->
    Bool ->
    Proxy r ->
    Term (bv l) ->
    Term (bv r)
  goPhantomBVExtend info PhantomDict signed n arg =
    BVExtendTerm' info signed n arg
  
  {-# NOINLINE goPhantomApply #-}
  goPhantomApply ::
    (PEvalApplyTerm f a t) =>
    CachedInfo ->
    PhantomDict t ->
    Term f ->
    Term a ->
    Term t
  goPhantomApply info PhantomDict f arg = ApplyTerm' info f arg
  
  {-# NOINLINE goPhantomFPTrait #-}
  goPhantomFPTrait ::
    (ValidFP eb sb, PEvalFPTerm fp) =>
    CachedInfo ->
    PhantomDict (fp eb sb) ->
    FPTrait ->
    Term (fp eb sb) ->
    Term Bool
  goPhantomFPTrait info PhantomDict trait arg = FPTraitTerm' info trait arg
  
  {-# NOINLINE goPhantomFPUnary #-}
  goPhantomFPUnary ::
    (ValidFP eb sb, PEvalFPTerm fp) =>
    CachedInfo ->
    PhantomDict (fp eb sb) ->
    FPUnaryOp ->
    Term (fp eb sb) ->
    Term (fp eb sb)
  goPhantomFPUnary info PhantomDict op arg = FPUnaryTerm' info op arg
  
  {-# NOINLINE goPhantomFPBinary #-}
  goPhantomFPBinary ::
    (ValidFP eb sb, PEvalFPTerm fp) =>
    CachedInfo ->
    PhantomDict (fp eb sb) ->
    FPBinaryOp ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term (fp eb sb)
  goPhantomFPBinary info PhantomDict op arg1 arg2 =
    FPBinaryTerm' info op arg1 arg2
  
  {-# NOINLINE goPhantomFPRoundingUnary #-}
  goPhantomFPRoundingUnary ::
    (ValidFP eb sb, PEvalFPTerm fp) =>
    CachedInfo ->
    PhantomDict (fp eb sb) ->
    FPRoundingUnaryOp ->
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term (fp eb sb)
  goPhantomFPRoundingUnary info PhantomDict op mode arg =
    FPRoundingUnaryTerm' info op mode arg
  
  {-# NOINLINE goPhantomFPRoundingBinary #-}
  goPhantomFPRoundingBinary ::
    (ValidFP eb sb, PEvalFPTerm fp) =>
    CachedInfo ->
    PhantomDict (fp eb sb) ->
    FPRoundingBinaryOp ->
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term (fp eb sb)
  goPhantomFPRoundingBinary info PhantomDict op mode arg1 arg2 =
    FPRoundingBinaryTerm' info op mode arg1 arg2
  
  {-# NOINLINE goPhantomFPFMA #-}
  goPhantomFPFMA ::
    (ValidFP eb sb, PEvalFPTerm fp) =>
    CachedInfo ->
    PhantomDict (fp eb sb) ->
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term (fp eb sb)
  goPhantomFPFMA info PhantomDict mode arg1 arg2 arg3 =
    FPFMATerm' info mode arg1 arg2 arg3
  
  {-# NOINLINE goPhantomFromIntegral #-}
  goPhantomFromIntegral ::
    (PEvalFromIntegralTerm a b) =>
    CachedInfo ->
    PhantomDict b ->
    Term a ->
    Term b
  goPhantomFromIntegral info PhantomDict arg = FromIntegralTerm' info arg
  
  {-# NOINLINE goPhantomToFP #-}
  goPhantomToFP ::
    forall a eb sb.
    (ValidFP eb sb, PEvalIEEEFPConvertibleTerm a) =>
    CachedInfo ->
    PhantomDict (FP eb sb) ->
    Term FPRoundingMode ->
    Term a ->
    Term (FP eb sb)
  goPhantomToFP info PhantomDict mode arg =
    ToFPTerm' info mode arg (Proxy @eb) (Proxy @sb)
  
  instance Eq (Description (Term t)) where
    DConTerm eqFunc _ l == DConTerm _ _ r =
      eqFunc l r
    DSymTerm _ ls == DSymTerm _ rs = ls == rs
    DForallTerm _ ls li == DForallTerm _ rs ri =
      eqHeteroSymbol ls rs && eqHashId li ri
    DExistsTerm _ ls li == DExistsTerm _ rs ri =
      eqHeteroSymbol ls rs && eqHashId li ri
    DNotTerm _ li == DNotTerm _ ri = eqHashId li ri
    DOrTerm _ li1 li2 _ == DOrTerm _ ri1 ri2 _ = eqHashId li1 ri1 && eqHashId li2 ri2
    DAndTerm _ li1 li2 _ == DAndTerm _ ri1 ri2 _ = eqHashId li1 ri1 && eqHashId li2 ri2
    DEqTerm _ lfp li1 li2 == DEqTerm _ rfp ri1 ri2 = lfp == rfp && eqHashId li1 ri1 && eqHashId li2 ri2
    DDistinctTerm _ lfp li == DDistinctTerm _ rfp ri =
      lfp == rfp
        && length li == length ri
        && and (zipWith eqHashId (toList li) (toList ri))
    DITETerm _ lc li1 li2 == DITETerm _ rc ri1 ri2 = eqHashId lc rc && eqHashId li1 ri1 && eqHashId li2 ri2
    DAddNumTerm _ li1 li2 == DAddNumTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DNegNumTerm _ li == DNegNumTerm _ ri = eqHashId li ri
    DMulNumTerm _ li1 li2 == DMulNumTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DAbsNumTerm _ li == DAbsNumTerm _ ri = eqHashId li ri
    DSignumNumTerm _ li == DSignumNumTerm _ ri = eqHashId li ri
    DLtOrdTerm _ lrep li1 li2 == DLtOrdTerm _ rrep ri1 ri2 = lrep == rrep && eqHashId li1 ri1 && eqHashId li2 ri2
    DLeOrdTerm _ lrep li1 li2 == DLeOrdTerm _ rrep ri1 ri2 = lrep == rrep && eqHashId li1 ri1 && eqHashId li2 ri2
    DAndBitsTerm _ li1 li2 == DAndBitsTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DOrBitsTerm _ li1 li2 == DOrBitsTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DXorBitsTerm _ li1 li2 == DXorBitsTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DComplementBitsTerm _ li == DComplementBitsTerm _ ri = eqHashId li ri
    DShiftLeftTerm _ li ln == DShiftLeftTerm _ ri rn = eqHashId li ri && eqHashId ln rn
    DShiftRightTerm _ li ln == DShiftRightTerm _ ri rn = eqHashId li ri && eqHashId ln rn
    DRotateLeftTerm _ li ln == DRotateLeftTerm _ ri rn = eqHashId li ri && eqHashId ln rn
    DRotateRightTerm _ li ln == DRotateRightTerm _ ri rn = eqHashId li ri && eqHashId ln rn
    DBitCastTerm _ li == DBitCastTerm _ ri = li == ri
    DBitCastOrTerm _ ld li == DBitCastOrTerm _ rd ri = ld == rd && li == ri
    DBVConcatTerm _ li1 li2 == DBVConcatTerm _ ri1 ri2 = li1 == ri1 && li2 == ri2
    DBVSelectTerm _ lix li == DBVSelectTerm _ rix ri =
      lix == rix && li == ri
    DBVExtendTerm _ lIsSigned _ li == DBVExtendTerm _ rIsSigned _ ri =
      lIsSigned == rIsSigned
        && li == ri
    DApplyTerm _ lf li == DApplyTerm _ rf ri = lf == rf && li == ri
    DDivIntegralTerm _ li1 li2 == DDivIntegralTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DModIntegralTerm _ li1 li2 == DModIntegralTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DQuotIntegralTerm _ li1 li2 == DQuotIntegralTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DRemIntegralTerm _ li1 li2 == DRemIntegralTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DFPTraitTerm _ lt li == DFPTraitTerm _ rt ri = lt == rt && li == ri
    DFdivTerm _ li1 li2 == DFdivTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DRecipTerm _ li == DRecipTerm _ ri = eqHashId li ri
    DFloatingUnaryTerm _ lop li == DFloatingUnaryTerm _ rop ri = lop == rop && eqHashId li ri
    DPowerTerm _ li1 li2 == DPowerTerm _ ri1 ri2 = eqHashId li1 ri1 && eqHashId li2 ri2
    DFPUnaryTerm _ lop li == DFPUnaryTerm _ rop ri = lop == rop && eqHashId li ri
    DFPBinaryTerm _ lop li1 li2 == DFPBinaryTerm _ rop ri1 ri2 = lop == rop && eqHashId li1 ri1 && eqHashId li2 ri2
    DFPRoundingUnaryTerm _ lop lmode li == DFPRoundingUnaryTerm _ rop rmode ri =
      lop == rop && eqHashId lmode rmode && eqHashId li ri
    DFPRoundingBinaryTerm _ lop lmode li1 li2 == DFPRoundingBinaryTerm _ rop rmode ri1 ri2 =
      lop == rop && eqHashId lmode rmode && eqHashId li1 ri1 && eqHashId li2 ri2
    DFPFMATerm _ lmode li1 li2 li3 == DFPFMATerm _ rmode ri1 ri2 ri3 =
      eqHashId lmode rmode && eqHashId li1 ri1 && eqHashId li2 ri2 && eqHashId li3 ri3
    DFromIntegralTerm _ li == DFromIntegralTerm _ ri = li == ri
    DFromFPOrTerm _ ld li lai == DFromFPOrTerm _ rd ri rai = eqHashId ld rd && eqHashId li ri && lai == rai
    DToFPTerm _ li lai == DToFPTerm _ ri rai = eqHashId li ri && lai == rai
    _ == _ = False
  
  -- {-# INLINE (==) #-}
  
  instance Hashable (Description (Term t)) where
    hashWithSalt s = hashWithSalt s . descriptionDigest
    {-# INLINE hashWithSalt #-}
  
  fullReconstructTerm1 ::
    forall a b.
    (Term a -> IO (Term b)) ->
    Term a ->
    IO (Term b)
  fullReconstructTerm1 f x = fullReconstructTerm x >>= f
  {-# INLINE fullReconstructTerm1 #-}
  
  fullReconstructTerm2 ::
    forall a b c.
    (Term a -> Term b -> IO (Term c)) ->
    Term a ->
    Term b ->
    IO (Term c)
  fullReconstructTerm2 f x y = do
    rx <- fullReconstructTerm x
    ry <- fullReconstructTerm y
    f rx ry
  {-# INLINE fullReconstructTerm2 #-}
  
  fullReconstructTerm3 ::
    forall a b c d.
    (Term a -> Term b -> Term c -> IO (Term d)) ->
    Term a ->
    Term b ->
    Term c ->
    IO (Term d)
  fullReconstructTerm3 f x y z = do
    rx <- fullReconstructTerm x
    ry <- fullReconstructTerm y
    rz <- fullReconstructTerm z
    f rx ry rz
  {-# INLINE fullReconstructTerm3 #-}
  
  fullReconstructTerm2Set ::
    forall a c.
    (Term a -> Term a -> HS.HashSet (Term a) -> IO (Term c)) ->
    Term a ->
    Term a ->
    HS.HashSet (Term a) ->
    IO (Term c)
  fullReconstructTerm2Set f x y s = do
    rx@SupportedTerm <- fullReconstructTerm x
    ry <- fullReconstructTerm y
    rs <- traverse fullReconstructTerm (HS.toList s)
    f rx ry (HS.fromList rs)
  {-# INLINE fullReconstructTerm2Set #-}
  
  fullReconstructTerm :: forall t. Term t -> IO (Term t)
  fullReconstructTerm (ConTerm i) = curThreadConTerm i
  fullReconstructTerm (SymTerm sym) = curThreadSymTerm sym
  fullReconstructTerm (ForallTerm sym arg) =
    fullReconstructTerm1 (curThreadForallTerm sym) arg
  fullReconstructTerm (ExistsTerm sym arg) =
    fullReconstructTerm1 (curThreadExistsTerm sym) arg
  fullReconstructTerm (NotTerm arg) =
    fullReconstructTerm1 curThreadNotTerm arg
  fullReconstructTerm (OrTermAll arg1 arg2 s) =
    fullReconstructTerm2Set curThreadOrTerm arg1 arg2 s
  fullReconstructTerm (OrTerm _ _) = error "Make compiler happy"
  fullReconstructTerm (AndTermAll arg1 arg2 s) =
    fullReconstructTerm2Set curThreadAndTerm arg1 arg2 s
  fullReconstructTerm (AndTerm _ _) = error "Make compiler happy"
  fullReconstructTerm (EqTerm arg1 arg2) =
    fullReconstructTerm2 curThreadEqTerm arg1 arg2
  fullReconstructTerm (DistinctTerm args) =
    traverse fullReconstructTerm args >>= curThreadDistinctTerm
  fullReconstructTerm (ITETerm cond arg1 arg2) =
    fullReconstructTerm3 curThreadIteTerm cond arg1 arg2
  fullReconstructTerm (AddNumTerm arg1 arg2) =
    fullReconstructTerm2 curThreadAddNumTerm arg1 arg2
  fullReconstructTerm (NegNumTerm arg) =
    fullReconstructTerm1 curThreadNegNumTerm arg
  fullReconstructTerm (MulNumTerm arg1 arg2) =
    fullReconstructTerm2 curThreadMulNumTerm arg1 arg2
  fullReconstructTerm (AbsNumTerm arg) =
    fullReconstructTerm1 curThreadAbsNumTerm arg
  fullReconstructTerm (SignumNumTerm arg) =
    fullReconstructTerm1 curThreadSignumNumTerm arg
  fullReconstructTerm (LtOrdTerm arg1 arg2) =
    fullReconstructTerm2 curThreadLtOrdTerm arg1 arg2
  fullReconstructTerm (LeOrdTerm arg1 arg2) =
    fullReconstructTerm2 curThreadLeOrdTerm arg1 arg2
  fullReconstructTerm (AndBitsTerm arg1 arg2) =
    fullReconstructTerm2 curThreadAndBitsTerm arg1 arg2
  fullReconstructTerm (OrBitsTerm arg1 arg2) =
    fullReconstructTerm2 curThreadOrBitsTerm arg1 arg2
  fullReconstructTerm (XorBitsTerm arg1 arg2) =
    fullReconstructTerm2 curThreadXorBitsTerm arg1 arg2
  fullReconstructTerm (ComplementBitsTerm arg) =
    fullReconstructTerm1 curThreadComplementBitsTerm arg
  fullReconstructTerm (ShiftLeftTerm arg n) =
    fullReconstructTerm1 (curThreadShiftLeftTerm arg) n
  fullReconstructTerm (ShiftRightTerm arg n) =
    fullReconstructTerm1 (curThreadShiftRightTerm arg) n
  fullReconstructTerm (RotateLeftTerm arg n) =
    fullReconstructTerm1 (curThreadRotateLeftTerm arg) n
  fullReconstructTerm (RotateRightTerm arg n) =
    fullReconstructTerm1 (curThreadRotateRightTerm arg) n
  fullReconstructTerm (BitCastTerm v) =
    fullReconstructTerm1 curThreadBitCastTerm v
  fullReconstructTerm (BitCastOrTerm d v) =
    fullReconstructTerm2 curThreadBitCastOrTerm d v
  fullReconstructTerm (BVConcatTerm arg1 arg2) =
    fullReconstructTerm2 curThreadBVConcatTerm arg1 arg2
  fullReconstructTerm (BVSelectTerm (_ :: Proxy ix) (_ :: Proxy w) arg) =
    fullReconstructTerm1 (curThreadBVSelectTerm (Proxy @ix) (Proxy @w)) arg
  fullReconstructTerm (BVExtendTerm signed p arg) =
    fullReconstructTerm1 (curThreadBVExtendTerm signed p) arg
  fullReconstructTerm (ApplyTerm f arg) =
    fullReconstructTerm2 curThreadApplyTerm f arg
  fullReconstructTerm (DivIntegralTerm arg1 arg2) =
    fullReconstructTerm2 curThreadDivIntegralTerm arg1 arg2
  fullReconstructTerm (ModIntegralTerm arg1 arg2) =
    fullReconstructTerm2 curThreadModIntegralTerm arg1 arg2
  fullReconstructTerm (QuotIntegralTerm arg1 arg2) =
    fullReconstructTerm2 curThreadQuotIntegralTerm arg1 arg2
  fullReconstructTerm (RemIntegralTerm arg1 arg2) =
    fullReconstructTerm2 curThreadRemIntegralTerm arg1 arg2
  fullReconstructTerm (FPTraitTerm trait arg) =
    fullReconstructTerm1 (curThreadFpTraitTerm trait) arg
  fullReconstructTerm (FdivTerm arg1 arg2) =
    fullReconstructTerm2 curThreadFdivTerm arg1 arg2
  fullReconstructTerm (RecipTerm arg) =
    fullReconstructTerm1 curThreadRecipTerm arg
  fullReconstructTerm (FloatingUnaryTerm op arg) =
    fullReconstructTerm1 (curThreadFloatingUnaryTerm op) arg
  fullReconstructTerm (PowerTerm arg1 arg2) =
    fullReconstructTerm2 curThreadPowerTerm arg1 arg2
  fullReconstructTerm (FPUnaryTerm op arg) =
    fullReconstructTerm1 (curThreadFpUnaryTerm op) arg
  fullReconstructTerm (FPBinaryTerm op arg1 arg2) =
    fullReconstructTerm2 (curThreadFpBinaryTerm op) arg1 arg2
  fullReconstructTerm (FPRoundingUnaryTerm op mode arg) =
    fullReconstructTerm2 (curThreadFpRoundingUnaryTerm op) mode arg
  fullReconstructTerm (FPRoundingBinaryTerm op mode arg1 arg2) =
    fullReconstructTerm3 (curThreadFpRoundingBinaryTerm op) mode arg1 arg2
  fullReconstructTerm (FPFMATerm mode arg1 arg2 arg3) = do
    rmode <- fullReconstructTerm mode
    rarg1 <- fullReconstructTerm arg1
    rarg2 <- fullReconstructTerm arg2
    rarg3 <- fullReconstructTerm arg3
    curThreadFpFMATerm rmode rarg1 rarg2 rarg3
  fullReconstructTerm (FromIntegralTerm arg) =
    fullReconstructTerm1 curThreadFromIntegralTerm arg
  fullReconstructTerm (FromFPOrTerm d r arg) =
    fullReconstructTerm3 curThreadFromFPOrTerm d r arg
  fullReconstructTerm (ToFPTerm r arg _ _) =
    fullReconstructTerm2 curThreadToFPTerm r arg
  
  toCurThreadImpl :: forall t. WeakThreadId -> Term t -> IO (Term t)
  toCurThreadImpl tid t | termThreadId t == tid = return t
  toCurThreadImpl _ t = fullReconstructTerm t
  {-# INLINE toCurThreadImpl #-}
  
  -- | Convert a term to the current thread.
  toCurThread :: forall t. Term t -> IO (Term t)
  toCurThread t = do
    tid <- myWeakThreadId
    toCurThreadImpl tid t
  {-# INLINE toCurThread #-}
  
  -- | Construct and internalizing a 'ConTerm'.
  curThreadConTerm :: forall t. (SupportedPrim t) => t -> IO (Term t)
  curThreadConTerm t = intern $ UConTerm t
  {-# INLINE curThreadConTerm #-}
  
  -- | Construct and internalizing a 'SymTerm'.
  curThreadSymTerm :: forall knd t. TypedSymbol knd t -> IO (Term t)
  curThreadSymTerm (TypedSymbol s) = intern $ USymTerm $ TypedSymbol s
  {-# INLINE curThreadSymTerm #-}
  
  -- | Construct and internalizing a 'ForallTerm'.
  curThreadForallTerm ::
    TypedSymbol 'ConstantKind t ->
    Term Bool ->
    IO (Term Bool)
  curThreadForallTerm sym arg = intern $ UForallTerm sym arg
  {-# INLINE curThreadForallTerm #-}
  
  -- | Construct and internalizing a 'ExistsTerm'.
  curThreadExistsTerm ::
    TypedSymbol 'ConstantKind t ->
    Term Bool ->
    IO (Term Bool)
  curThreadExistsTerm sym arg = intern $ UExistsTerm sym arg
  {-# INLINE curThreadExistsTerm #-}
  
  -- | Construct and internalizing a 'SymTerm' with an identifier, using simple
  -- symbols.
  curThreadSsymTerm :: (SupportedPrim t) => Identifier -> IO (Term t)
  curThreadSsymTerm ident =
    curThreadSymTerm @AnyKind $ TypedSymbol $ SimpleSymbol ident
  {-# INLINE curThreadSsymTerm #-}
  
  -- | Construct and internalizing a 'SymTerm' with an identifier and an index,
  -- using indexed symbols.
  curThreadIsymTerm :: (SupportedPrim t) => Identifier -> Int -> IO (Term t)
  curThreadIsymTerm str idx =
    curThreadSymTerm @AnyKind $ TypedSymbol $ IndexedSymbol str idx
  {-# INLINE curThreadIsymTerm #-}
  
  -- | Construct and internalizing a 'NotTerm'.
  curThreadNotTerm :: Term Bool -> IO (Term Bool)
  curThreadNotTerm = intern . UNotTerm
  {-# INLINE curThreadNotTerm #-}
  
  -- | Construct and internalizing a 'OrTerm'.
  curThreadOrTerm :: Term Bool -> Term Bool -> HS.HashSet (Term Bool) -> IO (Term Bool)
  curThreadOrTerm l r s = intern $ UOrTerm l r s
  {-# INLINE curThreadOrTerm #-}
  
  -- | Construct and internalizing a 'AndTerm'.
  curThreadAndTerm :: Term Bool -> Term Bool -> HS.HashSet (Term Bool) -> IO (Term Bool)
  curThreadAndTerm l r s = intern $ UAndTerm l r s
  {-# INLINE curThreadAndTerm #-}
  
  -- | Construct and internalizing a 'EqTerm'.
  curThreadEqTerm :: Term a -> Term a -> IO (Term Bool)
  curThreadEqTerm l r = intern $ UEqTerm l r
  {-# INLINE curThreadEqTerm #-}
  
  -- | Construct and internalizing a 'DistinctTerm'.
  curThreadDistinctTerm :: NonEmpty (Term a) -> IO (Term Bool)
  curThreadDistinctTerm args = intern $ UDistinctTerm args
  {-# INLINE curThreadDistinctTerm #-}
  
  -- | Construct and internalizing a 'ITETerm'.
  curThreadIteTerm :: Term Bool -> Term a -> Term a -> IO (Term a)
  curThreadIteTerm c l@SupportedTerm r = intern $ UITETerm c l r
  {-# INLINE curThreadIteTerm #-}
  
  -- | Construct and internalizing a 'AddNumTerm'.
  curThreadAddNumTerm :: (PEvalNumTerm a) => Term a -> Term a -> IO (Term a)
  curThreadAddNumTerm l@SupportedTerm r = intern $ UAddNumTerm l r
  {-# INLINE curThreadAddNumTerm #-}
  
  -- | Construct and internalizing a 'NegNumTerm'.
  curThreadNegNumTerm :: (PEvalNumTerm a) => Term a -> IO (Term a)
  curThreadNegNumTerm l@SupportedTerm = intern $ UNegNumTerm l
  {-# INLINE curThreadNegNumTerm #-}
  
  -- | Construct and internalizing a 'MulNumTerm'.
  curThreadMulNumTerm :: (PEvalNumTerm a) => Term a -> Term a -> IO (Term a)
  curThreadMulNumTerm l@SupportedTerm r = intern $ UMulNumTerm l r
  {-# INLINE curThreadMulNumTerm #-}
  
  -- | Construct and internalizing a 'AbsNumTerm'.
  curThreadAbsNumTerm :: (PEvalNumTerm a) => Term a -> IO (Term a)
  curThreadAbsNumTerm l@SupportedTerm = intern $ UAbsNumTerm l
  {-# INLINE curThreadAbsNumTerm #-}
  
  -- | Construct and internalizing a 'SignumNumTerm'.
  curThreadSignumNumTerm :: (PEvalNumTerm a) => Term a -> IO (Term a)
  curThreadSignumNumTerm l@SupportedTerm = intern $ USignumNumTerm l
  {-# INLINE curThreadSignumNumTerm #-}
  
  -- | Construct and internalizing a 'LtOrdTerm'.
  curThreadLtOrdTerm :: (PEvalOrdTerm a) => Term a -> Term a -> IO (Term Bool)
  curThreadLtOrdTerm l@SupportedTerm r = intern $ ULtOrdTerm l r
  {-# INLINE curThreadLtOrdTerm #-}
  
  -- | Construct and internalizing a 'LeOrdTerm'.
  curThreadLeOrdTerm :: (PEvalOrdTerm a) => Term a -> Term a -> IO (Term Bool)
  curThreadLeOrdTerm l@SupportedTerm r = intern $ ULeOrdTerm l r
  {-# INLINE curThreadLeOrdTerm #-}
  
  -- | Construct and internalizing a 'AndBitsTerm'.
  curThreadAndBitsTerm :: (PEvalBitwiseTerm a) => Term a -> Term a -> IO (Term a)
  curThreadAndBitsTerm l@SupportedTerm r = intern $ UAndBitsTerm l r
  {-# INLINE curThreadAndBitsTerm #-}
  
  -- | Construct and internalizing a 'OrBitsTerm'.
  curThreadOrBitsTerm :: (PEvalBitwiseTerm a) => Term a -> Term a -> IO (Term a)
  curThreadOrBitsTerm l@SupportedTerm r = intern $ UOrBitsTerm l r
  {-# INLINE curThreadOrBitsTerm #-}
  
  -- | Construct and internalizing a 'XorBitsTerm'.
  curThreadXorBitsTerm :: (PEvalBitwiseTerm a) => Term a -> Term a -> IO (Term a)
  curThreadXorBitsTerm l@SupportedTerm r = intern $ UXorBitsTerm l r
  {-# INLINE curThreadXorBitsTerm #-}
  
  -- | Construct and internalizing a 'ComplementBitsTerm'.
  curThreadComplementBitsTerm :: (PEvalBitwiseTerm a) => Term a -> IO (Term a)
  curThreadComplementBitsTerm l@SupportedTerm = intern $ UComplementBitsTerm l
  {-# INLINE curThreadComplementBitsTerm #-}
  
  -- | Construct and internalizing a 'ShiftLeftTerm'.
  curThreadShiftLeftTerm :: (PEvalShiftTerm a) => Term a -> Term a -> IO (Term a)
  curThreadShiftLeftTerm t@SupportedTerm n = intern $ UShiftLeftTerm t n
  {-# INLINE curThreadShiftLeftTerm #-}
  
  -- | Construct and internalizing a 'ShiftRightTerm'.
  curThreadShiftRightTerm :: (PEvalShiftTerm a) => Term a -> Term a -> IO (Term a)
  curThreadShiftRightTerm t@SupportedTerm n = intern $ UShiftRightTerm t n
  {-# INLINE curThreadShiftRightTerm #-}
  
  -- | Construct and internalizing a 'RotateLeftTerm'.
  curThreadRotateLeftTerm ::
    (PEvalRotateTerm a) => Term a -> Term a -> IO (Term a)
  curThreadRotateLeftTerm t@SupportedTerm n = intern $ URotateLeftTerm t n
  {-# INLINE curThreadRotateLeftTerm #-}
  
  -- | Construct and internalizing a 'RotateRightTerm'.
  curThreadRotateRightTerm ::
    (PEvalRotateTerm a) => Term a -> Term a -> IO (Term a)
  curThreadRotateRightTerm t@SupportedTerm n = intern $ URotateRightTerm t n
  {-# INLINE curThreadRotateRightTerm #-}
  
  -- | Construct and internalizing a 'BitCastTerm'.
  curThreadBitCastTerm ::
    forall a b.
    (SupportedPrim b, PEvalBitCastTerm a b) =>
    Term a ->
    IO (Term b)
  curThreadBitCastTerm = intern . UBitCastTerm
  {-# INLINE curThreadBitCastTerm #-}
  
  -- | Construct and internalizing a 'BitCastOrTerm'.
  curThreadBitCastOrTerm ::
    (PEvalBitCastOrTerm a b) =>
    Term b ->
    Term a ->
    IO (Term b)
  curThreadBitCastOrTerm d@SupportedTerm a = intern $ UBitCastOrTerm d a
  {-# INLINE curThreadBitCastOrTerm #-}
  
  -- | Construct and internalizing a 'BVConcatTerm'.
  curThreadBVConcatTerm ::
    forall bv l r.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      KnownNat (l + r),
      1 <= l,
      1 <= r,
      1 <= l + r,
      SupportedPrim (bv (l + r))
    ) =>
    Term (bv l) ->
    Term (bv r) ->
    IO (Term (bv (l + r)))
  curThreadBVConcatTerm l r = intern $ UBVConcatTerm l r
  {-# INLINE curThreadBVConcatTerm #-}
  
  -- | Construct and internalizing a 'BVSelectTerm'.
  curThreadBVSelectTerm ::
    forall bv n ix w p q.
    ( PEvalBVTerm bv,
      KnownNat n,
      KnownNat ix,
      KnownNat w,
      1 <= n,
      1 <= w,
      ix + w <= n,
      SupportedPrim (bv w)
    ) =>
    p ix ->
    q w ->
    Term (bv n) ->
    IO (Term (bv w))
  curThreadBVSelectTerm _ _ v = intern $ UBVSelectTerm (Proxy @ix) (Proxy @w) v
  {-# INLINE curThreadBVSelectTerm #-}
  
  -- | Construct and internalizing a 'BVExtendTerm'.
  curThreadBVExtendTerm ::
    forall bv l r proxy.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      1 <= l,
      1 <= r,
      l <= r,
      SupportedPrim (bv r)
    ) =>
    Bool ->
    proxy r ->
    Term (bv l) ->
    IO (Term (bv r))
  curThreadBVExtendTerm signed _ v = intern $ UBVExtendTerm signed (Proxy @r) v
  {-# INLINE curThreadBVExtendTerm #-}
  
  -- | Construct and internalizing a 'BVExtendTerm' with sign extension.
  curThreadBvsignExtendTerm ::
    forall bv l r proxy.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      1 <= l,
      1 <= r,
      l <= r,
      SupportedPrim (bv r)
    ) =>
    proxy r ->
    Term (bv l) ->
    IO (Term (bv r))
  curThreadBvsignExtendTerm _ v = intern $ UBVExtendTerm True (Proxy @r) v
  {-# INLINE curThreadBvsignExtendTerm #-}
  
  -- | Construct and internalizing a 'BVExtendTerm' with zero extension.
  curThreadBvzeroExtendTerm ::
    forall bv l r proxy.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      1 <= l,
      1 <= r,
      l <= r,
      SupportedPrim (bv r)
    ) =>
    proxy r ->
    Term (bv l) ->
    IO (Term (bv r))
  curThreadBvzeroExtendTerm _ v = intern $ UBVExtendTerm False (Proxy @r) v
  {-# INLINE curThreadBvzeroExtendTerm #-}
  
  -- | Construct and internalizing a 'ApplyTerm'.
  curThreadApplyTerm ::
    forall f a b.
    (PEvalApplyTerm f a b, SupportedPrim b) =>
    Term f ->
    Term a ->
    IO (Term b)
  curThreadApplyTerm f a = intern $ UApplyTerm f a
  {-# INLINE curThreadApplyTerm #-}
  
  -- | Construct and internalizing a 'DivIntegralTerm'.
  curThreadDivIntegralTerm ::
    (PEvalDivModIntegralTerm a) => Term a -> Term a -> IO (Term a)
  curThreadDivIntegralTerm l@SupportedTerm r = intern $ UDivIntegralTerm l r
  {-# INLINE curThreadDivIntegralTerm #-}
  
  -- | Construct and internalizing a 'ModIntegralTerm'.
  curThreadModIntegralTerm ::
    (PEvalDivModIntegralTerm a) => Term a -> Term a -> IO (Term a)
  curThreadModIntegralTerm l@SupportedTerm r = intern $ UModIntegralTerm l r
  {-# INLINE curThreadModIntegralTerm #-}
  
  -- | Construct and internalizing a 'QuotIntegralTerm'.
  curThreadQuotIntegralTerm ::
    (PEvalDivModIntegralTerm a) => Term a -> Term a -> IO (Term a)
  curThreadQuotIntegralTerm l@SupportedTerm r = intern $ UQuotIntegralTerm l r
  {-# INLINE curThreadQuotIntegralTerm #-}
  
  -- | Construct and internalizing a 'RemIntegralTerm'.
  curThreadRemIntegralTerm ::
    (PEvalDivModIntegralTerm a) => Term a -> Term a -> IO (Term a)
  curThreadRemIntegralTerm l@SupportedTerm r = intern $ URemIntegralTerm l r
  {-# INLINE curThreadRemIntegralTerm #-}
  
  -- | Construct and internalizing a 'FPTraitTerm'.
  curThreadFpTraitTerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPTrait ->
    Term (fp eb sb) ->
    IO (Term Bool)
  curThreadFpTraitTerm trait v = intern $ UFPTraitTerm trait v
  {-# INLINE curThreadFpTraitTerm #-}
  
  -- | Construct and internalizing a 'FdivTerm'.
  curThreadFdivTerm :: (PEvalFractionalTerm a) => Term a -> Term a -> IO (Term a)
  curThreadFdivTerm l@SupportedTerm r = intern $ UFdivTerm l r
  {-# INLINE curThreadFdivTerm #-}
  
  -- | Construct and internalizing a 'RecipTerm'.
  curThreadRecipTerm :: (PEvalFractionalTerm a) => Term a -> IO (Term a)
  curThreadRecipTerm l@SupportedTerm = intern $ URecipTerm l
  {-# INLINE curThreadRecipTerm #-}
  
  -- | Construct and internalizing a 'FloatingUnaryTerm'.
  curThreadFloatingUnaryTerm ::
    (PEvalFloatingTerm a) => FloatingUnaryOp -> Term a -> IO (Term a)
  curThreadFloatingUnaryTerm op a@SupportedTerm = intern $ UFloatingUnaryTerm op a
  {-# INLINE curThreadFloatingUnaryTerm #-}
  
  -- | Construct and internalizing a 'PowerTerm'.
  curThreadPowerTerm :: (PEvalFloatingTerm a) => Term a -> Term a -> IO (Term a)
  curThreadPowerTerm l@SupportedTerm r = intern $ UPowerTerm l r
  {-# INLINE curThreadPowerTerm #-}
  
  -- | Construct and internalizing a 'FPUnaryTerm'.
  curThreadFpUnaryTerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPUnaryOp ->
    Term (fp eb sb) ->
    IO (Term (fp eb sb))
  curThreadFpUnaryTerm op v = intern $ UFPUnaryTerm op v
  {-# INLINE curThreadFpUnaryTerm #-}
  
  -- | Construct and internalizing a 'FPBinaryTerm'.
  curThreadFpBinaryTerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPBinaryOp ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    IO (Term (fp eb sb))
  curThreadFpBinaryTerm op l r = intern $ UFPBinaryTerm op l r
  {-# INLINE curThreadFpBinaryTerm #-}
  
  -- | Construct and internalizing a 'FPRoundingUnaryTerm'.
  curThreadFpRoundingUnaryTerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPRoundingUnaryOp ->
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    IO (Term (fp eb sb))
  curThreadFpRoundingUnaryTerm op mode v = intern $ UFPRoundingUnaryTerm op mode v
  {-# INLINE curThreadFpRoundingUnaryTerm #-}
  
  -- | Construct and internalizing a 'FPRoundingBinaryTerm'.
  curThreadFpRoundingBinaryTerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPRoundingBinaryOp ->
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    IO (Term (fp eb sb))
  curThreadFpRoundingBinaryTerm op mode l r =
    intern $ UFPRoundingBinaryTerm op mode l r
  {-# INLINE curThreadFpRoundingBinaryTerm #-}
  
  -- | Construct and internalizing a 'FPFMATerm'.
  curThreadFpFMATerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    IO (Term (fp eb sb))
  curThreadFpFMATerm mode l r s = intern $ UFPFMATerm mode l r s
  {-# INLINE curThreadFpFMATerm #-}
  
  -- | Construct and internalizing a 'FromIntegralTerm'.
  curThreadFromIntegralTerm ::
    forall a b.
    (PEvalFromIntegralTerm a b, SupportedPrim b) =>
    Term a ->
    IO (Term b)
  curThreadFromIntegralTerm = intern . UFromIntegralTerm
  {-# INLINE curThreadFromIntegralTerm #-}
  
  -- | Construct and internalizing a 'FromFPOrTerm'.
  curThreadFromFPOrTerm ::
    forall a eb sb.
    ( PEvalIEEEFPConvertibleTerm a,
      ValidFP eb sb
    ) =>
    Term a ->
    Term FPRoundingMode ->
    Term (FP eb sb) ->
    IO (Term a)
  curThreadFromFPOrTerm d@SupportedTerm r f = intern $ UFromFPOrTerm d r f
  {-# INLINE curThreadFromFPOrTerm #-}
  
  -- | Construct and internalizing a 'ToFPTerm'.
  curThreadToFPTerm ::
    forall a eb sb.
    ( PEvalIEEEFPConvertibleTerm a,
      ValidFP eb sb,
      SupportedPrim (FP eb sb)
    ) =>
    Term FPRoundingMode ->
    Term a ->
    IO (Term (FP eb sb))
  curThreadToFPTerm r f = intern $ UToFPTerm r f (Proxy @eb) (Proxy @sb)
  {-# INLINE curThreadToFPTerm #-}
  
  inCurThread1 ::
    forall a b.
    (Term a -> IO (Term b)) ->
    Term a ->
    IO (Term b)
  inCurThread1 f t = do
    tid <- myWeakThreadId
    toCurThreadImpl tid t >>= f
  {-# INLINE inCurThread1 #-}
  
  inCurThread2 ::
    forall a b c.
    (Term a -> Term b -> IO (Term c)) ->
    Term a ->
    Term b ->
    IO (Term c)
  inCurThread2 f a b = do
    tid <- myWeakThreadId
    ra <- toCurThreadImpl tid a
    rb <- toCurThreadImpl tid b
    f ra rb
  {-# INLINE inCurThread2 #-}
  
  inCurThread3 ::
    forall a b c d.
    (Term a -> Term b -> Term c -> IO (Term d)) ->
    Term a ->
    Term b ->
    Term c ->
    IO (Term d)
  inCurThread3 f a b c = do
    tid <- myWeakThreadId
    ra <- toCurThreadImpl tid a
    rb <- toCurThreadImpl tid b
    rc <- toCurThreadImpl tid c
    f ra rb rc
  {-# INLINE inCurThread3 #-}
  
  unsafeInCurThread1 ::
    forall a b.
    (Term a -> IO (Term b)) ->
    Term a ->
    Term b
  unsafeInCurThread1 f = unsafePerformIO . inCurThread1 f
  {-# NOINLINE unsafeInCurThread1 #-}
  
  unsafeInCurThread2 ::
    forall a b c.
    (Term a -> Term b -> IO (Term c)) ->
    Term a ->
    Term b ->
    Term c
  unsafeInCurThread2 f a b = unsafePerformIO $ inCurThread2 f a b
  {-# NOINLINE unsafeInCurThread2 #-}
  
  unsafeInCurThread3 ::
    forall a b c d.
    (Term a -> Term b -> Term c -> IO (Term d)) ->
    Term a ->
    Term b ->
    Term c ->
    Term d
  unsafeInCurThread3 f a b c = unsafePerformIO $ inCurThread3 f a b c
  {-# NOINLINE unsafeInCurThread3 #-}
  
  -- | Construct and internalizing a 'ConTerm'.
  conTerm :: (SupportedPrim t) => t -> Term t
  conTerm = unsafePerformIO . curThreadConTerm
  {-# NOINLINE conTerm #-}
  
  -- | Construct and internalizing a 'SymTerm'.
  symTerm :: TypedSymbol knd t -> Term t
  symTerm = unsafePerformIO . curThreadSymTerm
  {-# NOINLINE symTerm #-}
  
  -- | Construct and internalizing a 'ForallTerm'.
  forallTerm ::
    TypedSymbol 'ConstantKind t ->
    Term Bool ->
    Term Bool
  forallTerm sym@TypedSymbol {} = unsafeInCurThread1 (curThreadForallTerm sym)
  {-# NOINLINE forallTerm #-}
  
  -- | Construct and internalizing a 'ExistsTerm'.
  existsTerm ::
    TypedSymbol 'ConstantKind t ->
    Term Bool ->
    Term Bool
  existsTerm sym@TypedSymbol {} = unsafeInCurThread1 (curThreadExistsTerm sym)
  {-# NOINLINE existsTerm #-}
  
  -- | Construct and internalizing a 'SymTerm' with an identifier, using simple
  -- symbols.
  ssymTerm :: (SupportedPrim t) => Identifier -> Term t
  ssymTerm = unsafePerformIO . curThreadSsymTerm
  {-# NOINLINE ssymTerm #-}
  
  -- | Construct and internalizing a 'SymTerm' with an identifier and an index,
  -- using indexed symbols.
  isymTerm :: (SupportedPrim t) => Identifier -> Int -> Term t
  isymTerm ident index = unsafePerformIO $ curThreadIsymTerm ident index
  {-# NOINLINE isymTerm #-}
  
  -- | Construct and internalizing a 'NotTerm'.
  notTerm :: Term Bool -> Term Bool
  notTerm = unsafeInCurThread1 curThreadNotTerm
  {-# NOINLINE notTerm #-}
  
  inCurThread2Set ::
    forall a c.
    (Term a -> Term a -> HS.HashSet (Term a) -> IO (Term c)) ->
    Term a ->
    Term a ->
    HS.HashSet (Term a) ->
    IO (Term c)
  inCurThread2Set f a b s = do
    tid <- myWeakThreadId
    ra@SupportedTerm <- toCurThreadImpl tid a
    rb <- toCurThreadImpl tid b
    rs <- traverse (toCurThreadImpl tid) (HS.toList s)
    f ra rb (HS.fromList rs)
  {-# INLINE inCurThread2Set #-}
  
  unsafeInCurThread2Set ::
    forall a c.
    (Term a -> Term a -> HS.HashSet (Term a) -> IO (Term c)) ->
    Term a ->
    Term a ->
    HS.HashSet (Term a) ->
    Term c
  unsafeInCurThread2Set f a b s = unsafePerformIO $ inCurThread2Set f a b s
  {-# NOINLINE unsafeInCurThread2Set #-}
  
  -- | Construct and internalizing a 'OrTerm'.
  orTerm :: Term Bool -> Term Bool -> Term Bool
  orTerm l@(OrTermAll _ _ s1) r@(OrTermAll _ _ s2) =
    unsafeInCurThread2Set
      curThreadOrTerm
      l
      r
      ( if HS.size s1 + HS.size s2 > 30
          then HS.fromList [l, r]
          else HS.insert l $ HS.insert r $ HS.union s1 s2
      )
  orTerm l@(OrTermAll _ _ s1) r =
    unsafeInCurThread2Set
      curThreadOrTerm
      l
      r
      ( if HS.size s1 > 30
          then HS.fromList [l, r]
          else HS.insert r $ HS.insert l s1
      )
  orTerm l r@(OrTermAll _ _ s2) =
    unsafeInCurThread2Set
      curThreadOrTerm
      l
      r
      ( if HS.size s2 > 30
          then HS.fromList [l, r]
          else HS.insert l $ HS.insert r s2
      )
  orTerm l r = unsafeInCurThread2Set curThreadOrTerm l r (HS.fromList [l, r])
  {-# NOINLINE orTerm #-}
  
  -- | Construct and internalizing a 'AndTerm'.
  andTerm :: Term Bool -> Term Bool -> Term Bool
  andTerm l@(AndTermAll _ _ s1) r@(AndTermAll _ _ s2) =
    unsafeInCurThread2Set
      curThreadAndTerm
      l
      r
      ( if HS.size s1 + HS.size s2 > 30
          then HS.fromList [l, r]
          else HS.insert l $ HS.insert r $ HS.union s1 s2
      )
  andTerm l@(AndTermAll _ _ s1) r =
    unsafeInCurThread2Set
      curThreadAndTerm
      l
      r
      ( if HS.size s1 > 30
          then HS.fromList [l, r]
          else HS.insert r $ HS.insert l s1
      )
  andTerm l r@(AndTermAll _ _ s2) =
    unsafeInCurThread2Set
      curThreadAndTerm
      l
      r
      ( if HS.size s2 > 30
          then HS.fromList [l, r]
          else HS.insert l $ HS.insert r s2
      )
  andTerm l r = unsafeInCurThread2Set curThreadAndTerm l r (HS.fromList [l, r])
  {-# NOINLINE andTerm #-}
  
  -- | Construct and internalizing a 'EqTerm'.
  eqTerm :: Term a -> Term a -> Term Bool
  eqTerm = unsafeInCurThread2 curThreadEqTerm
  {-# NOINLINE eqTerm #-}
  
  -- | Construct and internalizing a 'DistinctTerm'.
  distinctTerm :: NonEmpty (Term a) -> Term Bool
  distinctTerm args =
    unsafePerformIO $ do
      tid <- myWeakThreadId
      traverse (toCurThreadImpl tid) args >>= curThreadDistinctTerm
  {-# NOINLINE distinctTerm #-}
  
  -- | Construct and internalizing a 'ITETerm'.
  iteTerm :: Term Bool -> Term a -> Term a -> Term a
  iteTerm = unsafeInCurThread3 curThreadIteTerm
  {-# NOINLINE iteTerm #-}
  
  -- | Construct and internalizing a 'AddNumTerm'.
  addNumTerm :: (PEvalNumTerm a) => Term a -> Term a -> Term a
  addNumTerm = unsafeInCurThread2 curThreadAddNumTerm
  {-# NOINLINE addNumTerm #-}
  
  -- | Construct and internalizing a 'NegNumTerm'.
  negNumTerm :: (PEvalNumTerm a) => Term a -> Term a
  negNumTerm = unsafeInCurThread1 curThreadNegNumTerm
  {-# NOINLINE negNumTerm #-}
  
  -- | Construct and internalizing a 'MulNumTerm'.
  mulNumTerm :: (PEvalNumTerm a) => Term a -> Term a -> Term a
  mulNumTerm = unsafeInCurThread2 curThreadMulNumTerm
  {-# NOINLINE mulNumTerm #-}
  
  -- | Construct and internalizing a 'AbsNumTerm'.
  absNumTerm :: (PEvalNumTerm a) => Term a -> Term a
  absNumTerm = unsafeInCurThread1 curThreadAbsNumTerm
  {-# NOINLINE absNumTerm #-}
  
  -- | Construct and internalizing a 'SignumNumTerm'.
  signumNumTerm :: (PEvalNumTerm a) => Term a -> Term a
  signumNumTerm = unsafeInCurThread1 curThreadSignumNumTerm
  {-# NOINLINE signumNumTerm #-}
  
  -- | Construct and internalizing a 'LtOrdTerm'.
  ltOrdTerm :: (PEvalOrdTerm a) => Term a -> Term a -> Term Bool
  ltOrdTerm = unsafeInCurThread2 curThreadLtOrdTerm
  {-# NOINLINE ltOrdTerm #-}
  
  -- | Construct and internalizing a 'LeOrdTerm'.
  leOrdTerm :: (PEvalOrdTerm a) => Term a -> Term a -> Term Bool
  leOrdTerm = unsafeInCurThread2 curThreadLeOrdTerm
  {-# NOINLINE leOrdTerm #-}
  
  -- | Construct and internalizing a 'AndBitsTerm'.
  andBitsTerm :: (PEvalBitwiseTerm a) => Term a -> Term a -> Term a
  andBitsTerm a b =
    unsafeInCurThread2 curThreadAndBitsTerm a b
  {-# NOINLINE andBitsTerm #-}
  
  -- | Construct and internalizing a 'OrBitsTerm'.
  orBitsTerm :: (PEvalBitwiseTerm a) => Term a -> Term a -> Term a
  orBitsTerm = unsafeInCurThread2 curThreadOrBitsTerm
  {-# NOINLINE orBitsTerm #-}
  
  -- | Construct and internalizing a 'XorBitsTerm'.
  xorBitsTerm :: (PEvalBitwiseTerm a) => Term a -> Term a -> Term a
  xorBitsTerm = unsafeInCurThread2 curThreadXorBitsTerm
  {-# NOINLINE xorBitsTerm #-}
  
  -- | Construct and internalizing a 'ComplementBitsTerm'.
  complementBitsTerm :: (PEvalBitwiseTerm a) => Term a -> Term a
  complementBitsTerm = unsafeInCurThread1 curThreadComplementBitsTerm
  {-# NOINLINE complementBitsTerm #-}
  
  -- | Construct and internalizing a 'ShiftLeftTerm'.
  shiftLeftTerm :: (PEvalShiftTerm a) => Term a -> Term a -> Term a
  shiftLeftTerm = unsafeInCurThread2 curThreadShiftLeftTerm
  {-# NOINLINE shiftLeftTerm #-}
  
  -- | Construct and internalizing a 'ShiftRightTerm'.
  shiftRightTerm :: (PEvalShiftTerm a) => Term a -> Term a -> Term a
  shiftRightTerm = unsafeInCurThread2 curThreadShiftRightTerm
  {-# NOINLINE shiftRightTerm #-}
  
  -- | Construct and internalizing a 'RotateLeftTerm'.
  rotateLeftTerm :: (PEvalRotateTerm a) => Term a -> Term a -> Term a
  rotateLeftTerm = unsafeInCurThread2 curThreadRotateLeftTerm
  {-# NOINLINE rotateLeftTerm #-}
  
  -- | Construct and internalizing a 'RotateRightTerm'.
  rotateRightTerm :: (PEvalRotateTerm a) => Term a -> Term a -> Term a
  rotateRightTerm = unsafeInCurThread2 curThreadRotateRightTerm
  {-# NOINLINE rotateRightTerm #-}
  
  -- | Construct and internalizing a 'BitCastTerm'.
  bitCastTerm ::
    (PEvalBitCastTerm a b, SupportedPrim b) =>
    Term a ->
    Term b
  bitCastTerm = unsafeInCurThread1 curThreadBitCastTerm
  {-# NOINLINE bitCastTerm #-}
  
  -- | Construct and internalizing a 'BitCastOrTerm'.
  bitCastOrTerm ::
    (PEvalBitCastOrTerm a b) =>
    Term b ->
    Term a ->
    Term b
  bitCastOrTerm = unsafeInCurThread2 curThreadBitCastOrTerm
  {-# NOINLINE bitCastOrTerm #-}
  
  -- | Construct and internalizing a 'BVConcatTerm'.
  bvConcatTerm ::
    forall bv l r.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      KnownNat (l + r),
      1 <= l,
      1 <= r,
      1 <= l + r,
      SupportedPrim (bv (l + r))
    ) =>
    Term (bv l) ->
    Term (bv r) ->
    Term (bv (l + r))
  bvConcatTerm = unsafeInCurThread2 curThreadBVConcatTerm
  {-# NOINLINE bvConcatTerm #-}
  
  -- | Construct and internalizing a 'BVSelectTerm'.
  bvSelectTerm ::
    forall bv n ix w p q.
    ( PEvalBVTerm bv,
      KnownNat n,
      KnownNat ix,
      KnownNat w,
      1 <= n,
      1 <= w,
      ix + w <= n,
      SupportedPrim (bv w)
    ) =>
    p ix ->
    q w ->
    Term (bv n) ->
    Term (bv w)
  bvSelectTerm ix w = unsafeInCurThread1 (curThreadBVSelectTerm ix w)
  {-# NOINLINE bvSelectTerm #-}
  
  -- | Construct and internalizing a 'BVExtendTerm'.
  bvExtendTerm ::
    forall bv l r proxy.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      1 <= l,
      1 <= r,
      l <= r,
      SupportedPrim (bv r)
    ) =>
    Bool ->
    proxy r ->
    Term (bv l) ->
    Term (bv r)
  bvExtendTerm signed r = unsafeInCurThread1 (curThreadBVExtendTerm signed r)
  {-# NOINLINE bvExtendTerm #-}
  
  -- | Construct and internalizing a 'BVExtendTerm' with sign extension.
  bvsignExtendTerm ::
    forall bv l r proxy.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      1 <= l,
      1 <= r,
      l <= r,
      SupportedPrim (bv r)
    ) =>
    proxy r ->
    Term (bv l) ->
    Term (bv r)
  bvsignExtendTerm r = unsafeInCurThread1 (curThreadBvsignExtendTerm r)
  {-# NOINLINE bvsignExtendTerm #-}
  
  -- | Construct and internalizing a 'BVExtendTerm' with zero extension.
  bvzeroExtendTerm ::
    forall bv l r proxy.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      1 <= l,
      1 <= r,
      l <= r,
      SupportedPrim (bv r)
    ) =>
    proxy r ->
    Term (bv l) ->
    Term (bv r)
  bvzeroExtendTerm r = unsafeInCurThread1 (curThreadBvzeroExtendTerm r)
  {-# NOINLINE bvzeroExtendTerm #-}
  
  -- | Construct and internalizing a 'ApplyTerm'.
  applyTerm ::
    (PEvalApplyTerm f a b, SupportedPrim b) => Term f -> Term a -> Term b
  applyTerm = unsafeInCurThread2 curThreadApplyTerm
  {-# NOINLINE applyTerm #-}
  
  -- | Construct and internalizing a 'DivIntegralTerm'.
  divIntegralTerm :: (PEvalDivModIntegralTerm a) => Term a -> Term a -> Term a
  divIntegralTerm = unsafeInCurThread2 curThreadDivIntegralTerm
  {-# NOINLINE divIntegralTerm #-}
  
  -- | Construct and internalizing a 'ModIntegralTerm'.
  modIntegralTerm :: (PEvalDivModIntegralTerm a) => Term a -> Term a -> Term a
  modIntegralTerm = unsafeInCurThread2 curThreadModIntegralTerm
  {-# NOINLINE modIntegralTerm #-}
  
  -- | Construct and internalizing a 'QuotIntegralTerm'.
  quotIntegralTerm :: (PEvalDivModIntegralTerm a) => Term a -> Term a -> Term a
  quotIntegralTerm = unsafeInCurThread2 curThreadQuotIntegralTerm
  {-# NOINLINE quotIntegralTerm #-}
  
  -- | Construct and internalizing a 'RemIntegralTerm'.
  remIntegralTerm :: (PEvalDivModIntegralTerm a) => Term a -> Term a -> Term a
  remIntegralTerm = unsafeInCurThread2 curThreadRemIntegralTerm
  {-# NOINLINE remIntegralTerm #-}
  
  -- | Construct and internalizing a 'FPTraitTerm'.
  fpTraitTerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPTrait ->
    Term (fp eb sb) ->
    Term Bool
  fpTraitTerm trait = unsafeInCurThread1 (curThreadFpTraitTerm trait)
  {-# NOINLINE fpTraitTerm #-}
  
  -- | Construct and internalizing a 'FdivTerm'.
  fdivTerm :: (PEvalFractionalTerm a) => Term a -> Term a -> Term a
  fdivTerm = unsafeInCurThread2 curThreadFdivTerm
  {-# NOINLINE fdivTerm #-}
  
  -- | Construct and internalizing a 'RecipTerm'.
  recipTerm :: (PEvalFractionalTerm a) => Term a -> Term a
  recipTerm = unsafeInCurThread1 curThreadRecipTerm
  {-# NOINLINE recipTerm #-}
  
  -- | Construct and internalizing a 'FloatingUnaryTerm'.
  floatingUnaryTerm :: (PEvalFloatingTerm a) => FloatingUnaryOp -> Term a -> Term a
  floatingUnaryTerm op = unsafeInCurThread1 (curThreadFloatingUnaryTerm op)
  {-# NOINLINE floatingUnaryTerm #-}
  
  -- | Construct and internalizing a 'PowerTerm'.
  powerTerm :: (PEvalFloatingTerm a) => Term a -> Term a -> Term a
  powerTerm = unsafeInCurThread2 curThreadPowerTerm
  {-# NOINLINE powerTerm #-}
  
  -- | Construct and internalizing a 'FPUnaryTerm'.
  fpUnaryTerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPUnaryOp ->
    Term (fp eb sb) ->
    Term (fp eb sb)
  fpUnaryTerm op = unsafeInCurThread1 (curThreadFpUnaryTerm op)
  {-# NOINLINE fpUnaryTerm #-}
  
  -- | Construct and internalizing a 'FPBinaryTerm'.
  fpBinaryTerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPBinaryOp ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term (fp eb sb)
  fpBinaryTerm op = unsafeInCurThread2 (curThreadFpBinaryTerm op)
  {-# NOINLINE fpBinaryTerm #-}
  
  -- | Construct and internalizing a 'FPRoundingUnaryTerm'.
  fpRoundingUnaryTerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPRoundingUnaryOp ->
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term (fp eb sb)
  fpRoundingUnaryTerm op = unsafeInCurThread2 (curThreadFpRoundingUnaryTerm op)
  {-# NOINLINE fpRoundingUnaryTerm #-}
  
  -- | Construct and internalizing a 'FPRoundingBinaryTerm'.
  fpRoundingBinaryTerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    FPRoundingBinaryOp ->
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term (fp eb sb)
  fpRoundingBinaryTerm op = unsafeInCurThread3 (curThreadFpRoundingBinaryTerm op)
  {-# NOINLINE fpRoundingBinaryTerm #-}
  
  -- | Construct and internalizing a 'FPFMATerm'.
  fpFMATerm ::
    (ValidFP eb sb, SupportedPrim (fp eb sb), PEvalFPTerm fp) =>
    Term FPRoundingMode ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term (fp eb sb) ->
    Term (fp eb sb)
  fpFMATerm mode a b c = unsafePerformIO $ do
    tid <- myWeakThreadId
    mode' <- toCurThreadImpl tid mode
    a' <- toCurThreadImpl tid a
    b' <- toCurThreadImpl tid b
    c' <- toCurThreadImpl tid c
    curThreadFpFMATerm mode' a' b' c'
  {-# NOINLINE fpFMATerm #-}
  
  -- | Construct and internalizing a 'FromIntegralTerm'.
  fromIntegralTerm ::
    (PEvalFromIntegralTerm a b, SupportedPrim b) => Term a -> Term b
  fromIntegralTerm = unsafeInCurThread1 curThreadFromIntegralTerm
  {-# NOINLINE fromIntegralTerm #-}
  
  -- | Construct and internalizing a 'FromFPOrTerm'.
  fromFPOrTerm ::
    ( PEvalIEEEFPConvertibleTerm a,
      ValidFP eb sb
    ) =>
    Term a ->
    Term FPRoundingMode ->
    Term (FP eb sb) ->
    Term a
  fromFPOrTerm = unsafeInCurThread3 curThreadFromFPOrTerm
  {-# NOINLINE fromFPOrTerm #-}
  
  -- | Construct and internalizing a 'ToFPTerm'.
  toFPTerm ::
    forall a eb sb.
    ( PEvalIEEEFPConvertibleTerm a,
      ValidFP eb sb,
      SupportedPrim (FP eb sb)
    ) =>
    Term FPRoundingMode ->
    Term a ->
    Term (FP eb sb)
  toFPTerm = unsafeInCurThread2 curThreadToFPTerm
  {-# NOINLINE toFPTerm #-}
  
  -- Support for boolean type
  defaultValueForBool :: Bool
  defaultValueForBool = False
  
  -- | Construct and internalizing 'True' term.
  trueTerm :: Term Bool
  trueTerm = conTerm True
  {-# NOINLINE trueTerm #-}
  
  -- | Construct and internalizing 'False' term.
  falseTerm :: Term Bool
  falseTerm = conTerm False
  {-# NOINLINE falseTerm #-}
  
  boolConTermView :: forall a. Term a -> Maybe Bool
  boolConTermView (ConTerm b) = cast b
  boolConTermView _ = Nothing
  {-# INLINE boolConTermView #-}
  
  -- | Pattern matcher for concrete 'Bool' terms.
  pattern BoolConTerm :: Bool -> Term a
  pattern BoolConTerm b <- (boolConTermView -> Just b)
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE BoolConTerm #-}
  #endif
  
  -- | Pattern matcher for 'True' term.
  pattern TrueTerm :: Term a
  pattern TrueTerm <- BoolConTerm True
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE TrueTerm #-}
  #endif
  
  -- | Pattern matcher for 'False' term.
  pattern FalseTerm :: Term a
  pattern FalseTerm <- BoolConTerm False
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE FalseTerm #-}
  #endif
  
  boolTermView :: forall a. Term a -> Maybe (Term Bool)
  boolTermView t@SupportedTerm = cast t
  {-# INLINE boolTermView #-}
  
  -- | Pattern matcher for 'Bool' terms.
  pattern BoolTerm :: Term Bool -> Term a
  pattern BoolTerm b <- (boolTermView -> Just b)
  
  #if MIN_VERSION_base(4, 16, 4)
  {-# INLINE BoolTerm #-}
  #endif
  
  -- | Partial evaluation for not terms.
  pevalNotTerm :: Term Bool -> Term Bool
  pevalNotTerm (NotTerm tm) = tm
  pevalNotTerm (ConTerm a) = if a then falseTerm else trueTerm
  pevalNotTerm (OrTerm (NotTerm n1) n2) = pevalAndTerm n1 (pevalNotTerm n2)
  pevalNotTerm (OrTerm (DistinctTerm (n1 :| [n2])) n3) =
    pevalAndTerm (pevalEqTerm n1 n2) (pevalNotTerm n3)
  pevalNotTerm (OrTerm n1 (NotTerm n2)) = pevalAndTerm (pevalNotTerm n1) n2
  pevalNotTerm (OrTerm n1 (DistinctTerm (n2 :| [n3]))) =
    pevalAndTerm (pevalNotTerm n1) (pevalEqTerm n2 n3)
  pevalNotTerm (AndTerm (NotTerm n1) n2) = pevalOrTerm n1 (pevalNotTerm n2)
  pevalNotTerm (AndTerm (DistinctTerm (n1 :| [n2])) n3) =
    pevalOrTerm (pevalEqTerm n1 n2) (pevalNotTerm n3)
  pevalNotTerm (AndTerm n1 (NotTerm n2)) = pevalOrTerm (pevalNotTerm n1) n2
  pevalNotTerm (AndTerm n1 (DistinctTerm (n2 :| [n3]))) =
    pevalOrTerm (pevalNotTerm n1) (pevalEqTerm n2 n3)
  pevalNotTerm
    (EqTerm a (DynTerm (ConTerm b :: Term (WordN 1))))
      | b == 0 = eqTerm (unsafeCoerce a) (conTerm 1 :: Term (WordN 1))
      | b == 1 = eqTerm (unsafeCoerce a) (conTerm 0 :: Term (WordN 1))
  pevalNotTerm
    (EqTerm a (DynTerm (ConTerm b :: Term (IntN 1))))
      | b == 0 = eqTerm (unsafeCoerce a) (conTerm 1 :: Term (IntN 1))
      | b == 1 = eqTerm (unsafeCoerce a) (conTerm 0 :: Term (IntN 1))
  -- pevalNotTerm (EqTerm a b) = distinctTerm $ a :| [b]
  pevalNotTerm (DistinctTerm (a :| [b])) = eqTerm a b
  pevalNotTerm tm = notTerm tm
  {-# INLINEABLE pevalNotTerm #-}
  
  orEqFirst' :: Term Bool -> Term Bool -> Bool
  orEqFirst'
    (DistinctTerm ((e1 :: Term a) :| [ec1@ConTerm {} :: Term b]))
    (EqTerm (DynTerm (e2 :: Term a)) (DynTerm (ec2@ConTerm {} :: Term b)))
      | e1 == e2 && ec1 /= ec2 = True
  orEqFirst'
    (NotTerm (EqTerm (e1 :: Term a) (ec1@ConTerm {} :: Term b)))
    (EqTerm (DynTerm (e2 :: Term a)) (DynTerm (ec2@ConTerm {} :: Term b)))
      | e1 == e2 && ec1 /= ec2 = True
  orEqFirst' _ _ = False
  {-# INLINE orEqFirst' #-}
  
  orEqFirst :: Term Bool -> Term Bool -> Bool
  orEqFirst _ (ConTerm False) = True
  orEqFirst x y
    | x == y = True
    | otherwise = orEqFirst' x y
  {-# INLINE orEqFirst #-}
  
  orEqTrue' :: Term Bool -> Term Bool -> Bool
  orEqTrue'
    (DistinctTerm ((e1 :: Term a) :| [ec1@ConTerm {} :: Term b]))
    (DistinctTerm ((DynTerm (e2 :: Term a)) :| [DynTerm (ec2@ConTerm {} :: Term b)]))
      | e1 == e2 && ec1 /= ec2 = True
  orEqTrue'
    (NotTerm (EqTerm (e1 :: Term a) (ec1@ConTerm {} :: Term b)))
    (NotTerm (EqTerm (DynTerm (e2 :: Term a)) (DynTerm (ec2@ConTerm {} :: Term b))))
      | e1 == e2 && ec1 /= ec2 = True
  orEqTrue' _ _ = False
  {-# INLINE orEqTrue' #-}
  
  orEqTrue :: Term Bool -> Term Bool -> Bool
  orEqTrue (ConTerm True) ~_ = True
  orEqTrue _ (ConTerm True) = True
  orEqTrue (NotTerm l) r | l == r = True
  orEqTrue l (NotTerm r) | l == r = True
  orEqTrue l r = orEqTrue' l r
  {-# INLINE orEqTrue #-}
  
  -- | Partial evaluation for or terms.
  pevalOrTerm :: Term Bool -> Term Bool -> Term Bool
  pevalOrTerm (ConTerm True) ~_ = trueTerm
  pevalOrTerm _ (ConTerm True) = trueTerm
  pevalOrTerm (ConTerm False) y = y
  pevalOrTerm x (ConTerm False) = x
  pevalOrTerm (NotTerm x) y | x == y = trueTerm
  pevalOrTerm x (NotTerm y) | x == y = trueTerm
  pevalOrTerm x y | x == y = x
  pevalOrTerm l ~r
    | orEqTrue' l r = trueTerm
    | orEqFirst' l r = l
    | orEqFirst' r l = r
  pevalOrTerm l r@(OrTermAll r1 r2 s)
    | HS.member l s = r
    | HS.member (simpleNot l) s = trueTerm
    | orEqTrue' l r1 = trueTerm
    | orEqTrue' l r2 = trueTerm
    | orEqFirst' r1 l = r
    | orEqFirst' r2 l = r
    | orEqFirst' l r1 = pevalOrTerm l r2
    | orEqFirst' l r2 = pevalOrTerm l r1
  pevalOrTerm l@(OrTermAll l1 l2 s) r
    | HS.member r s = l
    | HS.member (simpleNot r) s = trueTerm
    | orEqTrue' l1 r = trueTerm
    | orEqTrue' l2 r = trueTerm
    | orEqFirst' l1 r = l
    | orEqFirst' l2 r = l
    | orEqFirst' r l1 = pevalOrTerm l2 r
    | orEqFirst' r l2 = pevalOrTerm l1 r
  pevalOrTerm (AndTerm l1 l2) (AndTerm r1 r2)
    | l1 == r1 = pevalAndTerm l1 (pevalOrTerm l2 r2)
    | l1 == r2 = pevalAndTerm l1 (pevalOrTerm l2 r1)
    | l2 == r1 = pevalAndTerm l2 (pevalOrTerm l1 r2)
    | l2 == r2 = pevalAndTerm l2 (pevalOrTerm l1 r1)
  pevalOrTerm l (AndTermAll r1 r2 s)
    | HS.member l s = l
    | orEqFirst l r1 = l
    | orEqFirst l r2 = l
    | orEqTrue l r1 = pevalOrTerm l r2
    | orEqTrue l r2 = pevalOrTerm l r1
  pevalOrTerm (AndTermAll l1 l2 s) r
    | HS.member r s = r
    | orEqFirst r l1 = r
    | orEqFirst r l2 = r
    | orEqTrue l1 r = pevalOrTerm l2 r
    | orEqTrue l2 r = pevalOrTerm l1 r
  pevalOrTerm
    (AndTerm nl1@(NotTerm l1) l2)
    (EqTerm (DynTerm (e1 :: Term Bool)) (DynTerm (e2 :: Term Bool)))
      | l1 == e1 && l2 == e2 = pevalOrTerm nl1 l2
  pevalOrTerm (NotTerm nl) (NotTerm nr) =
    pevalNotTerm $ pevalAndTerm nl nr
  pevalOrTerm (AndTermAll _ _ sa) r@(OrTermAll _ _ so)
    | sa `HS.intersection` so /= HS.empty = r
  pevalOrTerm l@(OrTermAll _ _ so) (AndTermAll _ _ sa)
    | sa `HS.intersection` so /= HS.empty = l
  pevalOrTerm
    (EqTerm a (BVTerm bt@(ConTerm (b :: bv n))))
    (EqTerm c (DynTerm (BVTerm (ConTerm d) :: Term (bv n))))
      | natVal (Proxy @n) == 1 && b == -1 && d == -1 =
          pevalEqTerm
            ( pevalOrBitsTerm
                (unsafeCoerce a :: Term (bv n))
                (unsafeCoerce c :: Term (bv n))
            )
            bt
  pevalOrTerm
    (EqTerm a (BVTerm bt@(ConTerm (b :: bv n))))
    (EqTerm c (DynTerm (BVTerm (ConTerm d) :: Term (bv n))))
      | natVal (Proxy @n) == 1 && b == 0 && d == 0 =
          pevalEqTerm
            ( pevalAndBitsTerm
                (unsafeCoerce a :: Term (bv n))
                (unsafeCoerce c :: Term (bv n))
            )
            bt
  pevalOrTerm l r = orTerm l r
  {-# INLINEABLE pevalOrTerm #-}
  
  andEqFalse' :: Term Bool -> Term Bool -> Bool
  andEqFalse'
    (EqTerm (e1 :: Term a) (ec1@ConTerm {} :: Term b))
    (EqTerm (DynTerm (e2 :: Term a)) (DynTerm (ec2@ConTerm {} :: Term b)))
      | e1 == e2 && ec1 /= ec2 = True
  andEqFalse' _ _ = False
  {-# INLINE andEqFalse' #-}
  
  andEqFalse :: Term Bool -> Term Bool -> Bool
  andEqFalse (NotTerm x) y | x == y = True
  andEqFalse x (NotTerm y) | x == y = True
  andEqFalse l r = andEqFalse' l r
  {-# INLINE andEqFalse #-}
  
  andEqFirst' :: Term Bool -> Term Bool -> Bool
  andEqFirst'
    (EqTerm (e1 :: Term a) (ec1@ConTerm {} :: Term b))
    (DistinctTerm ((DynTerm (e2 :: Term a)) :| [DynTerm (ec2@ConTerm {} :: Term b)]))
      | e1 == e2 && ec1 /= ec2 = True
  andEqFirst'
    (EqTerm (e1 :: Term a) (ec1@ConTerm {} :: Term b))
    (NotTerm (EqTerm (DynTerm (e2 :: Term a)) (DynTerm (ec2@ConTerm {} :: Term b))))
      | e1 == e2 && ec1 /= ec2 = True
  andEqFirst' _ _ = False
  {-# INLINE andEqFirst' #-}
  
  andEqFirst :: Term Bool -> Term Bool -> Bool
  andEqFirst _ (ConTerm True) = True
  andEqFirst x y
    | x == y = True
    | otherwise = andEqFirst' x y
  {-# INLINE andEqFirst #-}
  
  simpleNot :: Term Bool -> Term Bool
  simpleNot (NotTerm n) = n
  simpleNot n = notTerm n
  {-# INLINEABLE simpleNot #-}
  
  -- | Partial evaluation for and terms.
  pevalAndTerm :: Term Bool -> Term Bool -> Term Bool
  pevalAndTerm (ConTerm False) ~_ = falseTerm
  pevalAndTerm _ (ConTerm False) = falseTerm
  pevalAndTerm (ConTerm True) y = y
  pevalAndTerm x (ConTerm True) = x
  pevalAndTerm (NotTerm x) y | x == y = falseTerm
  pevalAndTerm x (NotTerm y) | x == y = falseTerm
  pevalAndTerm x y | x == y = x
  pevalAndTerm l ~r
    | andEqFalse' l r = falseTerm
    | andEqFirst' l r = l
    | andEqFirst' r l = r
  pevalAndTerm l r@(AndTermAll r1 r2 s)
    | HS.member l s = r
    | HS.member (simpleNot l) s = falseTerm
    | andEqFalse' l r1 = falseTerm
    | andEqFalse' l r2 = falseTerm
    | andEqFirst' r1 l = r
    | andEqFirst' r2 l = r
    | andEqFirst' l r1 = pevalAndTerm l r2
    | andEqFirst' l r2 = pevalAndTerm l r1
  pevalAndTerm l@(AndTermAll l1 l2 s) r
    | HS.member r s = l
    | HS.member (simpleNot r) s = falseTerm
    | andEqFalse' l1 r = falseTerm
    | andEqFalse' l2 r = falseTerm
    | andEqFirst' l1 r = l
    | andEqFirst' l2 r = l
    | andEqFirst' r l1 = pevalAndTerm l2 r
    | andEqFirst' r l2 = pevalAndTerm l1 r
  pevalAndTerm (OrTerm l1 l2) (OrTerm r1 r2)
    | l1 == r1 = pevalOrTerm l1 (pevalAndTerm l2 r2)
    | l1 == r2 = pevalOrTerm l1 (pevalAndTerm l2 r1)
    | l2 == r1 = pevalOrTerm l2 (pevalAndTerm l1 r2)
    | l2 == r2 = pevalOrTerm l2 (pevalAndTerm l1 r1)
  pevalAndTerm l (OrTermAll r1 r2 s)
    | HS.member l s = l
    | andEqFirst l r1 = l
    | andEqFirst l r2 = l
    | andEqFalse l r1 = pevalAndTerm l r2
    | andEqFalse l r2 = pevalAndTerm l r1
  pevalAndTerm (OrTermAll l1 l2 s) r
    | HS.member r s = r
    | andEqFirst r l1 = r
    | andEqFirst r l2 = r
    | andEqFalse l1 r = pevalAndTerm l2 r
    | andEqFalse l2 r = pevalAndTerm l1 r
  pevalAndTerm
    (OrTerm l1 nl2@(NotTerm l2))
    (NotTerm (EqTerm (DynTerm (e1 :: Term Bool)) (DynTerm (e2 :: Term Bool))))
      | l1 == e1 && l2 == e2 = pevalAndTerm l1 nl2
  pevalAndTerm (NotTerm nl) (NotTerm nr) = pevalNotTerm $ pevalOrTerm nl nr
  pevalAndTerm (OrTermAll _ _ so) r@(AndTermAll _ _ sa)
    | sa `HS.intersection` so /= HS.empty = r
  pevalAndTerm l@(AndTermAll _ _ sa) (OrTermAll _ _ so)
    | sa `HS.intersection` so /= HS.empty = l
  pevalAndTerm
    (EqTerm a (BVTerm bt@(ConTerm (b :: bv n))))
    (EqTerm c (DynTerm (BVTerm (ConTerm d) :: Term (bv n))))
      | natVal (Proxy @n) == 1 && b == 0 && d == 0 =
          pevalEqTerm
            ( pevalOrBitsTerm
                (unsafeCoerce a :: Term (bv n))
                (unsafeCoerce c :: Term (bv n))
            )
            bt
  pevalAndTerm
    (EqTerm a (BVTerm bt@(ConTerm (b :: bv n))))
    (EqTerm c (DynTerm (BVTerm (ConTerm d) :: Term (bv n))))
      | natVal (Proxy @n) == 1 && b == -1 && d == -1 =
          pevalEqTerm
            ( pevalAndBitsTerm
                (unsafeCoerce a :: Term (bv n))
                (unsafeCoerce c :: Term (bv n))
            )
            bt
  pevalAndTerm l r = andTerm l r
  {-# INLINEABLE pevalAndTerm #-}
  
  data BVTermView where
    BVTermView ::
      forall bv n.
      ( KnownNat n,
        1 <= n,
        PEvalBitwiseTerm (bv n),
        Eq (bv n),
        Num (bv n)
      ) =>
      Term (bv n) -> BVTermView
  
  bvTermViewPattern ::
    forall a.
    (SupportedPrim a) =>
    Term a ->
    Maybe BVTermView
  bvTermViewPattern b = case R.typeRep @a of
    R.App i _ -> case ( R.eqTypeRep i (R.typeRep @IntN),
                        R.eqTypeRep i (R.typeRep @WordN)
                      ) of
      (Just R.HRefl, _) -> withPrim @a $ Just (BVTermView b)
      (_, Just R.HRefl) -> withPrim @a $ Just (BVTermView b)
      _ -> Nothing
    _ -> Nothing
  
  pattern BVTerm ::
    forall a.
    (SupportedPrim a) =>
    forall bv n.
    (KnownNat n, 1 <= n, PEvalBitwiseTerm (bv n), Eq (bv n), Num (bv n)) =>
    Term (bv n) -> Term a
  pattern BVTerm x <- (bvTermViewPattern -> Just (BVTermView x))
  
  -- | Partial evaluation for imply terms.
  pevalImplyTerm :: Term Bool -> Term Bool -> Term Bool
  pevalImplyTerm l r | termImplies l r = trueTerm
  pevalImplyTerm l r = pevalOrTerm (pevalNotTerm l) r
  
  -- | Partial evaluation for xor terms.
  pevalXorTerm :: Term Bool -> Term Bool -> Term Bool
  pevalXorTerm l r = pevalOrTerm (pevalAndTerm (pevalNotTerm l) r) (pevalAndTerm l (pevalNotTerm r))
  
  termImplies :: Term Bool -> Term Bool -> Bool
  termImplies (ConTerm False) _ = True
  termImplies _ (ConTerm True) = True
  termImplies
    (EqTerm (e1 :: Term a) (ec1@ConTerm {} :: Term b))
    (DistinctTerm ((DynTerm (e2 :: Term a)) :| [(DynTerm (ec2@ConTerm {} :: Term b))]))
      | e1 == e2 && ec1 /= ec2 = True
  termImplies
    (EqTerm (e1 :: Term a) (ec1@ConTerm {} :: Term b))
    (NotTerm (EqTerm (DynTerm (e2 :: Term a)) ((DynTerm (ec2@ConTerm {} :: Term b)))))
      | e1 == e2 && ec1 /= ec2 = True
  termImplies a (OrTermAll _ _ s) | HS.member a s = True
  termImplies (AndTermAll _ _ s) b | HS.member b s = True
  termImplies (AndTermAll _ _ s) (OrTermAll _ _ s2) | HS.intersection s s2 /= HS.empty = True
  termImplies (AndTermAll _ _ s) (AndTermAll _ _ s2) | s2 `HS.isSubsetOf` s = True
  termImplies (OrTermAll _ _ s) (OrTermAll _ _ s2) | s `HS.isSubsetOf` s2 = True
  termImplies a b
    | a == b = True
    | otherwise = False
  {-# INLINE termImplies #-}
  
  pevalITEBoolLeftNot :: Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolLeftNot cond nIfTrue ifFalse
    -- need test
    | cond == nIfTrue = Just $ pevalAndTerm (pevalNotTerm cond) ifFalse
    | otherwise = case nIfTrue of
        AndTerm nt1 nt2 -> ra
          where
            ra
              | termImplies cond nt1 =
                  Just $ pevalITETerm cond (pevalNotTerm nt2) ifFalse
              | termImplies cond nt2 =
                  Just $ pevalITETerm cond (pevalNotTerm nt1) ifFalse
              | termImplies cond (pevalNotTerm nt1)
                  || termImplies cond (pevalNotTerm nt2) =
                  Just $ pevalOrTerm cond ifFalse
              | otherwise = Nothing
        OrTerm nt1 nt2 -> ra
          where
            ra
              | termImplies cond nt1 || termImplies cond nt2 =
                  Just $ pevalAndTerm (pevalNotTerm cond) ifFalse
              | termImplies cond (pevalNotTerm nt1) =
                  Just $ pevalITETerm cond (pevalNotTerm nt2) ifFalse
              | termImplies cond (pevalNotTerm nt2) =
                  Just $ pevalITETerm cond (pevalNotTerm nt1) ifFalse
              | otherwise = Nothing
        _ -> Nothing
  
  pevalITEBoolBothNot :: Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolBothNot cond nIfTrue nIfFalse =
    Just $ pevalNotTerm $ pevalITETerm cond nIfTrue nIfFalse
  
  pevalITEBoolRightNot :: Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolRightNot cond ifTrue nIfFalse
    -- need test
    | cond == nIfFalse = Just $ pevalOrTerm (pevalNotTerm cond) ifTrue
    | otherwise = Nothing -- need work
  
  pevalInferImplies :: Term Bool -> Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalInferImplies
    (EqTerm (e1 :: Term a) (ec1@ConTerm {} :: Term b))
    (NotTerm (EqTerm (DynTerm (e2 :: Term a)) (DynTerm (ec2@ConTerm {} :: Term b))))
    trueRes
    _
      | e1 == e2 && ec1 /= ec2 = Just trueRes
  pevalInferImplies cond (NotTerm nt1) _ falseRes
    | cond == nt1 = Just falseRes
    | otherwise = Nothing
  -- \| otherwise = case (cond, nt1) of
  --     ( EqTerm _ (e1 :: Term a) (ec1@(ConTerm _ _ _ _) :: Term b),
  --       EqTerm _ (Dyn (e2 :: Term a)) (Dyn (ec2@(ConTerm _ _ _ _) :: Term b))
  --       )
  --         | e1 == e2 && ec1 /= ec2 -> Just trueRes
  --     _ -> Nothing
  pevalInferImplies
    (EqTerm (e1 :: Term a) (ec1@ConTerm {} :: Term b))
    (DistinctTerm ((DynTerm (e2 :: Term a)) :| [DynTerm (ec2@ConTerm {} :: Term b)]))
    trueRes
    _
      | e1 == e2 && ec1 /= ec2 = Just trueRes
  pevalInferImplies
    (EqTerm (e1 :: Term a) (ec1@ConTerm {} :: Term b))
    (EqTerm (DynTerm (e2 :: Term a)) (DynTerm (ec2@ConTerm {} :: Term b)))
    _
    falseRes
      | e1 == e2 && ec1 /= ec2 = Just falseRes
  pevalInferImplies _ _ _ _ = Nothing
  
  pevalITEBoolLeftAnd :: Term Bool -> Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolLeftAnd cond t1 t2 ifFalse
    | t1 == ifFalse = Just $ pevalAndTerm t1 $ pevalImplyTerm cond t2
    | t2 == ifFalse = Just $ pevalAndTerm t2 $ pevalImplyTerm cond t1
    | cond == t1 = Just $ pevalITETerm cond t2 ifFalse
    | cond == t2 = Just $ pevalITETerm cond t1 ifFalse
    | otherwise =
        msum
          [ pevalInferImplies cond t1 (pevalITETerm cond t2 ifFalse) (pevalAndTerm (pevalNotTerm cond) ifFalse),
            pevalInferImplies cond t2 (pevalITETerm cond t1 ifFalse) (pevalAndTerm (pevalNotTerm cond) ifFalse)
          ]
  
  pevalITEBoolBothAnd :: Term Bool -> Term Bool -> Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolBothAnd cond t1 t2 f1 f2
    | t1 == f1 = Just $ pevalAndTerm t1 $ pevalITETerm cond t2 f2
    | t1 == f2 = Just $ pevalAndTerm t1 $ pevalITETerm cond t2 f1
    | t2 == f1 = Just $ pevalAndTerm t2 $ pevalITETerm cond t1 f2
    | t2 == f2 = Just $ pevalAndTerm t2 $ pevalITETerm cond t1 f1
    | otherwise = Nothing
  
  pevalITEBoolRightAnd :: Term Bool -> Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolRightAnd cond ifTrue f1 f2
    | f1 == ifTrue = Just $ pevalAndTerm f1 $ pevalOrTerm cond f2
    | f2 == ifTrue = Just $ pevalAndTerm f2 $ pevalOrTerm cond f1
    | otherwise = Nothing
  
  pevalITEBoolLeftOr :: Term Bool -> Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolLeftOr cond t1 t2 ifFalse
    | t1 == ifFalse = Just $ pevalOrTerm t1 $ pevalAndTerm cond t2
    | t2 == ifFalse = Just $ pevalOrTerm t2 $ pevalAndTerm cond t1
    | cond == t1 = Just $ pevalOrTerm cond ifFalse
    | cond == t2 = Just $ pevalOrTerm cond ifFalse
    | otherwise =
        msum
          [ pevalInferImplies cond t1 (pevalOrTerm cond ifFalse) (pevalITETerm cond t2 ifFalse),
            pevalInferImplies cond t2 (pevalOrTerm cond ifFalse) (pevalITETerm cond t1 ifFalse)
          ]
  
  pevalITEBoolBothOr :: Term Bool -> Term Bool -> Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolBothOr cond t1 t2 f1 f2
    | t1 == f1 = Just $ pevalOrTerm t1 $ pevalITETerm cond t2 f2
    | t1 == f2 = Just $ pevalOrTerm t1 $ pevalITETerm cond t2 f1
    | t2 == f1 = Just $ pevalOrTerm t2 $ pevalITETerm cond t1 f2
    | t2 == f2 = Just $ pevalOrTerm t2 $ pevalITETerm cond t1 f1
    | otherwise = Nothing
  
  pevalITEBoolRightOr :: Term Bool -> Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolRightOr cond ifTrue f1 f2
    | f1 == ifTrue = Just $ pevalOrTerm f1 $ pevalAndTerm (pevalNotTerm cond) f2
    | f2 == ifTrue = Just $ pevalOrTerm f2 $ pevalAndTerm (pevalNotTerm cond) f1
    | otherwise = Nothing
  
  pevalITEBoolLeft :: Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolLeft cond (AndTerm t1 t2) ifFalse =
    msum
      [ pevalITEBoolLeftAnd cond t1 t2 ifFalse,
        case ifFalse of
          AndTerm f1 f2 -> pevalITEBoolBothAnd cond t1 t2 f1 f2
          _ -> Nothing
      ]
  pevalITEBoolLeft cond (OrTerm t1 t2) ifFalse =
    msum
      [ pevalITEBoolLeftOr cond t1 t2 ifFalse,
        case ifFalse of
          OrTerm f1 f2 -> pevalITEBoolBothOr cond t1 t2 f1 f2
          _ -> Nothing
      ]
  pevalITEBoolLeft cond (NotTerm nIfTrue) ifFalse =
    msum
      [ pevalITEBoolLeftNot cond nIfTrue ifFalse,
        case ifFalse of
          NotTerm nIfFalse ->
            pevalITEBoolBothNot cond nIfTrue nIfFalse
          _ -> Nothing
      ]
  pevalITEBoolLeft _ _ _ = Nothing
  
  pevalITEBoolNoLeft :: Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolNoLeft cond ifTrue (AndTerm f1 f2) = pevalITEBoolRightAnd cond ifTrue f1 f2
  pevalITEBoolNoLeft cond ifTrue (OrTerm f1 f2) = pevalITEBoolRightOr cond ifTrue f1 f2
  pevalITEBoolNoLeft cond ifTrue (NotTerm nIfFalse) = pevalITEBoolRightNot cond ifTrue nIfFalse
  pevalITEBoolNoLeft _ _ _ = Nothing
  
  -- | Basic partial evaluation for ITE terms.
  pevalITEBasic :: (SupportedPrim a) => Term Bool -> Term a -> Term a -> Maybe (Term a)
  pevalITEBasic (ConTerm True) ~ifTrue ~_ = Just ifTrue
  pevalITEBasic (ConTerm False) ~_ ~ifFalse = Just ifFalse
  pevalITEBasic (NotTerm ncond) ifTrue ifFalse = Just $ pevalITETerm ncond ifFalse ifTrue
  pevalITEBasic _ ifTrue ifFalse | ifTrue == ifFalse = Just ifTrue
  pevalITEBasic (ITETerm cc ct cf) (ITETerm tc tt tf) (ITETerm fc ft ff) -- later
    | cc == tc && cc == fc = Just $ pevalITETerm cc (pevalITETerm ct tt ft) (pevalITETerm cf tf ff)
  pevalITEBasic cond (ITETerm tc tt tf) ifFalse -- later
    | tt == ifFalse = Just $ pevalITETerm (pevalOrTerm (pevalNotTerm cond) tc) tt tf
    | tf == ifFalse = Just $ pevalITETerm (pevalAndTerm cond tc) tt tf
    | termImplies cond tc = Just $ pevalITETerm cond tt ifFalse
  pevalITEBasic cond (ITETerm (AndTerm c1 c2) tt tf) ifFalse
    | cond == c1 = Just $ pevalITETerm cond (pevalITETerm c2 tt tf) ifFalse
    | cond == c2 = Just $ pevalITETerm cond (pevalITETerm c1 tt tf) ifFalse
  pevalITEBasic cond ifTrue (ITETerm fc ft ff) -- later
    | ifTrue == ft = Just $ pevalITETerm (pevalOrTerm cond fc) ifTrue ff
    | ifTrue == ff = Just $ pevalITETerm (pevalOrTerm cond (pevalNotTerm fc)) ifTrue ft
    | termImplies fc cond = Just $ pevalITETerm cond ifTrue ff
  pevalITEBasic cond ifTrue (ITETerm (OrTerm c1 c2) ft ff)
    | cond == c1 = Just $ pevalITETerm cond ifTrue (pevalITETerm c2 ft ff)
    | cond == c2 = Just $ pevalITETerm cond ifTrue (pevalITETerm c1 ft ff)
  pevalITEBasic _ _ _ = Nothing
  
  pevalITEBoolBasic :: Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBoolBasic cond ifTrue ifFalse
    | cond == ifTrue = Just $ pevalOrTerm cond ifFalse
    | cond == ifFalse = Just $ pevalAndTerm cond ifTrue
  pevalITEBoolBasic cond (ConTerm v) ifFalse
    | v = Just $ pevalOrTerm cond ifFalse
    | otherwise = Just $ pevalAndTerm (pevalNotTerm cond) ifFalse
  pevalITEBoolBasic cond ifTrue (ConTerm v)
    | v = Just $ pevalOrTerm (pevalNotTerm cond) ifTrue
    | otherwise = Just $ pevalAndTerm cond ifTrue
  pevalITEBoolBasic _ _ _ = Nothing
  
  pevalITEBool :: Term Bool -> Term Bool -> Term Bool -> Maybe (Term Bool)
  pevalITEBool cond ~ifTrue ~ifFalse =
    msum
      [ pevalITEBasic cond ifTrue ifFalse,
        pevalITEBoolBasic cond ifTrue ifFalse,
        pevalITEBoolLeft cond ifTrue ifFalse,
        pevalITEBoolNoLeft cond ifTrue ifFalse
      ]
  
  -- | Basic partial evaluation for ITE terms.
  pevalITEBasicTerm :: (SupportedPrim a) => Term Bool -> Term a -> Term a -> Term a
  pevalITEBasicTerm cond ~ifTrue ~ifFalse =
    fromMaybe (iteTerm cond ifTrue ifFalse) $
      pevalITEBasic cond ifTrue ifFalse
  
  -- | Default partial evaluation for equality terms.
  pevalDefaultEqTerm :: (SupportedNonFuncPrim a) => Term a -> Term a -> Term Bool
  pevalDefaultEqTerm l@ConTerm {} r@ConTerm {} = conTerm $ l == r
  pevalDefaultEqTerm l@ConTerm {} r = pevalDefaultEqTerm r l
  pevalDefaultEqTerm l (BoolConTerm rv) =
    if rv
      then unsafeCoerce l
      else pevalNotTerm (unsafeCoerce l)
  pevalDefaultEqTerm (NotTerm lv) r
    | lv == r = falseTerm
  pevalDefaultEqTerm l (NotTerm rv)
    | l == rv = falseTerm
  pevalDefaultEqTerm (AddNumTerm (ConTerm c) v) (ConTerm c2) =
    pevalDefaultEqTerm v (conTerm $ c2 - c)
  pevalDefaultEqTerm l (ITETerm c t f)
    | l == t = pevalOrTerm c (pevalDefaultEqTerm l f)
    | l == f = pevalOrTerm (pevalNotTerm c) (pevalDefaultEqTerm l t)
  pevalDefaultEqTerm (ITETerm c t f) r
    | t == r = pevalOrTerm c (pevalDefaultEqTerm f r)
    | f == r = pevalOrTerm (pevalNotTerm c) (pevalDefaultEqTerm t r)
  pevalDefaultEqTerm l r
    | l == r = trueTerm
    | otherwise = eqTerm l r
  {-# INLINEABLE pevalDefaultEqTerm #-}
  
  instance SBVRep Bool where
    type SBVType Bool = SBV.SBV Bool
  
  instance SupportedPrimConstraint Bool
  
  instance SupportedPrim Bool where
    pformatCon True = "true"
    pformatCon False = "false"
    defaultValue = defaultValueForBool
    pevalITETerm cond ~ifTrue ~ifFalse =
      fromMaybe (iteTerm cond ifTrue ifFalse) $
        pevalITEBool cond ifTrue ifFalse
    pevalEqTerm (EqTerm (DynTerm (l1 :: Term Bool)) l2) (EqTerm r1 r2)
      | l1 == unsafeCoerce r1 = pevalEqTerm l2 (unsafeCoerce r2)
      | l1 == unsafeCoerce r2 = pevalEqTerm l2 (unsafeCoerce r1)
      | l2 == unsafeCoerce r1 = pevalEqTerm (unsafeCoerce l1) r2
      | l2 == unsafeCoerce r2 = pevalEqTerm (unsafeCoerce l1) r1
    pevalEqTerm (EqTerm (DynTerm (l1 :: Term (WordN 1))) l2) (EqTerm r1 r2)
      | l1 == unsafeCoerce r1 = pevalEqTerm l2 (unsafeCoerce r2)
      | l1 == unsafeCoerce r2 = pevalEqTerm l2 (unsafeCoerce r1)
      | l2 == unsafeCoerce r1 = pevalEqTerm (unsafeCoerce l1) r2
      | l2 == unsafeCoerce r2 = pevalEqTerm (unsafeCoerce l1) r1
    pevalEqTerm (EqTerm (DynTerm (l1 :: Term (IntN 1))) l2) (EqTerm r1 r2)
      | l1 == unsafeCoerce r1 = pevalEqTerm l2 (unsafeCoerce r2)
      | l1 == unsafeCoerce r2 = pevalEqTerm l2 (unsafeCoerce r1)
      | l2 == unsafeCoerce r1 = pevalEqTerm (unsafeCoerce l1) r2
      | l2 == unsafeCoerce r2 = pevalEqTerm (unsafeCoerce l1) r1
    pevalEqTerm l r = pevalDefaultEqTerm l r
    pevalDistinctTerm (_ :| []) = conTerm True
    pevalDistinctTerm (a :| [b]) = pevalNotTerm $ pevalEqTerm a b
    pevalDistinctTerm _ = conTerm False
    conSBVTerm n = if n then SBV.sTrue else SBV.sFalse
    symSBVName symbol _ = show symbol
    symSBVTerm = sbvFresh
    withPrim r = r
    parseSMTModelResult _ = parseScalarSMTModelResult id
    castTypedSymbol ::
      forall knd knd'.
      (IsSymbolKind knd') =>
      TypedSymbol knd Bool ->
      Maybe (TypedSymbol knd' Bool)
    castTypedSymbol (TypedSymbol s) =
      case decideSymbolKind @knd' of
        Left HRefl -> Just $ TypedSymbol s
        Right HRefl -> Just $ TypedSymbol s
    funcDummyConstraint _ = SBV.sTrue
  
  instance NonFuncSBVRep Bool where
    type NonFuncSBVBaseType Bool = Bool
  
  instance SupportedNonFuncPrim Bool where
    conNonFuncSBVTerm = conSBVTerm
    symNonFuncSBVTerm = symSBVTerm @Bool
    withNonFuncPrim r = r
  
  data PhantomDict a where
    PhantomDict :: (SupportedPrim a) => PhantomDict a
  
  data PhantomNonFuncDict a where
    PhantomNonFuncDict ::
      (SupportedNonFuncPrim a) => PhantomNonFuncDict a
  
  {-# NOINLINE phantomDictCache #-}
  phantomDictCache :: IORef (HM.HashMap SomeTypeRep (PhantomDict Any))
  phantomDictCache = unsafePerformIO $ newIORef HM.empty
  
  -- TODO
  {-# NOINLINE getPhantomDict #-}
  getPhantomDict :: forall a. (SupportedPrim a) => PhantomDict a
  getPhantomDict = unsafePerformIO $ do
    cache <- readIORef phantomDictCache
    let !tr = SomeTypeRep $ primTypeRep @a
    case HM.lookup tr cache of
      Just p -> return $ unsafeCoerce p
      Nothing -> do
        let r = PhantomDict :: PhantomDict a
        atomicModifyIORefCAS_ phantomDictCache $ HM.insert tr $ unsafeCoerce r
        return r
  
  {-# NOINLINE phantomNonFuncDictCache #-}
  phantomNonFuncDictCache ::
    IORef (HM.HashMap SomeTypeRep (PhantomNonFuncDict Any))
  phantomNonFuncDictCache = unsafePerformIO $ newIORef HM.empty
  
  -- TODO
  {-# NOINLINE getPhantomNonFuncDict #-}
  getPhantomNonFuncDict ::
    forall a. (SupportedNonFuncPrim a) => PhantomNonFuncDict a
  getPhantomNonFuncDict = unsafePerformIO $ do
    cache <- readIORef phantomNonFuncDictCache
    let !tr = SomeTypeRep $ primTypeRep @a
    case HM.lookup tr cache of
      Just p -> return $ unsafeCoerce p
      Nothing -> do
        let r = PhantomNonFuncDict :: PhantomNonFuncDict a
        atomicModifyIORefCAS_ phantomNonFuncDictCache $
          HM.insert tr $
            unsafeCoerce r
        return r
  
  defaultValueForInteger :: Integer
  defaultValueForInteger = 0
  
  -- Basic Integer
  instance SBVRep Integer where
    type SBVType Integer = SBV.SBV Integer
  
  instance SupportedPrimConstraint Integer where
    type PrimConstraint Integer = (Integral (NonFuncSBVBaseType Integer))
  
  pairwiseHasConcreteEqual :: (SupportedNonFuncPrim a) => [Term a] -> Bool
  pairwiseHasConcreteEqual [] = False
  pairwiseHasConcreteEqual [_] = False
  pairwiseHasConcreteEqual (x : xs) =
    go x xs || pairwiseHasConcreteEqual xs
    where
      go _ [] = False
      go x (y : ys) = x == y || go x ys
  
  getAllConcrete :: [Term a] -> Maybe [a]
  getAllConcrete [] = return []
  getAllConcrete (ConTerm x : xs) = (x :) <$> getAllConcrete xs
  getAllConcrete _ = Nothing
  
  checkConcreteDistinct :: (Eq t) => [t] -> Bool
  checkConcreteDistinct [] = True
  checkConcreteDistinct (x : xs) = check0 x xs && checkConcreteDistinct xs
    where
      check0 _ [] = True
      check0 x (y : ys) = x /= y && check0 x ys
  
  pevalGeneralDistinct ::
    (SupportedNonFuncPrim a) => NonEmpty (Term a) -> Term Bool
  pevalGeneralDistinct (_ :| []) = conTerm True
  pevalGeneralDistinct (a :| [b]) = pevalNotTerm $ pevalEqTerm a b
  pevalGeneralDistinct l | pairwiseHasConcreteEqual $ toList l = conTerm False
  pevalGeneralDistinct l =
    case getAllConcrete (toList l) of
      Nothing -> distinctTerm l
      Just xs -> conTerm $ checkConcreteDistinct xs
  
  instance SupportedPrim Integer where
    pformatCon = show
    defaultValue = defaultValueForInteger
    pevalITETerm = pevalITEBasicTerm
    pevalEqTerm = pevalDefaultEqTerm
    pevalDistinctTerm = pevalGeneralDistinct
    conSBVTerm n = fromInteger n
    symSBVName symbol _ = show symbol
    symSBVTerm name = sbvFresh name
    parseSMTModelResult _ = parseScalarSMTModelResult id
    castTypedSymbol ::
      forall knd knd'.
      (IsSymbolKind knd') =>
      TypedSymbol knd Integer ->
      Maybe (TypedSymbol knd' Integer)
    castTypedSymbol s =
      case decideSymbolKind @knd' of
        Left HRefl -> Just $ typedConstantSymbol $ unTypedSymbol s
        Right HRefl -> Just $ typedAnySymbol $ unTypedSymbol s
    funcDummyConstraint _ = SBV.sTrue
  
  instance NonFuncSBVRep Integer where
    type NonFuncSBVBaseType Integer = Integer
  
  instance SupportedNonFuncPrim Integer where
    conNonFuncSBVTerm = conSBVTerm
    symNonFuncSBVTerm = symSBVTerm @Integer
    withNonFuncPrim r = r
  
  pevalITEBVTerm ::
    forall bv n.
    ( KnownNat n,
      1 <= n,
      forall m. (KnownNat m, 1 <= m) => SupportedPrim (bv m),
      forall m. (KnownNat m, 1 <= m) => Show (bv m),
      PEvalBVTerm bv
    ) =>
    Term Bool -> Term (bv n) -> Term (bv n) -> Maybe (Term (bv n))
  pevalITEBVTerm
    ( EqTerm
        (DynTerm (l :: Term (bv n)))
        (DynTerm (ConTerm (r :: bv n)))
      )
    (ConTerm t)
    (ConTerm f)
      | natVal (Proxy @n) == 1 && r == 1 && t == 0 && f == 1 = Just $ pevalComplementBitsTerm l
      | natVal (Proxy @n) == 1 && r == 1 && t == 1 && f == 0 = Just l
      | natVal (Proxy @n) == 1 && r == 0 && t == 0 && f == 1 = Just l
      | natVal (Proxy @n) == 1 && r == 0 && t == 1 && f == 0 = Just $ pevalComplementBitsTerm l
  pevalITEBVTerm
    (EqTerm (DynTerm (l :: Term (bv 1))) (DynTerm (ConTerm (r :: bv 1))))
    (ConTerm t)
    f
      | n > 1 && (t == 0 || t == -1) && (r == -1 || r == 0) =
          Just $
            (if t == 0 then pevalAndBitsTerm else pevalOrBitsTerm)
              ( unsafePevalBVExtendTerm
                  (natRepr @1)
                  (natRepr @n)
                  True
                  (if (r == 0) == (t == 0) then l else pevalComplementBitsTerm l)
              )
              f
      where
        n = natVal (Proxy @n)
  pevalITEBVTerm
    (EqTerm (DynTerm (_ :: Term (bv 1))) (DynTerm (ConTerm (_ :: bv 1))))
    (ConTerm _)
    (ConTerm _) = Nothing
  pevalITEBVTerm
    (EqTerm (DynTerm (l :: Term (bv 1))) (DynTerm (ConTerm (r :: bv 1))))
    t
    f@(ConTerm _) = pevalITEBVTerm (eqTerm l (conTerm $ complement r)) f t
  pevalITEBVTerm
    cond
    (BVConcatTerm (a :: Term (bv a)) (b :: Term (bv b)))
    (BVConcatTerm (DynTerm (c :: Term (bv a))) d) =
      Just $
        pevalBVConcatTerm
          (pevalITETerm cond a c)
          (pevalITETerm cond b (unsafeCoerce d))
  pevalITEBVTerm
    cond
    (BVExtendTerm True pl (a :: Term (bv a)))
    (BVExtendTerm True _ (DynTerm (b :: Term (bv a)))) =
      Just $
        pevalBVExtendTerm
          True
          pl
          (pevalITETerm cond a b)
  pevalITEBVTerm cond (AndBitsTerm a b) (AndBitsTerm c d)
    | a == c = Just $ andBitsTerm a $ pevalITETerm cond b d
    | a == d = Just $ andBitsTerm a $ pevalITETerm cond b c
    | b == c = Just $ andBitsTerm b $ pevalITETerm cond a d
    | b == d = Just $ andBitsTerm b $ pevalITETerm cond a c
  pevalITEBVTerm cond (AndBitsTerm a b) c
    | a == c = Just $ andBitsTerm c $ pevalOrBitsTerm (boolToBVTerm $ pevalNotTerm cond) b
    | b == c = Just $ andBitsTerm c $ pevalOrBitsTerm (boolToBVTerm $ pevalNotTerm cond) a
  pevalITEBVTerm cond a (AndBitsTerm b c)
    | a == b = Just $ andBitsTerm a $ pevalOrBitsTerm (boolToBVTerm cond) c
    | a == c = Just $ andBitsTerm a $ pevalOrBitsTerm (boolToBVTerm cond) b
  pevalITEBVTerm cond (OrBitsTerm a b) (OrBitsTerm c d)
    | a == c = Just $ orBitsTerm a $ pevalITETerm cond b d
    | a == d = Just $ orBitsTerm a $ pevalITETerm cond b c
    | b == c = Just $ orBitsTerm b $ pevalITETerm cond a d
    | b == d = Just $ orBitsTerm b $ pevalITETerm cond a c
  pevalITEBVTerm cond (OrBitsTerm a b) c
    | a == c = Just $ orBitsTerm c $ pevalAndBitsTerm (boolToBVTerm cond) b
    | b == c = Just $ orBitsTerm c $ pevalAndBitsTerm (boolToBVTerm cond) a
  pevalITEBVTerm cond a (OrBitsTerm b c)
    | a == b = Just $ orBitsTerm a $ pevalAndBitsTerm (boolToBVTerm $ pevalNotTerm cond) c
    | a == c = Just $ orBitsTerm a $ pevalAndBitsTerm (boolToBVTerm $ pevalNotTerm cond) b
  pevalITEBVTerm _ _ _ = Nothing
  
  -- | Convert boolean term to a 1-bit bitvector term.
  boolToBVTerm ::
    forall bv n.
    ( PEvalBVTerm bv,
      KnownNat n,
      1 <= n,
      forall m. (KnownNat m, 1 <= m) => SupportedPrim (bv m)
    ) =>
    Term Bool -> Term (bv n)
  boolToBVTerm cond =
    let bv =
          case cond of
            NotTerm c -> iteTerm c (conTerm 0) (conTerm 1)
            _ -> iteTerm cond (conTerm 1 :: Term (bv 1)) (conTerm 0) :: Term (bv 1)
     in if natVal (Proxy @n) == 1
          then unsafeCoerce bv
          else bvExtendTerm True (natRepr @n) bv
  
  -- Signed BV
  instance (KnownNat w, 1 <= w) => SupportedPrimConstraint (IntN w) where
    type PrimConstraint (IntN w) = (KnownNat w, 1 <= w, BVIsNonZero w)
  
  instance (KnownNat w, 1 <= w) => SBVRep (IntN w) where
    type SBVType (IntN w) = SBV.SBV (SBV.IntN w)
  
  instance (KnownNat w, 1 <= w) => SupportedPrim (IntN w) where
    sbvDistinct = withPrim @(IntN w) $ SBV.distinct . toList
    sbvEq = withPrim @(IntN w) (SBV..==)
    pformatCon = show
    defaultValue = 0
    pevalITETerm cond ifTrue ifFalse =
      fromMaybe (iteTerm cond ifTrue ifFalse) $
        msum
          [ pevalITEBasic cond ifTrue ifFalse,
            pevalITEBVTerm cond ifTrue ifFalse
          ]
    pevalEqTerm = pevalDefaultEqTerm
    pevalDistinctTerm = pevalGeneralDistinct
    conSBVTerm n = bvIsNonZeroFromGEq1 (Proxy @w) $ fromIntegral n
    symSBVName symbol _ = show symbol
    symSBVTerm name = bvIsNonZeroFromGEq1 (Proxy @w) $ sbvFresh name
    withPrim r = bvIsNonZeroFromGEq1 (Proxy @w) r
    {-# INLINE withPrim #-}
    parseSMTModelResult _ cv =
      withPrim @(IntN w) $
        parseScalarSMTModelResult (\(x :: SBV.IntN w) -> fromIntegral x) cv
    castTypedSymbol ::
      forall knd knd'.
      (IsSymbolKind knd') =>
      TypedSymbol knd (IntN w) ->
      Maybe (TypedSymbol knd' (IntN w))
    castTypedSymbol s =
      case decideSymbolKind @knd' of
        Left HRefl -> Just $ typedConstantSymbol $ unTypedSymbol s
        Right HRefl -> Just $ typedAnySymbol $ unTypedSymbol s
    funcDummyConstraint _ = SBV.sTrue
  
  -- | Construct the 'SBV.BVIsNonZero' constraint from the proof that the width is
  -- at least 1.
  bvIsNonZeroFromGEq1 ::
    forall w r proxy.
    (1 <= w) =>
    proxy w ->
    ((SBV.BVIsNonZero w) => r) ->
    r
  bvIsNonZeroFromGEq1 _ r1 = case unsafeAxiom :: w :~: 1 of
    Refl -> r1
  {-# INLINE bvIsNonZeroFromGEq1 #-}
  
  instance (KnownNat w, 1 <= w) => NonFuncSBVRep (IntN w) where
    type NonFuncSBVBaseType (IntN w) = SBV.IntN w
  
  instance (KnownNat w, 1 <= w) => SupportedNonFuncPrim (IntN w) where
    conNonFuncSBVTerm = conSBVTerm
    symNonFuncSBVTerm = symSBVTerm @(IntN w)
    withNonFuncPrim r = bvIsNonZeroFromGEq1 (Proxy @w) r
  
  -- Unsigned BV
  instance (KnownNat w, 1 <= w) => SupportedPrimConstraint (WordN w) where
    type PrimConstraint (WordN w) = (KnownNat w, 1 <= w, BVIsNonZero w)
  
  instance (KnownNat w, 1 <= w) => SBVRep (WordN w) where
    type SBVType (WordN w) = SBV.SBV (SBV.WordN w)
  
  instance (KnownNat w, 1 <= w) => SupportedPrim (WordN w) where
    sbvDistinct = withPrim @(WordN w) $ SBV.distinct . toList
    sbvEq = withPrim @(WordN w) (SBV..==)
    pformatCon = show
    defaultValue = 0
    pevalITETerm cond ifTrue ifFalse =
      fromMaybe (iteTerm cond ifTrue ifFalse) $
        msum
          [ pevalITEBasic cond ifTrue ifFalse,
            pevalITEBVTerm cond ifTrue ifFalse
          ]
    pevalEqTerm = pevalDefaultEqTerm
    pevalDistinctTerm = pevalGeneralDistinct
    conSBVTerm n = bvIsNonZeroFromGEq1 (Proxy @w) $ fromIntegral n
    symSBVName symbol _ = show symbol
    symSBVTerm name = bvIsNonZeroFromGEq1 (Proxy @w) $ sbvFresh name
    withPrim r = bvIsNonZeroFromGEq1 (Proxy @w) r
    {-# INLINE withPrim #-}
    parseSMTModelResult _ cv =
      withPrim @(WordN w) $
        parseScalarSMTModelResult (\(x :: SBV.WordN w) -> fromIntegral x) cv
    castTypedSymbol ::
      forall knd knd'.
      (IsSymbolKind knd') =>
      TypedSymbol knd (WordN w) ->
      Maybe (TypedSymbol knd' (WordN w))
    castTypedSymbol s =
      case decideSymbolKind @knd' of
        Left HRefl -> Just $ typedConstantSymbol $ unTypedSymbol s
        Right HRefl -> Just $ typedAnySymbol $ unTypedSymbol s
    funcDummyConstraint _ = SBV.sTrue
  
  instance (KnownNat w, 1 <= w) => NonFuncSBVRep (WordN w) where
    type NonFuncSBVBaseType (WordN w) = SBV.WordN w
  
  instance (KnownNat w, 1 <= w) => SupportedNonFuncPrim (WordN w) where
    conNonFuncSBVTerm = conSBVTerm
    symNonFuncSBVTerm = symSBVTerm @(WordN w)
    withNonFuncPrim r = bvIsNonZeroFromGEq1 (Proxy @w) r
  
  -- FP
  instance (ValidFP eb sb) => SupportedPrimConstraint (FP eb sb) where
    type PrimConstraint (FP eb sb) = ValidFP eb sb
  
  instance (ValidFP eb sb) => SBVRep (FP eb sb) where
    type SBVType (FP eb sb) = SBV.SBV (SBV.FloatingPoint eb sb)
  
  instance (ValidFP eb sb) => SupportedPrim (FP eb sb) where
    sameCon a b
      | isNaN a = isNaN b
      | fpIsPositiveZero a = fpIsPositiveZero b
      | fpIsNegativeZero a = fpIsNegativeZero b
      | otherwise = a == b
    hashConWithSalt s a
      | isNaN a = hashWithSalt s (2654435761 :: Int)
      | otherwise = hashWithSalt s a
    defaultValue = 0
    pevalITETerm = pevalITEBasicTerm
    pevalEqTerm (ConTerm l) (ConTerm r) = conTerm $ l == r
    pevalEqTerm l@ConTerm {} r = pevalEqTerm r l
    pevalEqTerm l r = eqTerm l r
    pevalDistinctTerm (_ :| []) = conTerm True
    pevalDistinctTerm (a :| [b]) = pevalNotTerm $ pevalEqTerm a b
    pevalDistinctTerm l =
      case getAllConcrete (toList l) of
        Nothing -> distinctTerm l
        Just xs | any isNaN xs -> distinctTerm l
        Just xs -> conTerm $ checkConcreteDistinct xs
    conSBVTerm (FP fp) = SBV.literal fp
    symSBVName symbol _ = show symbol
    symSBVTerm name = sbvFresh name
    parseSMTModelResult _ cv =
      withPrim @(FP eb sb) $
        parseScalarSMTModelResult (\(x :: SBV.FloatingPoint eb sb) -> coerce x) cv
    funcDummyConstraint _ = SBV.sTrue
  
    -- Workaround for sbv#702.
    sbvIte = withPrim @(FP eb sb) $ \c a b ->
      case (SBV.unliteral a, SBV.unliteral b) of
        (Just a', Just b')
          | isInfinite a' && isInfinite b' ->
              let correspondingZero x = if x > 0 then 0 else -0
               in 1
                    / sbvIte @(FP eb sb)
                      c
                      (conSBVTerm @(FP eb sb) $ correspondingZero a')
                      (conSBVTerm @(FP eb sb) $ correspondingZero b')
        _ -> SBV.ite c a b
    castTypedSymbol ::
      forall knd knd'.
      (IsSymbolKind knd') =>
      TypedSymbol knd (FP eb sb) ->
      Maybe (TypedSymbol knd' (FP eb sb))
    castTypedSymbol s =
      case decideSymbolKind @knd' of
        Left HRefl -> Just $ typedConstantSymbol $ unTypedSymbol s
        Right HRefl -> Just $ typedAnySymbol $ unTypedSymbol s
  
  instance (ValidFP eb sb) => NonFuncSBVRep (FP eb sb) where
    type NonFuncSBVBaseType (FP eb sb) = SBV.FloatingPoint eb sb
  
  instance (ValidFP eb sb) => SupportedNonFuncPrim (FP eb sb) where
    conNonFuncSBVTerm = conSBVTerm
    symNonFuncSBVTerm = symSBVTerm @(FP eb sb)
    withNonFuncPrim r = r
  
  -- FPRoundingMode
  instance SupportedPrimConstraint FPRoundingMode
  
  instance SBVRep FPRoundingMode where
    type SBVType FPRoundingMode = SBV.SBV SBV.RoundingMode
  
  instance SupportedPrim FPRoundingMode where
    defaultValue = RNE
    pevalITETerm = pevalITEBasicTerm
    pevalEqTerm (ConTerm l) (ConTerm r) = conTerm $ l == r
    pevalEqTerm l@ConTerm {} r = pevalEqTerm r l
    pevalEqTerm l r = eqTerm l r
    pevalDistinctTerm = pevalGeneralDistinct
    conSBVTerm RNE = SBV.sRNE
    conSBVTerm RNA = SBV.sRNA
    conSBVTerm RTP = SBV.sRTP
    conSBVTerm RTN = SBV.sRTN
    conSBVTerm RTZ = SBV.sRTZ
    symSBVName symbol _ = show symbol
    symSBVTerm name = sbvFresh name
    parseSMTModelResult _ cv =
      withPrim @(FPRoundingMode) $
        parseScalarSMTModelResult
          ( \(x :: SBV.RoundingMode) -> case x of
              SBV.RoundNearestTiesToEven -> RNE
              SBV.RoundNearestTiesToAway -> RNA
              SBV.RoundTowardPositive -> RTP
              SBV.RoundTowardNegative -> RTN
              SBV.RoundTowardZero -> RTZ
          )
          cv
    castTypedSymbol ::
      forall knd knd'.
      (IsSymbolKind knd') =>
      TypedSymbol knd FPRoundingMode ->
      Maybe (TypedSymbol knd' FPRoundingMode)
    castTypedSymbol s =
      case decideSymbolKind @knd' of
        Left HRefl -> Just $ typedConstantSymbol $ unTypedSymbol s
        Right HRefl -> Just $ typedAnySymbol $ unTypedSymbol s
    funcDummyConstraint _ = SBV.sTrue
  
  instance NonFuncSBVRep FPRoundingMode where
    type NonFuncSBVBaseType FPRoundingMode = SBV.RoundingMode
  
  instance SupportedNonFuncPrim FPRoundingMode where
    conNonFuncSBVTerm = conSBVTerm
    symNonFuncSBVTerm = symSBVTerm @FPRoundingMode
    withNonFuncPrim r = r
  
  -- AlgReal
  
  instance SupportedPrimConstraint AlgReal
  
  instance SBVRep AlgReal where
    type SBVType AlgReal = SBV.SBV SBV.AlgReal
  
  instance SupportedPrim AlgReal where
    defaultValue = 0
    pevalITETerm = pevalITEBasicTerm
    pevalEqTerm (ConTerm l) (ConTerm r) = conTerm $ l == r
    pevalEqTerm l@ConTerm {} r = pevalEqTerm r l
    pevalEqTerm l r = eqTerm l r
    pevalDistinctTerm = pevalGeneralDistinct
    conSBVTerm = SBV.literal . toSBVAlgReal
    symSBVName symbol _ = show symbol
    symSBVTerm name = sbvFresh name
    parseSMTModelResult _ cv =
      withPrim @AlgReal $
        parseScalarSMTModelResult fromSBVAlgReal cv
    castTypedSymbol ::
      forall knd knd'.
      (IsSymbolKind knd') =>
      TypedSymbol knd AlgReal ->
      Maybe (TypedSymbol knd' AlgReal)
    castTypedSymbol s =
      case decideSymbolKind @knd' of
        Left HRefl -> Just $ typedConstantSymbol $ unTypedSymbol s
        Right HRefl -> Just $ typedAnySymbol $ unTypedSymbol s
    funcDummyConstraint _ = SBV.sTrue
  
  instance NonFuncSBVRep AlgReal where
    type NonFuncSBVBaseType AlgReal = SBV.AlgReal
  
  instance SupportedNonFuncPrim AlgReal where
    conNonFuncSBVTerm = conSBVTerm
    symNonFuncSBVTerm = symSBVTerm @AlgReal
    withNonFuncPrim r = r
  
  -- Bitwise
  
  bitOpOnConcat ::
    forall bv m.
    ( forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n),
      PEvalBVTerm bv,
      KnownNat m,
      1 <= m
    ) =>
    ( forall n.
      (KnownNat n, 1 <= n) =>
      Term (bv n) -> Term (bv n) -> Term (bv n)
    ) ->
    Term (bv m) ->
    Term (bv m) ->
    Maybe (Term (bv m))
  bitOpOnConcat
    peval
    (BVConcatTerm (l0 :: Term (bv l)) (r0 :: Term (bv r)))
    (BVConcatTerm (DynTerm (l :: Term (bv l))) (DynTerm (r :: Term (bv r)))) =
      let r' = peval r0 r
          l' = peval l0 l
       in Just $ pevalBVConcatTerm l' r'
  bitOpOnConcat
    peval
    at@(ConTerm _)
    (BVConcatTerm (l :: Term (bv l)) (r :: Term (bv r))) =
      let nzero = natRepr @0
          nr = natRepr @r
          nl = natRepr @l
          nlpr = natRepr @(l + r)
          ar =
            unsafePevalBVSelectTerm
              nlpr
              nzero
              nr
              (unsafeCoerce at :: Term (bv (l + r)))
          al =
            unsafePevalBVSelectTerm
              nlpr
              nr
              nl
              (unsafeCoerce at :: Term (bv (l + r)))
          r' = peval ar r
          l' = peval al l
       in Just $ pevalBVConcatTerm l' r'
  bitOpOnConcat
    peval
    (BVExtendTerm True pl (l :: Term (bv n)))
    (BVExtendTerm True _ (DynTerm (r :: Term (bv n)))) =
      Just $ pevalBVExtendTerm True pl (peval l r)
  bitOpOnConcat _ _ _ = Nothing
  
  doPevalAndBitsTerm ::
    forall bv m.
    ( forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n),
      PEvalBVTerm bv,
      KnownNat m,
      1 <= m
    ) =>
    Term (bv m) ->
    Term (bv m) ->
    Maybe (Term (bv m))
  doPevalAndBitsTerm (ConTerm a) (ConTerm b) =
    Just $ conTerm (a .&. b)
  doPevalAndBitsTerm (ConTerm a) b
    | a == zeroBits = Just $ conTerm zeroBits
    | a == complement zeroBits = Just b
    | aok || acok =
        case ( mkPositiveNatRepr $ fromIntegral leadingBits,
               mkPositiveNatRepr $ fromIntegral trailingBits
             ) of
          ( SomePositiveNatRepr (pleadingBits :: NatRepr leadingBits),
            SomePositiveNatRepr (ptrailingBitsRepr :: NatRepr trailingBits)
            ) ->
              case ( unsafeAxiom @(leadingBits + trailingBits) @m,
                     unsafeAxiom @(trailingBits + leadingBits) @m,
                     unsafeLeqProof @trailingBits @m
                   ) of
                (Refl, Refl, LeqProof) ->
                  if aok
                    then
                      Just $
                        pevalBVConcatTerm (conTerm 0 :: Term (bv leadingBits)) $
                          pevalBVSelectTerm (natRepr @0) ptrailingBitsRepr b
                    else
                      Just $
                        pevalBVConcatTerm
                          (pevalBVSelectTerm ptrailingBitsRepr pleadingBits b)
                          (conTerm 0 :: Term (bv trailingBits))
    where
      leadingBits = if aok then countLeadingZeros a else countLeadingZeros ac
      trailingBits = fromIntegral (natVal @m a) - leadingBits
      ac = complement a
      aok = a .&. (a + 1) == 0
      acok = ac .&. (ac + 1) == 0
  doPevalAndBitsTerm a b@(ConTerm _) = doPevalAndBitsTerm b a
  doPevalAndBitsTerm a b | a == b = Just a
  doPevalAndBitsTerm (ITETerm cond a@(ConTerm av) b@(ConTerm bv)) c
    | av `elem` [0, -1] || bv `elem` [0, -1] =
        Just $ pevalITETerm cond (pevalAndBitsTerm a c) (pevalAndBitsTerm b c)
  doPevalAndBitsTerm a (ITETerm cond b@(ConTerm bv) c@(ConTerm cv))
    | bv `elem` [0, -1] || cv `elem` [0, -1] =
        Just $ pevalITETerm cond (pevalAndBitsTerm a b) (pevalAndBitsTerm a c)
  doPevalAndBitsTerm (ITETerm cond a@(ConTerm v) b) c
    | v == 0 = Just $ pevalITETerm cond a (pevalAndBitsTerm b c)
  doPevalAndBitsTerm (ITETerm cond a b@(ConTerm v)) c
    | v == 0 = Just $ pevalITETerm cond (pevalAndBitsTerm a c) b
  doPevalAndBitsTerm a (ITETerm cond b@(ConTerm v) c)
    | v == 0 = Just $ pevalITETerm cond b (pevalAndBitsTerm a c)
  doPevalAndBitsTerm a (ITETerm cond b c@(ConTerm v))
    | v == 0 = Just $ pevalITETerm cond (pevalAndBitsTerm a b) c
  doPevalAndBitsTerm (BVExtendTerm True pl (ITETerm cond at@(ConTerm a) bt@(ConTerm b))) c
    | a `elem` [0, -1] && b `elem` [0, -1] =
        Just $
          pevalITETerm
            cond
            (pevalAndBitsTerm (pevalBVExtendTerm True pl at) c)
            (pevalAndBitsTerm (pevalBVExtendTerm True pl bt) c)
  doPevalAndBitsTerm a (BVExtendTerm True pl (ITETerm cond bt@(ConTerm b) ct@(ConTerm c)))
    | b `elem` [0, -1] && c `elem` [0, -1] =
        Just $
          pevalITETerm
            cond
            (pevalAndBitsTerm a (pevalBVExtendTerm True pl bt))
            (pevalAndBitsTerm a (pevalBVExtendTerm True pl ct))
  doPevalAndBitsTerm a b = bitOpOnConcat @bv @m pevalDefaultAndBitsTerm a b
  
  pevalDefaultAndBitsTerm ::
    forall bv m.
    ( forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n),
      PEvalBVTerm bv,
      KnownNat m,
      1 <= m
    ) =>
    Term (bv m) ->
    Term (bv m) ->
    Term (bv m)
  pevalDefaultAndBitsTerm = binaryUnfoldOnce doPevalAndBitsTerm andBitsTerm
  
  doPevalOrBitsTerm ::
    forall bv m.
    ( forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n),
      PEvalBVTerm bv,
      KnownNat m,
      1 <= m
    ) =>
    Term (bv m) ->
    Term (bv m) ->
    Maybe (Term (bv m))
  doPevalOrBitsTerm (ConTerm a) (ConTerm b) = Just $ conTerm (a .|. b)
  doPevalOrBitsTerm (ConTerm a) b
    | a == zeroBits = Just b
    | a == complement zeroBits = Just $ conTerm $ complement zeroBits
    | aok || acok =
        case ( mkPositiveNatRepr $ fromIntegral leadingBits,
               mkPositiveNatRepr $ fromIntegral trailingBits
             ) of
          ( SomePositiveNatRepr (pleadingBits :: NatRepr leadingBits),
            SomePositiveNatRepr (ptrailingBitsRepr :: NatRepr trailingBits)
            ) ->
              case ( unsafeAxiom @(leadingBits + trailingBits) @m,
                     unsafeAxiom @(trailingBits + leadingBits) @m,
                     unsafeLeqProof @trailingBits @m
                   ) of
                (Refl, Refl, LeqProof) ->
                  if acok
                    then
                      Just $
                        pevalBVConcatTerm (conTerm $ -1 :: Term (bv leadingBits)) $
                          pevalBVSelectTerm (natRepr @0) ptrailingBitsRepr b
                    else
                      Just $
                        pevalBVConcatTerm
                          (pevalBVSelectTerm ptrailingBitsRepr pleadingBits b)
                          (conTerm $ -1 :: Term (bv trailingBits))
    where
      leadingBits = if aok then countLeadingZeros a else countLeadingZeros ac
      trailingBits = fromIntegral (natVal @m a) - leadingBits
      ac = complement a
      aok = a .&. (a + 1) == 0
      acok = ac .&. (ac + 1) == 0
  doPevalOrBitsTerm a b@(ConTerm _) = doPevalOrBitsTerm b a
  doPevalOrBitsTerm a b | a == b = Just a
  doPevalOrBitsTerm (ITETerm cond a@(ConTerm _) b@(ConTerm _)) c =
    Just $ pevalITETerm cond (pevalOrBitsTerm a c) (pevalOrBitsTerm b c)
  doPevalOrBitsTerm a (ITETerm cond b@(ConTerm _) c@(ConTerm _)) =
    Just $ pevalITETerm cond (pevalOrBitsTerm a b) (pevalOrBitsTerm a c)
  doPevalOrBitsTerm (ITETerm cond a@(ConTerm v) b) c
    | v == -1 = Just $ pevalITETerm cond a (pevalOrBitsTerm b c)
  doPevalOrBitsTerm (ITETerm cond a b@(ConTerm v)) c
    | v == -1 = Just $ pevalITETerm cond (pevalOrBitsTerm a c) b
  doPevalOrBitsTerm a (ITETerm cond b@(ConTerm v) c)
    | v == -1 = Just $ pevalITETerm cond b (pevalOrBitsTerm a c)
  doPevalOrBitsTerm a (ITETerm cond b c@(ConTerm v))
    | v == -1 = Just $ pevalITETerm cond (pevalOrBitsTerm a b) c
  doPevalOrBitsTerm (BVExtendTerm True pl (ITETerm cond at@(ConTerm a) bt@(ConTerm b))) c
    | a `elem` [0, -1] && b `elem` [0, -1] =
        Just $
          pevalITETerm
            cond
            (pevalOrBitsTerm (pevalBVExtendTerm True pl at) c)
            (pevalOrBitsTerm (pevalBVExtendTerm True pl bt) c)
  doPevalOrBitsTerm a (BVExtendTerm True pl (ITETerm cond bt@(ConTerm b) ct@(ConTerm c)))
    | b `elem` [0, -1] && c `elem` [0, -1] =
        Just $
          pevalITETerm
            cond
            (pevalOrBitsTerm a (pevalBVExtendTerm True pl bt))
            (pevalOrBitsTerm a (pevalBVExtendTerm True pl ct))
  doPevalOrBitsTerm a b = bitOpOnConcat @bv @m pevalDefaultOrBitsTerm a b
  
  pevalDefaultOrBitsTerm ::
    forall bv m.
    ( KnownNat m,
      1 <= m,
      forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n),
      PEvalBVTerm bv
    ) =>
    Term (bv m) -> Term (bv m) -> Term (bv m)
  pevalDefaultOrBitsTerm = binaryUnfoldOnce doPevalOrBitsTerm orBitsTerm
  
  pevalDefaultXorBitsTerm ::
    forall bv m.
    ( forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n),
      PEvalBVTerm bv,
      KnownNat m,
      1 <= m
    ) =>
    Term (bv m) -> Term (bv m) -> Term (bv m)
  pevalDefaultXorBitsTerm = binaryUnfoldOnce doPevalXorBitsTerm xorBitsTerm
    where
      doPevalXorBitsTerm (ConTerm a) (ConTerm b) =
        Just $ conTerm (a `xor` b)
      doPevalXorBitsTerm (ConTerm a) b
        | a == zeroBits = Just b
        | a == complement zeroBits = Just $ pevalComplementBitsTerm b
      doPevalXorBitsTerm a b@(ConTerm _) = doPevalXorBitsTerm b a
      doPevalXorBitsTerm a b | a == b = Just $ conTerm zeroBits
      doPevalXorBitsTerm (ComplementBitsTerm i) (ComplementBitsTerm j) =
        Just $ pevalXorBitsTerm i j
      doPevalXorBitsTerm (ComplementBitsTerm i) j =
        Just $ pevalComplementBitsTerm $ pevalXorBitsTerm i j
      doPevalXorBitsTerm i (ComplementBitsTerm j) =
        Just $ pevalComplementBitsTerm $ pevalXorBitsTerm i j
      doPevalXorBitsTerm a b = bitOpOnConcat @bv @m pevalDefaultXorBitsTerm a b
  
  pevalDefaultComplementBitsTerm ::
    (Bits a, SupportedPrim a, PEvalBitwiseTerm a) => Term a -> Term a
  pevalDefaultComplementBitsTerm =
    unaryUnfoldOnce doPevalComplementBitsTerm complementBitsTerm
    where
      doPevalComplementBitsTerm (ConTerm a) = Just $ conTerm $ complement a
      doPevalComplementBitsTerm (ComplementBitsTerm a) = Just a
      doPevalComplementBitsTerm (BVConcatTerm l r) =
        Just $
          pevalBVConcatTerm
            (pevalComplementBitsTerm l)
            (pevalComplementBitsTerm r)
      doPevalComplementBitsTerm (BVExtendTerm True pr t) =
        Just $ pevalBVExtendTerm True pr $ pevalComplementBitsTerm t
      doPevalComplementBitsTerm _ = Nothing
  
  instance (KnownNat n, 1 <= n) => PEvalBitwiseTerm (WordN n) where
    pevalAndBitsTerm = pevalDefaultAndBitsTerm
    pevalOrBitsTerm = pevalDefaultOrBitsTerm
    pevalXorBitsTerm = pevalDefaultXorBitsTerm
    pevalComplementBitsTerm = pevalDefaultComplementBitsTerm
    withSbvBitwiseTermConstraint r = withPrim @(WordN n) r
  
  instance (KnownNat n, 1 <= n) => PEvalBitwiseTerm (IntN n) where
    pevalAndBitsTerm = pevalDefaultAndBitsTerm
    pevalOrBitsTerm = pevalDefaultOrBitsTerm
    pevalXorBitsTerm = pevalDefaultXorBitsTerm
    pevalComplementBitsTerm = pevalDefaultComplementBitsTerm
    withSbvBitwiseTermConstraint r = withPrim @(IntN n) r
  
  -- BVTerm
  
  pevalDefaultBVSelectTerm ::
    forall (bv2 :: Nat -> Type) bv n ix w p q.
    ( KnownNat n,
      KnownNat ix,
      KnownNat w,
      1 <= n,
      1 <= w,
      ix + w <= n,
      PEvalBVTerm bv,
      forall x. (KnownNat x, 1 <= x) => PEvalBitCastTerm (bv2 x) (bv x),
      PEvalBVTerm bv2,
      Typeable bv,
      SupportedPrim (bv w),
      SupportedPrim (bv2 n)
    ) =>
    p ix ->
    q w ->
    Term (bv n) ->
    Term (bv w)
  pevalDefaultBVSelectTerm ix w =
    unaryUnfoldOnce (doPevalDefaultBVSelectTerm @bv2 ix w) (bvSelectTerm ix w)
  
  -- | Unsafe version of `pevalBVSelectTerm`. Use `NatRepr` for the bit-width
  -- representations.
  unsafePevalBVSelectTerm ::
    forall bv n ix w.
    (PEvalBVTerm bv) =>
    NatRepr n ->
    NatRepr ix ->
    NatRepr w ->
    Term (bv n) ->
    Term (bv w)
  unsafePevalBVSelectTerm n ix w term =
    withKnownNat n $
      withKnownNat ix $
        withKnownNat w $
          case ( unsafeLeqProof @1 @n,
                 unsafeLeqProof @1 @w,
                 unsafeLeqProof @(ix + w) @n
               ) of
            (LeqProof, LeqProof, LeqProof) -> pevalBVSelectTerm ix w term
  
  doPevalDefaultBVSelectTerm ::
    forall (bv2 :: Nat -> Type) bv n ix w p q.
    ( KnownNat n,
      KnownNat ix,
      KnownNat w,
      forall x. (KnownNat x, 1 <= x) => PEvalBitCastTerm (bv2 x) (bv x),
      1 <= n,
      1 <= w,
      ix + w <= n,
      PEvalBVTerm bv,
      PEvalBVTerm bv2,
      Typeable bv,
      SupportedPrim (bv w),
      SupportedPrim (bv2 n)
    ) =>
    p ix ->
    q w ->
    Term (bv n) ->
    Maybe (Term (bv w))
  doPevalDefaultBVSelectTerm _ _ rhs
    | isJust (sameNat (Proxy @ix) (Proxy @0))
        && isJust (sameNat (Proxy @w) (Proxy @n)) =
        Just rhs >>= castTerm
  doPevalDefaultBVSelectTerm ix w (ConTerm b) =
    Just $ conTerm $ sizedBVSelect ix w b
  doPevalDefaultBVSelectTerm ix w (BitCastTerm (DynTerm (b :: Term (bv2 n)))) =
    Just $ pevalBitCastTerm $ pevalBVSelectTerm ix w b
  doPevalDefaultBVSelectTerm ix w (AddNumTerm t1 t2)
    | natVal @ix ix == 0 =
        Just $
          AddNumTerm
            (pevalDefaultBVSelectTerm @bv2 @bv ix w t1)
            (pevalDefaultBVSelectTerm @bv2 @bv ix w t2)
  doPevalDefaultBVSelectTerm ix w (MulNumTerm t1 t2)
    | natVal @ix ix == 0 =
        Just $
          MulNumTerm
            (pevalDefaultBVSelectTerm @bv2 @bv ix w t1)
            (pevalDefaultBVSelectTerm @bv2 @bv ix w t2)
  doPevalDefaultBVSelectTerm ix w (AndBitsTerm t1 t2) =
    Just $
      AndBitsTerm
        (pevalDefaultBVSelectTerm @bv2 @bv ix w t1)
        (pevalDefaultBVSelectTerm @bv2 @bv ix w t2)
  doPevalDefaultBVSelectTerm ix w (OrBitsTerm t1 t2) =
    Just $
      OrBitsTerm
        (pevalDefaultBVSelectTerm @bv2 @bv ix w t1)
        (pevalDefaultBVSelectTerm @bv2 @bv ix w t2)
  doPevalDefaultBVSelectTerm ix w (XorBitsTerm t1 t2) =
    Just $
      XorBitsTerm
        (pevalDefaultBVSelectTerm @bv2 @bv ix w t1)
        (pevalDefaultBVSelectTerm @bv2 @bv ix w t2)
  doPevalDefaultBVSelectTerm
    pix
    pw
    (BVConcatTerm (b1 :: Term (bv n1)) (b2 :: Term (bv n2)))
      | ix + w <= n2 = Just $ unsafePevalBVSelectTerm n2Repr ixRepr wRepr b2
      | ix >= n2 =
          case mkNatRepr (ix - n2) of
            SomeNatRepr ixpn2Repr ->
              Just $ unsafePevalBVSelectTerm n1Repr ixpn2Repr wRepr b1
      | otherwise =
          case (mkNatRepr (w + ix - n2), mkNatRepr (n2 - ix)) of
            (SomeNatRepr wixpn2Repr, SomeNatRepr n2pixRepr) ->
              let b1Part =
                    unsafePevalBVSelectTerm n1Repr (natRepr @0) wixpn2Repr b1
                  b2Part = unsafePevalBVSelectTerm n2Repr ixRepr n2pixRepr b2
               in Just $
                    unsafePevalBVConcatTerm
                      wixpn2Repr
                      n2pixRepr
                      wRepr
                      b1Part
                      b2Part
      where
        ixRepr = natRepr @ix
        wRepr = natRepr @w
        n1Repr = natRepr @n1
        n2Repr = natRepr @n2
        ix = natVal @ix pix
        w = natVal @w pw
        n2 = natVal @n2 (Proxy @n2)
  doPevalDefaultBVSelectTerm
    _
    _
    (BVSelectTerm (_ :: proxy ix1) _ (b :: Term (bv n1))) =
      Just $
        unsafePevalBVSelectTerm
          (natRepr @n1)
          (addNat (natRepr @ix) (natRepr @ix1))
          (natRepr @w)
          b
  doPevalDefaultBVSelectTerm
    pix
    pw
    (BVExtendTerm signed _ (b :: Term (bv n1)))
      | ix + w <= n1 = Just $ unsafePevalBVSelectTerm n1Repr ixRepr wRepr b
      | ix < n1 =
          case mkNatRepr (n1 - ix) of
            SomeNatRepr n1pixRepr ->
              let bPart = unsafePevalBVSelectTerm n1Repr ixRepr n1pixRepr b
               in Just $ unsafePevalBVExtendTerm n1pixRepr wRepr signed bPart
      | otherwise = Nothing
      where
        ixRepr = natRepr @ix
        wRepr = natRepr @w
        n1Repr = natRepr @n1
        ix = natVal @ix pix
        w = natVal @w pw
        n1 = natVal @n1 (Proxy @n1)
  doPevalDefaultBVSelectTerm _ _ _ = Nothing
  
  pevalDefaultBVExtendTerm ::
    forall proxy l r bv.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      1 <= l,
      1 <= r,
      l <= r,
      Typeable bv,
      forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n)
    ) =>
    Bool ->
    proxy r ->
    Term (bv l) ->
    Term (bv r)
  pevalDefaultBVExtendTerm signed p =
    unaryUnfoldOnce (doPevalDefaultBVExtendTerm signed p) (bvExtendTerm signed p)
  
  -- | Unsafe version of `pevalBVExtendTerm`. Use `NatRepr` for the bit-width
  -- representations.
  unsafePevalBVExtendTerm ::
    forall bv l r.
    (PEvalBVTerm bv) =>
    NatRepr l ->
    NatRepr r ->
    Bool ->
    Term (bv l) ->
    Term (bv r)
  unsafePevalBVExtendTerm lRepr rRepr signed v =
    case (unsafeLeqProof @1 @l, unsafeLeqProof @1 @r, unsafeLeqProof @l @r) of
      (LeqProof, LeqProof, LeqProof) ->
        withKnownNat lRepr $
          withKnownNat rRepr $
            pevalBVExtendTerm signed (Proxy @r) v
  
  doPevalDefaultBVExtendTerm ::
    forall proxy l r bv.
    ( PEvalBVTerm bv,
      KnownNat l,
      KnownNat r,
      1 <= l,
      1 <= r,
      l <= r,
      Typeable bv,
      forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n)
    ) =>
    Bool ->
    proxy r ->
    Term (bv l) ->
    Maybe (Term (bv r))
  doPevalDefaultBVExtendTerm signed p (ConTerm b) =
    Just $ conTerm $ if signed then sizedBVSext p b else sizedBVZext p b
  doPevalDefaultBVExtendTerm _ _ b
    | isJust $ sameNat (Proxy @l) (Proxy @r) =
        Just b >>= castTerm
  doPevalDefaultBVExtendTerm False pr b =
    case (mkPositiveNatRepr $ r - l) of
      SomePositiveNatRepr (rplRepr :: NatRepr lpr) ->
        Just $
          unsafePevalBVConcatTerm
            rplRepr
            lRepr
            rRepr
            (conTerm $ sizedBVFromIntegral 0)
            b
    where
      lRepr = natRepr @l
      rRepr = natRepr @r
      l = natVal @l (Proxy @l)
      r = natVal @r pr
  doPevalDefaultBVExtendTerm True p (BVExtendTerm True _ (b :: Term (bv l1))) =
    case unsafeLeqProof @l1 @r of
      LeqProof -> Just $ pevalBVExtendTerm True p b
  doPevalDefaultBVExtendTerm _ _ _ = Nothing
  
  pevalDefaultBVConcatTerm ::
    forall bv a b.
    ( KnownNat a,
      KnownNat b,
      1 <= a,
      1 <= b,
      PEvalBVTerm bv,
      forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n)
    ) =>
    Term (bv a) ->
    Term (bv b) ->
    Term (bv (a + b))
  pevalDefaultBVConcatTerm =
    withKnownNat (addNat (natRepr @a) (natRepr @b)) $
      case (unsafeLeqProof @1 @(a + b)) of
        LeqProof ->
          binaryUnfoldOnce doPevalDefaultBVConcatTerm bvConcatTerm
  
  unsafeBVConcatTerm ::
    forall bv n1 n2 r.
    (PEvalBVTerm bv, forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n)) =>
    NatRepr n1 ->
    NatRepr n2 ->
    NatRepr r ->
    Term (bv n1) ->
    Term (bv n2) ->
    Term (bv r)
  unsafeBVConcatTerm n1Repr n2Repr rRepr lhs rhs =
    case ( unsafeAxiom :: (n1 + n2) :~: r,
           unsafeLeqProof @1 @r,
           unsafeLeqProof @1 @n1,
           unsafeLeqProof @1 @n2
         ) of
      (Refl, LeqProof, LeqProof, LeqProof) ->
        withKnownNat n1Repr $
          withKnownNat n2Repr $
            withKnownNat rRepr $
              bvConcatTerm lhs rhs
  
  -- | Unsafe version of `pevalBVConcatTerm`. Use `NatRepr` for the bit-width
  -- representations.
  unsafePevalBVConcatTerm ::
    forall bv n1 n2 r.
    (PEvalBVTerm bv) =>
    NatRepr n1 ->
    NatRepr n2 ->
    NatRepr r ->
    Term (bv n1) ->
    Term (bv n2) ->
    Term (bv r)
  unsafePevalBVConcatTerm n1Repr n2Repr rRepr lhs rhs =
    case ( unsafeAxiom :: (n1 + n2) :~: r,
           unsafeLeqProof @1 @r,
           unsafeLeqProof @1 @n1,
           unsafeLeqProof @1 @n2
         ) of
      (Refl, LeqProof, LeqProof, LeqProof) ->
        withKnownNat n1Repr $
          withKnownNat n2Repr $
            withKnownNat rRepr $
              pevalBVConcatTerm lhs rhs
  
  doPevalDefaultBVConcatTerm ::
    forall bv l r.
    ( KnownNat l,
      KnownNat r,
      KnownNat (l + r),
      1 <= l,
      1 <= r,
      1 <= (l + r),
      PEvalBVTerm bv,
      forall n. (KnownNat n, 1 <= n) => SupportedPrim (bv n)
    ) =>
    Term (bv l) ->
    Term (bv r) ->
    Maybe (Term (bv (l + r)))
  -- 1. [c1 c2] -> c1c2
  doPevalDefaultBVConcatTerm (ConTerm v) (ConTerm v') =
    withKnownNat (addNat (natRepr @l) (natRepr @r)) $
      Just $
        conTerm $
          sizedBVConcat v v'
  -- 2. [c1 (c2 ?)] -> (c1c2 ?)
  doPevalDefaultBVConcatTerm
    (ConTerm vl)
    (BVConcatTerm (ConTerm (vrl :: bv rl)) (rr :: Term (bv rr))) =
      case unsafeLeqProof @1 @(l + rl) of
        LeqProof ->
          Just $
            withKnownNat lRlRepr $
              unsafeBVConcatTerm
                lRlRepr
                (natRepr @rr)
                (addNat (natRepr @l) (natRepr @r))
                (conTerm $ sizedBVConcat vl vrl)
                rr
      where
        lRlRepr = addNat (natRepr @l) (natRepr @rl)
  -- 3. [c1 (s c2)] -> (c1 (s c2))
  doPevalDefaultBVConcatTerm (ConTerm {}) (BVConcatTerm _ ConTerm {}) = Nothing
  -- 4. [(c s) ?) -> (c [s ?])
  doPevalDefaultBVConcatTerm
    (BVConcatTerm (ll@ConTerm {} :: Term (bv ll)) (lr :: Term (bv lr)))
    r =
      Just $ unsafeBVConcatTerm llRepr lrRRepr lRRepr ll rhs
      where
        llRepr = natRepr @ll
        lrRepr = natRepr @lr
        lRepr = natRepr @l
        rRepr = natRepr @r
        lrRRepr = addNat lrRepr rRepr
        lRRepr = addNat lRepr rRepr
        rhs :: Term (bv (lr + r))
        rhs = unsafePevalBVConcatTerm lrRepr rRepr lrRRepr lr r
  -- 5. [? (c1 (s2 c2))] -> (([? c1] s2) c2)
  doPevalDefaultBVConcatTerm
    l
    ( BVConcatTerm
        (rl@ConTerm {} :: Term (bv rl))
        (BVConcatTerm (rrl :: Term (bv rrl)) (rrr@ConTerm {} :: Term (bv rrr)))
      ) =
      Just $ unsafeBVConcatTerm lRlRrlRepr rrrRepr lRRepr lRlRrl rrr
      where
        lRepr = natRepr @l
        rlRepr = natRepr @rl
        rrlRepr = natRepr @rrl
        rrrRepr = natRepr @rrr
        lRlRepr = addNat lRepr rlRepr
        rRepr = natRepr @r
        lRRepr = addNat lRepr rRepr
        lRl = unsafePevalBVConcatTerm lRepr rlRepr lRlRepr l rl
        lRlRrlRepr = addNat lRlRepr rrlRepr
        lRlRrl = unsafeBVConcatTerm lRlRepr rrlRepr lRlRrlRepr lRl rrl
  -- 6. [(s1 c1) c2] -> (s1 c1c2)
  doPevalDefaultBVConcatTerm
    (BVConcatTerm (ll :: Term (bv ll)) ((ConTerm vlr) :: Term (bv lr)))
    (ConTerm vr) =
      Just $ unsafeBVConcatTerm llRepr lrRRepr lRRepr ll rhs
      where
        llRepr = natRepr @ll
        lrRepr = natRepr @lr
        lRepr = natRepr @l
        rRepr = natRepr @r
        lrRRepr = addNat lrRepr rRepr
        lRRepr = addNat lRepr rRepr
        rhs :: Term (bv (lr + r))
        rhs = case unsafeLeqProof @1 @(lr + r) of
          LeqProof ->
            withKnownNat lrRRepr $ conTerm $ sizedBVConcat vlr vr
  -- 7. [(s1 c1) (c2 s2)] -> (s1 (c1c2 s2))
  doPevalDefaultBVConcatTerm
    (BVConcatTerm (ll :: Term (bv ll)) ((ConTerm vlr) :: Term (bv lr)))
    (BVConcatTerm ((ConTerm vrl) :: Term (bv rl)) (rr :: Term (bv rr))) =
      Just $ unsafeBVConcatTerm llRepr lrRlRrRepr lRRepr ll lrRlRR
      where
        lRepr = natRepr @l
        rRepr = natRepr @r
        llRepr = natRepr @ll
        lrRepr = natRepr @lr
        rlRepr = natRepr @rl
        rrRepr = natRepr @rr
        lRRepr = addNat lRepr rRepr
        lrRlRepr :: NatRepr (lr + rl)
        lrRlRepr = addNat lrRepr rlRepr
        lrRlRrRepr :: NatRepr ((lr + rl) + rr)
        lrRlRrRepr = addNat lrRlRepr rrRepr
        lrRl :: Term (bv (lr + rl))
        lrRl = case unsafeLeqProof @1 @(lr + rl) of
          LeqProof -> withKnownNat lrRlRepr $ conTerm $ sizedBVConcat vlr vrl
        lrRlRR :: Term (bv ((lr + rl) + rr))
        lrRlRR = unsafeBVConcatTerm lrRlRepr rrRepr lrRlRrRepr lrRl rr
  -- 8. [?notc (s2 c)] -> ((s1 s2) c)
  doPevalDefaultBVConcatTerm
    l
    (BVConcatTerm (rl :: Term (bv rl)) (rr@ConTerm {} :: Term (bv rr))) =
      Just $
        unsafeBVConcatTerm
          lRlRepr
          (natRepr @rr)
          (addNat (natRepr @l) (natRepr @r))
          lhs
          rr
      where
        lRepr = natRepr @l
        rlRepr = natRepr @rl
        lRlRepr = addNat lRepr rlRepr
        lhs :: Term (bv (l + rl))
        lhs = unsafeBVConcatTerm lRepr rlRepr lRlRepr l rl
  doPevalDefaultBVConcatTerm
    (BVSelectTerm ix0 (_ :: p w0) (bv0 :: Term (bv n)))
    (BVSelectTerm (ix1 :: p ix1) (w1 :: p w1) (DynTerm (bv1 :: Term (bv n))))
      | ix1v + w1v == ix0v && bv0 == bv1 =
          Just $ unsafePevalBVSelectTerm nRepr ix1Repr (addNat w0Repr w1Repr) bv0
      where
        nRepr = natRepr @n
        w1v = natVal w1
        ix0v = natVal ix0
        ix1v = natVal ix1
        ix1Repr = natRepr @ix1
        w0Repr = natRepr @w0
        w1Repr = natRepr @w1
  doPevalDefaultBVConcatTerm _ _ = Nothing
  
  instance PEvalBVTerm WordN where
    pevalBVSelectTerm = pevalDefaultBVSelectTerm @IntN
    pevalBVConcatTerm = pevalDefaultBVConcatTerm
    pevalBVExtendTerm = pevalDefaultBVExtendTerm
    sbvBVConcatTerm pl pr l r =
      bvIsNonZeroFromGEq1 pl $
        bvIsNonZeroFromGEq1 pr $
          l SBV.# r
    sbvBVSelectTerm (pix :: p0 ix) (pw :: p1 w) (pn :: p2 n) bv =
      bvIsNonZeroFromGEq1 (Proxy @n) $
        bvIsNonZeroFromGEq1 (Proxy @w) $
          sbvDefaultBVSelectTerm pix pw pn bv
    sbvBVExtendTerm (_ :: p0 l) (_ :: p1 r) signed bv =
      withKnownProof
        (unsafeKnownProof @(r - l) (natVal (Proxy @r) - natVal (Proxy @l)))
        $ case (unsafeLeqProof @(l + 1) @r, unsafeLeqProof @1 @(r - l)) of
          (LeqProof, LeqProof) ->
            bvIsNonZeroFromGEq1 (Proxy @r) $
              bvIsNonZeroFromGEq1 (Proxy @l) $
                bvIsNonZeroFromGEq1 (Proxy @(r - l)) $
                  if signed then SBV.signExtend bv else SBV.zeroExtend bv
  
  instance PEvalBVTerm IntN where
    pevalBVSelectTerm = pevalDefaultBVSelectTerm @WordN
    pevalBVConcatTerm = pevalDefaultBVConcatTerm
    pevalBVExtendTerm = pevalDefaultBVExtendTerm
    sbvBVConcatTerm (pl :: p l) (pr :: q r) l r =
      bvIsNonZeroFromGEq1 pl $
        bvIsNonZeroFromGEq1 pr $
          withKnownNat (addNat (natRepr @l) (natRepr @r)) $
            case unsafeLeqProof @1 @(l + r) of
              LeqProof ->
                bvIsNonZeroFromGEq1 (Proxy @(l + r)) $
                  sbvBitCast @(WordN (l + r)) @(IntN (l + r)) $
                    (sbvBitCast @(IntN l) @(WordN l) l)
                      SBV.# (sbvBitCast @(IntN r) @(WordN r) r)
    sbvBVSelectTerm (pix :: p0 ix) (pw :: p1 w) (pn :: p2 n) bv =
      bvIsNonZeroFromGEq1 (Proxy @n) $
        bvIsNonZeroFromGEq1 (Proxy @w) $
          sbvDefaultBVSelectTerm pix pw pn bv
    sbvBVExtendTerm (_ :: p0 l) (_ :: p1 r) signed bv =
      withKnownProof
        (unsafeKnownProof @(r - l) (natVal (Proxy @r) - natVal (Proxy @l)))
        $ case (unsafeLeqProof @(l + 1) @r, unsafeLeqProof @1 @(r - l)) of
          (LeqProof, LeqProof) ->
            bvIsNonZeroFromGEq1 (Proxy @r) $
              bvIsNonZeroFromGEq1 (Proxy @l) $
                bvIsNonZeroFromGEq1 (Proxy @(r - l)) $
                  if signed
                    then SBV.signExtend bv
                    else
                      SBV.sFromIntegral
                        ( SBV.zeroExtend
                            (SBV.sFromIntegral bv :: SBV.SBV (SBV.WordN l)) ::
                            SBV.SBV (SBV.WordN r)
                        )
  
  sbvDefaultBVSelectTerm ::
    ( KnownNat ix,
      KnownNat w,
      KnownNat n,
      1 <= n,
      1 <= w,
      (ix + w) <= n,
      SBV.SymVal (bv n)
    ) =>
    p1 ix ->
    p2 w ->
    p3 n ->
    SBV.SBV (bv n) ->
    SBV.SBV (bv w)
  sbvDefaultBVSelectTerm (_ :: p0 ix) (_ :: p1 w) (_ :: p2 n) bv =
    withKnownProof
      ( unsafeKnownProof @(w + ix - 1)
          (natVal (Proxy @w) + natVal (Proxy @ix) - 1)
      )
      $ case ( unsafeAxiom @(w + ix - 1 - ix + 1) @w,
               unsafeLeqProof @(((w + ix) - 1) + 1) @n,
               unsafeLeqProof @ix @(w + ix - 1)
             ) of
        (Refl, LeqProof, LeqProof) ->
          bvIsNonZeroFromGEq1 (Proxy @n) $
            bvIsNonZeroFromGEq1 (Proxy @w) $
              SBV.bvExtract (Proxy @(w + ix - 1)) (Proxy @ix) bv
  
  doPevalBitCastSameType ::
    forall x b. (SupportedPrim b) => Term x -> Maybe (Term b)
  doPevalBitCastSameType (BitCastTerm (DynTerm (b :: Term b))) = Just b
  doPevalBitCastSameType (BitCastTerm x) = doPevalBitCastSameType x
  doPevalBitCastSameType _ = Nothing
  
  -- | Partially evaluate a bitcast term. If no reduction is performed, return
  -- Nothing.
  doPevalBitCast :: (PEvalBitCastTerm a b, SupportedPrim b) => Term a -> Maybe (Term b)
  doPevalBitCast (ConTerm v) = Just $ conTerm $ bitCast v
  doPevalBitCast t = doPevalBitCastSameType t
  
  instance (KnownNat n, 1 <= n) => PEvalBitCastTerm (WordN n) (IntN n) where
    pevalBitCastTerm = unaryUnfoldOnce doPevalBitCastBV bitCastTerm
      where
        doPevalBitCastBV :: Term (WordN n) -> Maybe (Term (IntN n))
        doPevalBitCastBV
          (BVConcatTerm (l :: Term (WordN l)) (r :: Term (WordN r))) =
            Just $
              pevalBVConcatTerm
                (pevalBitCastTerm @(WordN l) @(IntN l) l)
                (pevalBitCastTerm @(WordN r) @(IntN r) r)
        doPevalBitCastBV (BVExtendTerm signed pr (b :: Term (WordN l))) =
          Just $
            pevalBVExtendTerm signed pr $
              pevalBitCastTerm @(WordN l) @(IntN l) b
        doPevalBitCastBV v = doPevalBitCast v
    sbvBitCast = bvIsNonZeroFromGEq1 (Proxy @n) SBV.sFromIntegral
  
  instance (KnownNat n, 1 <= n) => PEvalBitCastTerm (IntN n) (WordN n) where
    pevalBitCastTerm = unaryUnfoldOnce doPevalBitCastBV bitCastTerm
      where
        doPevalBitCastBV :: Term (IntN n) -> Maybe (Term (WordN n))
        doPevalBitCastBV
          (BVConcatTerm (l :: Term (IntN l)) (r :: Term (IntN r))) =
            Just $
              pevalBVConcatTerm
                (pevalBitCastTerm @(IntN l) @(WordN l) l)
                (pevalBitCastTerm @(IntN r) @(WordN r) r)
        doPevalBitCastBV (BVExtendTerm signed pr (b :: Term (IntN l))) =
          Just $
            pevalBVExtendTerm signed pr $
              pevalBitCastTerm @(IntN l) @(WordN l) b
        doPevalBitCastBV v = doPevalBitCast v
    sbvBitCast = bvIsNonZeroFromGEq1 (Proxy @n) SBV.sFromIntegral
  
  -- Num
  
  -- | Default partial evaluation of addition of numerical terms.
  pevalDefaultAddNumTerm :: (PEvalNumTerm a, Eq a) => Term a -> Term a -> Term a
  pevalDefaultAddNumTerm l@SupportedTerm r =
    binaryUnfoldOnce
      doPevalDefaultAddNumTerm
      (\a b -> normalizeAddNum $ addNumTerm a b)
      l
      r
  
  doPevalDefaultAddNumTerm ::
    (PEvalNumTerm a, Eq a) => Term a -> Term a -> Maybe (Term a)
  doPevalDefaultAddNumTerm (ConTerm a) (ConTerm b) = Just $ conTerm $ a + b
  doPevalDefaultAddNumTerm l@(ConTerm a) b = case (a, b) of
    (0, k) -> Just k
    (l1, AddNumTerm (ConTerm j) k) ->
      Just $ pevalAddNumTerm (conTerm $ l1 + j) k
    _ -> doPevalDefaultAddNumTermNoCon l b
  doPevalDefaultAddNumTerm a r@(ConTerm {}) = doPevalDefaultAddNumTerm r a
  doPevalDefaultAddNumTerm l r = doPevalDefaultAddNumTermNoCon l r
  
  doPevalDefaultAddNumTermNoCon ::
    (PEvalNumTerm a) => Term a -> Term a -> Maybe (Term a)
  doPevalDefaultAddNumTermNoCon (AddNumTerm i@ConTerm {} j) k =
    Just $ pevalAddNumTerm i $ pevalAddNumTerm j k
  doPevalDefaultAddNumTermNoCon i (AddNumTerm j@ConTerm {} k) =
    Just $ pevalAddNumTerm j $ pevalAddNumTerm i k
  doPevalDefaultAddNumTermNoCon (NegNumTerm i) (NegNumTerm j) =
    Just $ pevalNegNumTerm $ pevalAddNumTerm i j
  doPevalDefaultAddNumTermNoCon
    (MulNumTerm (ConTerm i) j)
    (MulNumTerm (ConTerm k) l)
      | j == l = Just $ pevalMulNumTerm (conTerm $ i + k) j
  doPevalDefaultAddNumTermNoCon
    (MulNumTerm i@ConTerm {} j)
    (MulNumTerm k@(ConTerm {}) l)
      | i == k = Just $ pevalMulNumTerm i (pevalAddNumTerm j l)
  doPevalDefaultAddNumTermNoCon _ _ = Nothing
  
  normalizeAddNum :: (PEvalNumTerm a) => Term a -> Term a
  normalizeAddNum (AddNumTerm l r@(ConTerm {})) = addNumTerm r l
  normalizeAddNum v = v
  
  -- | Default partial evaluation of negation of numerical terms.
  pevalDefaultNegNumTerm :: (PEvalNumTerm a, Eq a) => Term a -> Term a
  pevalDefaultNegNumTerm l@SupportedTerm =
    unaryUnfoldOnce doPevalDefaultNegNumTerm negNumTerm l
  
  doPevalDefaultNegNumTerm :: (PEvalNumTerm a) => Term a -> Maybe (Term a)
  doPevalDefaultNegNumTerm (ConTerm a) = Just $ conTerm $ -a
  doPevalDefaultNegNumTerm (NegNumTerm v) = Just v
  doPevalDefaultNegNumTerm (AddNumTerm (ConTerm l) r) =
    Just $ pevalSubNumTerm (conTerm $ -l) r
  doPevalDefaultNegNumTerm (AddNumTerm (NegNumTerm l) r) =
    Just $ pevalAddNumTerm l (pevalNegNumTerm r)
  doPevalDefaultNegNumTerm (AddNumTerm l (NegNumTerm r)) =
    Just $ pevalAddNumTerm (pevalNegNumTerm l) r
  doPevalDefaultNegNumTerm (MulNumTerm (ConTerm l) r) =
    Just $ pevalMulNumTerm (conTerm $ -l) r
  doPevalDefaultNegNumTerm (MulNumTerm (NegNumTerm {}) _) =
    error "Should not happen"
  doPevalDefaultNegNumTerm (MulNumTerm _ (NegNumTerm {})) =
    error "Should not happen"
  doPevalDefaultNegNumTerm (AddNumTerm _ ConTerm {}) = error "Should not happen"
  doPevalDefaultNegNumTerm _ = Nothing
  
  -- Mul
  
  -- | Default partial evaluation of multiplication of numerical terms.
  pevalDefaultMulNumTerm :: (PEvalNumTerm a, Eq a) => Term a -> Term a -> Term a
  pevalDefaultMulNumTerm l@SupportedTerm r =
    binaryUnfoldOnce
      doPevalDefaultMulNumTerm
      (\a b -> normalizeMulNum $ mulNumTerm a b)
      l
      r
  
  normalizeMulNum :: (PEvalNumTerm a) => Term a -> Term a
  normalizeMulNum (MulNumTerm l r@(ConTerm {})) = mulNumTerm r l
  normalizeMulNum v = v
  
  doPevalDefaultMulNumTerm ::
    (PEvalNumTerm a, Eq a) => Term a -> Term a -> Maybe (Term a)
  doPevalDefaultMulNumTerm (ConTerm a) (ConTerm b) =
    Just $ conTerm $ a * b
  doPevalDefaultMulNumTerm l@(ConTerm a) b = case (a, b) of
    (0, _) -> Just $ conTerm 0
    (1, k) -> Just k
    (-1, k) -> Just $ pevalNegNumTerm k
    (l1, MulNumTerm (ConTerm j) k) ->
      Just $ pevalMulNumTerm (conTerm $ l1 * j) k
    (l1, AddNumTerm (ConTerm j) k) ->
      Just $ pevalAddNumTerm (conTerm $ l1 * j) (pevalMulNumTerm (conTerm l1) k)
    (l1, NegNumTerm j) -> Just (pevalMulNumTerm (conTerm $ -l1) j)
    (_, MulNumTerm _ ConTerm {}) -> error "Should not happen"
    (_, AddNumTerm _ ConTerm {}) -> error "Should not happen"
    _ -> doPevalDefaultMulNumTermNoCon l b
  doPevalDefaultMulNumTerm a r@(ConTerm {}) = doPevalDefaultMulNumTerm r a
  doPevalDefaultMulNumTerm l r = doPevalDefaultMulNumTermNoCon l r
  
  doPevalDefaultMulNumTermNoCon ::
    (PEvalNumTerm a) => Term a -> Term a -> Maybe (Term a)
  doPevalDefaultMulNumTermNoCon (MulNumTerm i@ConTerm {} j) k =
    Just $ pevalMulNumTerm i $ pevalMulNumTerm j k
  doPevalDefaultMulNumTermNoCon i (MulNumTerm j@ConTerm {} k) =
    Just $ pevalMulNumTerm j $ pevalMulNumTerm i k
  doPevalDefaultMulNumTermNoCon (NegNumTerm i) j =
    Just $ pevalNegNumTerm $ pevalMulNumTerm i j
  doPevalDefaultMulNumTermNoCon i (NegNumTerm j) =
    Just $ pevalNegNumTerm $ pevalMulNumTerm i j
  doPevalDefaultMulNumTermNoCon i j@ConTerm {} = Just $ pevalMulNumTerm j i
  doPevalDefaultMulNumTermNoCon (MulNumTerm _ ConTerm {}) _ =
    error "Should not happen"
  doPevalDefaultMulNumTermNoCon _ (MulNumTerm _ ConTerm {}) =
    error "Should not happen"
  doPevalDefaultMulNumTermNoCon _ _ = Nothing
  
  -- Abs
  
  -- | Default partial evaluation of absolute value of finite-bit numerical terms.
  pevalBitsAbsNumTerm :: (PEvalNumTerm a, Bits a) => Term a -> Term a
  pevalBitsAbsNumTerm l@SupportedTerm =
    unaryUnfoldOnce doPevalBitsAbsNumTerm absNumTerm l
  
  doPevalGeneralAbsNumTerm :: (PEvalNumTerm a) => Term a -> Maybe (Term a)
  doPevalGeneralAbsNumTerm (ConTerm a) = Just $ conTerm $ abs a
  doPevalGeneralAbsNumTerm (NegNumTerm v) = Just $ pevalAbsNumTerm v
  doPevalGeneralAbsNumTerm t@(AbsNumTerm {}) = Just t
  doPevalGeneralAbsNumTerm _ = Nothing
  
  doPevalBitsAbsNumTerm ::
    forall a. (PEvalNumTerm a, Bits a) => Term a -> Maybe (Term a)
  doPevalBitsAbsNumTerm t =
    msum
      [ if isSigned (undefined :: a) then Nothing else Just t,
        doPevalGeneralAbsNumTerm t
      ]
  
  -- | Partial evaluation of absolute value of numerical terms that does not
  -- overflow.
  doPevalNoOverflowAbsNumTerm :: (PEvalNumTerm a) => Term a -> Maybe (Term a)
  doPevalNoOverflowAbsNumTerm t =
    msum
      [ doPevalGeneralAbsNumTerm t,
        case t of
          MulNumTerm l r ->
            Just $ pevalMulNumTerm (pevalAbsNumTerm l) $ pevalAbsNumTerm r
          _ -> Nothing
      ]
  
  -- Signum
  
  -- | Default partial evaluation of signum of numerical terms.
  pevalGeneralSignumNumTerm :: (PEvalNumTerm a) => Term a -> Term a
  pevalGeneralSignumNumTerm l@SupportedTerm =
    unaryUnfoldOnce doPevalGeneralSignumNumTerm signumNumTerm l
  
  doPevalGeneralSignumNumTerm :: (PEvalNumTerm a) => Term a -> Maybe (Term a)
  doPevalGeneralSignumNumTerm (ConTerm a) = Just $ conTerm $ signum a
  doPevalGeneralSignumNumTerm _ = Nothing
  
  -- | Partial evaluation of signum of numerical terms that does not overflow.
  doPevalNoOverflowSignumNumTerm :: (PEvalNumTerm a) => Term a -> Maybe (Term a)
  doPevalNoOverflowSignumNumTerm t =
    msum
      [ doPevalGeneralSignumNumTerm t,
        case t of
          NegNumTerm v -> Just $ pevalNegNumTerm $ pevalSignumNumTerm v
          MulNumTerm l r ->
            Just $
              pevalMulNumTerm (pevalSignumNumTerm l) $
                pevalSignumNumTerm r
          _ -> Nothing
      ]
  
  instance (KnownNat n, 1 <= n) => PEvalNumTerm (WordN n) where
    pevalAddNumTerm = pevalDefaultAddNumTerm
    pevalNegNumTerm = pevalDefaultNegNumTerm
    pevalMulNumTerm = pevalDefaultMulNumTerm
    pevalAbsNumTerm = pevalBitsAbsNumTerm
    pevalSignumNumTerm = pevalGeneralSignumNumTerm
    withSbvNumTermConstraint r = withPrim @(WordN n) r
  
  instance (KnownNat n, 1 <= n) => PEvalNumTerm (IntN n) where
    pevalAddNumTerm = pevalDefaultAddNumTerm
    pevalNegNumTerm = pevalDefaultNegNumTerm
    pevalMulNumTerm = pevalDefaultMulNumTerm
    pevalAbsNumTerm = pevalBitsAbsNumTerm
    pevalSignumNumTerm = pevalGeneralSignumNumTerm
    withSbvNumTermConstraint r = withPrim @(IntN n) r
  
  -- Partial evaluation
  
  -- | A partial function from a to b.
  type PartialFun a b = a -> Maybe b
  
  -- | A partial rule for unary operations.
  type PartialRuleUnary a b = PartialFun (Term a) (Term b)
  
  -- | A total rule for unary operations.
  type TotalRuleUnary a b = Term a -> Term b
  
  -- | A partial rule for binary operations.
  type PartialRuleBinary a b c = Term a -> PartialFun (Term b) (Term c)
  
  -- | A total rule for binary operations.
  type TotalRuleBinary a b c = Term a -> Term b -> Term c
  
  -- | Totalize a partial function with a fallback function.
  totalize :: PartialFun a b -> (a -> b) -> a -> b
  totalize partial fallback a =
    case partial a of
      Just b -> b
      Nothing -> fallback a
  
  -- | Totalize a binary partial function with a fallback function.
  totalize2 :: (a -> PartialFun b c) -> (a -> b -> c) -> a -> b -> c
  totalize2 partial fallback a b =
    case partial a b of
      Just c -> c
      Nothing -> fallback a b
  
  -- | A strategy for partially evaluating unary operations.
  class UnaryPartialStrategy tag a b | tag a -> b where
    extractor :: tag -> Term a -> Maybe a
    constantHandler :: tag -> a -> Maybe (Term b)
    nonConstantHandler :: tag -> Term a -> Maybe (Term b)
  
  -- | Partially evaluate a unary operation.
  unaryPartial :: forall tag a b. (UnaryPartialStrategy tag a b) => tag -> PartialRuleUnary a b
  unaryPartial tag a = case extractor tag a of
    Nothing -> nonConstantHandler tag a
    Just a' -> constantHandler tag a'
  
  -- | A strategy for partially evaluating commutative binary operations.
  class BinaryCommPartialStrategy tag a c | tag a -> c where
    singleConstantHandler :: tag -> a -> Term a -> Maybe (Term c)
  
  -- | A strategy for partially evaluating operations.
  class BinaryPartialStrategy tag a b c | tag a b -> c where
    extractora :: tag -> Term a -> Maybe a
    extractorb :: tag -> Term b -> Maybe b
    allConstantHandler :: tag -> a -> b -> Maybe (Term c)
    leftConstantHandler :: tag -> a -> Term b -> Maybe (Term c)
    default leftConstantHandler :: (a ~ b, BinaryCommPartialStrategy tag a c) => tag -> a -> Term b -> Maybe (Term c)
    leftConstantHandler = singleConstantHandler @tag @a
    rightConstantHandler :: tag -> Term a -> b -> Maybe (Term c)
    default rightConstantHandler :: (a ~ b, BinaryCommPartialStrategy tag a c) => tag -> Term a -> b -> Maybe (Term c)
    rightConstantHandler tag = flip $ singleConstantHandler @tag @a tag
    nonBinaryConstantHandler :: tag -> Term a -> Term b -> Maybe (Term c)
  
  -- | Partially evaluate a binary operation.
  binaryPartial :: forall tag a b c. (BinaryPartialStrategy tag a b c) => tag -> PartialRuleBinary a b c
  binaryPartial tag a b = case (extractora @tag @a @b @c tag a, extractorb @tag @a @b @c tag b) of
    (Nothing, Nothing) -> nonBinaryConstantHandler @tag @a @b @c tag a b
    (Just a', Nothing) ->
      leftConstantHandler @tag @a @b @c tag a' b
        `catchError` \_ -> nonBinaryConstantHandler @tag @a @b @c tag a b
    (Nothing, Just b') ->
      rightConstantHandler @tag @a @b @c tag a b'
        `catchError` \_ -> nonBinaryConstantHandler @tag @a @b @c tag a b
    (Just a', Just b') ->
      allConstantHandler @tag @a @b @c tag a' b'
  
  unaryPartialUnfoldOnce ::
    forall a b.
    (SupportedPrim b) =>
    PartialRuleUnary a b ->
    TotalRuleUnary a b ->
    PartialRuleUnary a b
  unaryPartialUnfoldOnce partial fallback = ret
    where
      oneLevel :: TotalRuleUnary a b -> PartialRuleUnary a b
      oneLevel fallback' x = case (x, partial x) of
        (ITETerm cond vt vf, pr) ->
          let pt = partial vt
              pf = partial vf
           in case (pt, pf) of
                (Nothing, Nothing) -> pr
                (mt, mf) ->
                  pevalITETerm cond
                    <$> catchError mt (\_ -> Just $ totalize (oneLevel fallback') fallback' vt)
                    <*> catchError mf (\_ -> Just $ totalize (oneLevel fallback') fallback vf)
        (_, pr) -> pr
      ret :: PartialRuleUnary a b
      ret = oneLevel (totalize @(Term a) @(Term b) partial fallback)
  
  -- | Unfold a unary operation once.
  unaryUnfoldOnce ::
    forall a b.
    (SupportedPrim b) =>
    PartialRuleUnary a b ->
    TotalRuleUnary a b ->
    TotalRuleUnary a b
  unaryUnfoldOnce partial fallback = totalize (unaryPartialUnfoldOnce partial fallback) fallback
  
  binaryPartialUnfoldOnce ::
    forall a b c.
    (SupportedPrim c) =>
    PartialRuleBinary a b c ->
    TotalRuleBinary a b c ->
    PartialRuleBinary a b c
  binaryPartialUnfoldOnce partial fallback = ret
    where
      oneLevel :: PartialRuleBinary x y c -> TotalRuleBinary x y c -> PartialRuleBinary x y c
      oneLevel partial' fallback' x y =
        catchError
          (partial' x y)
          ( \_ ->
              case (x, y) of
                (ITETerm _ ITETerm {} _, ITETerm {}) -> Nothing
                (ITETerm _ _ ITETerm {}, ITETerm {}) -> Nothing
                (ITETerm {}, ITETerm _ ITETerm {} _) -> Nothing
                (ITETerm {}, ITETerm _ _ ITETerm {}) -> Nothing
                (ITETerm cond vt vf, _) ->
                  left cond vt vf y partial' fallback'
                (_, ITETerm cond vt vf) ->
                  left cond vt vf x (flip partial') (flip fallback')
                _ -> Nothing
          )
      left ::
        Term Bool ->
        Term x ->
        Term x ->
        Term y ->
        PartialRuleBinary x y c ->
        TotalRuleBinary x y c ->
        Maybe (Term c)
      left cond vt vf y partial' fallback' =
        let pt = partial' vt y
            pf = partial' vf y
         in case (pt, pf) of
              (Nothing, Nothing) -> Nothing
              (mt, mf) ->
                pevalITETerm cond
                  <$> catchError mt (\_ -> Just $ totalize2 (oneLevel partial' fallback') fallback' vt y)
                  <*> catchError mf (\_ -> Just $ totalize2 (oneLevel partial' fallback') fallback' vf y)
      ret :: PartialRuleBinary a b c
      ret = oneLevel partial (totalize2 @(Term a) @(Term b) @(Term c) partial fallback)
  
  -- | Unfold a binary operation once.
  binaryUnfoldOnce ::
    forall a b c.
    (SupportedPrim c) =>
    PartialRuleBinary a b c ->
    TotalRuleBinary a b c ->
    TotalRuleBinary a b c
  binaryUnfoldOnce partial fallback = totalize2 (binaryPartialUnfoldOnce partial fallback) fallback
  
  -- | Unfold a unary operation once.
  generalUnaryUnfolded ::
    forall a b.
    (Typeable a, SupportedPrim b) =>
    (a -> b) ->
    (Term a -> Term b) ->
    Term a ->
    Term b
  generalUnaryUnfolded compute =
    unaryUnfoldOnce
      ( \case
          ConTerm lv -> Just $ conTerm $ compute lv
          _ -> Nothing
      )
  
  -- | Unfold a binary operation once.
  generalBinaryUnfolded ::
    forall a b c.
    (Typeable a, Typeable b, SupportedPrim c) =>
    (a -> b -> c) ->
    (Term a -> Term b -> Term c) ->
    Term a ->
    Term b ->
    Term c
  generalBinaryUnfolded compute =
    binaryUnfoldOnce
      ( \l r -> case (l, r) of
          (ConTerm lv, ConTerm rv) -> Just $ conTerm $ compute lv rv
          _ -> Nothing
      )
  