packages feed

grisette-0.8.0.0: src/Grisette/Internal/SymPrim/SomeBV.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}

-- |
-- Module      :   Grisette.Internal.SymPrim.SomeBV
-- Copyright   :   (c) Sirui Lu 2024
-- License     :   BSD-3-Clause (see the LICENSE file)
--
-- Maintainer  :   siruilu@cs.washington.edu
-- Stability   :   Experimental
-- Portability :   GHC only
module Grisette.Internal.SymPrim.SomeBV
  ( SomeBV (..),
    SomeBVException (..),

    -- * Constructing and pattern matching on SomeBV
    unsafeSomeBV,
    conBV,
    conBVView,
    pattern ConBV,
    symBV,
    ssymBV,
    isymBV,
    arbitraryBV,

    -- * Synonyms
    pattern SomeIntN,
    type SomeIntN,
    pattern SomeWordN,
    type SomeWordN,
    pattern SomeSymIntN,
    type SomeSymIntN,
    pattern SomeSymWordN,
    type SomeSymWordN,

    -- * Helpers for manipulating SomeBV
    unarySomeBV,
    unarySomeBVR1,
    binSomeBV,
    binSomeBVR1,
    binSomeBVR2,
    binSomeBVSafe,
    binSomeBVSafeR1,
    binSomeBVSafeR2,
  )
where

import Control.DeepSeq (NFData (rnf))
import Control.Exception (Exception, throw)
import Control.Monad.Except (ExceptT, MonadError (throwError), runExceptT)
import Data.Bifunctor (Bifunctor (bimap))
import Data.Bits
  ( Bits
      ( bit,
        bitSize,
        bitSizeMaybe,
        clearBit,
        complement,
        complementBit,
        isSigned,
        popCount,
        rotate,
        rotateL,
        rotateR,
        setBit,
        shift,
        shiftL,
        shiftR,
        testBit,
        unsafeShiftL,
        unsafeShiftR,
        xor,
        zeroBits,
        (.&.),
        (.|.)
      ),
    FiniteBits (countLeadingZeros, countTrailingZeros, finiteBitSize),
  )
import Data.Data (Proxy (Proxy))
import Data.Hashable (Hashable (hashWithSalt))
import Data.Maybe (catMaybes, fromJust, isJust)
import qualified Data.Text as T
import Data.Type.Equality (type (:~:) (Refl))
import GHC.Exception (Exception (displayException))
import GHC.Generics (Generic)
import GHC.TypeNats
  ( KnownNat,
    Nat,
    natVal,
    sameNat,
    type (+),
    type (<=),
  )
import Generics.Deriving (Default (Default))
import Grisette.Internal.Core.Control.Monad.Union (Union)
import Grisette.Internal.Core.Data.Class.BitVector
  ( BV (bv, bvConcat, bvExt, bvSelect, bvSext, bvZext),
    SizedBV
      ( sizedBVConcat,
        sizedBVExt,
        sizedBVFromIntegral,
        sizedBVSelect,
        sizedBVSext,
        sizedBVZext
      ),
  )
import Grisette.Internal.Core.Data.Class.EvalSym
  ( EvalSym (evalSym),
  )
import Grisette.Internal.Core.Data.Class.ExtractSym
  ( ExtractSym (extractSymMaybe),
  )
import Grisette.Internal.Core.Data.Class.GenSym
  ( GenSym (fresh),
    GenSymSimple (simpleFresh),
  )
import Grisette.Internal.Core.Data.Class.ITEOp (ITEOp (symIte))
import Grisette.Internal.Core.Data.Class.Mergeable
  ( Mergeable (rootStrategy),
    MergingStrategy (SortedStrategy),
    wrapStrategy,
  )
import Grisette.Internal.Core.Data.Class.PPrint
  ( PPrint (pformat),
  )
import Grisette.Internal.Core.Data.Class.SafeDiv
  ( DivOr (divModOr, divOr, modOr, quotOr, quotRemOr, remOr),
    SafeDiv (safeDiv, safeDivMod, safeMod, safeQuot, safeQuotRem, safeRem),
  )
import Grisette.Internal.Core.Data.Class.SafeLinearArith
  ( SafeLinearArith (safeAdd, safeNeg, safeSub),
  )
import Grisette.Internal.Core.Data.Class.SafeSymRotate
  ( SafeSymRotate (safeSymRotateL, safeSymRotateR),
  )
import Grisette.Internal.Core.Data.Class.SafeSymShift
  ( SafeSymShift
      ( safeSymShiftL,
        safeSymShiftR,
        safeSymStrictShiftL,
        safeSymStrictShiftR
      ),
  )
import Grisette.Internal.Core.Data.Class.SignConversion
  ( SignConversion (toSigned, toUnsigned),
  )
import Grisette.Internal.Core.Data.Class.Solvable
  ( Solvable (con, conView, isym, ssym, sym),
  )
import Grisette.Internal.Core.Data.Class.SubstSym
  ( SubstSym (substSym),
  )
import Grisette.Internal.Core.Data.Class.SymEq
  ( SymEq (symDistinct, (./=), (.==)),
  )
import Grisette.Internal.Core.Data.Class.SymOrd
  ( SymOrd (symCompare, (.<), (.<=), (.>), (.>=)),
  )
import Grisette.Internal.Core.Data.Class.SymRotate
  ( SymRotate (symRotate, symRotateNegated),
  )
import Grisette.Internal.Core.Data.Class.SymShift
  ( SymShift (symShift, symShiftNegated),
  )
import Grisette.Internal.Core.Data.Class.ToCon (ToCon (toCon))
import Grisette.Internal.Core.Data.Class.ToSym (ToSym (toSym))
import Grisette.Internal.Core.Data.Class.TryMerge (TryMerge, tryMerge)
import Grisette.Internal.Core.Data.Symbol (Identifier, Symbol)
import Grisette.Internal.SymPrim.AllSyms (AllSyms (allSyms, allSymsS))
import Grisette.Internal.SymPrim.BV
  ( IntN,
    WordN,
  )
import Grisette.Internal.SymPrim.SymBV
  ( SymIntN,
    SymWordN,
  )
import Grisette.Internal.Utils.Parameterized
  ( KnownProof (KnownProof),
    LeqProof (LeqProof),
    NatRepr,
    SomePositiveNatRepr (SomePositiveNatRepr),
    knownAdd,
    leqAddPos,
    mkPositiveNatRepr,
    unsafeKnownProof,
    unsafeLeqProof,
  )
import Grisette.Lib.Data.Functor (mrgFmap)
import Language.Haskell.TH.Syntax (Lift (liftTyped))
import Test.QuickCheck (Arbitrary (arbitrary), Gen)
import Unsafe.Coerce (unsafeCoerce)

-- | An exception that would be thrown when operations are performed on
-- incompatible bit widths.
data SomeBVException = BitwidthMismatch | UndeterminedBitwidth T.Text
  deriving (Show, Eq, Ord, Generic)
  deriving anyclass (Hashable, NFData)
  deriving
    ( Mergeable,
      ExtractSym,
      PPrint,
      SubstSym,
      EvalSym,
      SymEq,
      SymOrd,
      ToCon SomeBVException,
      ToSym SomeBVException
    )
    via (Default (SomeBVException))

instance Exception SomeBVException where
  displayException BitwidthMismatch = "Bit width does not match"
  displayException (UndeterminedBitwidth msg) =
    "Cannot determine bit-width for literals: " <> T.unpack msg

assignBitWidthList ::
  forall bv.
  (forall n. (KnownNat n, 1 <= n) => Num (bv n)) =>
  T.Text ->
  [SomeBV bv] ->
  Either SomeBVException [SomeBV bv]
assignBitWidthList msg bvs = case allNonMaybeBitWidth of
  [] -> Left $ UndeterminedBitwidth msg
  (x : xs) ->
    if all (== x) xs
      then case allHasBitWidth of
        (SomeBV (i :: bv i) : _) -> Right $ fmap (assignSingleBitWidth i) bvs
        _ -> error "Should not happen"
      else Left BitwidthMismatch
  where
    maybeBitWidth :: SomeBV bv -> Maybe Int
    maybeBitWidth (SomeBV (_ :: bv n)) = Just $ fromIntegral $ natVal (Proxy @n)
    maybeBitWidth (SomeBVLit _) = Nothing
    allMaybeBitWidth = map maybeBitWidth bvs
    allNonMaybeBitWidth = catMaybes allMaybeBitWidth
    allHasBitWidth = filter (isJust . maybeBitWidth) bvs
    assignSingleBitWidth ::
      forall i. (KnownNat i, 1 <= i) => bv i -> SomeBV bv -> SomeBV bv
    assignSingleBitWidth _ s@(SomeBV _) = s
    assignSingleBitWidth _ (SomeBVLit i) = SomeBV (fromIntegral i :: bv i)

class AssignBitWidth a where
  assignBitWidth :: T.Text -> a -> Either SomeBVException a

instance
  (forall n. (KnownNat n, 1 <= n) => Num (bv n)) =>
  AssignBitWidth (SomeBV bv, SomeBV bv)
  where
  assignBitWidth msg (a, b) = do
    l <- assignBitWidthList msg [a, b]
    case l of
      [a', b'] -> Right (a', b')
      _ -> error "Should not happen"

instance
  (forall n. (KnownNat n, 1 <= n) => Num (bv n)) =>
  AssignBitWidth (SomeBV bv, SomeBV bv, SomeBV bv)
  where
  assignBitWidth msg (a, b, c) = do
    l <- assignBitWidthList msg [a, b, c]
    case l of
      [a', b', c'] -> Right (a', b', c')
      _ -> error "Should not happen"

instance
  (forall n. (KnownNat n, 1 <= n) => Num (bv n)) =>
  AssignBitWidth (SomeBV bv, SomeBV bv, SomeBV bv, SomeBV bv)
  where
  assignBitWidth msg (a, b, c, d) = do
    l <- assignBitWidthList msg [a, b, c, d]
    case l of
      [a', b', c', d'] -> Right (a', b', c', d')
      _ -> error "Should not happen"

-- $setup
-- >>> import Grisette.Core
-- >>> import Grisette.SymPrim

-- | Non-indexed bitvectors.
--
-- The creation of t'SomeBV' can be done with the `bv` function with a positive
-- bit width and a value:
--
-- >>> bv 4 0xf :: SomeBV IntN
-- 0xf
--
-- Operations on two t'SomeBV' values require the bitwidths to be the same. So
-- you should check for the bit width (via `finiteBitSize`) before performing
-- operations:
--
-- >>> bv 4 0x3 + bv 4 0x3 :: SomeBV IntN
-- 0x6
-- >>> bv 4 0x3 + bv 8 0x3 :: SomeBV IntN
-- *** Exception: BitwidthMismatch
--
-- One exception is that the equality testing (both concrete and symbolic via
-- 'SymEq') does not require the bitwidths to be the same. Different bitwidths
-- means the values are not equal:
--
-- >>> (bv 4 0x3 :: SomeBV IntN) == (bv 8 0x3)
-- False
--
-- __Note__: t'SomeBV' can be constructed out of integer literals without the
-- bit width provided. Further binary operations will usually require at least
-- one operand has the bit-width, and will use that as the bit-width for the
-- result.
--
-- For example:
--
-- 3 :: SomeBV IntN
-- bvlit(3)
-- >>> bv 4 0x1 + 3 :: SomeBV IntN
-- 0x4
-- >>> 3 * bv 4 0x1  :: SomeBV IntN
-- 0x3
-- >>> 3 * 3 :: SomeBV IntN
-- *** Exception: UndeterminedBitwidth "(*)"
--
-- Some operations allows the literals to be used without the bit-width, such as
-- '(+)', '(-)', 'negate', 'toUnsigned', 'toSigned', '.&.', '.|.', 'xor',
-- 'complement', 'setBit', 'clearBit', 'complementBit', 'shiftL', and
-- 'unsafeShiftL'.
--
-- >>> 3 + 3 :: SomeBV IntN
-- bvlit(6)
data SomeBV bv where
  SomeBV :: (KnownNat n, 1 <= n) => bv n -> SomeBV bv
  SomeBVLit :: Integer -> SomeBV bv

instance
  ( forall n. (KnownNat n, 1 <= n) => Hashable (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  Hashable (SomeBV bv)
  where
  hashWithSalt s (SomeBV (bv :: bv n)) =
    s `hashWithSalt` (natVal (Proxy @n)) `hashWithSalt` bv
  hashWithSalt s (SomeBVLit i) = s `hashWithSalt` i
  {-# INLINE hashWithSalt #-}

instance
  (forall n. (KnownNat n, 1 <= n) => Lift (bv n)) =>
  Lift (SomeBV bv)
  where
  liftTyped (SomeBV bv) = [||SomeBV bv||]
  liftTyped (SomeBVLit i) = [||SomeBVLit i||]
  {-# INLINE liftTyped #-}

instance
  (forall n. (KnownNat n, 1 <= n) => Show (bv n)) =>
  Show (SomeBV bv)
  where
  show (SomeBV bv) = show bv
  show (SomeBVLit i) = "bvlit(" <> show i <> ")"
  {-# INLINE show #-}

instance
  (forall n. (KnownNat n, 1 <= n) => NFData (bv n)) =>
  NFData (SomeBV bv)
  where
  rnf (SomeBV bv) = rnf bv
  rnf (SomeBVLit i) = rnf i
  {-# INLINE rnf #-}

instance
  ( forall n. (KnownNat n, 1 <= n) => Eq (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  Eq (SomeBV bv)
  where
  SomeBV (l :: bv l) == SomeBV (r :: bv r) =
    case sameNat (Proxy @l) (Proxy @r) of
      Just Refl -> l == r
      Nothing -> False
  SomeBV (l :: bv l) == SomeBVLit r = l == fromIntegral r
  SomeBVLit l == SomeBV r = fromIntegral l == r
  SomeBVLit _ == SomeBVLit _ = throw $ UndeterminedBitwidth "=="
  {-# INLINE (==) #-}
  SomeBV (l :: bv l) /= SomeBV (r :: bv r) =
    case sameNat (Proxy @l) (Proxy @r) of
      Just Refl -> l /= r
      Nothing -> True
  SomeBV (l :: bv l) /= SomeBVLit r = l /= fromIntegral r
  SomeBVLit l /= SomeBV r = fromIntegral l /= r
  SomeBVLit _ /= SomeBVLit _ = throw $ UndeterminedBitwidth "/="
  {-# INLINE (/=) #-}

instance
  ( forall n. (KnownNat n, 1 <= n) => Ord (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  Ord (SomeBV bv)
  where
  (<) = binSomeBV (<) (const $ const $ throw $ UndeterminedBitwidth "<")
  {-# INLINE (<) #-}
  (<=) = binSomeBV (<=) (const $ const $ throw $ UndeterminedBitwidth "(<=)")
  {-# INLINE (<=) #-}
  (>) = binSomeBV (>) (const $ const $ throw $ UndeterminedBitwidth ">")
  {-# INLINE (>) #-}
  (>=) = binSomeBV (>=) (const $ const $ throw $ UndeterminedBitwidth "(>=)")
  {-# INLINE (>=) #-}
  max = binSomeBVR1 max (const $ const $ throw $ UndeterminedBitwidth "max")
  {-# INLINE max #-}
  min = binSomeBVR1 min (const $ const $ throw $ UndeterminedBitwidth "min")
  {-# INLINE min #-}
  compare =
    binSomeBV compare (const $ const $ throw $ UndeterminedBitwidth "compare")
  {-# INLINE compare #-}

instance (forall n. (KnownNat n, 1 <= n) => Num (bv n)) => Num (SomeBV bv) where
  (+) = binSomeBVR1 (+) (+)
  {-# INLINE (+) #-}
  (-) = binSomeBVR1 (-) (-)
  {-# INLINE (-) #-}
  (*) = binSomeBVR1 (*) (const $ const $ throw $ UndeterminedBitwidth "(*)")
  {-# INLINE (*) #-}
  negate = unarySomeBVR1 negate negate
  {-# INLINE negate #-}
  abs = unarySomeBVR1 abs (const $ throw $ UndeterminedBitwidth "abs")
  {-# INLINE abs #-}
  signum = unarySomeBVR1 signum (const $ throw $ UndeterminedBitwidth "signum")
  {-# INLINE signum #-}
  fromInteger = SomeBVLit
  {-# INLINE fromInteger #-}

instance
  ( forall n. (KnownNat n, 1 <= n) => Bits (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  Bits (SomeBV bv)
  where
  (.&.) = binSomeBVR1 (.&.) (.&.)
  (.|.) = binSomeBVR1 (.|.) (.|.)
  xor = binSomeBVR1 xor xor
  complement = unarySomeBVR1 complement complement
  shift s i =
    unarySomeBVR1 (`shift` i) (const $ throw $ UndeterminedBitwidth "shift") s
  rotate s i =
    unarySomeBVR1 (`rotate` i) (const $ throw $ UndeterminedBitwidth "rotate") s
  zeroBits =
    error $
      "zeroBits is not defined for SomeBV as no bitwidth is known, use "
        <> "(bv <bitwidth> 0) or (SomeBV (zeroBits :: bv <bitwidth>)) instead"
  bit =
    error $
      "bit is not defined for SomeBV as no bitwidth is known, use "
        <> "(SomeBV (bit <bit> :: bv <bitwidth>)) instead"
  setBit s i = unarySomeBVR1 (`setBit` i) (`setBit` i) s
  clearBit s i = unarySomeBVR1 (`clearBit` i) (`clearBit` i) s
  complementBit s i = unarySomeBVR1 (`complementBit` i) (`complementBit` i) s
  testBit s i =
    unarySomeBV (`testBit` i) (const $ throw $ UndeterminedBitwidth "testBit") s
  bitSizeMaybe =
    unarySomeBV
      bitSizeMaybe
      (const $ throw $ UndeterminedBitwidth "bitSizeMaybe")
  bitSize =
    fromJust
      . unarySomeBV
        bitSizeMaybe
        (const $ throw $ UndeterminedBitwidth "bitSize")
  isSigned _ = False
  shiftL s i = unarySomeBVR1 (`shiftL` i) (`shiftL` i) s
  unsafeShiftL s i = unarySomeBVR1 (`unsafeShiftL` i) (`unsafeShiftL` i) s
  shiftR s i =
    unarySomeBVR1 (`shiftR` i) (const $ throw $ UndeterminedBitwidth "shiftR") s
  unsafeShiftR s i =
    unarySomeBVR1
      (`unsafeShiftR` i)
      (const $ throw $ UndeterminedBitwidth "unsafeShiftR")
      s
  rotateL s i =
    unarySomeBVR1
      (`rotateL` i)
      (const $ throw $ UndeterminedBitwidth "rotateL")
      s
  rotateR s i =
    unarySomeBVR1
      (`rotateR` i)
      (const $ throw $ UndeterminedBitwidth "rotateR")
      s
  popCount =
    unarySomeBV popCount (const $ throw $ UndeterminedBitwidth "popCount")

instance
  ( forall n. (KnownNat n, 1 <= n) => FiniteBits (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  FiniteBits (SomeBV bv)
  where
  finiteBitSize =
    unarySomeBV
      finiteBitSize
      (const $ throw $ UndeterminedBitwidth "finiteBitSize")
  {-# INLINE finiteBitSize #-}
  countLeadingZeros =
    unarySomeBV
      countLeadingZeros
      (const $ throw $ UndeterminedBitwidth "countLeadingZeros")
  {-# INLINE countLeadingZeros #-}
  countTrailingZeros =
    unarySomeBV
      countTrailingZeros
      (const $ throw $ UndeterminedBitwidth "countTrailingZeros")
  {-# INLINE countTrailingZeros #-}

instance
  (forall n. (KnownNat n, 1 <= n) => Enum (bv n)) =>
  Enum (SomeBV bv)
  where
  toEnum =
    error $
      "toEnum is not defined for SomeBV, use "
        <> "(SomeBV (toEnum <value> :: bv <bitwidth>)) instead"
  {-# INLINE toEnum #-}
  fromEnum =
    unarySomeBV fromEnum (const $ throw $ UndeterminedBitwidth "fromEnum")
  {-# INLINE fromEnum #-}

instance
  (forall n. (KnownNat n, 1 <= n) => Real (bv n)) =>
  Real (SomeBV bv)
  where
  toRational =
    unarySomeBV toRational (const $ throw $ UndeterminedBitwidth "toRational")
  {-# INLINE toRational #-}

instance
  (forall n. (KnownNat n, 1 <= n) => Integral (bv n)) =>
  Integral (SomeBV bv)
  where
  toInteger =
    unarySomeBV
      toInteger
      (const $ throw $ UndeterminedBitwidth "toInteger")
  {-# INLINE toInteger #-}
  quot = binSomeBVR1 quot (const $ throw $ UndeterminedBitwidth "quot")
  {-# INLINE quot #-}
  rem = binSomeBVR1 rem (const $ throw $ UndeterminedBitwidth "rem")
  {-# INLINE rem #-}
  div = binSomeBVR1 div (const $ throw $ UndeterminedBitwidth "div")
  {-# INLINE div #-}
  mod = binSomeBVR1 mod (const $ throw $ UndeterminedBitwidth "mod")
  {-# INLINE mod #-}
  quotRem = binSomeBVR2 quotRem (const $ throw $ UndeterminedBitwidth "quotRem")
  {-# INLINE quotRem #-}
  divMod = binSomeBVR2 divMod (const $ throw $ UndeterminedBitwidth "divMod")
  {-# INLINE divMod #-}

instance (SizedBV bv) => BV (SomeBV bv) where
  bvConcat (SomeBV (a :: bv l)) (SomeBV (b :: bv r)) =
    case ( leqAddPos (Proxy @l) (Proxy @r),
           knownAdd @l @r KnownProof KnownProof
         ) of
      (LeqProof, KnownProof) ->
        SomeBV $ sizedBVConcat a b
  bvConcat _ _ = throw $ UndeterminedBitwidth "bvConcat"
  {-# INLINE bvConcat #-}
  bvZext l (SomeBV (a :: bv n))
    | l < n = error "bvZext: trying to zero extend a value to a smaller size"
    | otherwise = res (Proxy @n)
    where
      n = fromIntegral $ natVal (Proxy @n)
      res :: forall (l :: Nat). Proxy l -> SomeBV bv
      res p =
        case ( unsafeKnownProof @l (fromIntegral l),
               unsafeLeqProof @1 @l,
               unsafeLeqProof @n @l
             ) of
          (KnownProof, LeqProof, LeqProof) -> SomeBV $ sizedBVZext p a
  bvZext _ _ = throw $ UndeterminedBitwidth "bvZext"
  {-# INLINE bvZext #-}
  bvSext l (SomeBV (a :: bv n))
    | l < n = error "bvSext: trying to zero extend a value to a smaller size"
    | otherwise = res (Proxy @n)
    where
      n = fromIntegral $ natVal (Proxy @n)
      res :: forall (l :: Nat). Proxy l -> SomeBV bv
      res p =
        case ( unsafeKnownProof @l (fromIntegral l),
               unsafeLeqProof @1 @l,
               unsafeLeqProof @n @l
             ) of
          (KnownProof, LeqProof, LeqProof) -> SomeBV $ sizedBVSext p a
  bvSext _ _ = throw $ UndeterminedBitwidth "bvSext"
  {-# INLINE bvSext #-}
  bvExt l (SomeBV (a :: bv n))
    | l < n = error "bvExt: trying to zero extend a value to a smaller size"
    | otherwise = res (Proxy @n)
    where
      n = fromIntegral $ natVal (Proxy @n)
      res :: forall (l :: Nat). Proxy l -> SomeBV bv
      res p =
        case ( unsafeKnownProof @l (fromIntegral l),
               unsafeLeqProof @1 @l,
               unsafeLeqProof @n @l
             ) of
          (KnownProof, LeqProof, LeqProof) -> SomeBV $ sizedBVExt p a
  bvExt _ _ = throw $ UndeterminedBitwidth "bvExt"
  {-# INLINE bvExt #-}
  bvSelect ix w (SomeBV (a :: bv n))
    | ix + w > n =
        error $
          "bvSelect: trying to select a bitvector outside the bounds of the "
            <> "input"
    | w == 0 = error "bvSelect: trying to select a bitvector of size 0"
    | otherwise = res (Proxy @n) (Proxy @n)
    where
      n = fromIntegral $ natVal (Proxy @n)
      res :: forall (w :: Nat) (ix :: Nat). Proxy w -> Proxy ix -> SomeBV bv
      res _ _ =
        case ( unsafeKnownProof @ix (fromIntegral ix),
               unsafeKnownProof @w (fromIntegral w),
               unsafeLeqProof @1 @w,
               unsafeLeqProof @(ix + w) @n
             ) of
          (KnownProof, KnownProof, LeqProof, LeqProof) ->
            SomeBV $ sizedBVSelect (Proxy @ix) (Proxy @w) a
  bvSelect _ _ _ = throw $ UndeterminedBitwidth "bvSelect"
  bv n i = unsafeSomeBV n $ \_ -> sizedBVFromIntegral i
  {-# INLINE bv #-}

instance
  (forall n. (KnownNat n, 1 <= n) => EvalSym (bv n)) =>
  EvalSym (SomeBV bv)
  where
  evalSym fillDefault model = unarySomeBVR1 (evalSym fillDefault model) id
  {-# INLINE evalSym #-}

instance
  (forall n. (KnownNat n, 1 <= n) => ExtractSym (bv n)) =>
  ExtractSym (SomeBV bv)
  where
  extractSymMaybe = unarySomeBV extractSymMaybe extractSymMaybe
  {-# INLINE extractSymMaybe #-}

instance
  (forall n. (KnownNat n, 1 <= n) => PPrint (bv n)) =>
  PPrint (SomeBV bv)
  where
  pformat (SomeBV bv) = pformat bv
  pformat (SomeBVLit i) = "bvlit(" <> pformat i <> ")"
  {-# INLINE pformat #-}

data CompileTimeNat where
  CompileTimeNat :: (KnownNat n, 1 <= n) => Proxy n -> CompileTimeNat

instance Show CompileTimeNat where
  show (CompileTimeNat (Proxy :: Proxy n)) = show (natVal (Proxy @n))
  {-# INLINE show #-}

instance Eq CompileTimeNat where
  CompileTimeNat (Proxy :: Proxy n) == CompileTimeNat (Proxy :: Proxy m) =
    case sameNat (Proxy @n) (Proxy @m) of
      Just Refl -> True
      Nothing -> False
  {-# INLINE (==) #-}

instance Ord CompileTimeNat where
  compare
    (CompileTimeNat (Proxy :: Proxy n))
    (CompileTimeNat (Proxy :: Proxy m)) =
      compare (natVal (Proxy @n)) (natVal (Proxy @m))
  {-# INLINE compare #-}

instance
  (forall n. (KnownNat n, 1 <= n) => Mergeable (bv n)) =>
  Mergeable (SomeBV bv)
  where
  rootStrategy =
    SortedStrategy @CompileTimeNat
      (\(SomeBV (_ :: bv n)) -> CompileTimeNat (Proxy @n))
      ( \(CompileTimeNat (_ :: proxy n)) ->
          wrapStrategy
            (rootStrategy @(bv n))
            SomeBV
            (\(SomeBV x) -> unsafeCoerce x)
      )

-- | The 'symDistinct' instance for t'SomeBV' will have the following behavior:
--
-- * If the list is empty or has only one element, it will return 'True'.
-- * If none of the elements have a bit-width, it will throw
--   'UndeterminedBitwidth' exception.
-- * If the elements have different bit-widths, it will throw a
--   'BitwidthMismatch' exception.
-- * If there are at least one element have a bit-width, and all elements with
--   known bit-width have the same bit-width, it will generate a single symbolic
--   formula using @distinct@.
instance
  ( forall n. (KnownNat n, 1 <= n) => SymEq (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  SymEq (SomeBV bv)
  where
  SomeBV (l :: bv l) .== SomeBV (r :: bv r) =
    case sameNat (Proxy @l) (Proxy @r) of
      Just Refl -> l .== r
      Nothing -> con False
  SomeBV (l :: bv l) .== SomeBVLit r = l .== fromIntegral r
  SomeBVLit l .== SomeBV (r :: bv r) = fromIntegral l .== r
  SomeBVLit _ .== SomeBVLit _ = throw $ UndeterminedBitwidth ".=="
  {-# INLINE (.==) #-}
  SomeBV (l :: bv l) ./= SomeBV (r :: bv r) =
    case sameNat (Proxy @l) (Proxy @r) of
      Just Refl -> l ./= r
      Nothing -> con True
  SomeBV (l :: bv l) ./= SomeBVLit r = l ./= fromIntegral r
  SomeBVLit l ./= SomeBV (r :: bv r) = fromIntegral l ./= r
  SomeBVLit _ ./= SomeBVLit _ = throw $ UndeterminedBitwidth "./="
  symDistinct l = case l of
    [] -> con True
    [_] -> con True
    _ -> case assignBitWidthList "symDistinct" l of
      Right (SomeBV (a :: bv a) : l) -> symDistinct $ a : go l
        where
          go :: [SomeBV bv] -> [bv a]
          go [] = []
          go (SomeBV (x :: bv x) : xs) = case sameNat (Proxy @x) (Proxy @a) of
            Just Refl -> x : go xs
            Nothing -> error "Should not happen"
          go (SomeBVLit _ : _) = error "Should not happen"
      Right _ -> error "Should not happen"
      Left UndeterminedBitwidth {} -> throw $ UndeterminedBitwidth "symDistinct"
      Left BitwidthMismatch -> throw BitwidthMismatch
  {-# INLINE (./=) #-}

instance
  ( forall n. (KnownNat n, 1 <= n) => SymOrd (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  SymOrd (SomeBV bv)
  where
  (.<) = binSomeBV (.<) (const $ const $ throw $ UndeterminedBitwidth "(.<)")
  {-# INLINE (.<) #-}
  (.<=) = binSomeBV (.<=) (const $ const $ throw $ UndeterminedBitwidth "(.<=)")
  {-# INLINE (.<=) #-}
  (.>) = binSomeBV (.>) (const $ const $ throw $ UndeterminedBitwidth "(.>)")
  {-# INLINE (.>) #-}
  (.>=) = binSomeBV (.>=) (const $ const $ throw $ UndeterminedBitwidth "(.>=)")
  {-# INLINE (.>=) #-}
  symCompare =
    binSomeBV
      symCompare
      (const $ const $ throw $ UndeterminedBitwidth "symCompare")
  {-# INLINE symCompare #-}

instance
  (forall n. (KnownNat n, 1 <= n) => SubstSym (bv n)) =>
  SubstSym (SomeBV bv)
  where
  substSym c s = unarySomeBVR1 (substSym c s) id
  {-# INLINE substSym #-}

instance
  ( KnownNat n,
    1 <= n,
    forall m. (KnownNat m, 1 <= m) => GenSym () (bv m),
    Mergeable (SomeBV bv)
  ) =>
  GenSym (Proxy n) (SomeBV bv)
  where
  fresh _ =
    (\(i :: Union (bv n)) -> mrgFmap SomeBV i) <$> fresh ()
  {-# INLINE fresh #-}

instance
  ( KnownNat n,
    1 <= n,
    forall m. (KnownNat m, 1 <= m) => GenSymSimple () (bv m),
    Mergeable (SomeBV bv)
  ) =>
  GenSymSimple (Proxy n) (SomeBV bv)
  where
  simpleFresh _ = (\(i :: bv n) -> SomeBV i) <$> simpleFresh ()
  {-# INLINE simpleFresh #-}

instance
  ( forall m. (KnownNat m, 1 <= m) => GenSym () (bv m),
    Mergeable (SomeBV bv)
  ) =>
  GenSym (SomeBV bv) (SomeBV bv)
  where
  fresh (SomeBV (_ :: bv x)) = fresh (Proxy @x)
  fresh (SomeBVLit _) = throw $ UndeterminedBitwidth "fresh"
  {-# INLINE fresh #-}

instance
  ( forall m. (KnownNat m, 1 <= m) => GenSymSimple () (bv m),
    Mergeable (SomeBV bv)
  ) =>
  GenSymSimple (SomeBV bv) (SomeBV bv)
  where
  simpleFresh (SomeBV (_ :: bv x)) = simpleFresh (Proxy @x)
  simpleFresh (SomeBVLit _) = throw $ UndeterminedBitwidth "simpleFresh"
  {-# INLINE simpleFresh #-}

instance
  ( forall n. (KnownNat n, 1 <= n) => GenSym () (bv n),
    Mergeable (SomeBV bv)
  ) =>
  GenSym Int (SomeBV bv)
  where
  fresh n
    | n <= 0 = error "fresh: cannot generate a bitvector of non-positive size"
    | otherwise = case mkPositiveNatRepr (fromIntegral n) of
        SomePositiveNatRepr (_ :: NatRepr x) -> fresh (Proxy @x)
  {-# INLINE fresh #-}

instance
  ( forall n. (KnownNat n, 1 <= n) => GenSymSimple () (bv n),
    Mergeable (SomeBV bv)
  ) =>
  GenSymSimple Int (SomeBV bv)
  where
  simpleFresh n
    | n <= 0 = error "fresh: cannot generate a bitvector of non-positive size"
    | otherwise = case mkPositiveNatRepr (fromIntegral n) of
        SomePositiveNatRepr (_ :: NatRepr x) -> simpleFresh (Proxy @x)
  {-# INLINE simpleFresh #-}

instance
  ( forall n. (KnownNat n, 1 <= n) => SignConversion (ubv n) (sbv n),
    -- Add this to help the type checker resolve the functional dependency
    SignConversion (ubv 1) (sbv 1)
  ) =>
  SignConversion (SomeBV ubv) (SomeBV sbv)
  where
  toSigned (SomeBV (n :: ubv n)) = SomeBV (toSigned n :: sbv n)
  toSigned (SomeBVLit i) = SomeBVLit i
  {-# INLINE toSigned #-}
  toUnsigned (SomeBV (n :: sbv n)) = SomeBV (toUnsigned n :: ubv n)
  toUnsigned (SomeBVLit i) = SomeBVLit i
  {-# INLINE toUnsigned #-}

instance
  (forall n. (KnownNat n, 1 <= n) => ToCon (sbv n) (cbv n)) =>
  ToCon (SomeBV sbv) (SomeBV cbv)
  where
  toCon (SomeBV (n :: sbv n)) = SomeBV <$> (toCon n :: Maybe (cbv n))
  toCon (SomeBVLit i) = Just $ SomeBVLit i
  {-# INLINE toCon #-}

instance
  (forall n. (KnownNat n, 1 <= n) => ToSym (cbv n) (sbv n)) =>
  ToSym (SomeBV cbv) (SomeBV sbv)
  where
  toSym (SomeBV (n :: cbv n)) = SomeBV (toSym n :: sbv n)
  toSym (SomeBVLit i) = SomeBVLit i
  {-# INLINE toSym #-}

divRemOrBase0 ::
  ( forall n.
    (KnownNat n, 1 <= n) =>
    (bv n, bv n) ->
    bv n ->
    bv n ->
    (bv n, bv n)
  ) ->
  (SomeBV bv, SomeBV bv) ->
  SomeBV bv ->
  SomeBV bv ->
  (SomeBV bv, SomeBV bv)
divRemOrBase0
  f
  (SomeBV (dd :: bv dd), SomeBV (dm :: bv dm))
  (SomeBV (a :: bv a))
  (SomeBV (b :: bv b)) =
    case ( sameNat (Proxy @a) (Proxy @b),
           sameNat (Proxy @a) (Proxy @dd),
           sameNat (Proxy @a) (Proxy @dm)
         ) of
      (Just Refl, Just Refl, Just Refl) -> bimap SomeBV SomeBV $ f (dd, dm) a b
      _ -> error "Should not happen"
divRemOrBase0 _ _ _ _ = error "Should not happen"
{-# INLINE divRemOrBase0 #-}

divRemOrBase ::
  (forall n. (KnownNat n, 1 <= n) => Num (bv n)) =>
  ( forall n.
    (KnownNat n, 1 <= n) =>
    (bv n, bv n) ->
    bv n ->
    bv n ->
    (bv n, bv n)
  ) ->
  (SomeBV bv, SomeBV bv) ->
  SomeBV bv ->
  SomeBV bv ->
  (SomeBV bv, SomeBV bv)
divRemOrBase f (a, b) c d =
  case assignBitWidth "divRemOrBase" (a, b, c, d) of
    Right (a', b', c', d') -> divRemOrBase0 f (a', b') c' d'
    Left e -> throw e

instance
  ( forall n. (KnownNat n, 1 <= n) => DivOr (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  DivOr (SomeBV bv)
  where
  divOr = ternSomeBVR1 divOr
  {-# INLINE divOr #-}
  modOr = ternSomeBVR1 modOr
  {-# INLINE modOr #-}
  quotOr = ternSomeBVR1 quotOr
  {-# INLINE quotOr #-}
  remOr = ternSomeBVR1 remOr
  {-# INLINE remOr #-}
  divModOr = divRemOrBase divModOr
  {-# INLINE divModOr #-}
  quotRemOr = divRemOrBase quotRemOr
  {-# INLINE quotRemOr #-}

instance
  ( forall n.
    (KnownNat n, 1 <= n) =>
    SafeDiv e (bv n) (ExceptT e m),
    MonadError (Either SomeBVException e) m,
    TryMerge m,
    Mergeable e,
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  SafeDiv (Either SomeBVException e) (SomeBV bv) m
  where
  safeDiv =
    binSomeBVSafeR1
      (safeDiv @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeDiv")
  {-# INLINE safeDiv #-}
  safeMod =
    binSomeBVSafeR1
      (safeMod @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeMod")
  {-# INLINE safeMod #-}
  safeQuot =
    binSomeBVSafeR1
      (safeQuot @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeQuot")
  {-# INLINE safeQuot #-}
  safeRem =
    binSomeBVSafeR1
      (safeRem @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeRem")
  {-# INLINE safeRem #-}
  safeDivMod =
    binSomeBVSafeR2
      (safeDivMod @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeDivMod")
  {-# INLINE safeDivMod #-}
  safeQuotRem =
    binSomeBVSafeR2
      (safeQuotRem @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeQuotRem")
  {-# INLINE safeQuotRem #-}

instance
  ( forall n.
    (KnownNat n, 1 <= n) =>
    SafeLinearArith e (bv n) (ExceptT e m),
    MonadError (Either SomeBVException e) m,
    TryMerge m,
    Mergeable e,
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  SafeLinearArith (Either SomeBVException e) (SomeBV bv) m
  where
  safeAdd =
    binSomeBVSafeR1
      (safeAdd @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeAdd")
  {-# INLINE safeAdd #-}
  safeSub =
    binSomeBVSafeR1
      (safeSub @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeSub")
  {-# INLINE safeSub #-}
  safeNeg =
    unarySomeBV
      ( \v ->
          mrgFmap SomeBV $
            runExceptT (safeNeg @e v) >>= either (throwError . Right) pure
      )
      (const $ throwError $ Left $ UndeterminedBitwidth "safeNeg")
  {-# INLINE safeNeg #-}

instance
  ( forall n. (KnownNat n, 1 <= n) => SymShift (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  SymShift (SomeBV bv)
  where
  symShift =
    binSomeBVR1
      symShift
      (const $ const $ throw $ UndeterminedBitwidth "safeShift")
  {-# INLINE symShift #-}
  symShiftNegated =
    binSomeBVR1
      symShiftNegated
      (const $ const $ throw $ UndeterminedBitwidth "safeShiftNegated")
  {-# INLINE symShiftNegated #-}

instance
  ( forall n. (KnownNat n, 1 <= n) => SymRotate (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  SymRotate (SomeBV bv)
  where
  symRotate =
    binSomeBVR1
      symRotate
      (const $ const $ throw $ UndeterminedBitwidth "safeRotate")
  {-# INLINE symRotate #-}
  symRotateNegated =
    binSomeBVR1
      symRotateNegated
      (const $ const $ throw $ UndeterminedBitwidth "safeRotateNegated")
  {-# INLINE symRotateNegated #-}

instance
  ( forall n.
    (KnownNat n, 1 <= n) =>
    SafeSymShift e (bv n) (ExceptT e m),
    MonadError (Either SomeBVException e) m,
    TryMerge m,
    Mergeable e,
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  SafeSymShift (Either SomeBVException e) (SomeBV bv) m
  where
  safeSymShiftL =
    binSomeBVSafeR1
      (safeSymShiftL @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeSymShiftL")
  {-# INLINE safeSymShiftL #-}
  safeSymShiftR =
    binSomeBVSafeR1
      (safeSymShiftR @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeSymShiftR")
  {-# INLINE safeSymShiftR #-}
  safeSymStrictShiftL =
    binSomeBVSafeR1
      (safeSymStrictShiftL @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeSymStrictShiftL")
  {-# INLINE safeSymStrictShiftL #-}
  safeSymStrictShiftR =
    binSomeBVSafeR1
      (safeSymStrictShiftR @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeSymStrictShiftR")
  {-# INLINE safeSymStrictShiftR #-}

instance
  ( forall n.
    (KnownNat n, 1 <= n) =>
    SafeSymRotate e (bv n) (ExceptT e m),
    MonadError (Either SomeBVException e) m,
    TryMerge m,
    Mergeable e,
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  SafeSymRotate (Either SomeBVException e) (SomeBV bv) m
  where
  safeSymRotateL =
    binSomeBVSafeR1
      (safeSymRotateL @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeSymRotateL")
  {-# INLINE safeSymRotateL #-}
  safeSymRotateR =
    binSomeBVSafeR1
      (safeSymRotateR @e)
      (const $ const $ throwError $ Left $ UndeterminedBitwidth "safeSymRotateR")
  {-# INLINE safeSymRotateR #-}

instance
  ( forall n. (KnownNat n, 1 <= n) => ITEOp (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  ITEOp (SomeBV bv)
  where
  symIte cond =
    binSomeBVR1
      (symIte cond)
      (const $ const $ throw $ UndeterminedBitwidth "symIte")

instance
  (forall n. (KnownNat n, 1 <= n) => AllSyms (bv n)) =>
  AllSyms (SomeBV bv)
  where
  allSyms = unarySomeBV allSyms allSyms
  {-# INLINE allSyms #-}
  allSymsS = unarySomeBV allSymsS allSymsS
  {-# INLINE allSymsS #-}

-- Synonyms

-- | Type synonym for t'SomeBV' for concrete signed bitvectors.
type SomeIntN = SomeBV IntN

-- | Pattern synonym for t'SomeBV' for concrete signed bitvectors.
pattern SomeIntN :: () => (KnownNat n, 1 <= n) => IntN n -> SomeIntN
pattern SomeIntN a = SomeBV a

-- | Type synonym for t'SomeBV' for concrete unsigned bitvectors.
type SomeWordN = SomeBV WordN

-- | Pattern synonym for t'SomeBV' for concrete unsigned bitvectors.
pattern SomeWordN :: () => (KnownNat n, 1 <= n) => WordN n -> SomeWordN
pattern SomeWordN a = SomeBV a

-- | Type synonym for t'SomeBV' for symbolic signed bitvectors.
type SomeSymIntN = SomeBV SymIntN

-- | Pattern synonym for t'SomeBV' for symbolic signed bitvectors.
pattern SomeSymIntN :: () => (KnownNat n, 1 <= n) => SymIntN n -> SomeSymIntN
pattern SomeSymIntN a = SomeBV a

-- | Type synonym for t'SomeBV' for symbolic unsigned bitvectors.
type SomeSymWordN = SomeBV SymWordN

-- | Pattern synonym for t'SomeBV' for symbolic unsigned bitvectors.
pattern SomeSymWordN :: () => (KnownNat n, 1 <= n) => SymWordN n -> SomeSymWordN
pattern SomeSymWordN a = SomeBV a

-- Construction

-- | Construct a t'SomeBV' with a given run-time bitwidth and a polymorphic
-- value for the underlying bitvector.
unsafeSomeBV ::
  forall bv.
  Int ->
  (forall proxy n. (KnownNat n, 1 <= n) => proxy n -> bv n) ->
  SomeBV bv
unsafeSomeBV n i
  | n <= 0 = error "unsafeBV: trying to create a bitvector of non-positive size"
  | otherwise = case mkPositiveNatRepr (fromIntegral n) of
      SomePositiveNatRepr (_ :: NatRepr x) -> SomeBV (i (Proxy @x))

-- | Construct a symbolic t'SomeBV' with a given concrete t'SomeBV'. Similar to
-- 'con' but for t'SomeBV'.
--
-- >>> a = bv 8 0x12 :: SomeIntN
-- >>> conBV a :: SomeSymIntN
-- 0x12
conBV ::
  forall cbv bv.
  ( forall n. (KnownNat n, 1 <= n) => Solvable (cbv n) (bv n),
    Solvable (cbv 1) (bv 1)
  ) =>
  SomeBV cbv ->
  SomeBV bv
conBV (SomeBV (v :: cbv n)) = SomeBV $ con @(cbv n) @(bv n) v
conBV (SomeBVLit i) = SomeBVLit i

-- | View pattern for symbolic t'SomeBV' to see if it contains a concrete value
-- and extract it. Similar to 'conView' but for t'SomeBV'.
--
-- >>> conBVView (bv 8 0x12 :: SomeSymIntN)
-- Just 0x12
-- >>> conBVView (ssymBV 4 "a" :: SomeSymIntN)
-- Nothing
conBVView ::
  forall cbv bv.
  ( forall n. (KnownNat n, 1 <= n) => Solvable (cbv n) (bv n),
    Solvable (cbv 1) (bv 1)
  ) =>
  SomeBV bv ->
  Maybe (SomeBV cbv)
conBVView (SomeBV (bv :: bv n)) = case conView @(cbv n) bv of
  Just c -> Just $ SomeBV c
  Nothing -> Nothing
conBVView (SomeBVLit i) = Just $ SomeBVLit i

-- | Pattern synonym for symbolic t'SomeBV' to see if it contains a concrete
-- value and extract it. Similar to 'Grisette.Core.Con' but for t'SomeBV'.
--
-- >>> case (bv 8 0x12 :: SomeSymIntN) of { ConBV c -> c; _ -> error "impossible" }
-- 0x12
pattern ConBV ::
  forall cbv bv.
  ( forall n. (KnownNat n, 1 <= n) => Solvable (cbv n) (bv n),
    Solvable (cbv 1) (bv 1)
  ) =>
  SomeBV cbv ->
  SomeBV bv
pattern ConBV c <- (conBVView -> Just c)
  where
    ConBV c = conBV c

-- | Construct a symbolic t'SomeBV' with a given run-time bitwidth and a symbol.
-- Similar to 'sym' but for t'SomeBV'.
--
-- >>> symBV 8 "a" :: SomeSymIntN
-- a
symBV ::
  forall cbv bv.
  ( forall n. (KnownNat n, 1 <= n) => Solvable (cbv n) (bv n),
    Solvable (cbv 1) (bv 1)
  ) =>
  Int ->
  Symbol ->
  SomeBV bv
symBV n s = unsafeSomeBV n $ \(_ :: proxy n) -> sym @(cbv n) s

-- | Construct a symbolic t'SomeBV' with a given run-time bitwidth and an
-- identifier. Similar to 'ssym' but for t'SomeBV'.
--
-- >>> ssymBV 8 "a" :: SomeSymIntN
-- a
ssymBV ::
  forall cbv bv.
  ( forall n. (KnownNat n, 1 <= n) => Solvable (cbv n) (bv n),
    Solvable (cbv 1) (bv 1)
  ) =>
  Int ->
  Identifier ->
  SomeBV bv
ssymBV n s = unsafeSomeBV n $ \(_ :: proxy n) -> ssym @(cbv n) s

-- | Construct a symbolic t'SomeBV' with a given run-time bitwidth, an identifier
-- and an index. Similar to 'isym' but for t'SomeBV'.
--
-- >>> isymBV 8 "a" 1 :: SomeSymIntN
-- a@1
isymBV ::
  forall cbv bv.
  ( forall n. (KnownNat n, 1 <= n) => Solvable (cbv n) (bv n),
    Solvable (cbv 1) (bv 1)
  ) =>
  Int ->
  Identifier ->
  Int ->
  SomeBV bv
isymBV n s i = unsafeSomeBV n $ \(_ :: proxy n) -> isym @(cbv n) s i

-- | Generate an arbitrary t'SomeBV' with a given run-time bitwidth.
arbitraryBV ::
  forall bv.
  (forall n. (KnownNat n, 1 <= n) => Arbitrary (bv n)) =>
  Int ->
  Gen (SomeBV bv)
arbitraryBV n
  | n <= 0 =
      error "arbitraryBV: trying to create a bitvector of non-positive size"
  | otherwise = case mkPositiveNatRepr (fromIntegral n) of
      SomePositiveNatRepr (_ :: NatRepr x) -> do
        v <- arbitrary :: Gen (bv x)
        return $ SomeBV v

-- Helpers

-- | Lift a unary operation on sized bitvectors that returns anything to
-- t'SomeBV'.
unarySomeBV ::
  forall bv r.
  (forall n. (KnownNat n, 1 <= n) => bv n -> r) ->
  (Integer -> r) ->
  SomeBV bv ->
  r
unarySomeBV f _ (SomeBV bv) = f bv
unarySomeBV _ g (SomeBVLit i) = g i
{-# INLINE unarySomeBV #-}

-- | Lift a unary operation on sized bitvectors that returns a bitvector to
-- t'SomeBV'. The result will also be wrapped with t'SomeBV'.
unarySomeBVR1 ::
  (forall n. (KnownNat n, 1 <= n) => bv n -> bv n) ->
  (Integer -> Integer) ->
  SomeBV bv ->
  SomeBV bv
unarySomeBVR1 f g = unarySomeBV (SomeBV . f) (SomeBVLit . g)
{-# INLINE unarySomeBVR1 #-}

-- | Lift a binary operation on sized bitvectors that returns anything to
-- t'SomeBV'. Crash if the bitwidths do not match.
binSomeBV ::
  (forall n. (KnownNat n, 1 <= n) => Num (bv n)) =>
  (forall n. (KnownNat n, 1 <= n) => bv n -> bv n -> r) ->
  (Integer -> Integer -> r) ->
  SomeBV bv ->
  SomeBV bv ->
  r
binSomeBV f _ (SomeBV (l :: bv l)) (SomeBV (r :: bv r)) =
  case sameNat (Proxy @l) (Proxy @r) of
    Just Refl -> f l r
    Nothing -> throw BitwidthMismatch
binSomeBV f _ (SomeBV (l :: bv l)) (SomeBVLit r) = f l $ fromIntegral r
binSomeBV f _ (SomeBVLit l) (SomeBV (r :: bv r)) = f (fromIntegral l) r
binSomeBV _ g (SomeBVLit l) (SomeBVLit r) = g l r
{-# INLINE binSomeBV #-}

-- | Lift a ternary operation on sized bitvectors that returns anything to
-- t'SomeBV'. Crash if the bitwidths do not match.
ternSomeBV ::
  (forall n. (KnownNat n, 1 <= n) => Num (bv n)) =>
  (forall n. (KnownNat n, 1 <= n) => bv n -> bv n -> bv n -> r) ->
  SomeBV bv ->
  SomeBV bv ->
  SomeBV bv ->
  r
ternSomeBV f (SomeBV (a :: bv a)) (SomeBV (b :: bv b)) (SomeBV (c :: bv c)) =
  case (sameNat (Proxy @a) (Proxy @b), sameNat (Proxy @a) (Proxy @c)) of
    (Just Refl, Just Refl) -> f a b c
    _ -> throw BitwidthMismatch
ternSomeBV f a b c =
  case assignBitWidth "ternSomeBV" (a, b, c) of
    Right (a', b', c') -> ternSomeBV f a' b' c'
    Left e -> throw e
{-# INLINE ternSomeBV #-}

-- | Lift a binary operation on sized bitvectors that returns a bitvector to
-- t'SomeBV'. The result will also be wrapped with t'SomeBV'. Crash if the
-- bitwidths do not match.
binSomeBVR1 ::
  (forall n. (KnownNat n, 1 <= n) => Num (bv n)) =>
  (forall n. (KnownNat n, 1 <= n) => bv n -> bv n -> bv n) ->
  (Integer -> Integer -> Integer) ->
  SomeBV bv ->
  SomeBV bv ->
  SomeBV bv
binSomeBVR1 f g = binSomeBV (\a b -> SomeBV $ f a b) (\a b -> SomeBVLit $ g a b)
{-# INLINE binSomeBVR1 #-}

-- | Lift a binary operation on sized bitvectors that returns two bitvectors to
-- t'SomeBV'. The results will also be wrapped with t'SomeBV'. Crash if the
-- bitwidths do not match.
binSomeBVR2 ::
  (forall n. (KnownNat n, 1 <= n) => Num (bv n)) =>
  (forall n. (KnownNat n, 1 <= n) => bv n -> bv n -> (bv n, bv n)) ->
  (Integer -> Integer -> (Integer, Integer)) ->
  SomeBV bv ->
  SomeBV bv ->
  (SomeBV bv, SomeBV bv)
binSomeBVR2 f g =
  binSomeBV
    (\a b -> let (x, y) = f a b in (SomeBV x, SomeBV y))
    (\a b -> let (x, y) = g a b in (SomeBVLit x, SomeBVLit y))
{-# INLINE binSomeBVR2 #-}

-- | Lift a ternary operation on sized bitvectors that returns a bitvector to
-- t'SomeBV'. The result will also be wrapped with t'SomeBV'. Crash if the
-- bitwidths do not match.
ternSomeBVR1 ::
  (forall n. (KnownNat n, 1 <= n) => Num (bv n)) =>
  (forall n. (KnownNat n, 1 <= n) => bv n -> bv n -> bv n -> bv n) ->
  SomeBV bv ->
  SomeBV bv ->
  SomeBV bv ->
  SomeBV bv
ternSomeBVR1 f = ternSomeBV (\a b c -> SomeBV $ f a b c)
{-# INLINE ternSomeBVR1 #-}

-- | Lift a binary operation on sized bitvectors that returns anything wrapped
-- with 'ExceptT' to t'SomeBV'. If the bitwidths do not match, throw an
-- 'BitwidthMismatch' error to the monadic context.
binSomeBVSafe ::
  ( MonadError (Either SomeBVException e) m,
    TryMerge m,
    Mergeable e,
    Mergeable r,
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  (forall n. (KnownNat n, 1 <= n) => bv n -> bv n -> ExceptT e m r) ->
  (Integer -> Integer -> ExceptT (Either SomeBVException e) m r) ->
  SomeBV bv ->
  SomeBV bv ->
  m r
binSomeBVSafe f _ (SomeBV (l :: bv l)) (SomeBV (r :: bv r)) =
  case sameNat (Proxy @l) (Proxy @r) of
    Just Refl ->
      tryMerge $ runExceptT (f l r) >>= either (throwError . Right) pure
    Nothing -> tryMerge $ throwError $ Left BitwidthMismatch
binSomeBVSafe _ g (SomeBVLit l) (SomeBVLit r) =
  tryMerge $ runExceptT (g l r) >>= either throwError pure
binSomeBVSafe f g l r =
  case assignBitWidth "binSomeBVSafe" (l, r) of
    Right (l', r') -> binSomeBVSafe f g l' r'
    Left e -> tryMerge $ throwError $ Left e
{-# INLINE binSomeBVSafe #-}

-- | Lift a binary operation on sized bitvectors that returns a bitvector
-- wrapped with 'ExceptT' to t'SomeBV'. The result will also be wrapped with
-- t'SomeBV'.
--
-- If the bitwidths do not match, throw an 'BitwidthMismatch' error to the
-- monadic context.
binSomeBVSafeR1 ::
  ( MonadError (Either SomeBVException e) m,
    TryMerge m,
    Mergeable e,
    forall n. (KnownNat n, 1 <= n) => Mergeable (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  (forall n. (KnownNat n, 1 <= n) => bv n -> bv n -> ExceptT e m (bv n)) ->
  (Integer -> Integer -> ExceptT (Either SomeBVException e) m Integer) ->
  SomeBV bv ->
  SomeBV bv ->
  m (SomeBV bv)
binSomeBVSafeR1 f g =
  binSomeBVSafe
    (\l r -> mrgFmap SomeBV $ f l r)
    (\l r -> mrgFmap SomeBVLit $ g l r)
{-# INLINE binSomeBVSafeR1 #-}

-- | Lift a binary operation on sized bitvectors that returns two bitvectors
-- wrapped with 'ExceptT' to t'SomeBV'. The results will also be wrapped with
-- t'SomeBV'.
--
-- If the bitwidths do not match, throw an 'BitwidthMismatch' error to the
-- monadic context.
binSomeBVSafeR2 ::
  ( MonadError (Either SomeBVException e) m,
    TryMerge m,
    Mergeable e,
    forall n. (KnownNat n, 1 <= n) => Mergeable (bv n),
    forall n. (KnownNat n, 1 <= n) => Num (bv n)
  ) =>
  ( forall n.
    (KnownNat n, 1 <= n) =>
    bv n ->
    bv n ->
    ExceptT e m (bv n, bv n)
  ) ->
  ( Integer ->
    Integer ->
    ExceptT (Either SomeBVException e) m (Integer, Integer)
  ) ->
  SomeBV bv ->
  SomeBV bv ->
  m (SomeBV bv, SomeBV bv)
binSomeBVSafeR2 f g =
  binSomeBVSafe
    (\l r -> mrgFmap (bimap SomeBV SomeBV) $ f l r)
    (\l r -> mrgFmap (bimap SomeBVLit SomeBVLit) $ g l r)
{-# INLINE binSomeBVSafeR2 #-}