packages feed

free-theorems-0.2: src/FrontendCheckGlobalTests.hs



module FrontendCheckGlobalTests (tests) where



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

import Tests
import Language.Haskell.FreeTheorems.Syntax
import Language.Haskell.FreeTheorems.Frontend as FT 
import Language.Haskell.FreeTheorems.Frontend.CheckGlobal




-- | Runs all tests.

tests :: IO ()
tests = do
  doTest "check global at most one declaration per name"
    prop_checkGlobalAtMostOneDeclPerName
  doTest "check global arities match" prop_checkGlobalAritiesMatch
  doTest "check global type class hierarchy acyclic"
    prop_checkGlobalAcyclicTypeClasses
  doTest "check global type synonyms not mutually recursive"
    prop_checkGlobalTypeSynonymsNotMutuallyRecursive
  doTest "check global only declared classes occur"
    prop_checkGlobalTypeClasses    
  doTest "check global only declared constructors occur"
    prop_checkGlobalConstructors





-- | Property: Check that there are no duplicate declarations by comparing the 
--   names of the declarations.

prop_checkGlobalAtMostOneDeclPerName ds0 =
  checkDecls ds0 $ \ds ->
    let names = map getDeclarationName ds
     in length (nub names) == length names



-- | Property: Checks that every type constructor is used with the arity it was
--   declared with. If any occurring type constructor is not declared, no arity
--   check is performed for it.

prop_checkGlobalAritiesMatch ds0 =
  checkDecls ds0 $ \ds ->
    everything (&&) (True `mkQ` checkArity ds) ds
    
checkArity ds t = case t of
  TypeCon con ts -> correctArity ds con (length ts)
  otherwise      -> True

correctArity ds con arity = case con of
  ConUnit    -> arity == 0
  ConList    -> arity == 1
  ConTuple n -> arity == n
  ConInt     -> arity == 0
  ConInteger -> arity == 0
  ConFloat   -> arity == 0
  ConDouble  -> arity == 0
  ConChar    -> arity == 0
  Con c      -> maybe True (== arity) (getArityFromDecl ds c)

getArityFromDecl ds c =
  case find (\d -> getDeclarationName d == c) ds of
    Just (DataDecl d)    -> Just . length . dataVars $ d
    Just (NewtypeDecl d) -> Just . length . newtypeVars $ d
    Just (TypeDecl d)    -> Just . length . typeVars $ d
    otherwise            -> Nothing



-- | Property: Checks that the type class hierarchy is acyclic.

prop_checkGlobalAcyclicTypeClasses ds0 =
  checkDecls ds0 $ \ds ->
    hasCycle classDeps ds

classDeps d = case d of
  ClassDecl d -> map (\(TC c) -> c) (superClasses d)
  otherwise   -> []



-- | Property: Checks that type synonyms are not mutually recursively declared.

prop_checkGlobalTypeSynonymsNotMutuallyRecursive ds0 =
  checkDecls ds0 $ \ds ->
    hasCycle (typeDeps (mapMaybe getTypeSynName ds)) ds

getTypeSynName d = case d of
  TypeDecl d -> Just (typeName d)
  otherwise  -> Nothing

typeDeps ds = everything (++) ([] `mkQ` getTypeCon)
  where
    getTypeCon t = case t of
      TypeCon (Con c) _ -> if c `elem` ds then [c] else []
      otherwise         -> []



-- | Property: Check that every occurring type class is declared.

prop_checkGlobalTypeClasses ds0 =
  checkDecls ds0 $ \ds ->
    occurringClasses ds `Set.isSubsetOf` declaredClasses ds
  
occurringClasses = everything Set.union (Set.empty `mkQ` occurring)
  where
    occurring (TC c) = Set.singleton c

declaredClasses = Set.fromList . mapMaybe getClassName
  where
    getClassName d = case d of
      ClassDecl d -> Just (className d)
      otherwise   -> Nothing



-- | Property: Check that every occurring type constructor is declared.

prop_checkGlobalConstructors ds0 =
  checkDecls ds0 $ \ds ->
    occurringCons ds `Set.isSubsetOf` declaredCons ds

occurringCons = everything Set.union (Set.empty `mkQ` occurring)
  where
    occurring con = case con of
      Con c     -> Set.singleton c
      otherwise -> Set.empty

declaredCons = Set.fromList . mapMaybe getConName
  where
    getConName d = case d of
      DataDecl d    -> Just (dataName d)
      NewtypeDecl d -> Just (newtypeName d)
      TypeDecl d    -> Just (typeName d)
      otherwise     -> Nothing







-- | Runs a property on to list of declarations. The first list is checked
--   and then fed to 'checkGlobal' along with the second list. The result
--   and the first (checked) list are then given to the property.

checkDecls :: ListOfDeclarations -> ([Declaration] -> Bool) -> Property
checkDecls ds prop =
  let ds' = fst . runWriter . checkGlobal [] . getDeclarations $ ds
   in not (null ds') ==> prop ds'
        


-- | Checks if the given list of declarations has any cycles. The test is based
--   on the provided function which computes the dependencies of a declaration.

hasCycle :: (Declaration -> [Identifier]) -> [Declaration] -> Bool
hasCycle deps ds = 
  any (\d -> cycle (length ds) d d) ds
  where
    cycle i d1 d2 =
      if i == 0
        then False
        else null (deps d2)
             || getDeclarationName d1 `elem` deps d2
             || any (cycle (i-1) d1) (declsDependingOn d2)

    declsDependingOn d =
      filter (\d' -> getDeclarationName d' `elem` deps d) ds