packages feed

should-not-typecheck 1.0.1 → 2.0

raw patch · 5 files changed

+104/−11 lines, 5 filesdep +deepseq

Dependencies added: deepseq

Files

+ CHANGELOG.md view
@@ -0,0 +1,14 @@+# `should-not-typecheck` changelog++## 2.0+* Changed API to require `NFData a` so we can fully evaluate expressions, rather than just converting to WHNF.++## 1.0.1+* Use `throwIO` instead of `throw` for exception ordering safety.++## 1.0+* Stabilise API at 1.0 release.+* Allow building on 7.6.3.++## 0.1.0.0+* Initial version.
README.md view
@@ -1,12 +1,10 @@-# should-not-typecheck [![Build Status](https://travis-ci.org/CRogers/should-not-typecheck.svg?branch=master)](https://travis-ci.org/CRogers/should-not-typecheck)--`should-not-typecheck` is a Haskell library which allows you to assert that an expression does not typecheck in your unit tests. It provides one function, `shouldNotTypecheck :: a -> Assertion`, which takes an expression and will fail the test if it typechecks. `shouldNotTypecheck` returns an HUnit `Assertion` (so it can be used with both `HUnit` and `hspec`).+# should-not-typecheck [![Build Status](https://travis-ci.org/CRogers/should-not-typecheck.svg?branch=master)](https://travis-ci.org/CRogers/should-not-typecheck) [![Hackage](https://img.shields.io/hackage/v/should-not-typecheck.svg)](https://hackage.haskell.org/package/should-not-typecheck) -Avaliable on Hackage as [`should-not-typecheck`](https://hackage.haskell.org/package/should-not-typecheck).+`should-not-typecheck` is a Haskell library which allows you to assert that an expression does not typecheck in your tests. It provides one function, `shouldNotTypecheck`, which takes an expression and will fail the test if it typechecks. `shouldNotTypecheck` returns an HUnit `Assertion` (so it can be used with both `HUnit` and `hspec`).  ## Example (hspec) -The secret sauce is the [Deferred Type Errors GHC extension](https://downloads.haskell.org/~ghc/7.10.1/docs/html/users_guide/defer-type-errors.html). This allows you to write a non-typechecking expression which will throw an exception at run time (rather than erroring out at compile time). `shouldNotTypecheck` tries to catch that exception and fails the test if no deferred type error is caught.+The secret sauce is the [Deferred Type Errors GHC extension](https://downloads.haskell.org/~ghc/7.10.1/docs/html/users_guide/defer-type-errors.html). This allows you to write a ill-typed expression which will throw an exception at run time (rather than erroring out at compile time). `shouldNotTypecheck` tries to catch that exception and fails the test if no deferred type error is caught.  ```haskell {-# OPTIONS_GHC -fdefer-type-errors #-} -- Very important!@@ -24,6 +22,52 @@ ```  It can be used similarly with HUnit.++### `NFData a` constraint++Haskell is a lazy language - deferred type errors will not get evaluated unless we explicitly and deeply force (evaluate) the value. [`NFData`](https://hackage.haskell.org/package/deepseq-1.4.1.1/docs/Control-DeepSeq.html#t:NFData) is a typeclass from the [`deepseq`](https://hackage.haskell.org/package/deepseq) library which allows you to describe how to fully evaluate an expression (convert it to Normal Form). `shouldNotTypecheck` uses this typeclass to fully evaluate expressions passed to it. For vanilla Haskell types you only need to derive `Generic` and the `deepseq` class will handle it for you:++```haskell+{-# LANGUAGE DeriveGeneric #-}++import GHC.Generics (Generic)++data SomeType a = WithSome | DataConstructors a+  deriving Generic++instance NFData a => NFData (SomeType a)+```++In GHC 7.10 [`DeriveAnyClass` can be used](https://hackage.haskell.org/package/deepseq-1.4.1.1/docs/Control-DeepSeq.html#v:rnf) to make it even more succinct.++With `deepseq >= 1.4`, this autoderiving `Generic` option is included with the library. With `deepseq <= 1.3` you'll have to use the [`deepseq-generics`](https://hackage.haskell.org/package/deepseq-generics) library as well.++#### GADTs++With more complex datatypes, like GADTs and those existentially quantified, `DeriveGeneric` does not work. You will need to provide an instance for `NFData` yourself, but not to worry as it follows a pattern:++```haskell+{-# LANGUAGE GADTs #-}++import Control.DeepSeq (NFData)++data Expr t where+  IntVal :: Int -> Expr Int+  BoolVal :: Bool -> Expr Bool+  Add :: Expr Int -> Expr Int -> Expr Int++instance NFData (Expr t) where+  rnf expr = case expr of+    IntVal i  -> rnf i -- call rnf on every subvalue+    BoolVal b -> rnf b+    Add l r   -> rnf l `seq` rnf r -- and `seq` multiple values together++-- Now we can test expressions like:+badExpr = Add (IntVal 4) (BoolVal True)+-- do not typecheck!+```++If you forget to specify provide an `NFData` instance for a type `should-not-typecheck` should warn you.  ## Motivation 
should-not-typecheck.cabal view
@@ -1,5 +1,5 @@ name:                should-not-typecheck-version:             1.0.1+version:             2.0 synopsis:            A HUnit/hspec assertion library to verify that an expression does not typecheck description:   For examples and an introduction to the library please take a look at the <https://github.com/CRogers/should-not-typecheck#should-not-typecheck- README> on github.@@ -13,6 +13,7 @@ build-type:          Simple extra-source-files:     README.md+  , CHANGELOG.md   , stack.yaml cabal-version:       >=1.10 tested-with:         GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.1@@ -22,6 +23,7 @@   exposed-modules:     Test.ShouldNotTypecheck   build-depends:       base >= 4.6 && < 5                      , HUnit >= 1.2+                     , deepseq >= 1.3   default-language:    Haskell2010  test-suite tests@@ -33,6 +35,7 @@                      , HUnit >= 1.2                      , hspec >= 2.1                      , hspec-expectations >= 0.6+                     , deepseq   default-language:    Haskell2010  source-repository head
src/Test/ShouldNotTypecheck.hs view
@@ -1,9 +1,17 @@ module Test.ShouldNotTypecheck (shouldNotTypecheck) where +import Control.DeepSeq (force, NFData) import Control.Exception (evaluate, try, throwIO, ErrorCall(..)) import Data.List (isSuffixOf) import Test.HUnit.Lang (Assertion, assertFailure) +-- Taken from base-4.8.0.0:Data.List+isSubsequenceOf :: (Eq a) => [a] -> [a] -> Bool+isSubsequenceOf []    _                    = True+isSubsequenceOf _     []                   = False+isSubsequenceOf a@(x:a') (y:b) | x == y    = isSubsequenceOf a' b+                               | otherwise = isSubsequenceOf a b+ {-|   Takes one argument, an expression that should not typecheck.   It will fail the test if the expression does typecheck.@@ -11,11 +19,13 @@   See the <https://github.com/CRogers/should-not-typecheck#should-not-typecheck- README>   for examples and more infomation. -}-shouldNotTypecheck :: a -> Assertion+shouldNotTypecheck :: NFData a => a -> Assertion shouldNotTypecheck a = do-  result <- try (evaluate a)+  result <- try (evaluate $ force a)   case result of     Right _ -> assertFailure "Expected expression to not compile but it did compile"     Left (ErrorCall msg) -> case isSuffixOf "(deferred type error)" msg of-      True -> return ()+      True -> case isSubsequenceOf "No instance for" msg && isSubsequenceOf "NFData" msg of+        True -> assertFailure $ "Make sure the expression has an NFData instance! See docs at https://github.com/CRogers/should-not-typecheck#nfdata-a-constraint. Full error:\n" ++ msg+        False -> return ()       False -> throwIO (ErrorCall msg)
test/ShouldNotTypecheckSpec.hs view
@@ -1,8 +1,11 @@+{-# LANGUAGE GADTs, TemplateHaskell #-} {-# OPTIONS_GHC -fdefer-type-errors #-}  module Main where +import Control.DeepSeq import Control.Exception+import GHC.Generics (Generic) import Test.Hspec import Test.Hspec.Expectations (expectationFailure) import Test.HUnit.Lang (performTestCase)@@ -26,6 +29,19 @@       True -> return ()       False -> expectationFailure "Incorrect exception propagated" +data Expr t where+  IntVal :: Int -> Expr Int+  BoolVal :: Bool -> Expr Bool+  Add :: Expr Int -> Expr Int -> Expr Int++instance NFData (Expr t) where+  rnf expr = case expr of+    IntVal i -> rnf i+    BoolVal b -> rnf b+    Add l r -> rnf l `seq` rnf r++data NoNFDataInstance = NoNFDataInstance+ main :: IO () main = hspec $ do   describe "shouldNotCompile" $ do@@ -37,8 +53,14 @@      it "should throw an actual exception and not fail the assertion if the expression contains an non-HUnitFailure exception" $ do       let exception = NoMethodError "lol"-      shouldThrowException exception (shouldNotTypecheck (throw exception))+      shouldThrowException exception (shouldNotTypecheck (throw exception :: Int))      it "should propagate an actual exception and not fail the assertion if the expression contains a non-deferred ErrorCall exception" $ do       let exception = ErrorCall "yay"-      shouldThrowException exception (shouldNotTypecheck (throw exception))+      shouldThrowException exception (shouldNotTypecheck (throw exception :: Int))++    it "should not throw an assertion when an expression with more than one level of constructors is ill typed" $ do+      shouldNotTypecheck (Add (BoolVal True) (IntVal 4))++    it "should warn if an expression had a type error due to lack of NFData instance" $ do+      shouldFailAssertion (shouldNotTypecheck NoNFDataInstance)