packages feed

liquid-fixpoint-0.9.6.3.2: tests/tasty/Arbitrary.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TupleSections #-}

{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}

module Arbitrary
  ( subexprs
  , Env(..)
  , NoAnfEnv(..)
  , AnfSymbol(..)
  , FlatAnfEnv(..)
  , ChainedAnfEnv(..)
  ) where

import qualified Data.Text                 as Text
import qualified Data.HashMap.Strict       as M
import Test.Tasty.QuickCheck
import GHC.Generics

import Language.Fixpoint.Types.Refinements as R
import Language.Fixpoint.Parse             (isNotReserved)
import Language.Fixpoint.Types             as T hiding (Result)
import Language.Fixpoint.Types.Spans       as Spans
import Data.Traversable                    (for)

{-

-- An example of how the Arbitrary Expr instance can be used.
-- Note that `expr == rr (showpp expr)` is *not* something we expect to hold.
-- See https://github.com/ucsd-progsys/liquid-fixpoint/issues/46

quickCheckTests :: TestTree
quickCheckTests
  = testGroup "Properties"
      [ testProperty "prop_pprint_parse_inv_expr" prop_pprint_parse_inv_expr
      ]

prop_pprint_parse_inv_expr :: Expr -> Bool
prop_pprint_parse_inv_expr expr = expr == rr (showpp expr)

-}

-- NOTE: `shrink _ = mempty` is identical to the default (implicit) shrink implementation.
-- We prefer to make it explicit.

instance Arbitrary Expr where
  arbitrary = sized arbitraryExpr
  shrink x = filter valid $ genericShrink x
    where
      valid e@(T.PAnd es)     = length es >= 2 && all valid (subexprs e)
      valid e@(T.POr es)      = length es >= 2 && all valid (subexprs e)
      valid e@(T.PAll es _)   = length es >= 2 && all valid (subexprs e)
      valid e@(T.PExist es _) = length es >= 2 && all valid (subexprs e)
      valid e                 = all valid (subexprs e)

subexprs :: Expr -> [Expr]
subexprs (ESym _)        = []
subexprs (ECon _)        = []
subexprs (EVar _)        = []
subexprs (EApp e0 e1)    = [e0, e1]
subexprs (ENeg e)        = [e]
subexprs (EBin _ e0 e1)  = [e0, e1]
subexprs (EIte e0 e1 e2) = [e0, e1, e2]
subexprs (ECst e _)      = [e]
subexprs (ELam _ e)      = [e]
subexprs (ETApp e _)     = [e]
subexprs (ETAbs e _)     = [e]
subexprs (T.PAnd es)     = es
subexprs (T.POr es)      = es
subexprs (T.PNot e)      = [e]
subexprs (PImp e0 e1)    = [e0, e1]
subexprs (PIff e0 e1)    = [e0, e1]
subexprs (PAtom _ e0 e1) = [e0, e1]
subexprs (PKVar _ _)     = []
subexprs (PAll _ e)      = [e]
subexprs (PExist _ e)    = [e]
subexprs (PGrad _ _ _ e) = [e]
subexprs (ECoerc _ _ e)  = [e]

-- TODO: Adjust frequencies
-- | To ensure this reliably terminates we require that `zeroExprGen` generates
-- atomic expressions.
arbitraryFiniteExpr
  :: Gen Expr -- ^ called when the Int is zero.
  -> Int
  -> Gen Expr
arbitraryFiniteExpr zeroExprGen 0 = zeroExprGen
arbitraryFiniteExpr zeroExprGen n = frequency
  [ (1, EApp <$> arbitraryExpr' <*> arbitraryExpr')
  , (1, ENeg <$> arbitraryExpr')
  , (1, EBin <$> arbitrary <*> arbitraryExpr' <*> arbitraryExpr')
  , (1, EIte <$> arbitraryExpr' <*> arbitraryExpr' <*> arbitraryExpr')
  , (1, ECst <$> arbitraryExpr' <*> arbitrary)
  , (1, ELam <$> arbitrary <*> arbitraryExpr')
  , (1, ETApp <$> arbitraryExpr' <*> arbitrary)
  , (1, ETAbs <$> arbitraryExpr' <*> arbitrary)
  , (1, T.PAnd <$> arbitraryExprList)
  , (1, T.POr <$> arbitraryExprList)
  , (1, T.PNot <$> arbitraryExpr')
  , (1, PImp <$> arbitraryExpr' <*> arbitraryExpr')
  , (1, PIff <$> arbitraryExpr' <*> arbitraryExpr')
  , (1, PAtom <$> arbitrary <*> arbitraryExpr' <*> arbitraryExpr')
  , (1, PKVar <$> arbitrary <*> arbitrary)
  , (1, PAll <$> arbitraryList arbitrary <*> arbitraryExpr')
  , (1, PExist <$> arbitraryList arbitrary <*> arbitraryExpr')
  , (1, PGrad <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitraryExpr')
  , (1, ECoerc <$> arbitrary <*> arbitrary <*> arbitraryExpr')
  ]
  where
    arbitraryExpr' = arbitraryFiniteExpr zeroExprGen (n `div` 2)
    arbitraryList :: Gen a -> Gen [a]
    arbitraryList gen = choose (2, 3) >>= (`vectorOf` gen)
    arbitraryExprList = arbitraryList arbitraryExpr'

-- | Generates a finite expression, with the logarithm of the Int given
-- suggesting the depth of the expression tree.
arbitraryExpr :: Int -> Gen Expr
arbitraryExpr = arbitraryFiniteExpr arbitraryAtomicExpr

arbitraryAtomicExpr :: Gen Expr
arbitraryAtomicExpr = oneof [ESym <$> arbitrary, ECon <$> arbitrary, EVar <$> arbitrary]

arbitraryEqualityConstraint :: Gen Brel
arbitraryEqualityConstraint = oneof [pure Eq, pure Ueq]

-- | Generates an expression that involves the given Symbol in an Eq or Ueq
-- PAtom with an arbitrary non-lq_anf$ EVar.
arbitraryExprInvolving :: Symbol -> Int -> Gen Expr
arbitraryExprInvolving sym = arbitraryFiniteExpr . pure $ EVar sym

instance Arbitrary KVar where
  arbitrary = KV <$> arbitrary

-- NOTE: This dummy Arbitrary instance returns a constant GradInfo.
instance Arbitrary GradInfo where
  arbitrary = pure $ GradInfo (SS pos pos) Nothing
    where pos = Spans.dummyPos "<unknown>"
  shrink _ = mempty

instance Arbitrary Subst where
  arbitrary = do
    n <- choose (0, 3)
    l <- vectorOf n arbitrary
    return $ Su $ M.fromList l
  shrink _ = mempty

-- | This instance only creates `FVar` when they would be in scope from an
-- enclosing `FAbs`, and does not create `FObj`s
instance Arbitrary Sort where
  arbitrary = sized arbitrarySort
  shrink = genericShrink

-- | Create an arbitrary well-formed sort that does not contain `FObj`s.
--
-- The sort is \"well-formed\" in the sense that all `FVar`s have an enclosing
-- `FAbs` bringing them into scope.
arbitrarySort :: Int -> Gen Sort
arbitrarySort = arbitrarySortPossiblyInvolving []

-- | Create an arbitrary sort, possibly involving the variables represented by
-- the list of Ints. Can possibly create a `FAbs` that will also possibly
-- reference the new variable in an `FVar`, even when the first argument is
-- [].
arbitrarySortPossiblyInvolving :: [Int] -> Int -> Gen Sort
arbitrarySortPossiblyInvolving [] n = frequency
  [ (4, arbitrarySortNoAbs n)
  , (1, newAbs [] n) ]
arbitrarySortPossiblyInvolving vars n = do
  let fvar = oneof $ pure . FVar <$> vars
  frequency
    [ (1, arbitrarySortNoAbs n)
    , (1, FFunc <$> fvar <*> arbitrarySortPossiblyInvolving vars (n `div` 2))
    , (1, FFunc <$> arbitrarySortPossiblyInvolving vars (n `div` 2) <*> fvar)
    , (1, FApp <$> fvar <*> arbitrarySortPossiblyInvolving vars (n `div` 2))
    , (1, FApp <$> arbitrarySortPossiblyInvolving vars (n `div` 2) <*> fvar)
    , (1, fvar)
    , (1, newAbs vars n)
    ]

-- | Create a new FAbs sort whose body might involve the newly created variable.
-- First argument is the variables already in scope.
newAbs :: [Int] -> Int -> Gen Sort
newAbs vars n = do
  v <- arbitrary
  if v `elem` vars
    then discard v
    else FAbs v <$> arbitrarySortPossiblyInvolving (v:vars) (n `div` 2)

-- | Does not create FObj, FAbs, or FVar
arbitrarySortNoAbs :: Int -> Gen Sort
arbitrarySortNoAbs 0 = oneof (pure <$> [FInt, FReal, FNum, FFrac])
arbitrarySortNoAbs n = frequency
  [ (1, FFunc <$> arbitrarySortNoAbs' <*> arbitrarySortNoAbs')
  , (1, FTC <$> arbitrary)
  , (1, FApp <$> arbitrarySortNoAbs' <*> arbitrarySortNoAbs')
  ]
  where
    arbitrarySortNoAbs' = arbitrarySortNoAbs (n `div` 2)

instance Arbitrary Brel where
  arbitrary = oneof (map return [Eq, Ne, Gt, Ge, Lt, Le, Ueq, Une])
  shrink _ = mempty

instance Arbitrary Bop where
  arbitrary = oneof (map return [Plus, Minus, Times, Div, Mod])
  shrink _ = mempty

instance Arbitrary SymConst where
  arbitrary = SL . unShortLowercaseAlphabeticText <$> arbitrary

-- | Note that we rely below on the property that the Arbitrary instance for
-- Symbol cannot create lq_anf$ vars.
instance Arbitrary Symbol where
  arbitrary = (symbol :: Text.Text -> Symbol) . unShortLowercaseAlphabeticText <$> arbitrary
  shrink _ = mempty

newtype ShortLowercaseAlphabeticText = ShortLowercaseAlphabeticText { unShortLowercaseAlphabeticText :: Text.Text }
  deriving (Eq, Show, Generic)

instance Arbitrary ShortLowercaseAlphabeticText where
  arbitrary = ShortLowercaseAlphabeticText <$> (choose (5, 12) >>= \n -> Text.pack <$> (vectorOf n char `suchThat` valid))
    where
      char = elements ['a'..'z']
      valid x = isNotReserved x && not (isFixKey (Text.pack x))
  shrink _ = mempty

instance Arbitrary FTycon where
  arbitrary = do
    c <- elements ['A'..'Z']
    t <- unShortLowercaseAlphabeticText <$> arbitrary
    return $ symbolFTycon $ dummyLoc $ symbol $ c `Text.cons` t
  shrink _ = mempty

instance Arbitrary Constant where
  arbitrary = oneof [ I <$> arbitrary `suchThat` (>= 0) -- Negative values use `ENeg`
                    , R <$> arbitrary `suchThat` (>= 0) -- Negative values use `ENeg`
                    , L . unShortLowercaseAlphabeticText <$> arbitrary <*> arbitrary
                    ]
  shrink (I x) = I <$> shrink x
  shrink (R x) = R <$> shrink x
  shrink (L x y) = L x <$> shrink y

-- | Used in UndoANFTests.
newtype AnfSymbol = AnfSymbol { unAnfSymbol :: Symbol }
  deriving (Eq, Show, Generic)
instance Arbitrary AnfSymbol where
  arbitrary = AnfSymbol . mappendSym anfPrefix <$> arbitrary
  shrink = mempty

-- | This instance does **not** create Refts with anf symbols.
instance Arbitrary Reft where
  arbitrary = reft <$> arbitrary <*> arbitrary
  shrink = genericShrink

-- | This instance does **not** create SortedRefts with anf symbols.
instance Arbitrary SortedReft where
  arbitrary = sized $ arbitrarySortedReft (const arbitrary) (const arbitrary)
  shrink = genericShrink

arbitrarySortedReft :: (Int -> Gen Sort) -> (Int -> Gen Symbol) -> Int -> Gen SortedReft
arbitrarySortedReft sortGen symGen n = do
  sort <- sortGen n
  eq <- arbitraryEqualityConstraint
  sym <- symGen n
  RR sort . reft sym . PAtom eq (EVar sym) <$> arbitrary

newtype IntSortedReft = IntSortedReft { unIntSortedReft :: SortedReft }
  deriving (Eq, Show)

instance Arbitrary IntSortedReft where
  arbitrary = sized $ fmap IntSortedReft . arbitrarySortedReft (const . pure $ FInt) (const arbitrary)

-- | Base environment with no declared properties; do not add an Arbitrary
-- instance to this and instead use newtypes.
newtype Env = Env { unEnv :: [(Symbol, SortedReft)] }
  deriving (Eq, Show, Generic)

shrinkEnv :: Env -> [Env]
shrinkEnv = fmap Env . traverse (traverse shrink) . unEnv

-- | Env without anf vars.
newtype NoAnfEnv = NoAnfEnv { unNoAnfEnv :: Env }
  deriving (Eq, Show, Generic)
instance Arbitrary NoAnfEnv where
  arbitrary = sized (fmap NoAnfEnv . arbitraryEnv gen)
    where
      -- | Note that this relies on the property that the Arbitrary instance for
      -- Symbol cannot create lq_anf$ vars.
      gen n = vectorOf n ((\a b -> (a, unIntSortedReft b)) <$> arbitrary <*> arbitrary)
  shrink = fmap NoAnfEnv . shrinkEnv . unNoAnfEnv

-- | Env with anf vars that do not reference further anf vars.
newtype FlatAnfEnv = FlatAnfEnv { unFlatAnfEnv :: Env }
  deriving (Eq, Show, Generic)
instance Arbitrary FlatAnfEnv where
  arbitrary = sized (fmap FlatAnfEnv . arbitraryEnv gen)
    where
      anfsGen n = vectorOf n ((\a b -> (unAnfSymbol a, unIntSortedReft b)) <$> arbitrary <*> arbitrary)
      gen = finalAnfGen anfsGen finalFlatGen
      finalFlatGen :: [(Symbol, SortedReft)] -> Gen (Symbol, SortedReft)
      -- This creates a final symbol which is either the conjunction or
      -- disjunction of all the anf symbols.
      finalFlatGen anfs = do
        conjOrDisj <- oneof $ pure <$> [T.PAnd, T.POr]
        let ultimateAnfExpr = conjOrDisj $ EVar . fst <$> anfs
        sym <- arbitrary
        ultimateAnfSym <- arbitrary
        pure (sym, RR FInt (reft ultimateAnfSym (PAtom Eq (EVar ultimateAnfSym) ultimateAnfExpr)))
  -- TODO
  shrink (FlatAnfEnv (Env (_x : xs))) = pure . FlatAnfEnv . Env $ xs
  shrink _ = mempty

-- | Given a generator for a bunch of (`Symbol`, `SortedReft`) pairs which bind
-- lq_anf$ vars, and another generator that takes those pairs and binds a
-- non-lq_anf$ var to some subset of them, this function generates those pairs
-- plus the \"final\" non-lq_anf$ expression, which represents the \"original\"
-- expression brought to ANF.
finalAnfGen :: (Int -> Gen [(Symbol, SortedReft)]) -> ([(Symbol, SortedReft)] -> Gen (Symbol, SortedReft)) -> Int -> Gen [(Symbol, SortedReft)]
finalAnfGen anfsGen finalGen n = do
  anfs <- anfsGen n
  ultimateAnf <- finalGen anfs
  pure $ ultimateAnf : anfs

-- | Create an arbitrary env up to size k with the given generator for Symbols
-- and SortedRefts
arbitraryEnv :: (Int -> Gen [(Symbol, SortedReft)]) -> Int -> Gen Env

arbitraryEnv gen k = Env <$> (choose (0, k) >>= gen)

-- | Env with anf vars that form a list of references.
newtype ChainedAnfEnv = ChainedAnfEnv { unChainedAnfEnv :: Env }
  deriving (Eq, Show, Generic)
instance Arbitrary ChainedAnfEnv where
  arbitrary = sized (fmap ChainedAnfEnv . arbitraryEnv gen)
    where
      gen = finalAnfGen (chainedAnfGen anfSymNGen) finalChainedGen
      finalChainedGen :: [(Symbol, SortedReft)] -> Gen (Symbol, SortedReft)
      finalChainedGen anfs =
        case anfs of
          -- No ANFs, so just an arbitrary int sorted expression will do
          [] -> fmap unIntSortedReft <$> arbitrary
          ((penultimateSym, _):_) -> do
            sym <- arbitrary
            let sreft = RR FInt (reft sym (PAtom Eq (EVar sym) (EVar penultimateSym)))
            (, sreft) <$> arbitrary
  -- TODO
  shrink (ChainedAnfEnv (Env (_x : xs))) = pure . ChainedAnfEnv . Env $ xs
  shrink _ = mempty

-- | Creates a "chain" of referencing `lq_anf$` var Symbols of length `n` such
-- that the first symbol references the second which references the third, and
-- so on.  The last symbol is bound to an arbitrary non-lq_anf$ var.
--
-- This list is in an acceptable form to be passed to `finalAnfGen` to close the
-- loop.
chainedAnfGen :: (Int -> Gen AnfSymbol) -> Int -> Gen [(Symbol, SortedReft)]
chainedAnfGen _ 0 = pure []
chainedAnfGen symGen n = do
  syms <- fmap unAnfSymbol <$> for [1..n+1] symGen
  finalSym <- arbitrary
  let symPairs :: [(Symbol, Symbol)]
      symPairs = pairs (syms ++ [finalSym])
  for symPairs $ \(sym, prevSym) -> do
    otherSym <- arbitrary
    prevSymExpr <- arbitraryExprInvolving prevSym n
    pure (sym, RR FInt (reft otherSym (PAtom Eq (EVar otherSym) prevSymExpr)))
  where
    pairs xs = zip xs (drop 1 xs)

-- This is not random, but is simplified so that you can make chains more
-- easily.
anfSymNGen :: Int -> Gen AnfSymbol
anfSymNGen i = pure . AnfSymbol . mappendSym anfPrefix . symbol . show $ i