Skip to content

Commit f0e9bfd

Browse files
committed
Make Generic constraint optional inside quickcheckTool
It doesn't force callers to have a 'Generic' instance defined for the type, but they can opt-in later.
1 parent cd833b7 commit f0e9bfd

1 file changed

Lines changed: 26 additions & 16 deletions

File tree

src/Data/API/Tools/QuickCheck.hs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Data.API.Tools.Combinators
1111
import Data.API.Tools.Datatypes
1212
import Data.API.Types
1313

14+
import GHC.Generics
1415
import Control.Applicative
1516
import Data.Monoid
1617
import Data.Time
@@ -25,6 +26,26 @@ import Prelude
2526
quickCheckTool :: APITool
2627
quickCheckTool = apiNodeTool $ apiSpecTool gen_sn_ab gen_sr_ab gen_su_ab gen_se_ab mempty
2728

29+
-- | Helper to create an 'Arbitrary' implementation. It will check if we have a 'Generic'
30+
-- instance for the underlying type and, if we have, we will implement 'shrink' in terms of
31+
-- 'genericShrink', otherwise we will just alias it to '[]' (i.e. a no-op). This avoids
32+
-- imposing to the caller a mandatory 'Generic' instance on the type when using this tool,
33+
-- but it will get them a \"shrinker for free\" if they define a 'Generic' instance.
34+
mkArbitraryInstance :: ToolSettings
35+
-> TypeQ
36+
-> ExpQ
37+
-- ^ The body of the 'arbitrary' method.
38+
-> Q [Dec]
39+
mkArbitraryInstance ts typeQ arbitraryBody = do
40+
tq <- sequence [typeQ]
41+
hasGeneric <- isInstance ''Generic tq
42+
let shrinkBody = case hasGeneric of
43+
True -> [e| genericShrink |]
44+
False -> [e| [] |]
45+
optionalInstanceD ts ''QC.Arbitrary [typeQ]
46+
[ simpleD 'arbitrary arbitraryBody
47+
, simpleD 'shrink shrinkBody
48+
]
2849

2950
-- | Generate an 'Arbitrary' instance for a newtype that respects its
3051
-- filter. We don't try to generate arbitrary data matching a regular
@@ -40,10 +61,8 @@ gen_sn_ab = mkTool $ \ ts (an, sn) -> case snFilter sn of
4061
Just (FtrUTC ur) -> mk_instance ts an sn [e| arbitraryUTCRange ur |]
4162
Just (FtrStrg _) -> return []
4263
where
43-
mk_instance ts an sn arb = optionalInstanceD ts ''Arbitrary [nodeRepT an]
44-
[ simpleD 'arbitrary [e| fmap $(nodeNewtypeConE ts an sn) $arb |]
45-
, simpleD 'shrink [e| genericShrink |]
46-
]
64+
mk_instance ts an sn arb =
65+
mkArbitraryInstance ts (nodeRepT an) [e| fmap $(nodeNewtypeConE ts an sn) $arb |]
4766

4867

4968
-- | Generate an 'Arbitrary' instance for a record:
@@ -53,10 +72,7 @@ gen_sn_ab = mkTool $ \ ts (an, sn) -> case snFilter sn of
5372
-- > shrink = genericShrink
5473

5574
gen_sr_ab :: Tool (APINode, SpecRecord)
56-
gen_sr_ab = mkTool $ \ ts (an, sr) -> optionalInstanceD ts ''QC.Arbitrary [nodeRepT an]
57-
[ simpleD 'arbitrary (bdy an sr)
58-
, simpleD 'shrink [e| genericShrink |]
59-
]
75+
gen_sr_ab = mkTool $ \ ts (an, sr) -> mkArbitraryInstance ts (nodeRepT an) (bdy an sr)
6076
where
6177
-- Reduce size of fields to avoid generating massive test data
6278
-- by giving an arbitrary implementation like this:
@@ -74,10 +90,7 @@ gen_sr_ab = mkTool $ \ ts (an, sr) -> optionalInstanceD ts ''QC.Arbitrary [nodeR
7490
-- > arbitrary = oneOf [ fmap Bar arbitrary, fmap Baz arbitrary ]
7591

7692
gen_su_ab :: Tool (APINode, SpecUnion)
77-
gen_su_ab = mkTool $ \ ts (an, su) -> optionalInstanceD ts ''QC.Arbitrary [nodeRepT an]
78-
[ simpleD 'arbitrary (bdy an su)
79-
, simpleD 'shrink [e| genericShrink |]
80-
]
93+
gen_su_ab = mkTool $ \ ts (an, su) -> mkArbitraryInstance ts (nodeRepT an) (bdy an su)
8194
where
8295
bdy an su | null (suFields su) = nodeConE an
8396
| otherwise = [e| oneof $(listE alts) |]
@@ -92,10 +105,7 @@ gen_su_ab = mkTool $ \ ts (an, su) -> optionalInstanceD ts ''QC.Arbitrary [nodeR
92105
-- > arbitrary = elements [Bar, Baz]
93106

94107
gen_se_ab :: Tool (APINode, SpecEnum)
95-
gen_se_ab = mkTool $ \ ts (an, se) -> optionalInstanceD ts ''QC.Arbitrary [nodeRepT an]
96-
[ simpleD 'arbitrary (bdy an se)
97-
, simpleD 'shrink [e| genericShrink |]
98-
]
108+
gen_se_ab = mkTool $ \ ts (an, se) -> mkArbitraryInstance ts (nodeRepT an) (bdy an se)
99109
where
100110
bdy an se | null ks = nodeConE an
101111
| otherwise = varE 'elements `appE` listE ks

0 commit comments

Comments
 (0)