packages feed

free-theorems-0.2: src/FrontendCheckLocalTests.hs



module FrontendCheckLocalTests (tests) where



import Control.Monad.Writer (runWriter)
import Data.Generics (Typeable, Data, everything, mkQ)
import Data.List (nub)
import Data.Maybe (fromJust, isJust, mapMaybe)
import Data.Set as Set
    (Set, empty, union, fromList, isSubsetOf, member, singleton)
import Test.QuickCheck

import Tests

import Language.Haskell.FreeTheorems.Syntax
import Language.Haskell.FreeTheorems.Frontend
import Language.Haskell.FreeTheorems.Frontend.Error
import Language.Haskell.FreeTheorems.Frontend.TypeExpressions
import Language.Haskell.FreeTheorems.Frontend.CheckLocal
import Language.Haskell.FreeTheorems.Frontend.CheckGlobal



-- | Runs all tests.

tests :: IO ()
tests = do

  doTest "check local data free variables" prop_checkLocalDataFreeVars
  doTest "check local data distinct variables" prop_checkLocalDataVars
  doTest "check local data not primitive" prop_checkLocalDataNotPrim
  doTest "check local data no fixed types" prop_checkLocalDataNoFixedTEs
  doTest "check local data has data constructor" prop_checkLocalDataNotEmpty
  doTest "check local data not nested" prop_checkLocalDataNotNested
  doTest "check local data no function nor abstraction"
    prop_checkLocalDataAbsFun

  doTest "check local newtype free variables" prop_checkLocalNewtypeFreeVars
  doTest "check local newtype distinct variables" prop_checkLocalNewtypeVars
  doTest "check local newtype not primitive" prop_checkLocalNewtypeNotPrim
  doTest "check local newtype no fixed types" prop_checkLocalNewtypeNoFixedTEs
  doTest "check local newtype not nested" prop_checkLocalNewtypeNotNested
  doTest "check local newtype no function nor abstraction"
    prop_checkLocalNewtypeAbsFun

  doTest "check local type free variables" prop_checkLocalTypeFreeVars
  doTest "check local type distinct variables" prop_checkLocalTypeVars
  doTest "check local type not primitive" prop_checkLocalTypeNotPrim
  doTest "check local type no fixed types" prop_checkLocalTypeNoFixedTEs
  doTest "check local type not nested" prop_checkLocalTypeNotNested

  doTest "check local class methods distinct"
    prop_checkLocalClassMethodsDistinct
  doTest "check local class variable is free in methods"
    prop_checkLocalClassFreeVar
  doTest "check local class not recursive" prop_checkLocalClassNotRecursive
  doTest "check local class not primitive" prop_checkLocalClassNotPrim
  doTest "check local class no fixed types" prop_checkLocalClassNoFixedTEs

  doTest "check local signature no fixed types"
    prop_checkLocalSignatureNoFixedTEs

   




------- Test local checks -----------------------------------------------------


-- | Property: Checks in data declarations that free variables of the right-hand
--   side are declared on the left-hand side.

prop_checkLocalDataFreeVars d = 
  checkInData d $ \d' ->
    allFreeVars (dataCons d') `Set.isSubsetOf` Set.fromList (dataVars d')
  where
    allFreeVars :: (Typeable a, Data a) => a -> Set.Set TypeVariable
    allFreeVars = everything (Set.union) (Set.empty `mkQ` freeVars)

    freeVars :: BangTypeExpression -> Set.Set TypeVariable
    freeVars = freeTypeVariables . withoutBang



-- | Property: Checks in data declarations that the left-hand side variables are
--   pairwise distinct.

prop_checkLocalDataVars d =
  checkInData d $ \d' ->
    length (nub (dataVars d')) == length (dataVars d')



-- | Property: Checks in data declarations that the declared type constructor is
--   not a primitive type.

prop_checkLocalDataNotPrim d =
  checkInData d $ \d' ->
    isNotPrimitive (dataName d')



-- | Property: Checks in data declarations that no FixedTypeExpression occurs
--   anywhere.

prop_checkLocalDataNoFixedTEs d =
  checkInData d $ \d' ->
    not (hasFixedTypeExpressions d')



-- | Property: Checks that data declarations have at least one data constructor.

prop_checkLocalDataNotEmpty d =
  checkInData d $ \d' ->
    not (null (dataCons d'))



-- | Property: Checks that data declarations are not nested.

prop_checkLocalDataNotNested d =
  checkInData d $ \d' ->
    not (isNested (dataName d') (dataCons d'))



-- | Property: Checks in newtype declarations that variables of the right-hand
--   side are declared on the left-hand side.

prop_checkLocalNewtypeFreeVars d =
  checkInNewtype d $ \d' ->
    freeTypeVariables (newtypeRhs d') 
        `Set.isSubsetOf` Set.fromList (newtypeVars d')



-- | Property: Checks in newtype declarations that left-hand side variables
--   are pairwise distinct.

prop_checkLocalNewtypeVars d =
  checkInNewtype d $ \d' ->
    length (nub (newtypeVars d')) == length (newtypeVars d')



-- | Property: Checks in newtype declarations that the declared type constructor
--   is not equal to the name of a primitive type.

prop_checkLocalNewtypeNotPrim d =
  checkInNewtype d $ \d' ->
    isNotPrimitive (newtypeName d')



-- | Property: Checks in newtype declarations that no FixedTypeExpression 
--   occurs.

prop_checkLocalNewtypeNoFixedTEs d =
  checkInNewtype d $ \d' ->
    not (hasFixedTypeExpressions d')



-- | Property: Checks that newtype declarations are not nested.

prop_checkLocalNewtypeNotNested d =
  checkInNewtype d $ \d' ->
    not (isNested (newtypeName d') (newtypeRhs d'))



-- | Property: Checks in type declarations that variables of the right-hand
--   side are declared on the left-hand side.

prop_checkLocalTypeFreeVars d =
  checkInType d $ \d' ->
    freeTypeVariables (typeRhs d') `Set.isSubsetOf` Set.fromList (typeVars d')



-- | Property: Checks in type declarations that left-hand side variables
--   are pairwise distinct.

prop_checkLocalTypeVars d =
  checkInType d $ \d' ->
    length (nub (typeVars d')) == length (typeVars d')



-- | Property: Checks in type declarations that the declared type constructor
--   is not equal to the name of a primitive type.

prop_checkLocalTypeNotPrim d =
  checkInType d $ \d' ->
    isNotPrimitive (typeName d')



-- | Property: Checks in type declarations that no FixedTypeExpression 
--   occurs.

prop_checkLocalTypeNoFixedTEs d =
  checkInType d $ \d' ->
    not (hasFixedTypeExpressions d')



-- | Property: Checks that type declarations are not recursive.

prop_checkLocalTypeNotNested d =
  checkInType d $ \d' ->
    not (isRecursive (typeName d') (typeRhs d'))



-- | Property: Checks in class declarations that the class methods have pairwise
--   distinct names.

prop_checkLocalClassMethodsDistinct d =
  checkInClass d $ \d' ->
    let methodNames = map signatureName (classFuns d')
     in length (nub methodNames) == length methodNames



-- | Property: Checks in class declarations that the class variable occurs free
--   in all class method types.

prop_checkLocalClassFreeVar d =
  checkInClass d $ \d' ->
    let set = Set.singleton (classVar d')
     in all (\t -> (classVar d') `Set.member` freeTypeVariables t)
            (map signatureType (classFuns d'))



-- | Property: Checks in class declarations that the class name does not occur
--   in a type expression of any class method.

prop_checkLocalClassNotRecursive d =
  checkInClass d $ \d' ->
    not (isRecursive (className d') (classFuns d'))    



-- | Property: Checks in class declarations that the declared class name
--   is not equal to the name of a primitive type.

prop_checkLocalClassNotPrim d =
  checkInClass d $ \d' ->
    isNotPrimitive (className d')



-- | Property: Checks in class declarations that no FixedTypeExpression 
--   occurs.

prop_checkLocalClassNoFixedTEs d =
  checkInClass d $ \d' ->
    not (hasFixedTypeExpressions d')



-- | Property: Checks in type signatures that no FixedTypeExpression occurs.

prop_checkLocalSignatureNoFixedTEs d =
  checkInSignature d $ \d' ->
    not (hasFixedTypeExpressions d')



-- | Property: Checks in data declarations that there is no type abstraction
--   and no function type constructor.

prop_checkLocalDataAbsFun d = prop_checkAbsFun [DataDecl d] forcedCheck
  where types = d :: DataDeclaration



-- | Property: Checks in newtype declarations that there is no type abstraction
--   and no function type constructor.

prop_checkLocalNewtypeAbsFun d = prop_checkAbsFun [NewtypeDecl d] forcedCheck
  where types = d :: NewtypeDeclaration



-- | Helper function to check that a value does not contain type abstractions
--   nor function type constructors.

prop_checkAbsFun d test = test hasNoAbsNorFun . runCheck $ d
  where
    runCheck = fst . runWriter . checkDataAndNewtypeDeclarations
    
    hasNoAbsNorFun = everything (&&) (True `mkQ` noAbsFun)
    
    noAbsFun t = case t of
      TypeFun _ _   -> False
      TypeAbs _ _ _ -> False
      otherwise     -> True





-- Test helper functions ------------------------------------------------------


-- | Runs a local check on a data declaration.

checkInData :: DataDeclaration -> (DataDeclaration -> Bool) -> Property
checkInData d prop = 
  trivialCheck prop . mapMaybe toData . runLocalCheck $ [DataDecl d]
  where
    toData d = case d of { DataDecl d' -> Just d' ; otherwise -> Nothing }



-- | Runs a local check on a newtype declaration.

checkInNewtype :: NewtypeDeclaration -> (NewtypeDeclaration -> Bool) -> Property
checkInNewtype d prop =
  forcedCheck prop . mapMaybe toNewtype . runLocalCheck $ [NewtypeDecl d]
  where
    toNewtype d = case d of { NewtypeDecl d' -> Just d' ; otherwise -> Nothing }



-- | Runs a local check on a type declaration.

checkInType :: TypeDeclaration -> (TypeDeclaration -> Bool) -> Property
checkInType d prop = 
  forcedCheck prop . mapMaybe toType . runLocalCheck $ [TypeDecl d]
  where
    toType d = case d of { TypeDecl d' -> Just d' ; otherwise -> Nothing }



-- | Runs a local check on a class declaration.

checkInClass :: ClassDeclaration -> (ClassDeclaration -> Bool) -> Property
checkInClass d prop = 
  forcedCheck prop . mapMaybe toClass . runLocalCheck $ [ClassDecl d]
  where
    toClass d = case d of { ClassDecl d' -> Just d' ; otherwise -> Nothing }



-- | Runs a local check on a type signature.

checkInSignature :: Signature -> (Signature -> Bool) -> Property
checkInSignature d prop = 
  forcedCheck prop . mapMaybe toSig . runLocalCheck $ [TypeSig d]
  where
    toSig d = case d of { TypeSig d' -> Just d' ; otherwise -> Nothing }



-- | Runs a check on the head of a list. This check forces the list ot have at
--   least one element.

forcedCheck :: (a -> Bool) -> [a] -> Property
forcedCheck prop xs = not (null xs) ==> prop (head xs)



-- | Runs a trivial check on a list. If the list is empty, the property is not
--   checked.

trivialCheck :: (a -> Bool) -> [a] -> Property
trivialCheck prop xs = 
  trivial (null xs) (if null xs then True else prop (head xs))



-- | Runs a local check.

runLocalCheck :: [Declaration] -> [Declaration]
runLocalCheck = fst . runWriter . checkLocal



-- | Tests if an identifier is not equal to one of the primitive type names.

isNotPrimitive :: Identifier -> Bool
isNotPrimitive i =
  unpackIdent i `notElem` [ "Int", "Integer", "Float", "Double", "Char" ]



-- | Tests if a given element contains FixedTypeExpressions.

hasFixedTypeExpressions :: (Typeable a, Data a) => a -> Bool
hasFixedTypeExpressions =
  everything (||) (False `mkQ` (const True :: FixedTypeExpression -> Bool))



-- | Tests if an element is nested.

isNested :: (Typeable a, Data a) => Identifier -> a -> Bool
isNested con = everything (||) (False `mkQ` nested)
  where
    nested t = case t of
      TypeCon (Con c) ts -> (c == con) && (any (not . isTypeVar) ts)
      otherwise          -> False

    isTypeVar t = case t of
      TypeVar _ -> True
      otherwise -> False



-- | Tests if an element is recursive.

isRecursive :: (Typeable a, Data a) => Identifier -> a -> Bool
isRecursive con = everything (||) (False `mkQ` (\c -> c == con))