CurryCheck.curry 54.8 KB
Newer Older
Michael Hanus 's avatar
Michael Hanus committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
-------------------------------------------------------------------------
--- This is the implementation of the currycheck tool.
--- It performs various checks on Curry programs:
---
--- * Correct usage of set functions, non-strict unification,
---   default rules, DET annotations, contracts
--- * All EasyCheck tests are extracted and checked
--- * For all functions declared as deterministic,
---   determinism properties are generated and checked.
--- * For functions with postconditions (f'post), checks for postconditions
---   are generated (together with possible preconditions)
--- * For functions with specification (f'spec), checks for satisfaction
---   of these specifications are generated
---   (together with possible preconditions).
---
--- @author Michael Hanus, Jan-Patrick Baye
17
--- @version November 2018
Michael Hanus 's avatar
Michael Hanus committed
18 19
-------------------------------------------------------------------------

20 21 22 23 24 25 26 27 28
import AnsiCodes
import Char                    ( toUpper )
import Distribution
import FilePath                ( (</>), pathSeparator, takeDirectory )
import GetOpt
import List
import Maybe                   ( fromJust, isJust )
import System                  ( system, exitWith, getArgs, getPID, getEnviron )

Michael Hanus 's avatar
Michael Hanus committed
29 30 31 32
import AbstractCurry.Types
import AbstractCurry.Files
import AbstractCurry.Select
import AbstractCurry.Build
33 34 35
import AbstractCurry.Pretty    ( showCProg )
import AbstractCurry.Transform ( renameCurryModule, updCProg
                               , updQNamesInCProg, updQNamesInCFuncDecl )
Michael Hanus 's avatar
Michael Hanus committed
36 37 38 39
import qualified FlatCurry.Types as FC
import FlatCurry.Files
import qualified FlatCurry.Goodies as FCG

40
import CC.Config         ( packagePath, packageVersion )
41
import CC.Options
42
import CheckDetUsage     ( checkDetUse, containsDetOperations)
Michael Hanus 's avatar
Michael Hanus committed
43
import ContractUsage
44
import DefaultRuleUsage  ( checkDefaultRules, containsDefaultRules )
Michael Hanus 's avatar
Michael Hanus committed
45
import PropertyUsage
46 47 48 49
import SimplifyPostConds ( simplifyPostConditionsWithTheorems )
import TheoremUsage      ( determinismTheoremFor, existsProofFor
                         , getModuleProofFiles, getTheoremFunctions )
import UsageCheck        ( checkBlacklistUse, checkSetUse )
Michael Hanus 's avatar
Michael Hanus committed
50 51 52 53 54 55

-- Banner of this tool:
ccBanner :: String
ccBanner = unlines [bannerLine,bannerText,bannerLine]
 where
   bannerText = "CurryCheck: a tool for testing Curry programs (Version " ++
56
                packageVersion ++ " of 01/11/2018)"
Michael Hanus 's avatar
Michael Hanus committed
57 58 59 60
   bannerLine = take (length bannerText) (repeat '-')

-- Help text
usageText :: String
61
usageText = usageInfo ("Usage: curry-check [options] <module names>\n") options
Michael Hanus 's avatar
Michael Hanus committed
62

63 64 65
--- Maximal arity of check functions and tuples currently supported:
maxArity :: Int
maxArity = 5
Michael Hanus 's avatar
Michael Hanus committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

-------------------------------------------------------------------------
-- The names of suffixes added to specific tests.

defTypeSuffix :: String
defTypeSuffix = "_ON_BASETYPE"

postCondSuffix :: String
postCondSuffix = "SatisfiesPostCondition"

satSpecSuffix :: String
satSpecSuffix = "SatisfiesSpecification"

isDetSuffix :: String
isDetSuffix = "IsDeterministic"

-------------------------------------------------------------------------
-- Internal representation of tests extracted from a Curry module.
84 85 86 87 88 89 90 91
-- A test is
-- * a property test (with a name, type, source line number),
-- * an IO test (with a name and source line number), or
-- * an operation equivalence test (with a name, the names of both operations,
--   and their type, and the source line number).
data Test = PropTest  QName CTypeExpr Int
          | IOTest    QName Int
          | EquivTest QName QName QName CTypeExpr Int
Michael Hanus 's avatar
Michael Hanus committed
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106

-- Is the test an IO test?
isIOTest :: Test -> Bool
isIOTest t = case t of IOTest _ _ -> True
                       _          -> False
-- Is the test a unit test?
isUnitTest :: Test -> Bool
isUnitTest t = case t of PropTest _ texp _ -> null (argTypes texp)
                         _                 -> False

-- Is the test a property test?
isPropTest :: Test -> Bool
isPropTest t = case t of PropTest _ texp _ -> not (null (argTypes texp))
                         _                 -> False

107 108 109 110 111
-- Is the test an equivalence test?
isEquivTest :: Test -> Bool
isEquivTest t = case t of EquivTest _ _ _ _ _ -> True
                          _                   -> False

112 113 114 115 116
-- Returns the names of the operations of an equivalence test.
equivTestOps :: Test -> [QName]
equivTestOps t = case t of EquivTest _ f1 f2 _ _ -> [f1,f2]
                           _                     -> []

Michael Hanus 's avatar
Michael Hanus committed
117
-- The name of a test:
118 119 120 121
testName :: Test -> QName
testName (PropTest  n _     _) = n
testName (IOTest    n       _) = n
testName (EquivTest n _ _ _ _) = n
Michael Hanus 's avatar
Michael Hanus committed
122 123

-- The line number of a test:
124 125 126 127
testLine :: Test -> Int
testLine (PropTest  _ _     n) = n
testLine (IOTest    _       n) = n
testLine (EquivTest _ _ _ _ n) = n
Michael Hanus 's avatar
Michael Hanus committed
128 129 130 131

-- Generates a useful error message for tests (with module and line number)
genTestMsg :: String -> Test -> String
genTestMsg file test =
132 133 134 135 136 137 138 139
  snd (testName test) ++
  " (module " ++ file ++ ", line " ++ show (testLine test) ++ ")"

-- Generates the name of a test in the main test module from the test name.
genTestName :: Test -> String
genTestName test =
  let (modName, fName) = testName test
  in fName ++ "_" ++ modNameToId modName
Michael Hanus 's avatar
Michael Hanus committed
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166

-------------------------------------------------------------------------
-- Representation of the information about a module to be tested:
-- * the original name of the module to be tested
-- * the name of the transformed (public) test module
-- * static errors (e.g., illegal uses of set functions)
-- * test operations
-- * name of generators defined in this module (i.e., starting with "gen"
--   and of appropriate result type)
data TestModule = TestModule
  { orgModuleName  :: String
  , testModuleName :: String
  , staticErrors   :: [String]
  , propTests      :: [Test]
  , generators     :: [QName]
  }

-- A test module with only static errors.
staticErrorTestMod :: String -> [String] -> TestModule
staticErrorTestMod modname staterrs =
 TestModule modname modname staterrs [] []

-- Is this a test module that should be tested?
testThisModule :: TestModule -> Bool
testThisModule tm = null (staticErrors tm) && not (null (propTests tm))

-- Extracts all user data types used as test data generators.
167 168 169
-- Each type has a flag which is `True` if the test data should contain
-- partial values (for checking equivalence of operations).
userTestDataOfModule :: TestModule -> [(QName,Bool)]
Michael Hanus 's avatar
Michael Hanus committed
170 171 172
userTestDataOfModule testmod = concatMap testDataOf (propTests testmod)
 where
  testDataOf (IOTest _ _) = []
173 174 175 176 177 178 179 180 181 182 183 184 185
  testDataOf (PropTest _ texp _) =
    map (\t -> (t,False)) (filter (\ (mn,_) -> mn /= preludeName)
                                  (unionOn tconsOf (argTypes texp)))
  testDataOf (EquivTest _ _ _ texp _) =
    map (\t -> (t,True)) (unionOn tconsOf (argTypes texp))

-- Extracts all result data types used in equivalence properties.
equivPropTypes :: TestModule -> [QName]
equivPropTypes testmod = concatMap equivTypesOf (propTests testmod)
 where
  equivTypesOf (IOTest _ _) = []
  equivTypesOf (PropTest _ _ _) = []
  equivTypesOf (EquivTest _ _ _ texp _) = tconsOf (resultType texp)
Michael Hanus 's avatar
Michael Hanus committed
186 187

-------------------------------------------------------------------------
188 189
-- Transform all tests of a module into operations that perform
-- appropriate calls to EasyCheck:
190 191
genTestFuncs :: Options -> String -> TestModule -> [CFuncDecl]
genTestFuncs opts mainmod tm = map createTest (propTests tm)
Michael Hanus 's avatar
Michael Hanus committed
192 193
 where
  createTest test =
194
    cfunc (mainmod, (genTestName $ testName test)) 0 Public
Michael Hanus 's avatar
Michael Hanus committed
195
          (ioType (maybeType stringType))
196 197 198 199 200 201 202 203 204 205 206 207 208
          (case test of
             PropTest  name t _       -> propBody   name t test
             IOTest    name   _       -> ioTestBody name test
             EquivTest name f1 f2 t _ ->
               -- if test name has suffix "'TERMINATE", the test for terminating
               -- operations is used, otherwise the test for arbitrary
               -- operations (which limits the size of computed results
               -- but might find counter-examples later)
               -- (TODO: automatic selection by using CASS)
               if "'TERMINATE" `isSuffixOf` map toUpper (snd name)
                 then equivBodyTerm f1 f2 t test
                 else equivBodyAny  f1 f2 t test
          )
Michael Hanus 's avatar
Michael Hanus committed
209 210 211 212 213 214 215 216 217 218 219 220 221

  msgOf test = string2ac $ genTestMsg (orgModuleName tm) test

  testmname = testModuleName tm
  
  genTestName (modName, fName) = fName ++ "_" ++ modNameToId modName

  easyCheckFuncName arity =
    if arity>maxArity
    then error $ "Properties with more than " ++ show maxArity ++
                 " parameters are currently not supported!"
    else (easyCheckExecModule,"checkWithValues" ++ show arity)

222 223
  -- Operation equivalence test for terminating operations:
  equivBodyTerm f1 f2 texp test =
224
    let xvars = map (\i -> (i,"x"++show i)) [1 .. arityOfType texp]
225 226
        pvalOfFunc = ctype2pvalOf mainmod "pvalOf" (resultType texp)
    in propOrEquivBody (map (\t -> (t,True)) (argTypes texp)) test
227
         (CLambda (map CPVar xvars)
228
            (applyF (easyCheckModule,"<~>")
229 230
                    [applyE pvalOfFunc [applyF f1 (map CVar xvars)],
                     applyE pvalOfFunc [applyF f2 (map CVar xvars)]]))
231 232 233

  -- Operation equivalence test for arbitrary operations:
  equivBodyAny f1 f2 texp test =
234 235
    let xvars = map (\i -> (i,"x"++show i)) [1 .. arityOfType texp]
        pvar  = (2,"p")
236 237 238 239 240
        pvalOfFunc = ctype2pvalOf mainmod "peval" (resultType texp)
    in propOrEquivBody
         (map (\t -> (t,True)) (argTypes texp) ++
          [(ctype2BotType mainmod  (resultType texp), False)])
         test
241
         (CLambda (map CPVar xvars ++ [CPVar pvar])
242
            (applyF (easyCheckModule,"<~>")
243 244
               [applyE pvalOfFunc [applyF f1 (map CVar xvars), CVar pvar],
                applyE pvalOfFunc [applyF f2 (map CVar xvars), CVar pvar]]))
245 246 247 248 249 250

  propBody qname texp test =
    propOrEquivBody (map (\t -> (t,False)) (argTypes texp))
                    test (CSymbol (testmname,snd qname))

  propOrEquivBody argtypes test propexp =
Michael Hanus 's avatar
Michael Hanus committed
251
    [simpleRule [] $
252 253 254 255
      CLetDecl [CLocalPat (CPVar msgvar) (CSimpleRhs (msgOf test) [])]
               (applyF (easyCheckExecModule, "checkPropWithMsg")
                 [ CVar msgvar
                 , applyF (easyCheckFuncName (length argtypes)) $
Michael Hanus 's avatar
Michael Hanus committed
256
                    [configOpWithMaxFail, CVar msgvar] ++
257
                    (map (\ (t,genpart) ->
Michael Hanus 's avatar
Michael Hanus committed
258 259
                          applyF (easyCheckModule,"valuesOfSearchTree")
                            [if isPAKCS || useUserDefinedGen t || isFloatType t
260
                             then type2genop mainmod tm genpart t
Michael Hanus 's avatar
Michael Hanus committed
261 262 263
                             else applyF (searchTreeModule,"someSearchTree")
                                         [constF (pre "unknown")]])
                         argtypes) ++
264 265
                    [propexp]
                 ])]
Michael Hanus 's avatar
Michael Hanus committed
266
   where
267
    useUserDefinedGen te = case te of
Michael Hanus 's avatar
Michael Hanus committed
268 269 270 271 272 273 274 275 276 277
      CTVar _         -> error "No polymorphic generator!"
      CFuncType _ _   -> error "No generator for functional types!"
      CTCons (_,tc) _ -> isJust
                           (find (\qn -> "gen"++tc == snd qn) (generators tm))

    configOpWithMaxTest =
      let n = optMaxTest opts
       in if n==0 then stdConfigOp
                  else applyF (easyCheckExecModule,"setMaxTest")
                              [cInt n, stdConfigOp]
278

Michael Hanus 's avatar
Michael Hanus committed
279 280 281 282 283
    configOpWithMaxFail =
      let n = optMaxFail opts
       in if n==0 then configOpWithMaxTest
                  else applyF (easyCheckExecModule,"setMaxFail")
                              [cInt n, configOpWithMaxTest]
284

Michael Hanus 's avatar
Michael Hanus committed
285
    msgvar = (0,"msg")
286

Michael Hanus 's avatar
Michael Hanus committed
287
  stdConfigOp = constF (easyCheckConfig opts)
288

Michael Hanus 's avatar
Michael Hanus committed
289 290 291 292 293 294 295 296 297 298 299 300 301
  ioTestBody (_, name) test =
    [simpleRule [] $ applyF (easyCheckExecModule,"checkPropIOWithMsg")
                            [stdConfigOp, msgOf test, CSymbol (testmname,name)]]

-- The configuration option for EasyCheck
easyCheckConfig :: Options -> QName
easyCheckConfig opts =
  (easyCheckExecModule,
   if isQuiet opts     then "quietConfig"   else
   if optVerb opts > 2 then "verboseConfig"
                       else "easyConfig")

-- Translates a type expression into calls to generator operations.
302 303 304 305 306 307 308
-- If the third argument is `True`, calls to partial generators are used.
type2genop :: String -> TestModule -> Bool -> CTypeExpr -> CExpr
type2genop _ _ _ (CTVar _)       = error "No polymorphic generator!"
type2genop _ _ _ (CFuncType _ _) = error "No generator for functional types!"
type2genop mainmod tm genpart (CTCons qt targs) =
  applyF (typename2genopname mainmod (generators tm) genpart qt)
         (map (type2genop mainmod tm genpart) targs)
Michael Hanus 's avatar
Michael Hanus committed
309 310 311 312 313

isFloatType :: CTypeExpr -> Bool
isFloatType texp = case texp of CTCons tc [] -> tc == (preludeName,"Float")
                                _            -> False

314 315 316 317 318 319 320 321 322
-- Translates the name of a type constructor into the name of the
-- generator operation for values of this type.
-- The first argument is the name of the main module.
-- The second argument contains the user-defined generator operations.
-- If the third argument is `True`, generators for partial values are used.
typename2genopname :: String -> [QName] -> Bool -> QName -> QName
typename2genopname mainmod definedgenops genpart (mn,tc)
  | genpart  -- we use our own generator for partial values:
  = (mainmod, "gen_" ++ modNameToId mn ++ "_" ++ transQN tc ++ "_PARTIAL")
Michael Hanus 's avatar
Michael Hanus committed
323 324
  | isJust maybeuserdefined -- take user-defined generator:
  = fromJust maybeuserdefined
325 326
  | mn==preludeName
  = (generatorModule, "gen" ++ transQN tc)
Michael Hanus 's avatar
Michael Hanus committed
327
  | otherwise -- we use our own generator:
328 329
  = (mainmod, "gen_" ++ modNameToId mn ++ "_" ++ transQN tc ++
              if genpart then "_PARTIAL" else "")
Michael Hanus 's avatar
Michael Hanus committed
330 331
 where
  maybeuserdefined = find (\qn -> "gen"++tc == snd qn) definedgenops
332 333 334 335 336 337 338 339 340 341 342 343

-- Transform a qualified (typ) constructor name into a name
-- with alpha-numeric characters.
transQN :: String -> String
transQN tcons | tcons == "[]"     = "List"
              | tcons == ":"      = "Cons"
              | tcons == "()"     = "Unit"
              | tcons == "(,)"    = "Pair"
              | tcons == "(,,)"   = "Triple"
              | tcons == "(,,,)"  = "Tuple4"
              | tcons == "(,,,,)" = "Tuple5"
              | otherwise         = tcons
Michael Hanus 's avatar
Michael Hanus committed
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372

-------------------------------------------------------------------------
-- Turn all functions into public ones.
-- This ensures that all tests can be executed.
makeAllPublic :: CurryProg -> CurryProg
makeAllPublic (CurryProg modname imports typedecls functions opdecls) =
  CurryProg modname stimports typedecls publicFunctions opdecls
 where
  stimports = if generatorModule `elem` imports &&
                 searchTreeModule `notElem` imports
              then searchTreeModule : imports -- just to be safe if module
                                              -- contains generator definitions
              else imports

  publicFunctions = map makePublic $ map ignoreComment functions

  -- since we create a copy of the module, we can ignore unnecessary data
  ignoreComment :: CFuncDecl -> CFuncDecl
  ignoreComment (CmtFunc _ name arity visibility typeExpr rules) =
    CFunc name arity visibility typeExpr rules
  ignoreComment x@(CFunc      _     _          _        _     _) = x

  makePublic :: CFuncDecl -> CFuncDecl
  makePublic (CFunc name arity _      typeExpr rules) =
              CFunc name arity Public typeExpr rules
  makePublic (CmtFunc cmt name arity _      typeExpr rules) =
              CmtFunc cmt name arity Public typeExpr rules

-- classify the tests as either PropTest or IOTest
373 374
classifyTests :: Options -> CurryProg -> [CFuncDecl] -> [Test]
classifyTests opts prog = map makeProperty
Michael Hanus 's avatar
Michael Hanus committed
375
 where
376 377 378 379 380
  makeProperty test =
    if isPropIOType (funcType test)
      then IOTest tname 0
      else maybe (PropTest tname (funcType test) 0)
                 (\ (f1,f2) -> EquivTest tname f1 f2
381
                                         (poly2defaultType opts (funcTypeOf f1))
382 383 384 385 386 387 388
                                         0)
                 (isEquivProperty test)
    where tname = funcName test

  funcTypeOf f = maybe (error $ "Cannot find type of " ++ show f ++ "!")
                       funcType
                       (find (\fd -> funcName fd == f) (functions prog))
Michael Hanus 's avatar
Michael Hanus committed
389 390 391

-- Extracts all tests from a given Curry module and transforms
-- all polymorphic tests into tests on a base type.
392 393
-- The second argument contains the names of existing proof files.
-- It is used to ignore tests when the properties are already proved.
394 395
-- The third argument contains the list of function representing
-- proved properties. It is used to simplify post conditions to be tested.
Michael Hanus 's avatar
Michael Hanus committed
396 397
-- The result contains a triple consisting of all actual tests,
-- all ignored tests, and the public version of the original module.
398
transformTests :: Options -> [String] -> [CFuncDecl] -> CurryProg
Michael Hanus 's avatar
Michael Hanus committed
399
               -> IO ([CFuncDecl],[CFuncDecl],CurryProg)
400
transformTests opts prfnames theofuncs
Michael Hanus 's avatar
Michael Hanus committed
401 402 403 404 405 406 407 408
               prog@(CurryProg mname imps typeDecls functions opDecls) = do
  simpfuncs <- simplifyPostConditionsWithTheorems (optVerb opts) theofuncs funcs
  let preCondOps  = preCondOperations simpfuncs
      postCondOps = map ((\ (mn,fn) -> (mn, fromPostCondName fn)) . funcName)
                        (funDeclsWith isPostCondName simpfuncs)
      specOps     = map ((\ (mn,fn) -> (mn, fromSpecName fn)) . funcName)
                        (funDeclsWith isSpecName simpfuncs)
      -- generate post condition tests:
409 410
      postCondTests =
        concatMap (genPostCondTest preCondOps postCondOps prfnames) funcs
Michael Hanus 's avatar
Michael Hanus committed
411
      -- generate specification tests:
412
      specOpTests   = concatMap (genSpecTest preCondOps specOps prfnames) funcs
Michael Hanus 's avatar
Michael Hanus committed
413 414 415 416

      (realtests,ignoredtests) = partition fst $
        if not (optProp opts)
        then []
417
        else concatMap (poly2default opts) $
Michael Hanus 's avatar
Michael Hanus committed
418
               -- ignore already proved properties:
419 420
               filter (\fd -> not (existsProofFor (orgQName (funcName fd))
                                                  prfnames))
Michael Hanus 's avatar
Michael Hanus committed
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
                      usertests ++
               (if optSpec opts then postCondTests ++ specOpTests else [])
  return (map snd realtests,
          map snd ignoredtests,
          CurryProg mname
                    (nub (easyCheckModule:imps))
                    typeDecls
                    (simpfuncs ++ map snd (realtests ++ ignoredtests))
                    opDecls)
 where
  (usertests, funcs) = partition isProperty functions


-- Extracts all determinism tests from a given Curry module and
-- transforms deterministic operations back into non-deterministic operations
-- in order to test their determinism property.
-- The result contains a triple consisting of all actual determinism tests,
-- all ignored tests (since they are polymorphic), and the public version
-- of the transformed original module.
transformDetTests :: Options -> [String] -> CurryProg
                  -> ([CFuncDecl],[CFuncDecl],CurryProg)
transformDetTests opts prooffiles
                  (CurryProg mname imports typeDecls functions opDecls) =
  (map snd realtests, map snd ignoredtests,
   CurryProg mname
             (nub (easyCheckModule:imports))
             typeDecls
             (map (revertDetOpTrans detOpNames) functions ++
              map snd (realtests ++ ignoredtests))
             opDecls)
 where
  preCondOps = preCondOperations functions
453

Michael Hanus 's avatar
Michael Hanus committed
454 455 456 457 458 459 460 461 462 463 464
  -- generate determinism tests:
  detOpTests = genDetOpTests prooffiles preCondOps functions

  -- names of deterministic operations:
  detOpNames = map (stripIsDet . funcName) detOpTests

  stripIsDet (mn,fn) = (mn, take (length fn -15) fn)

  (realtests,ignoredtests) = partition fst $
    if not (optProp opts)
    then []
465
    else concatMap (poly2default opts)
Michael Hanus 's avatar
Michael Hanus committed
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
                   (if optDet opts then detOpTests else [])

-- Get all operations with a defined precondition from a list of functions.
preCondOperations :: [CFuncDecl] -> [QName]
preCondOperations fdecls =
  map ((\ (mn,fn) -> (mn,fromPreCondName fn)) . funcName)
      (funDeclsWith isPreCondName fdecls)

-- Filter functions having a name satisfying a given predicate.
funDeclsWith :: (String -> Bool) -> [CFuncDecl] -> [CFuncDecl]
funDeclsWith pred = filter (pred . snd . funcName)

-- Transforms a function type into a property type, i.e.,
-- t1 -> ... -> tn -> t  is transformed into  t1 -> ... -> tn -> Prop
propResultType :: CTypeExpr -> CTypeExpr
propResultType te = case te of
  CFuncType from to -> CFuncType from (propResultType to)
  _                 -> baseType (easyCheckModule,"Prop")

-- Transforms a function declaration into a post condition test if
-- there is a post condition for this function (i.e., a relation named
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
-- f'post) and there is no proof file for this post condition.
-- The generated post condition test is of the form
--     fSatisfiesPostCondition x1...xn y = always (f'post x1...xn (f x1...xn))
genPostCondTest :: [QName] -> [QName] -> [String] -> CFuncDecl -> [CFuncDecl]
genPostCondTest prefuns postops prooffnames (CmtFunc _ qf ar vis texp rules) =
  genPostCondTest prefuns postops prooffnames (CFunc qf ar vis texp rules)
genPostCondTest prefuns postops prooffnames (CFunc qf@(mn,fn) _ _ texp _) =
 if qf `notElem` postops || existsProofFor (orgQName postname) prooffnames
   then []
   else
    [CFunc postname ar Public (propResultType texp)
      [simpleRule (map CPVar cvars) $
        if qf `elem` prefuns
         then applyF (easyCheckModule,"==>")
                     [applyF (mn,toPreCondName fn) (map CVar cvars), postprop]
         else postprop
      ]]
Michael Hanus 's avatar
Michael Hanus committed
504
 where
505
  postname = (mn, fn ++ postCondSuffix) -- name of generated post cond. test
Michael Hanus 's avatar
Michael Hanus committed
506 507 508 509 510 511 512 513 514
  ar       = arityOfType texp
  cvars    = map (\i -> (i,"x"++show i)) [1 .. ar]
  rcall    = applyF qf (map CVar cvars)
  postprop = applyF (easyCheckModule,"always")
                    [applyF (mn,toPostCondName fn)
                            (map CVar cvars ++ [rcall])]

-- Transforms a function declaration into a specification test if
-- there is a specification for this function (i.e., an operation named
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
-- f'spec) and there is no proof file for the satisfaction of the specification.
-- The generated specification test is of the form
--     fSatisfiesSpecification x1...xn =
--       f'pre x1...xn  ==> (f x1...xn <~> f'spec x1...xn)
genSpecTest :: [QName] -> [QName] -> [String] -> CFuncDecl -> [CFuncDecl]
genSpecTest prefuns specops prooffnames (CmtFunc _ qf ar vis texp rules) =
  genSpecTest prefuns specops prooffnames (CFunc qf ar vis texp rules)
genSpecTest prefuns specops prooffnames (CFunc qf@(mn,fn) _ _ texp _) =
 if qf `notElem` specops || existsProofFor (orgQName sptname) prooffnames
   then []
   else
    [CFunc sptname ar Public (propResultType texp)
      [simpleRule (map CPVar cvars) $
         addPreCond (applyF (easyCheckModule,"<~>")
                            [applyF qf (map CVar cvars),
                             applyF (mn,toSpecName fn) (map CVar cvars)])]]
Michael Hanus 's avatar
Michael Hanus committed
531
 where
532 533 534
  sptname = (mn, fn ++ satSpecSuffix) -- name of generated specification test
  cvars   = map (\i -> (i,"x"++show i)) [1 .. ar]
  ar      = arityOfType texp
Michael Hanus 's avatar
Michael Hanus committed
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555

  addPreCond exp = if qf `elem` prefuns
                   then applyF (easyCheckModule,"==>")
                          [applyF (mn,toPreCondName fn) (map CVar cvars), exp]
                   else exp

-- Revert the transformation for deterministic operations performed
-- by currypp, i.e., replace rule "f x = selectValue (set f_ORGNDFUN x)"
-- with "f = f_ORGNDFUN".
revertDetOpTrans :: [QName] -> CFuncDecl -> CFuncDecl
revertDetOpTrans  detops (CmtFunc _ qf ar vis texp rules) =
  revertDetOpTrans detops (CFunc qf ar vis texp rules)
revertDetOpTrans detops fdecl@(CFunc qf@(mn,fn) ar vis texp _) =
  if qf `elem` detops
  then CFunc qf ar vis texp [simpleRule [] (constF (mn,fn++"_ORGNDFUN"))]
  else fdecl

-- Look for operations named f_ORGNDFUN and create a determinism property
-- for f.
genDetOpTests :: [String] -> [QName] -> [CFuncDecl] -> [CFuncDecl]
genDetOpTests prooffiles prefuns fdecls =
556
  map (genDetProp prefuns) (filter (isDetOrgOp . funcName) fdecls)
Michael Hanus 's avatar
Michael Hanus committed
557
 where
558 559 560 561 562
  isDetOrgOp (mn,fn) =
    "_ORGNDFUN" `isSuffixOf` fn &&
    not (existsProofFor (mnorg, determinismTheoremFor (take (length fn - 9) fn))
                        prooffiles)
   where mnorg = take (length mn - 10) mn -- remove _PUBLICDET suffix
Michael Hanus 's avatar
Michael Hanus committed
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585

-- Transforms a declaration of a deterministic operation f_ORGNDFUN
-- into a determinisim property test of the form
-- fIsDeterministic x1...xn = f x1...xn #< 2
genDetProp :: [QName] -> CFuncDecl -> CFuncDecl
genDetProp prefuns (CmtFunc _ qf ar vis texp rules) =
  genDetProp prefuns (CFunc qf ar vis texp rules)
genDetProp prefuns (CFunc (mn,fn) ar _ texp _) =
  CFunc (mn, forg ++ isDetSuffix) ar Public
   (propResultType texp)
   [simpleRule (map CPVar cvars) $
      if (mn,forg) `elem` prefuns
       then applyF (easyCheckModule,"==>")
                   [applyF (mn,toPreCondName forg) (map CVar cvars), rnumcall]
       else rnumcall ]
 where
  forg     = take (length fn - 9) fn
  cvars    = map (\i -> (i,"x"++show i)) [1 .. ar]
  forgcall = applyF (mn,forg) (map CVar cvars)
  rnumcall = applyF (easyCheckModule,"#<") [forgcall, cInt 2]

-- Generates auxiliary (base-type instantiated) test functions for
-- polymorphically typed test function.
586 587 588 589 590 591 592
-- The returned flag indicates whether the test function should actually
-- be passed to the test tool.
-- Thus, IO tests are flagged by `False` if the corresponding option is set.
poly2default :: Options -> CFuncDecl -> [(Bool,CFuncDecl)]
poly2default opts (CmtFunc _ name arity vis ftype rules) =
  poly2default opts (CFunc name arity vis ftype rules)
poly2default opts fdecl@(CFunc (mn,fname) arity vis ftype rs)
Michael Hanus 's avatar
Michael Hanus committed
593 594
  | isPolyType ftype
  = [(False,fdecl)
595
    ,(True, CFunc (mn,fname++defTypeSuffix) arity vis
596
                  (poly2defaultType opts ftype)
597
                  [simpleRule [] (applyF (mn,fname) [])])
Michael Hanus 's avatar
Michael Hanus committed
598
    ]
599 600
  | not (optIOTest opts) && isPropIOType ftype
  = [(False,fdecl)]
Michael Hanus 's avatar
Michael Hanus committed
601 602
  | otherwise
  = [(True,fdecl)]
603

604 605
poly2defaultType :: Options -> CTypeExpr -> CTypeExpr
poly2defaultType opts texp = p2dt texp 
Michael Hanus 's avatar
Michael Hanus committed
606
 where
607
  p2dt (CTVar _)         = baseType (pre (optDefType opts))
Michael Hanus 's avatar
Michael Hanus committed
608
  p2dt (CFuncType t1 t2) = CFuncType (p2dt t1) (p2dt t2)
609
  p2dt (CTCons ct ts)    = CTCons ct (map p2dt ts)
Michael Hanus 's avatar
Michael Hanus committed
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624

-- Transforms a possibly changed test name (like "test_ON_BASETYPE")
-- back to its original name.
orgTestName :: QName -> QName
orgTestName (mn,tname)
  | defTypeSuffix `isSuffixOf` tname
  = orgTestName (mn, stripSuffix tname defTypeSuffix)
  | isDetSuffix `isSuffixOf` tname
  = orgTestName (mn, take (length tname - 15) tname)
  | postCondSuffix `isSuffixOf` tname
  = orgTestName (mn, stripSuffix tname postCondSuffix)
  | satSpecSuffix `isSuffixOf` tname
  = orgTestName (mn, stripSuffix tname satSpecSuffix)
  | otherwise = (mn,tname)

625 626 627 628 629 630 631 632 633 634 635 636 637 638
-- Transforms a possibly changed qualified name, e.g., `("Mod_PUBLIC","f")`
-- or `("Mod_PUBLICDET","f")`, back to its original name by removing the
-- module suffix.
orgQName :: QName -> QName
orgQName (mn,fn)
  | publicSuffix `isSuffixOf` mn
  = (stripSuffix mn publicSuffix, fn)
  | publicdetSuffix `isSuffixOf` mn
  = (stripSuffix mn publicdetSuffix, fn)
  | otherwise = (mn,fn)
 where
  publicSuffix    = "_PUBLIC"
  publicdetSuffix = "_PUBLICDET"

Michael Hanus 's avatar
Michael Hanus committed
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
-- This function implements the first phase of CurryCheck: it analyses
-- a module to be checked, i.e., it finds the tests, creates new tests
-- (e.g., for polymorphic properties, deterministic functions, post
-- conditions, specifications)
-- and generates a copy of the module appropriate for the main operation
-- of CurryCheck (e.g., all operations are made public).
-- If there are determinism tests, it also generates a second copy
-- where all deterministic functions are defined as non-deterministic
-- so that these definitions are tested.
analyseModule :: Options -> String -> IO [TestModule]
analyseModule opts modname = do
  putStrIfNormal opts $ withColor opts blue $
                        "Analyzing module '" ++ modname ++ "'...\n"
  catch (readCurryWithParseOptions modname (setQuiet True defaultParams) >>=
         analyseCurryProg opts modname)
        (\_ -> return [staticErrorTestMod modname
                         ["Module '"++modname++"': incorrect source program"]])

-- Analyse a Curry module for static errors:
staticProgAnalysis :: Options -> String -> String -> CurryProg
                   -> IO ([String],[(QName,String)])
staticProgAnalysis opts modname progtxt prog = do
661
  putStrIfDetails opts "Checking source code for static errors...\n"
Michael Hanus 's avatar
Michael Hanus committed
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
  useerrs <- if optSource opts then checkBlacklistUse prog else return []
  seterrs <- if optSource opts then readFlatCurry modname >>= checkSetUse
                               else return []
  let defruleerrs = if optSource opts then checkDefaultRules prog else []
  untypedprog <- readUntypedCurry modname
  let detuseerrs   = if optSource opts then checkDetUse untypedprog else []
      contracterrs = checkContractUse prog
      staticerrs = concat [seterrs,useerrs,defruleerrs,detuseerrs,contracterrs]
      missingCPP =
       if (containsDefaultRules prog || containsDetOperations untypedprog)
          && not (containsPPOptionLine progtxt)
       then ["'" ++ modname ++
           "' uses default rules or det. operations but not the preprocessor!",
           "Hint: insert line: {-# OPTIONS_CYMAKE -F --pgmF=currypp #-}"]
       else []
  return (missingCPP,staticerrs)

-- Analyse a Curry module and generate property tests:
analyseCurryProg :: Options -> String -> CurryProg -> IO [TestModule]
analyseCurryProg opts modname orgprog = do
  -- First we rename all references to Test.Prop into Test.EasyCheck
  let prog = renameProp2EasyCheck orgprog
  (topdir,srcfilename) <- lookupModuleSourceInLoadPath modname >>=
        return .
        maybe (error $ "Source file of module '"++modname++"' not found!") id
  let srcdir = takeDirectory srcfilename
688
  putStrLnIfDebug opts $ "Source file: " ++ srcfilename
689 690 691
  prooffiles <- if optProof opts
                  then getModuleProofFiles srcdir modname
                  else return []
692
  unless (null prooffiles) $ putStrIfDetails opts $
Michael Hanus 's avatar
Michael Hanus committed
693 694 695 696 697
    unlines ("Proof files found:" : map ("- " ++) prooffiles)
  progtxt <- readFile srcfilename
  (missingCPP,staticoperrs) <- staticProgAnalysis opts modname progtxt prog
  let words      = map firstWord (lines progtxt)
      staticerrs = missingCPP ++ map (showOpError words) staticoperrs
698
  putStrIfDetails opts "Generating property tests...\n"
699 700 701 702 703 704 705
  theofuncs <- if optProof opts then getTheoremFunctions srcdir prog
                                else return []
  -- compute already proved theorems for public module:
  let pubmodname = modname++"_PUBLIC"
      rnm2pub mn@(mod,n) | mod == modname = (pubmodname,n)
                         | otherwise      = mn
      theopubfuncs = map (updQNamesInCFuncDecl rnm2pub) theofuncs
Michael Hanus 's avatar
Michael Hanus committed
706
  (rawTests,ignoredTests,pubmod) <-
707
        transformTests opts prooffiles theopubfuncs
708
          . renameCurryModule pubmodname . makeAllPublic $ prog
Michael Hanus 's avatar
Michael Hanus committed
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
  let (rawDetTests,ignoredDetTests,pubdetmod) =
        transformDetTests opts prooffiles
              . renameCurryModule (modname++"_PUBLICDET")
              . makeAllPublic $ prog
  unless (not (null staticerrs) || null rawTests && null rawDetTests) $
    putStrIfNormal opts $
      "Properties to be tested:\n" ++
      unwords (map (snd . funcName) (rawTests++rawDetTests)) ++ "\n"
  unless (not (null staticerrs) || null ignoredTests && null ignoredDetTests) $
    putStrIfNormal opts $
      "Properties ignored for testing:\n" ++
      unwords (map (snd . funcName) (ignoredTests++ignoredDetTests)) ++ "\n"
  let tm    = TestModule modname
                         (progName pubmod)
                         staticerrs
724 725
                         (addLinesNumbers words
                            (classifyTests opts pubmod rawTests))
Michael Hanus 's avatar
Michael Hanus committed
726 727 728 729
                         (generatorsOfProg pubmod)
      dettm = TestModule modname
                         (progName pubdetmod)
                         []
730 731
                         (addLinesNumbers words
                            (classifyTests opts pubdetmod rawDetTests))
Michael Hanus 's avatar
Michael Hanus committed
732 733 734 735 736 737 738 739 740 741 742 743
                         (generatorsOfProg pubmod)
  when (testThisModule tm) $ writeCurryProgram opts topdir pubmod ""
  when (testThisModule dettm) $ writeCurryProgram opts topdir pubdetmod ""
  return (if testThisModule dettm then [tm,dettm] else [tm])
 where
  showOpError words (qf,err) =
    snd qf ++ " (module " ++ modname ++ ", line " ++
    show (getLineNumber words qf) ++"): " ++ err

  addLinesNumbers words = map (addLineNumber words)

  addLineNumber :: [String] -> Test -> Test
744
  addLineNumber words (PropTest name texp _) =
Michael Hanus 's avatar
Michael Hanus committed
745 746 747
    PropTest   name texp $ getLineNumber words (orgTestName name)
  addLineNumber words (IOTest name _) =
    IOTest name $ getLineNumber words (orgTestName name)
748 749
  addLineNumber words (EquivTest name f1 f2 texp _) =
    EquivTest name f1 f2 texp $ getLineNumber words (orgTestName name)
Michael Hanus 's avatar
Michael Hanus committed
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764

  getLineNumber :: [String] -> QName -> Int
  getLineNumber words (_, name) = maybe 0 (+1) (elemIndex name words)

-- Extracts all user-defined defined generators defined in a module.
generatorsOfProg :: CurryProg -> [QName]
generatorsOfProg = map funcName . filter isGen . functions
 where
   isGen fdecl = "gen" `isPrefixOf` snd (funcName fdecl) &&
                 isSearchTreeType (resultType (funcType fdecl))

   isSearchTreeType (CTVar _) = False
   isSearchTreeType (CFuncType _ _) = False
   isSearchTreeType (CTCons tc _) = tc == searchTreeTC

765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957
-------------------------------------------------------------------------
-- Auxiliaries to support equivalence checking of operations.

-- Create data type with explicit bottom constructors.
genBottomType :: String -> FC.TypeDecl -> CTypeDecl
genBottomType _ (FC.TypeSyn _ _ _ _) =
  error "genBottomType: cannot translate type synonyms"
genBottomType mainmod (FC.Type qtc@(_,tc) _ tvars consdecls) =
  CType (mainmod,t2bt tc) Public (map transTVar tvars)
        (CCons (mainmod,"Bot_"++transQN tc) Public [] :
         if isBasicExtType qtc
           then [CCons (mainmod,"Value_"++tc) Public [baseType qtc]]
           else map transConsDecl consdecls)
 where
  transConsDecl (FC.Cons (_,cons) _ _ argtypes) =
    CCons (mainmod,t2bt cons) Public (map transTypeExpr argtypes)

  transTypeExpr (FC.TVar i) = CTVar (transTVar i)
  transTypeExpr (FC.FuncType t1 t2) =
    CFuncType (transTypeExpr t1) (transTypeExpr t2)
  transTypeExpr (FC.TCons (_,tcons) tes) =
    CTCons (mainmod,t2bt tcons) (map transTypeExpr tes)

  transTVar i = (i,'a':show i)

-- Is the type name an external basic prelude type?
isBasicExtType :: QName -> Bool
isBasicExtType (mn,tc) = mn == preludeName && tc `elem` ["Int","Float","Char"]

-- Default value for external basic prelude types.
defaultValueOfBasicExtType :: String -> CLiteral
defaultValueOfBasicExtType qn
  | qn == "Int"   = CIntc   0
  | qn == "Float" = CFloatc 0.0
  | qn == "Char"  = CCharc  'A'
  | otherwise     = error $ "defaultValueOfBasicExtType: unknown type: "++qn
  
ctype2BotType :: String -> CTypeExpr -> CTypeExpr
ctype2BotType _ (CTVar i) = CTVar i
ctype2BotType mainmod (CFuncType t1 t2) =
  CFuncType (ctype2BotType mainmod t1) (ctype2BotType mainmod t2)
ctype2BotType mainmod (CTCons qtc tes) =
  CTCons (mainmod, t2bt (snd qtc)) (map (ctype2BotType mainmod) tes)

-- Translate a type constructor name to its bottom type constructor name
t2bt :: String -> String
t2bt s = "P_" ++ transQN s

-- Create `peval_` operation for a data type with explicit bottom constructors
-- according to the following scheme:
{-
peval_AB :: AB -> P_AB -> P_AB
peval_AB _ Bot_AB = Bot_AB                 -- no evaluation
peval_AB A P_A    = P_A
peval_AB B P_B    = P_B

peval_C :: C -> P_C -> P_C
peval_C _     Bot_C   = Bot_C              -- no evaluation
peval_C (C x) (P_C y) = P_C (peval_AB x y)

f_equiv_g x p = peval_C (f x) p <~> peval_C (g x) p
-}
genPeval :: String -> FC.TypeDecl -> CFuncDecl
genPeval _ (FC.TypeSyn _ _ _ _) =
  error "genPeval: cannot translate type synonyms"
genPeval mainmod (FC.Type qtc@(_,tc) _ tvars consdecls) =
  cmtfunc ("Evaluate a `"++tc++"` value up to a partial approxmiation.")
    (mainmod,"peval_"++transQN tc) 1 Public
    (foldr1 (~>) (map (\ (a,b) -> CTVar a ~> CTVar b ~> CTVar b)
                      (zip polyavars polyrvars) ++
                  [CTCons qtc (map CTVar polyavars),
                   CTCons (mainmod,t2bt tc) (map CTVar polyrvars),
                   CTCons (mainmod,t2bt tc) (map CTVar polyrvars)]))
    (simpleRule (map CPVar (polyavars ++ [(0,"_")]) ++ [CPComb botSym []])
                (constF botSym) :
     if isBasicExtType qtc
       then [valueRule]
       else map genConsRule consdecls)
 where
  botSym = (mainmod,"Bot_"++transQN tc) -- bottom constructor
  
  -- variables for polymorphic type arguments:
  polyavars = [ (i,"a"++show i) | i <- tvars]
  polyrvars = [ (i,"b"++show i) | i <- tvars]
  
  genConsRule (FC.Cons qc@(_,cons) _ _ argtypes) =
    let args  = [(i,"x"++show i) | i <- [0 .. length argtypes - 1]]
        pargs = [(i,"y"++show i) | i <- [0 .. length argtypes - 1]]
        pcons = (mainmod,t2bt cons)
    in simpleRule (map CPVar polyavars ++
                   [CPComb qc (map CPVar args), CPComb pcons (map CPVar pargs)])
         (applyF pcons
                 (map (\ (e1,e2,te) ->
                        applyE (ftype2pvalOf mainmod "peval" polyavars te)
                               [e1,e2])
                      (zip3 (map CVar args) (map CVar pargs) argtypes)))

  valueRule =
    let xvar    = (0,"x")
        yvar    = (1,"y")
        valcons = (mainmod,"Value_"++tc)
    in guardedRule [CPVar xvar, CPComb valcons [CPVar yvar]]
                   [(constF (pre "True"), --applyF (pre "=:=") [CVar xvar, CVar yvar],
                     applyF valcons [CVar xvar])]
                   []

-- Create `pvalOf` operation for a data type with explicit bottom constructors
-- according to the following scheme:
{-
pvalOf_AB :: AB -> P_AB
pvalOf_AB _ = Bot_AB
pvalOf_AB A = P_A
pvalOf_AB B = P_B

pvalOf_C :: C -> P_C
pvalOf_C _     = Bot_C
pvalOf_C (C x) = P_C (pvalOf_AB x)

f_equiv_g x = pvalOf_C (f x) <~> pvalOf_C (g x)
-}
genPValOf :: String -> FC.TypeDecl -> CFuncDecl
genPValOf _ (FC.TypeSyn _ _ _ _) =
  error "genPValOf: cannot translate type synonyms"
genPValOf mainmod (FC.Type qtc@(_,tc) _ tvars consdecls) =
  cmtfunc ("Map a `"++tc++"` value into all its partial approxmiations.")
    (mainmod,"pvalOf_"++transQN tc) 1 Public
    (foldr1 (~>) (map (\ (a,b) -> CTVar a ~> CTVar b)
                      (zip polyavars polyrvars) ++
                  [CTCons qtc (map CTVar polyavars),
                   CTCons (mainmod,t2bt tc) (map CTVar polyrvars)]))
    (simpleRule (map CPVar (polyavars ++ [(0,"_")]))
                (constF (mainmod,"Bot_"++transQN tc)) :
     if isBasicExtType qtc
       then [valueRule]
       else map genConsRule consdecls)
 where
  -- variables for polymorphic type arguments:
  polyavars = [ (i,"a"++show i) | i <- tvars]
  polyrvars = [ (i,"b"++show i) | i <- tvars]
  
  genConsRule (FC.Cons qc@(_,cons) _ _ argtypes) =
    let args = [(i,"x"++show i) | i <- [0 .. length argtypes - 1]]
    in simpleRule (map CPVar polyavars ++ [CPComb qc (map CPVar args)])
         (applyF (mainmod,t2bt cons)
            (map (\ (e,te) ->
                   applyE (ftype2pvalOf mainmod "pvalOf" polyavars te) [e])
                 (zip (map CVar args) argtypes)))

  valueRule =
    let var = (0,"x")
    in simpleRule [CPVar var] (applyF (mainmod,"Value_"++tc) [CVar var])

-- Translate a FlatCurry type into a corresponding call to `pvalOf`:
ftype2pvalOf :: String -> String -> [(Int,String)] -> FC.TypeExpr -> CExpr
ftype2pvalOf mainmod pvalname polyvars (FC.TCons (_,tc) texps) =
  applyF (mainmod,pvalname++"_"++transQN tc)
         (map (ftype2pvalOf mainmod pvalname polyvars) texps)
ftype2pvalOf _ _ _ (FC.FuncType _ _) =
  error "genPValOf: cannot handle functional types in as constructor args"
ftype2pvalOf _ _ polyvars (FC.TVar i) =
  maybe (error "genPValOf: unbound type variable")
        CVar
        (find ((==i) . fst) polyvars)

-- Translate an AbstractCurry type into a corresponding call to `pvalOf`:
ctype2pvalOf :: String -> String -> CTypeExpr -> CExpr
ctype2pvalOf mainmod pvalname (CTCons (_,tc) texps) =
  applyF (mainmod,pvalname++"_"++transQN tc)
         (map (ctype2pvalOf mainmod pvalname) texps)
ctype2pvalOf _ _ (CFuncType _ _) =
  error "genPValOf: cannot handle functional types in as constructor args"
ctype2pvalOf _ _ (CTVar _) = error "genPValOf: unbound type variable"


-- Translate an AbstractCurry type declaration into a FlatCurry type decl:
ctypedecl2ftypedecl :: CTypeDecl -> FC.TypeDecl
ctypedecl2ftypedecl (CTypeSyn _ _ _ _) =
  error "ctypedecl2ftypedecl: cannot translate type synonyms"
ctypedecl2ftypedecl (CNewType _ _ _ _) =
  error "ctypedecl2ftypedecl: cannot translate newtype"
ctypedecl2ftypedecl (CType qtc _ tvars consdecls) =
  FC.Type qtc FC.Public (map fst tvars) (map transConsDecl consdecls)
 where
  transConsDecl (CCons qc _ argtypes) =
    FC.Cons qc (length argtypes) FC.Public (map transTypeExpr argtypes)
  transConsDecl (CRecord _ _ _) =
    error "ctypedecl2ftypedecl: cannot translate records"

  transTypeExpr (CTVar (i,_)) = FC.TVar i
  transTypeExpr (CFuncType t1 t2) =
    FC.FuncType (transTypeExpr t1) (transTypeExpr t2)
  transTypeExpr (CTCons qtcons tes) = FC.TCons qtcons (map transTypeExpr tes)

Michael Hanus 's avatar
Michael Hanus committed
958 959
-------------------------------------------------------------------------
-- Create the main test module containing all tests of all test modules as
960
-- a Curry program with name `mainmod`.
Michael Hanus 's avatar
Michael Hanus committed
961 962 963 964 965
-- The main test module contains a wrapper operation for each test
-- and a main function to execute these tests.
-- Furthermore, if PAKCS is used, test data generators
-- for user-defined types are automatically generated.
genMainTestModule :: Options -> String -> [TestModule] -> IO ()
966
genMainTestModule opts mainmod modules = do
Michael Hanus 's avatar
Michael Hanus committed
967 968
  let testtypes = nub (concatMap userTestDataOfModule modules)
  testtypedecls <- collectAllTestTypeDecls [] testtypes
969 970 971 972 973 974 975
  equvtypedecls <- collectAllTestTypeDecls []
                     (map (\t->(t,True))
                          (nub (concatMap equivPropTypes modules)))
                     >>= return . map fst
  let bottypes  = map (genBottomType mainmod) equvtypedecls
      pevalfuns = map (genPeval mainmod) equvtypedecls
      pvalfuns  = map (genPValOf mainmod) equvtypedecls
976
      generators   = map (genTestDataGenerator mainmod)
977 978
                         (testtypedecls ++
                          map (\td -> (ctypedecl2ftypedecl td,False)) bottypes)
979
      funcs        = concatMap (genTestFuncs opts mainmod) modules ++
Michael Hanus 's avatar
Michael Hanus committed
980
                               generators
981
      mainFunction = genMainFunction opts mainmod
Michael Hanus 's avatar
Michael Hanus committed
982 983 984
                                     (concatMap propTests modules)
      imports      = nub $ [ easyCheckModule, easyCheckExecModule
                           , searchTreeModule, generatorModule
985
                           , "AnsiCodes","Maybe","System","Profile"] ++
986 987
                           map (fst . fst) testtypes ++
                           map testModuleName modules
Michael Hanus 's avatar
Michael Hanus committed
988 989
  appendix <- readFile (packagePath </> "src" </> "TestAppendix.curry")
  writeCurryProgram opts "."
990 991
                    (CurryProg mainmod imports bottypes
                            (mainFunction : funcs ++ pvalfuns ++ pevalfuns) [])
Michael Hanus 's avatar
Michael Hanus committed
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
                    appendix

-- Generates the main function which executes all property tests
-- of all test modules.
genMainFunction :: Options -> String -> [Test] -> CFuncDecl
genMainFunction opts testModule tests =
  CFunc (testModule, "main") 0 Public (ioType unitType) [simpleRule [] body]
 where
  body = CDoExpr $
     (if isQuiet opts
        then []
        else [CSExpr (applyF (pre "putStrLn")
                             [string2ac "Executing all tests..."])]) ++
     [ CSPat (cpvar "x1") $ -- run all tests:
          applyF (testModule, "runPropertyTests")
                 [constF (pre (if optColor opts then "True" else "False")),
1008
                  constF (pre (if optTime  opts then "True" else "False")),
Michael Hanus 's avatar
Michael Hanus committed
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
                  easyCheckExprs]
     , CSExpr $ applyF (pre "when")
                  [applyF (pre "/=") [cvar "x1", cInt 0],
                   applyF ("System", "exitWith") [cvar "x1"]]
     ]

  easyCheckExprs = list2ac $ map makeExpr tests

  makeExpr :: Test -> CExpr
  makeExpr (PropTest (mn, name) _ _) =
    constF (testModule, name ++ "_" ++ modNameToId mn)
  makeExpr (IOTest (mn, name) _) =
1021 1022 1023
    constF (testModule, name ++ "_" ++ modNameToId  mn)
  makeExpr (EquivTest (mn, name) _ _ _ _) =
    constF (testModule, name ++ "_" ++ modNameToId mn)
Michael Hanus 's avatar
Michael Hanus committed
1024 1025 1026 1027 1028

-------------------------------------------------------------------------
-- Collect all type declarations for a given list of type
-- constructor names, including the type declarations which are
-- used in these type declarations.
1029 1030
collectAllTestTypeDecls :: [(FC.TypeDecl,Bool)] -> [(QName,Bool)]
                        -> IO [(FC.TypeDecl,Bool)]
Michael Hanus 's avatar
Michael Hanus committed
1031 1032 1033
collectAllTestTypeDecls tdecls testtypenames = do
  newtesttypedecls <- mapIO getTypeDecl testtypenames
  let alltesttypedecls = tdecls ++ newtesttypedecls
1034 1035 1036
      newtcons = filter (\ ((mn,_),genpart) -> genpart || mn /= preludeName)
                        (nub (concatMap allTConsInDecl' newtesttypedecls)
                         \\ map (\(t,p) -> (FCG.typeName t,p)) alltesttypedecls)
Michael Hanus 's avatar
Michael Hanus committed
1037 1038 1039 1040 1041
  if null newtcons then return alltesttypedecls
                   else collectAllTestTypeDecls alltesttypedecls newtcons
 where
  -- gets the type declaration for a given type constructor
  -- (could be improved by caching programs that are already read)
1042 1043
  getTypeDecl :: (QName,Bool) -> IO (FC.TypeDecl,Bool)
  getTypeDecl (qt@(mn,_),genpartial) = do
Michael Hanus 's avatar
Michael Hanus committed
1044 1045 1046
    fprog <- readFlatCurry mn
    maybe (error $ "Definition of type '" ++ FC.showQNameInModule "" qt ++
                   "' not found!")
1047
          (\td -> return (td,genpartial))
Michael Hanus 's avatar
Michael Hanus committed
1048 1049
          (find (\t -> FCG.typeName t == qt) (FCG.progTypes fprog))

1050 1051 1052
  allTConsInDecl' :: (FC.TypeDecl,Bool) -> [(QName,Bool)]
  allTConsInDecl' (td,genpart) = map (\t->(t,genpart)) (allTConsInDecl td)

Michael Hanus 's avatar
Michael Hanus committed
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
  -- compute all type constructors used in a type declaration
  allTConsInDecl :: FC.TypeDecl -> [QName]
  allTConsInDecl = FCG.trType (\_ _ _ -> concatMap allTConsInConsDecl)
                              (\_ _ _ -> allTConsInTypeExpr)
  
  allTConsInConsDecl :: FC.ConsDecl -> [QName]
  allTConsInConsDecl = FCG.trCons (\_ _ _ -> concatMap allTConsInTypeExpr)
  
  allTConsInTypeExpr :: FC.TypeExpr -> [QName]
  allTConsInTypeExpr =
    FCG.trTypeExpr (\_ -> []) (\tc targs -> tc : concat targs) (++)

-- Creates a test data generator for a given type declaration.
1066 1067
-- If the flag of the type declaration is `True`, a generator
-- for partial values is created.
1068 1069
genTestDataGenerator :: String -> (FC.TypeDecl,Bool) -> CFuncDecl
genTestDataGenerator mainmod (tdecl,part) = type2genData tdecl
Michael Hanus 's avatar
Michael Hanus committed
1070 1071 1072 1073 1074 1075
 where
  qt       = FCG.typeName tdecl
  qtString = FC.showQNameInModule "" qt

  type2genData (FC.TypeSyn _ _ _ _) =
    error $ "Cannot create generator for type synonym " ++ qtString
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098
  type2genData (FC.Type _ _ tvars cdecls)
    | null cdecls && (fst qt /= preludeName || not part)
    = error $ "Cannot create value generator for type '" ++ qtString ++
              "' without constructors!"
    | otherwise
    = cmtfunc
        ("Generator for " ++ (if part then "partial " else "") ++
         "`" ++ qtString ++ "` values.")
        (typename2genopname mainmod [] part qt) (length tvars) Public
        (foldr (~>) (CTCons searchTreeTC [CTCons qt ctvars])
                    (map (\v -> CTCons searchTreeTC [v]) ctvars))
        [simpleRule (map CPVar cvars)
          (let gencstrs =  foldr1 (\e1 e2 -> applyF choiceGen [e1,e2])
                                  (map cons2gen cdecls)
           in if part
                then applyF choiceGen
                            [ applyF (generatorModule, "genCons0")
                                     [constF (pre "failed")]
                            , if null cdecls
                                then constF (generatorModule,
                                             "gen" ++ transQN (snd qt))
                                else gencstrs ]
                else gencstrs)]
Michael Hanus 's avatar
Michael Hanus committed
1099
   where
1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
    cons2gen (FC.Cons qn@(mn,cn) ar _ ctypes)
      | ar>maxArity
      = error $ "Test data constructors with more than " ++ show maxArity ++
                " arguments are currently not supported!"
      | not part && mn == mainmod && "Value_" `isPrefixOf` cn
        -- specific generator for bottom types of external basic types
        -- like Int (actually, do not generate values in order to reduce
        -- search space):
      = applyF (generatorModule, "genCons1")
               [CSymbol qn,
                applyF (searchTreeModule,"Value")
                       [CLit (defaultValueOfBasicExtType (drop 6 cn))]]
      | otherwise
      = applyF (generatorModule, "genCons" ++ show ar)
               ([CSymbol qn] ++ map type2gen ctypes)
Michael Hanus 's avatar
Michael Hanus committed
1115 1116 1117 1118 1119 1120

    type2gen (FC.TVar i) = CVar (i,"a"++show i)
    type2gen (FC.FuncType _ _) =
      error $ "Type '" ++ qtString ++
              "': cannot create value generators for functions!"
    type2gen (FC.TCons qtc argtypes) =
1121 1122
      applyF (typename2genopname mainmod [] part qtc)
             (map type2gen argtypes)
Michael Hanus 's avatar
Michael Hanus committed
1123 1124 1125 1126 1127 1128 1129

    ctvars = map (\i -> CTVar (i,"a"++show i)) tvars
    cvars  = map (\i -> (i,"a"++show i)) tvars

-------------------------------------------------------------------------
-- remove the generated files (except if option "--keep" is set)
cleanup :: Options -> String -> [TestModule] -> IO ()
1130
cleanup opts mainmod modules =
Michael Hanus 's avatar
Michael Hanus committed
1131
  unless (optKeep opts) $ do
1132
    removeCurryModule mainmod
Michael Hanus 's avatar
Michael Hanus committed
1133 1134 1135 1136 1137 1138 1139
    mapIO_ removeCurryModule (map testModuleName modules)
 where
  removeCurryModule modname =
    lookupModuleSourceInLoadPath modname >>=
    maybe done
          (\ (_,srcfilename) -> do
            system $ installDir </> "bin" </> "cleancurry" ++ " " ++ modname
1140
            system $ "/bin/rm -f " ++ srcfilename
Michael Hanus 's avatar
Michael Hanus committed
1141 1142 1143
            done )

-- Show some statistics about number of tests:
1144 1145
showTestStatistics :: Options -> [TestModule] -> String
showTestStatistics opts testmodules =
Michael Hanus 's avatar
Michael Hanus committed
1146 1147 1148
  let numtests  = sumOf (const True) testmodules
      unittests = sumOf isUnitTest   testmodules
      proptests = sumOf isPropTest   testmodules
1149
      equvtests = sumOf isEquivTest  testmodules
Michael Hanus 's avatar
Michael Hanus committed
1150 1151 1152
      iotests   = sumOf isIOTest     testmodules
   in "TOTAL NUMBER OF TESTS: " ++ show numtests ++
      " (UNIT: " ++ show unittests ++ ", PROPERTIES: " ++
1153
      show proptests ++ ", EQUIVALENCE: " ++ show equvtests ++
1154
      (if optIOTest opts then ", IO: " ++ show iotests else "") ++ ")"
Michael Hanus 's avatar
Michael Hanus committed
1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166
 where
  sumOf p = foldr (+) 0 . map (length . filter p . propTests)

-------------------------------------------------------------------------
main :: IO ()
main = do
  argv <- getArgs
  pid  <- getPID
  let (funopts, args, opterrors) = getOpt Permute options argv
  opts <- processOpts (foldl (flip id) defaultOptions funopts)
  unless (null opterrors)
         (putStr (unlines opterrors) >> putStrLn usageText >> exitWith 1)
1167
  putStrIfNormal opts ccBanner
Michael Hanus 's avatar
Michael Hanus committed
1168
  when (null args || optHelp opts) (putStrLn usageText >> exitWith 1)
1169 1170 1171
  let mods = map stripCurrySuffix args
  mapIO_ checkModuleName mods
  testModules <- mapIO (analyseModule opts) mods
Michael Hanus 's avatar
Michael Hanus committed
1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184
  let staticerrs       = concatMap staticErrors (concat testModules)
      finaltestmodules = filter testThisModule (concat testModules)
      testmodname = if null (optMainProg opts)
                      then "TEST" ++ show pid
                      else optMainProg opts
  if not (null staticerrs)
   then do showStaticErrors opts staticerrs
           putStrLn $ withColor opts red "Testing aborted!"
           cleanup opts testmodname finaltestmodules
           exitWith 1
   else if null finaltestmodules then exitWith 0 else do
    putStrIfNormal opts $ withColor opts blue $
                          "Generating main test module '"++testmodname++"'..."
1185
    putStrIfDetails opts "\n"
Michael Hanus 's avatar
Michael Hanus committed
1186
    genMainTestModule opts testmodname finaltestmodules
1187
    showGeneratedModule opts "main test" testmodname
Michael Hanus 's avatar
Michael Hanus committed
1188
    putStrIfNormal opts $ withColor opts blue $ "and compiling it...\n"
1189 1190 1191
    ecurrypath <- getEnviron "CURRYPATH"
    let currypath = case ecurrypath of ':':_ -> '.':ecurrypath
                                       _     -> ecurrypath
1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
    let runcmd = unwords $
                   [ installDir </> "bin" </> "curry"
                   , "--noreadline"
                   , ":set -time"
                   , ":set " ++ if optVerb opts > 3 then "v1" else "v0"
                   , ":set parser -Wnone"
                   , if null currypath then "" else ":set path " ++ currypath
                   , ":l "++testmodname,":eval main :q" ]
    putStrLnIfDebug opts $ "Executing command:\n" ++ runcmd
    ret <- system runcmd
Michael Hanus 's avatar
Michael Hanus committed
1202 1203
    cleanup opts testmodname finaltestmodules
    unless (isQuiet opts || ret /= 0) $
1204
      putStrLn $ withColor opts green $ showTestStatistics opts finaltestmodules
Michael Hanus 's avatar
Michael Hanus committed
1205 1206 1207 1208
    exitWith ret
 where
  showStaticErrors opts errs = putStrLn $ withColor opts red $
    unlines (line : "STATIC ERRORS IN PROGRAMS:" : errs) ++ line
1209 1210 1211 1212 1213 1214

  checkModuleName mn =
    when (pathSeparator `elem` mn) $ do
      putStrLn $ "Module names with path prefixes not allowed: " ++ mn
      exitWith 1

Michael Hanus 's avatar
Michael Hanus committed
1215 1216
  line = take 78 (repeat '=')

1217 1218 1219 1220 1221 1222 1223 1224 1225 1226
showGeneratedModule :: Options -> String -> String -> IO ()
showGeneratedModule opts mkind modname = when (optVerb opts > 3) $ do
  putStrLn $ '\n' : line
  putStrLn $ "Generated " ++ mkind ++ " module `" ++ modname ++ ".curry':"
  putStrLn line
  readFile (modname ++ ".curry") >>= putStr
  putStrLn line
 where
  line = take 78 (repeat '=')

Michael Hanus 's avatar
Michael Hanus committed
1227 1228 1229
-------------------------------------------------------------------------
-- Auxiliaries

1230
-- Rename all module references to "Test.Prop" into "Test.EasyCheck"
Michael Hanus 's avatar
Michael Hanus committed
1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263
renameProp2EasyCheck :: CurryProg -> CurryProg
renameProp2EasyCheck prog =
  updCProg id (map rnmMod) id id id
           (updQNamesInCProg (\ (mod,n) -> (rnmMod mod,n)) prog)
 where
  rnmMod mod | mod == propModule = easyCheckModule
             | otherwise         = mod

-- Extracts the first word of a string
firstWord :: String -> String
firstWord = head . splitOn "\t" . head . splitOn " "

-- Strips a suffix from a string.
stripSuffix :: String -> String -> String
stripSuffix str suf = if suf `isSuffixOf` str
                      then take (length str - length suf) str
                      else str

-- Translate a module name to an identifier, i.e., replace '.' by '_':
modNameToId :: String -> String
modNameToId = intercalate "_" . split (=='.')

-- Computes the arity from a type expression.
arityOfType :: CTypeExpr -> Int
arityOfType = length . argTypes

--- Name of the SearchTree module.
searchTreeModule :: String
searchTreeModule = "SearchTree"

--- Name of SearchTree type constructor.
searchTreeTC :: QName
searchTreeTC = (searchTreeModule,"SearchTree")
1264

Michael Hanus 's avatar
Michael Hanus committed
1265 1266 1267 1268
--- Name of the SearchTreeGenerator module.
generatorModule :: String
generatorModule = "SearchTreeGenerators"

1269 1270 1271
choiceGen :: QName
choiceGen = (generatorModule,"|||")

Michael Hanus 's avatar
Michael Hanus committed
1272 1273 1274 1275
-- Writes a Curry module (together with an appendix) to its file.
writeCurryProgram :: Options -> String -> CurryProg -> String -> IO ()
writeCurryProgram opts srcdir p appendix = do
  let progfile = srcdir </> modNameToPath (progName p) ++ ".curry"
1276
  putStrLnIfDebug opts $ "Writing program: " ++ progfile
Michael Hanus 's avatar
Michael Hanus committed
1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289
  writeFile progfile
            (showCProg p ++ "\n" ++ appendix ++ "\n")

isPAKCS :: Bool
isPAKCS = curryCompiler == "pakcs"

-- Does a program text contains a OPTIONS_CYMAKE line to call currypp?
containsPPOptionLine :: String -> Bool
containsPPOptionLine = any isOptionLine . lines
 where
   isOptionLine s = "{-# OPTIONS_CYMAKE " `isPrefixOf` s -- -}
                    && "currypp" `isInfixOf` s

1290 1291 1292 1293 1294 1295 1296 1297
tconsOf :: CTypeExpr -> [QName]
tconsOf (CTVar _) = []
tconsOf (CFuncType from to) = union (tconsOf from) (tconsOf to)
tconsOf (CTCons tc argtypes) = union [tc] (unionOn tconsOf argtypes)

unionOn :: (a -> [b]) -> [a] -> [b]
unionOn f = foldr union [] . map f

Michael Hanus 's avatar
Michael Hanus committed
1298
-------------------------------------------------------------------------