diff --git a/bench/Main.hs b/bench/Main.hs index 67c27a3eb..ae5260f29 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -1,50 +1,43 @@ -{-# LANGUAGE DeriveAnyClass, DeriveGeneric, FlexibleInstances, StandaloneDeriving #-} +{-# LANGUAGE DeriveAnyClass, FlexibleInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Main where +import Arguments import Criterion.Main import Data.Function import Data.List (genericLength) import Data.String import Patch import Prologue -import SES -import Test.QuickCheck hiding (Fixed) -import Arguments -import SemanticDiff (fetchDiffs) import qualified Renderer as R +import SemanticDiff (fetchDiffs) import qualified SemanticDiffPar +import SES import System.Directory (makeAbsolute) main :: IO () -main = do - benchmarks <- sequenceA [ generativeBenchmarkWith "ses" 10 arbitrarySESInputs (uncurry ((*) `on` length)) (nf (uncurry benchmarkSES)) ] - defaultMain (syncAsyncBenchmark : benchmarks) - where arbitrarySESInputs = (,) <$> sized (`vectorOf` arbitrary) <*> sized (`vectorOf` arbitrary) +main = defaultMain + [ bgroup "ses" + [ bench "0,0" (nf (uncurry benchmarkSES) ([], [])) + , bench "1,1, =" (nf (uncurry benchmarkSES) ([lower], [lower])) + , bench "1,1, ≠" (nf (uncurry benchmarkSES) ([lower], [upper])) + , bench "10,10, =" (nf (uncurry benchmarkSES) (replicate 10 lower, replicate 10 lower)) + , bench "10,10, ≠" (nf (uncurry benchmarkSES) (replicate 10 lower, replicate 10 upper)) + , bench "100,100, =" (nf (uncurry benchmarkSES) (replicate 100 lower, replicate 100 lower)) + , bench "100,100, ≠" (nf (uncurry benchmarkSES) (replicate 100 lower, replicate 100 upper)) + ] + , syncAsyncBenchmark + ] + where lower = ['a'..'z'] + upper = ['A'..'Z'] benchmarkSES :: [String] -> [String] -> [Either String (Patch String)] -benchmarkSES as bs = ses compare cost as bs +benchmarkSES = ses compare cost where compare a b = if a == b then Just (Left a) else Nothing cost = either (const 0) (sum . fmap genericLength) instance NFData a => NFData (Patch a) --- | Defines a named group of n benchmarks over inputs generated by an `Arbitrary` instance. --- | --- | The inputs’ sizes at each iteration are measured by a metric function, which gives the name of the benchmark. This makes it convenient to correlate a benchmark of some function over lists with e.g. input `length`. -generativeBenchmark :: (Arbitrary a, Show m, Ord m) => String -> Int -> (a -> m) -> (a -> Benchmarkable) -> IO Benchmark -generativeBenchmark name n metric benchmark = generativeBenchmarkWith name n arbitrary metric benchmark - -generativeBenchmarkWith :: (Show m, Ord m) => String -> Int -> Gen a -> (a -> m) -> (a -> Benchmarkable) -> IO Benchmark -generativeBenchmarkWith name n generator metric benchmark = do - benchmarks <- traverse measure (take n [0,(defaultSize `div` n)..defaultSize]) - pure $! bgroup name (snd <$> (sortOn fst benchmarks)) - where measure n = do - input <- generate (resize n generator) - let measurement = metric input - pure $! (measurement, bench (show measurement) (benchmark input)) - defaultSize = 100 - syncAsyncBenchmark :: Benchmark syncAsyncBenchmark = bgroup "async vs par" [ diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 5df0bbfcb..caaa5ed28 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -18,14 +18,14 @@ library , Arguments , Category , Data.Align.Generic - , Data.Bifunctor.Join.Arbitrary , Data.Functor.Both + , Data.Functor.Listable , Data.Mergeable , Data.Mergeable.Generic , Data.RandomWalkSimilarity , Data.Record + , Data.Text.Listable , Diff - , Diff.Arbitrary , Diffing , DiffSummary , Info @@ -39,7 +39,6 @@ library , Parse , Parser , Patch - , Patch.Arbitrary , Paths_semantic_diff , Prologue , Range @@ -56,7 +55,6 @@ library , SplitDiff , Syntax , Term - , Term.Arbitrary , TreeSitter , FDoc.Term , FDoc.RecursionSchemes @@ -81,14 +79,13 @@ library , gitrev , hashable , kdt + , leancheck , MonadRandom , mtl , optparse-applicative , pointed , protolude , QuickCheck >= 2.8.1 - , quickcheck-instances - , quickcheck-text , recursion-schemes , regex-compat , semigroups @@ -144,9 +141,9 @@ benchmark semantic-diff-bench build-depends: base , criterion , directory + , leancheck , monad-par , mtl - , QuickCheck >= 2.8.1 , semantic-diff , text >= 1.2.1.3 ghc-options: -threaded -rtsopts -with-rtsopts=-N -j -O2 -static -pgml=script/g++ @@ -168,17 +165,18 @@ test-suite test , RangeSpec , Source.Spec , TermSpec + , Test.Hspec.LeanCheck build-depends: base , bifunctors , deepseq , filepath , Glob , hspec >= 2.1.10 + , hspec-core , hspec-expectations-pretty-diff + , leancheck , mtl , protolude - , QuickCheck >= 2.8.1 - , quickcheck-text , recursion-schemes >= 4.1 , semantic-diff , text >= 1.2.1.3 diff --git a/src/Category.hs b/src/Category.hs index fd7e5ee72..73807b583 100644 --- a/src/Category.hs +++ b/src/Category.hs @@ -3,9 +3,9 @@ module Category where import Prologue -import Test.QuickCheck hiding (Args) +import Data.Functor.Listable import Data.Text (pack) -import Data.Text.Arbitrary() +import Data.Text.Listable -- | A standardized category of AST node. Used to determine the semantics for -- | semantic diffing and define comparability of nodes. @@ -227,108 +227,119 @@ instance Hashable Category instance (StringConv Category Text) where strConv _ = pack . show -instance Arbitrary Category where - arbitrary = oneof [ - pure Program - , pure Error - , pure Boolean - , pure BooleanOperator - , pure MathOperator - , pure DictionaryLiteral - , pure Pair - , pure FunctionCall - , pure Function - , pure Identifier - , pure Params - , pure ExpressionStatements - , pure MethodCall - , pure Args - , pure StringLiteral - , pure IntegerLiteral - , pure NumberLiteral - , pure FloatLiteral - , pure Regex - , pure Return - , pure SymbolLiteral - , pure TemplateString - , pure ArrayLiteral - , pure Assignment - , pure MathAssignment - , pure MemberAccess - , pure SubscriptAccess - , pure VarAssignment - , pure VarDecl - , pure For - , pure DoWhile - , pure While - , pure Switch - , pure Ternary - , pure Case - , pure Operator - , pure Object - , pure Throw - , pure Constructor - , pure Try - , pure Catch - , pure Finally - , pure Class - , pure Method - , pure Module - , pure Import - , pure Export - , pure Interpolation - , pure Subshell - , pure OperatorAssignment - , pure Yield - , pure Until - , pure Unless - , pure Begin - , pure Else - , pure Elsif - , pure Ensure - , pure Rescue - , pure RescueModifier - , pure RescuedException - , pure RescueArgs - , pure When - , pure Negate - , pure Select - , pure Defer - , pure Go - , pure Slice - , pure TypeAssertion - , pure TypeConversion - , pure ArgumentPair - , pure KeywordParameter - , pure OptionalParameter - , pure SplatParameter - , pure HashSplatParameter - , pure BlockParameter - , pure ArrayTy - , pure DictionaryTy - , pure Break - , pure Continue - , pure Binary - , pure Unary - , pure Constant - , pure Superclass - , pure SingletonClass - , pure ParameterDecl - , pure Default - , pure TypeDecl - , pure PointerTy - , pure FieldDecl - , pure SliceTy - , pure Element - , pure IndexExpression - , pure FunctionTy - , pure IncrementStatement - , pure DecrementStatement - , pure QualifiedIdentifier - , pure FieldDeclarations - , pure RuneLiteral - , Other <$> arbitrary - ] - - shrink (Other s) = Other <$> shrink s - shrink _ = [] +instance Listable Category where + tiers + = cons0 Program + \/ cons0 Error + \/ cons0 Boolean + \/ cons0 BooleanOperator + \/ cons0 MathOperator + \/ cons0 DictionaryLiteral + \/ cons0 Pair + \/ cons0 FunctionCall + \/ cons0 Function + \/ cons0 Identifier + \/ cons0 Params + \/ cons0 ExpressionStatements + \/ cons0 MethodCall + \/ cons0 Args + \/ cons0 StringLiteral + \/ cons0 IntegerLiteral + \/ cons0 NumberLiteral + \/ cons0 Regex + \/ cons0 Return + \/ cons0 SymbolLiteral + \/ cons0 TemplateString + \/ cons0 ArrayLiteral + \/ cons0 Assignment + \/ cons0 MathAssignment + \/ cons0 MemberAccess + \/ cons0 SubscriptAccess + \/ cons0 VarAssignment + \/ cons0 VarDecl + \/ cons0 For + \/ cons0 DoWhile + \/ cons0 While + \/ cons0 Switch + \/ cons0 If + \/ cons0 Ternary + \/ cons0 Case + \/ cons0 Operator + \/ cons0 CommaOperator + \/ cons0 Object + \/ cons0 Throw + \/ cons0 Constructor + \/ cons0 Try + \/ cons0 Catch + \/ cons0 Finally + \/ cons0 Class + \/ cons0 Method + \/ cons0 Comment + \/ cons0 RelationalOperator + \/ cons0 Empty + \/ cons0 Module + \/ cons0 Import + \/ cons0 Export + \/ cons0 AnonymousFunction + \/ cons0 Interpolation + \/ cons0 Subshell + \/ cons0 OperatorAssignment + \/ cons0 Yield + \/ cons0 Until + \/ cons0 Unless + \/ cons0 Begin + \/ cons0 Else + \/ cons0 Elsif + \/ cons0 Ensure + \/ cons0 Rescue + \/ cons0 RescueModifier + \/ cons0 RescuedException + \/ cons0 RescueArgs + \/ cons0 When + \/ cons0 Negate + \/ cons0 Select + \/ cons0 Defer + \/ cons0 Go + \/ cons0 Slice + \/ cons0 TypeAssertion + \/ cons0 TypeConversion + \/ cons0 ArgumentPair + \/ cons0 KeywordParameter + \/ cons0 OptionalParameter + \/ cons0 SplatParameter + \/ cons0 HashSplatParameter + \/ cons0 BlockParameter + \/ cons0 FloatLiteral + \/ cons0 ArrayTy + \/ cons0 DictionaryTy + \/ cons0 StructTy + \/ cons0 Struct + \/ cons0 Break + \/ cons0 Continue + \/ cons0 Binary + \/ cons0 Unary + \/ cons0 Constant + \/ cons0 Superclass + \/ cons0 SingletonClass + \/ cons0 RangeExpression + \/ cons0 ScopeOperator + \/ cons0 BeginBlock + \/ cons0 EndBlock + \/ cons0 ParameterDecl + \/ cons0 Default + \/ cons0 TypeDecl + \/ cons0 PointerTy + \/ cons0 FieldDecl + \/ cons0 SliceTy + \/ cons0 Element + \/ cons0 Literal + \/ cons0 ChannelTy + \/ cons0 Send + \/ cons0 IndexExpression + \/ cons0 FunctionTy + \/ cons0 IncrementStatement + \/ cons0 DecrementStatement + \/ cons0 QualifiedIdentifier + \/ cons0 FieldDeclarations + \/ cons0 RuneLiteral + \/ cons1 (Other . unListableText) diff --git a/src/Data/Bifunctor/Join/Arbitrary.hs b/src/Data/Bifunctor/Join/Arbitrary.hs deleted file mode 100644 index 26b80a44c..000000000 --- a/src/Data/Bifunctor/Join/Arbitrary.hs +++ /dev/null @@ -1,16 +0,0 @@ -{-# OPTIONS_GHC -fno-warn-orphans #-} -module Data.Bifunctor.Join.Arbitrary where - -import Data.Bifunctor.Join -import Data.Functor.Both as Both -import Data.These -import Prologue -import Test.QuickCheck - -instance Arbitrary a => Arbitrary (Join These a) where - arbitrary = Join <$> arbitrary - shrink (Join a) = Join <$> shrink a - -instance Arbitrary a => Arbitrary (Join (,) a) where - arbitrary = both <$> arbitrary <*> arbitrary - shrink b = both <$> shrink (Both.fst b) <*> shrink (Both.snd b) diff --git a/src/Data/Functor/Listable.hs b/src/Data/Functor/Listable.hs new file mode 100644 index 000000000..2ff37f8ab --- /dev/null +++ b/src/Data/Functor/Listable.hs @@ -0,0 +1,127 @@ +module Data.Functor.Listable +( Listable(..) +, mapT +, cons0 +, cons1 +, cons2 +, cons3 +, cons4 +, cons5 +, cons6 +, (\/) +, Tier +, Listable1(..) +, tiers1 +, Listable2(..) +, tiers2 +, liftCons1 +, liftCons2 +, liftCons3 +, liftCons4 +, ListableF(..) +) where + +import Data.Bifunctor.Join +import Data.These +import Prologue +import Test.LeanCheck + +type Tier a = [a] + +-- | Lifting of 'Listable' to @* -> *@. +class Listable1 l where + -- | The tiers for @l :: * -> *@, parameterized by the tiers for @a :: *@. + liftTiers :: [Tier a] -> [Tier (l a)] + +-- | A suitable definition of 'tiers' for 'Listable1' type constructors parameterized by 'Listable' types. +tiers1 :: (Listable a, Listable1 l) => [Tier (l a)] +tiers1 = liftTiers tiers + + +-- | Lifting of 'Listable' to @* -> * -> *@. +class Listable2 l where + -- | The tiers for @l :: * -> * -> *@, parameterized by the tiers for @a :: *@ & @b :: *@. + liftTiers2 :: [Tier a] -> [Tier b] -> [Tier (l a b)] + +-- | A suitable definition of 'tiers' for 'Listable2' type constructors parameterized by 'Listable' types. +tiers2 :: (Listable a, Listable b, Listable2 l) => [Tier (l a b)] +tiers2 = liftTiers2 tiers tiers + + +-- | Lifts a unary constructor to a list of tiers, given a list of tiers for its argument. +-- +-- Commonly used in the definition of 'Listable1' and 'Listable2' instances. +liftCons1 :: [Tier a] -> (a -> b) -> [Tier b] +liftCons1 tiers f = mapT f tiers `addWeight` 1 + +-- | Lifts a binary constructor to a list of tiers, given lists of tiers for its arguments. +-- +-- Commonly used in the definition of 'Listable1' and 'Listable2' instances. +liftCons2 :: [Tier a] -> [Tier b] -> (a -> b -> c) -> [Tier c] +liftCons2 tiers1 tiers2 f = mapT (uncurry f) (productWith (,) tiers1 tiers2) `addWeight` 1 + +-- | Lifts a ternary constructor to a list of tiers, given lists of tiers for its arguments. +-- +-- Commonly used in the definition of 'Listable1' and 'Listable2' instances. +liftCons3 :: [Tier a] -> [Tier b] -> [Tier c] -> (a -> b -> c -> d) -> [Tier d] +liftCons3 tiers1 tiers2 tiers3 f = mapT (uncurry3 f) (productWith (\ x (y, z) -> (x, y, z)) tiers1 (liftCons2 tiers2 tiers3 (,)) ) `addWeight` 1 + where uncurry3 f (a, b, c) = f a b c + +-- | Lifts a quaternary constructor to a list of tiers, given lists of tiers for its arguments. +-- +-- Commonly used in the definition of 'Listable1' and 'Listable2' instances. +liftCons4 :: [Tier a] -> [Tier b] -> [Tier c] -> [Tier d] -> (a -> b -> c -> d -> e) -> [Tier e] +liftCons4 tiers1 tiers2 tiers3 tiers4 f = mapT (uncurry4 f) (productWith (\ x (y, z, w) -> (x, y, z, w)) tiers1 (liftCons3 tiers2 tiers3 tiers4 (,,)) ) `addWeight` 1 + where uncurry4 f (a, b, c, d) = f a b c d + +-- | Convenient wrapper for 'Listable1' type constructors and 'Listable' types, where a 'Listable' instance would necessarily be orphaned. +newtype ListableF f a = ListableF { unListableF :: f a } + deriving Show + + +-- Instances + +instance Listable1 Maybe where + liftTiers tiers = cons0 Nothing \/ liftCons1 tiers Just + +instance Listable2 (,) where + liftTiers2 = productWith (,) + +instance Listable2 Either where + liftTiers2 leftTiers rightTiers = liftCons1 leftTiers Left \/ liftCons1 rightTiers Right + +instance Listable a => Listable1 ((,) a) where + liftTiers = liftTiers2 tiers + +instance Listable1 [] where + liftTiers tiers = go + where go = cons0 [] \/ liftCons2 tiers go (:) + +instance Listable2 p => Listable1 (Join p) where + liftTiers tiers = liftCons1 (liftTiers2 tiers tiers) Join + +instance Listable2 These where + liftTiers2 this that = liftCons1 this This \/ liftCons1 that That \/ liftCons2 this that These + +instance Listable1 f => Listable2 (CofreeF f) where + liftTiers2 annotationTiers recurTiers = liftCons2 annotationTiers (liftTiers recurTiers) (:<) + +instance (Listable1 f, Listable a) => Listable1 (CofreeF f a) where + liftTiers = liftTiers2 tiers + +instance Listable1 f => Listable1 (Cofree f) where + liftTiers annotationTiers = go + where go = liftCons1 (liftTiers2 annotationTiers go) cofree + +instance Listable1 f => Listable2 (FreeF f) where + liftTiers2 pureTiers recurTiers = liftCons1 pureTiers Pure \/ liftCons1 (liftTiers recurTiers) Free + +instance (Listable1 f, Listable a) => Listable1 (FreeF f a) where + liftTiers = liftTiers2 tiers + +instance Listable1 f => Listable1 (Free f) where + liftTiers pureTiers = go + where go = liftCons1 (liftTiers2 pureTiers go) free + +instance (Listable1 f, Listable a) => Listable (ListableF f a) where + tiers = ListableF `mapT` tiers1 diff --git a/src/Data/RandomWalkSimilarity.hs b/src/Data/RandomWalkSimilarity.hs index 82ccb0a8d..deded9cbe 100644 --- a/src/Data/RandomWalkSimilarity.hs +++ b/src/Data/RandomWalkSimilarity.hs @@ -18,23 +18,23 @@ module Data.RandomWalkSimilarity import Control.Applicative import Control.Monad.Random import Control.Monad.State +import Data.Align.Generic import Data.Functor.Both hiding (fst, snd) +import Data.Functor.Listable import Data.Hashable import qualified Data.IntMap as IntMap import qualified Data.KdTree.Static as KdTree -import Data.Semigroup (Min(..), Option(..)) import Data.Record +import Data.Semigroup (Min(..), Option(..)) +import Data.These import qualified Data.Vector as Vector +import Diff +import Info import Patch import Prologue as P -import Term (termSize, zipTerms, Term, TermF) -import Test.QuickCheck hiding (Fixed) -import Test.QuickCheck.Random import qualified SES -import Info -import Data.Align.Generic -import Data.These -import Diff +import Term (termSize, zipTerms, Term, TermF) +import Test.QuickCheck.Random (mkQCGen) type Label f fields label = forall b. TermF f (Record fields) b -> label type DiffTerms f fields = Term f (Record fields) -> Term f (Record fields) -> Maybe (Diff f (Record fields)) @@ -300,11 +300,8 @@ instance Hashable label => Hashable (Gram label) where hashWithSalt _ = hash hash gram = hash (stem gram <> base gram) --- | Construct a generator for arbitrary `Gram`s of size `(p, q)`. -gramWithPQ :: Arbitrary label => Int -> Int -> Gen (Gram label) -gramWithPQ p q = Gram <$> vectorOf p arbitrary <*> vectorOf q arbitrary +instance Listable1 Gram where + liftTiers tiers = liftCons2 (liftTiers (liftTiers tiers)) (liftTiers (liftTiers tiers)) Gram -instance Arbitrary label => Arbitrary (Gram label) where - arbitrary = join $ gramWithPQ <$> arbitrary <*> arbitrary - - shrink (Gram a b) = Gram <$> shrink a <*> shrink b +instance Listable a => Listable (Gram a) where + tiers = tiers1 diff --git a/src/Data/Record.hs b/src/Data/Record.hs index 0fb1a2ea1..4c5f68bd1 100644 --- a/src/Data/Record.hs +++ b/src/Data/Record.hs @@ -1,15 +1,14 @@ {-# LANGUAGE DataKinds, GADTs, KindSignatures, MultiParamTypeClasses, TypeOperators, ConstraintKinds #-} module Data.Record where -import GHC.Show -import Prologue -import Test.QuickCheck import Category -import Range -import SourceSpan import Data.Aeson import Data.Aeson.Types - +import Data.Functor.Listable +import GHC.Show +import Prologue +import Range +import SourceSpan -- | A type alias for HasField constraints commonly used throughout semantic-diff. type DefaultFields fields = (HasField fields Category, HasField fields Range, HasField fields SourceSpan) @@ -63,12 +62,21 @@ instance (Show h, Show (Record t)) => Show (Record (h ': t)) where instance Show (Record '[]) where showsPrec n RNil = showParen (n > 0) ("RNil" <>) -instance (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSON (Record (a ': b ': c ': d ': '[])) where - toJSON (RCons a (RCons b (RCons c (RCons d RNil)))) = toJSONList [toJSON a, toJSON b, toJSON c, toJSON d] +instance (ToJSON h, ToJSONList (Record t)) => ToJSON (Record (h ': t)) where + toJSON r = toJSONList (toJSONValues r) instance ToJSON (Record '[]) where toJSON _ = emptyArray +class ToJSONList t where + toJSONValues :: t -> [Value] + +instance (ToJSON h, ToJSONList (Record t)) => ToJSONList (Record (h ': t)) where + toJSONValues (RCons h t) = toJSON h : toJSONValues t + +instance ToJSONList (Record '[]) where + toJSONValues _ = [] + instance (Eq h, Eq (Record t)) => Eq (Record (h ': t)) where RCons h1 t1 == RCons h2 t2 = h1 == h2 && t1 == t2 @@ -85,12 +93,8 @@ instance Ord (Record '[]) where _ `compare` _ = EQ -instance (Arbitrary field, Arbitrary (Record fields)) => Arbitrary (Record (field ': fields)) where - arbitrary = RCons <$> arbitrary <*> arbitrary +instance (Listable head, Listable (Record tail)) => Listable (Record (head ': tail)) where + tiers = cons2 RCons - shrink (RCons h t) = RCons <$> shrink h <*> shrink t - -instance Arbitrary (Record '[]) where - arbitrary = pure RNil - - shrink _ = [] +instance Listable (Record '[]) where + tiers = cons0 RNil diff --git a/src/Data/Text/Listable.hs b/src/Data/Text/Listable.hs new file mode 100644 index 000000000..6e6b6e421 --- /dev/null +++ b/src/Data/Text/Listable.hs @@ -0,0 +1,10 @@ +module Data.Text.Listable where + +import Data.Functor.Listable +import Data.Text +import Prologue + +newtype ListableText = ListableText { unListableText :: Text } + +instance Listable ListableText where + tiers = cons1 (ListableText . pack) diff --git a/src/Diff/Arbitrary.hs b/src/Diff/Arbitrary.hs deleted file mode 100644 index c4504d844..000000000 --- a/src/Diff/Arbitrary.hs +++ /dev/null @@ -1,55 +0,0 @@ -module Diff.Arbitrary where - -import Diff -import Term -import Data.Bifunctor.Join -import Data.Bifunctor.Join.Arbitrary () -import Data.Functor.Both -import Data.Functor.Foldable (unfold) -import Patch -import Patch.Arbitrary () -import Syntax -import Prologue -import Term.Arbitrary -import Test.QuickCheck hiding (Fixed) - -data ArbitraryDiff leaf annotation - = ArbitraryFree (Join (,) annotation) (Syntax leaf (ArbitraryDiff leaf annotation)) - | ArbitraryPure (Patch (ArbitraryTerm leaf annotation)) - deriving (Show, Eq, Generic) - -unArbitraryDiff :: ArbitraryDiff leaf annotation -> FreeF (TermF (Syntax leaf) (Both annotation)) (Patch (ArbitraryTerm leaf annotation)) (ArbitraryDiff leaf annotation) -unArbitraryDiff (ArbitraryFree a s) = Free (a :< s) -unArbitraryDiff (ArbitraryPure p) = Pure p - -toDiff :: ArbitraryDiff leaf annotation -> Diff (Syntax leaf) annotation -toDiff = fmap (fmap toTerm) . unfold unArbitraryDiff - -diffOfSize :: (Arbitrary leaf, Arbitrary annotation) => Int -> Gen (ArbitraryDiff leaf annotation) -diffOfSize n - | n <= 0 = ArbitraryFree <$> arbitrary <*> syntaxOfSize diffOfSize n - | otherwise = oneof - [ ArbitraryFree <$> arbitrary <*> syntaxOfSize diffOfSize n - , ArbitraryPure <$> patchOfSize n ] - where patchOfSize 1 = oneof [ Insert <$> termOfSize 1 - , Delete <$> termOfSize 1 ] - patchOfSize n = do - m <- choose (1, n - 1) - left <- termOfSize m - right <- termOfSize (n - m) - oneof [ Insert <$> termOfSize n - , Delete <$> termOfSize n - , pure (Replace left right) ] - -arbitraryDiffSize :: ArbitraryDiff leaf annotation -> Int -arbitraryDiffSize = iter (succ . sum) . fmap (sum . fmap (arbitraryTermSize . unfold runCofree)) . toDiff - - --- Instances - -instance (Eq leaf, Eq annotation, Arbitrary leaf, Arbitrary annotation) => Arbitrary (ArbitraryDiff leaf annotation) where - arbitrary = sized $ \ n -> do - m <- choose (0, n) - diffOfSize m - - shrink = genericShrink diff --git a/src/DiffSummary.hs b/src/DiffSummary.hs index 00540eb11..73609775b 100644 --- a/src/DiffSummary.hs +++ b/src/DiffSummary.hs @@ -13,9 +13,9 @@ import Syntax as S import Category as C import Data.Functor.Both hiding (fst, snd) import qualified Data.Functor.Both as Both +import Data.Functor.Listable import qualified Data.Text as Text -import Test.QuickCheck hiding (Fixed) -import Patch.Arbitrary() +import Data.Text.Listable import Data.Record import Data.These import Text.PrettyPrint.Leijen.Text ((<+>), squotes, space, string, Doc, punctuate, pretty, hsep) @@ -93,7 +93,7 @@ data DiffSummary a = DiffSummary { } deriving (Eq, Functor, Show, Generic) -- Returns a list of diff summary texts given two source blobs and a diff. -diffSummaries :: (HasCategory leaf, DefaultFields fields) => Both SourceBlob -> SyntaxDiff leaf fields -> [JSONSummary Text SourceSpans] +diffSummaries :: (StringConv leaf Text, DefaultFields fields) => Both SourceBlob -> SyntaxDiff leaf fields -> [JSONSummary Text SourceSpans] diffSummaries blobs diff = summaryToTexts =<< diffToDiffSummaries (source <$> blobs) diff -- Takes a 'DiffSummary DiffInfo' and returns a list of JSON Summaries whose text summaries represent the LeafInfo summaries of the 'DiffSummary'. @@ -103,7 +103,7 @@ summaryToTexts DiffSummary{..} = appendParentContexts <$> summaries patch jsonSummary { summary = show $ summary jsonSummary <+> parentContexts parentAnnotation } -- Returns a list of 'DiffSummary' given two source blobs and a diff. -diffToDiffSummaries :: (HasCategory leaf, DefaultFields fields) => Both (Source Char) -> SyntaxDiff leaf fields -> [DiffSummary DiffInfo] +diffToDiffSummaries :: (StringConv leaf Text, DefaultFields fields) => Both (Source Char) -> SyntaxDiff leaf fields -> [DiffSummary DiffInfo] diffToDiffSummaries sources = para $ \diff -> let diff' = free (Prologue.fst <$> diff) @@ -183,7 +183,7 @@ toLeafInfos LeafInfo{..} = pure $ JSONSummary (summary leafCategory termName) so vowels = Text.singleton <$> ("aeiouAEIOU" :: [Char]) -- Returns a text representing a specific term given a source and a term. -toTermName :: forall leaf fields. (HasCategory leaf, DefaultFields fields) => Source Char -> SyntaxTerm leaf fields -> Text +toTermName :: forall leaf fields. (StringConv leaf Text, DefaultFields fields) => Source Char -> SyntaxTerm leaf fields -> Text toTermName source term = case unwrap term of S.Send _ _ -> termNameFromSource term S.Ty _ -> termNameFromSource term @@ -195,7 +195,7 @@ toTermName source term = case unwrap term of S.AnonymousFunction params _ -> "anonymous" <> paramsToArgNames params S.Fixed children -> termNameFromChildren term children S.Indexed children -> fromMaybe "branch" $ (toCategoryName . category) . extract <$> head children - Leaf leaf -> toCategoryName leaf + Leaf leaf -> toS leaf S.Assignment identifier _ -> toTermName' identifier S.Function identifier _ _ -> toTermName' identifier S.ParameterDecl _ _ -> termNameFromSource term @@ -249,7 +249,7 @@ toTermName source term = case unwrap term of S.Array ty _ -> maybe (termNameFromSource term) termNameFromSource ty S.Class identifier _ _ -> toTermName' identifier S.Method identifier args _ -> toTermName' identifier <> paramsToArgNames args - S.Comment a -> toCategoryName a + S.Comment a -> toS a S.Commented _ _ -> termNameFromChildren term (toList $ unwrap term) S.Module identifier _ -> toTermName' identifier S.Import identifier [] -> termNameFromSource identifier @@ -319,7 +319,7 @@ parentContexts contexts = hsep $ either identifiableDoc annotatableDoc <$> conte toDoc :: Text -> Doc toDoc = string . toS -termToDiffInfo :: (HasCategory leaf, DefaultFields fields) => Source Char -> SyntaxTerm leaf fields -> DiffInfo +termToDiffInfo :: (StringConv leaf Text, DefaultFields fields) => Source Char -> SyntaxTerm leaf fields -> DiffInfo termToDiffInfo blob term = case unwrap term of S.Indexed children -> BranchInfo (termToDiffInfo' <$> children) (category $ extract term) BIndexed S.Fixed children -> BranchInfo (termToDiffInfo' <$> children) (category $ extract term) BFixed @@ -336,7 +336,7 @@ termToDiffInfo blob term = case unwrap term of -- | For a DiffSummary without a parentAnnotation, we append a parentAnnotation with the first identifiable term. -- | For a DiffSummary with a parentAnnotation, we append the next annotatable term to the extant parentAnnotation. -- | If a DiffSummary already has a parentAnnotation, and a (grand) parentAnnotation, then we return the summary without modification. -appendSummary :: (HasCategory leaf, DefaultFields fields) => Source Char -> SyntaxTerm leaf fields -> DiffSummary DiffInfo -> DiffSummary DiffInfo +appendSummary :: (StringConv leaf Text, DefaultFields fields) => Source Char -> SyntaxTerm leaf fields -> DiffSummary DiffInfo -> DiffSummary DiffInfo appendSummary source term summary = case (parentAnnotation summary, identifiable term, annotatable term) of ([], Identifiable _, _) -> appendParentAnnotation Left @@ -481,13 +481,15 @@ instance HasCategory Category where instance HasField fields Category => HasCategory (SyntaxTerm leaf fields) where toCategoryName = toCategoryName . category . extract -instance Arbitrary Branch where - arbitrary = oneof [ pure BIndexed, pure BFixed ] - shrink = genericShrink +instance Listable Branch where + tiers = cons0 BIndexed \/ cons0 BFixed \/ cons0 BCommented \/ cons0 BIf -instance Arbitrary a => Arbitrary (DiffSummary a) where - arbitrary = DiffSummary <$> arbitrary <*> arbitrary - shrink = genericShrink +instance Listable1 DiffSummary where + liftTiers termTiers = liftCons2 (liftTiers termTiers) (liftTiers (eitherTiers (liftTiers (mapT unListableText tiers)))) DiffSummary + where eitherTiers tiers = liftTiers2 tiers tiers + +instance Listable a => Listable (DiffSummary a) where + tiers = tiers1 instance P.Pretty DiffInfo where pretty LeafInfo{..} = squotes (string $ toSL termName) <+> string (toSL (toCategoryName leafCategory)) diff --git a/src/Info.hs b/src/Info.hs index 84e550297..b5eaa73b4 100644 --- a/src/Info.hs +++ b/src/Info.hs @@ -1,12 +1,12 @@ {-# LANGUAGE DataKinds, GeneralizedNewtypeDeriving #-} module Info (Range(..), characterRange, setCharacterRange, Category(..), category, setCategory, Cost(..), cost, setCost, SourceSpan(..), SourcePos(..), SourceSpans(..), SourceText(..), sourceText) where +import Data.Functor.Listable import Data.Record import Prologue import Category import Range import SourceSpan -import Test.QuickCheck import Data.Aeson newtype Cost = Cost { unCost :: Int } @@ -39,7 +39,5 @@ setCost = setField -- Instances -instance Arbitrary Cost where - arbitrary = Cost <$> arbitrary - - shrink = fmap Cost . shrink . unCost +instance Listable Cost where + tiers = cons1 Cost diff --git a/src/Patch.hs b/src/Patch.hs index 5ffbff72a..da267f947 100644 --- a/src/Patch.hs +++ b/src/Patch.hs @@ -13,6 +13,7 @@ module Patch , mapPatch ) where +import Data.Functor.Listable import Data.These import Prologue @@ -69,3 +70,12 @@ maybeFst = these Just (const Nothing) ((Just .) . const) -- | Return Just the value in That, or the second value in These, if any. maybeSnd :: These a b -> Maybe b maybeSnd = these (const Nothing) Just ((Just .) . flip const) + + +-- Instances + +instance Listable1 Patch where + liftTiers t = liftCons1 t Insert \/ liftCons1 t Delete \/ liftCons2 t t Replace + +instance Listable a => Listable (Patch a) where + tiers = tiers1 diff --git a/src/Patch/Arbitrary.hs b/src/Patch/Arbitrary.hs deleted file mode 100644 index 5fc479ef9..000000000 --- a/src/Patch/Arbitrary.hs +++ /dev/null @@ -1,18 +0,0 @@ -{-# OPTIONS_GHC -fno-warn-orphans #-} -module Patch.Arbitrary where - -import Patch -import Prologue -import Test.QuickCheck - -patchOf :: Gen a -> Gen a -> Gen (Patch a) -patchOf l r = oneof - [ Insert <$> r - , Delete <$> l - , Replace <$> l <*> r - ] - -instance Arbitrary a => Arbitrary (Patch a) where - arbitrary = patchOf arbitrary arbitrary - - shrink patch = traverse shrink patch diff --git a/src/Range.hs b/src/Range.hs index 04d5f4a12..d77fcddd4 100644 --- a/src/Range.hs +++ b/src/Range.hs @@ -5,8 +5,7 @@ import Data.List (span) import Data.Semigroup import Data.String import Prologue -import Test.QuickCheck - +import Test.LeanCheck -- | A half-open interval of integers, defined by start & end indices. data Range = Range { start :: Int, end :: Int } @@ -82,7 +81,5 @@ instance Semigroup Range where instance Ord Range where a <= b = start a <= start b -instance Arbitrary Range where - arbitrary = Range <$> arbitrary <*> arbitrary - - shrink s = Range <$> shrink (start s) <*> shrink (end s) +instance Listable Range where + tiers = cons2 Range diff --git a/src/SourceSpan.hs b/src/SourceSpan.hs index fe0b8739a..514d68b5b 100644 --- a/src/SourceSpan.hs +++ b/src/SourceSpan.hs @@ -6,13 +6,12 @@ -- module SourceSpan where -import Prologue import Data.Aeson ((.=), (.:)) import qualified Data.Aeson as A -import Test.QuickCheck -import Data.These -import Data.Text.Arbitrary() import Data.Semigroup +import Data.These +import Prologue +import Test.LeanCheck -- | -- Source position information @@ -99,10 +98,8 @@ instance A.ToJSON SourceSpans where (That span) -> A.pairs $ "insert" .= span (These span1 span2) -> A.pairs $ "replace" .= (span1, span2) -instance Arbitrary SourcePos where - arbitrary = SourcePos <$> arbitrary <*> arbitrary - shrink = genericShrink +instance Listable SourcePos where + tiers = cons2 SourcePos -instance Arbitrary SourceSpan where - arbitrary = SourceSpan <$> arbitrary <*> arbitrary - shrink = genericShrink +instance Listable SourceSpan where + tiers = cons2 SourceSpan diff --git a/src/Syntax.hs b/src/Syntax.hs index 18157a668..8c37e046b 100644 --- a/src/Syntax.hs +++ b/src/Syntax.hs @@ -1,11 +1,11 @@ {-# LANGUAGE DeriveAnyClass #-} module Syntax where -import Prologue +import Data.Aeson +import Data.Functor.Listable import Data.Mergeable import GHC.Generics -import Test.QuickCheck hiding (Fixed) -import Data.Aeson +import Prologue -- | A node in an abstract syntax tree. -- @@ -109,21 +109,63 @@ data Syntax a f -- Instances -instance (Arbitrary leaf, Arbitrary f) => Arbitrary (Syntax leaf f) where - arbitrary = sized (syntaxOfSize (`resize` arbitrary) ) +instance Listable2 Syntax where + liftTiers2 leaf recur + = liftCons1 leaf Leaf + \/ liftCons1 (liftTiers recur) Indexed + \/ liftCons1 (liftTiers recur) Fixed + \/ liftCons2 recur (liftTiers recur) FunctionCall + \/ liftCons2 recur (liftTiers recur) Ternary + \/ liftCons2 (liftTiers recur) (liftTiers recur) AnonymousFunction + \/ liftCons3 recur (liftTiers recur) (liftTiers recur) Function + \/ liftCons2 recur recur Assignment + \/ liftCons2 recur recur OperatorAssignment + \/ liftCons2 recur recur MemberAccess + \/ liftCons3 recur recur (liftTiers recur) MethodCall + \/ liftCons1 (liftTiers recur) Operator + \/ liftCons2 recur (liftTiers recur) VarDecl + \/ liftCons2 recur recur VarAssignment + \/ liftCons2 recur recur SubscriptAccess + \/ liftCons2 (liftTiers recur) (liftTiers recur) Switch + \/ liftCons2 recur (liftTiers recur) Case + \/ liftCons1 (liftTiers recur) Select + \/ liftCons2 (liftTiers recur) (liftTiers recur) Syntax.Object + \/ liftCons2 recur recur Pair + \/ liftCons1 leaf Comment + \/ liftCons2 (liftTiers recur) (liftTiers recur) Commented + \/ liftCons1 (liftTiers recur) Syntax.Error + \/ liftCons2 (liftTiers recur) (liftTiers recur) For + \/ liftCons2 recur recur DoWhile + \/ liftCons2 recur (liftTiers recur) While + \/ liftCons1 (liftTiers recur) Return + \/ liftCons1 recur Throw + \/ liftCons1 recur Constructor + \/ liftCons4 (liftTiers recur) (liftTiers recur) (liftTiers recur) (liftTiers recur) Try + \/ liftCons2 (liftTiers recur) (liftTiers recur) Syntax.Array + \/ liftCons3 recur (liftTiers recur) (liftTiers recur) Class + \/ liftCons3 recur (liftTiers recur) (liftTiers recur) Method + \/ liftCons2 recur (liftTiers recur) If + \/ liftCons2 recur (liftTiers recur) Module + \/ liftCons2 recur (liftTiers recur) Import + \/ liftCons2 (liftTiers recur) (liftTiers recur) Export + \/ liftCons1 (liftTiers recur) Yield + \/ liftCons1 recur Negate + \/ liftCons2 (liftTiers recur) (liftTiers recur) Rescue + \/ liftCons1 recur Go + \/ liftCons1 recur Defer + \/ liftCons2 recur recur TypeAssertion + \/ liftCons2 recur recur TypeConversion + \/ liftCons1 (liftTiers recur) Break + \/ liftCons1 (liftTiers recur) Continue + \/ liftCons1 (liftTiers recur) BlockStatement + \/ liftCons2 (liftTiers recur) recur ParameterDecl + \/ liftCons2 recur recur TypeDecl + \/ liftCons3 recur (liftTiers recur) (liftTiers recur) FieldDecl + \/ liftCons1 recur Ty + \/ liftCons2 recur recur Send - shrink = genericShrink +instance Listable leaf => Listable1 (Syntax leaf) where + liftTiers = liftTiers2 tiers -syntaxOfSize :: Arbitrary leaf => (Int -> Gen f) -> Int -> Gen (Syntax leaf f) -syntaxOfSize recur n | n <= 1 = oneof $ (Leaf <$> arbitrary) : branchGeneratorsOfSize n - | otherwise = oneof $ branchGeneratorsOfSize n - where branchGeneratorsOfSize n = - [ Indexed <$> childrenOfSize (pred n) - , Fixed <$> childrenOfSize (pred n) - ] - childrenOfSize n | n <= 0 = pure [] - childrenOfSize n = do - m <- choose (1, n) - first <- recur m - rest <- childrenOfSize (n - m) - pure $! first : rest +instance (Listable leaf, Listable recur) => Listable (Syntax leaf recur) where + tiers = tiers1 diff --git a/src/Term.hs b/src/Term.hs index d1f1d2f03..492d6e753 100644 --- a/src/Term.hs +++ b/src/Term.hs @@ -11,7 +11,7 @@ import Data.These import Syntax -- | A Term with an abstract syntax tree and an annotation. -type Term f annotation = Cofree f annotation +type Term f = Cofree f type TermF = CofreeF -- | A Term with a Syntax leaf and a record of fields. diff --git a/src/Term/Arbitrary.hs b/src/Term/Arbitrary.hs deleted file mode 100644 index e17727bef..000000000 --- a/src/Term/Arbitrary.hs +++ /dev/null @@ -1,37 +0,0 @@ -{-# LANGUAGE TypeFamilies #-} -module Term.Arbitrary where - -import Data.Functor.Foldable (Base, unfold, Corecursive(embed)) -import Data.Text.Arbitrary () -import Prologue -import Syntax -import Term -import Test.QuickCheck hiding (Fixed) - -data ArbitraryTerm leaf annotation = ArbitraryTerm { annotation :: annotation, syntax :: Syntax leaf (ArbitraryTerm leaf annotation)} - deriving (Show, Eq, Generic) - -unArbitraryTerm :: ArbitraryTerm leaf annotation -> TermF (Syntax leaf) annotation (ArbitraryTerm leaf annotation) -unArbitraryTerm (ArbitraryTerm a s) = a :< s - -toTerm :: ArbitraryTerm leaf annotation -> Term (Syntax leaf) annotation -toTerm = unfold unArbitraryTerm - -termOfSize :: (Arbitrary leaf, Arbitrary annotation) => Int -> Gen (ArbitraryTerm leaf annotation) -termOfSize n = ArbitraryTerm <$> arbitrary <*> syntaxOfSize termOfSize n - -arbitraryTermSize :: ArbitraryTerm leaf annotation -> Int -arbitraryTermSize = cata (succ . sum) . toTerm - - --- Instances - -type instance Base (ArbitraryTerm leaf annotation) = TermF (Syntax leaf) annotation -instance Corecursive (ArbitraryTerm leaf annotation) where embed (a :< s) = ArbitraryTerm a s - -instance (Eq leaf, Eq annotation, Arbitrary leaf, Arbitrary annotation) => Arbitrary (ArbitraryTerm leaf annotation) where - arbitrary = sized $ \ n -> do - m <- choose (0, n) - termOfSize m - - shrink = genericShrink diff --git a/test/AlignmentSpec.hs b/test/AlignmentSpec.hs index e9775f282..ef57f93a2 100644 --- a/test/AlignmentSpec.hs +++ b/test/AlignmentSpec.hs @@ -6,13 +6,12 @@ import Control.Monad.State import Data.Align hiding (align) import Data.Bifunctor import Data.Bifunctor.Join -import Data.Bifunctor.Join.Arbitrary () import Data.Functor.Both as Both +import Data.Functor.Listable import Data.List (nub) import Data.Monoid hiding ((<>)) import Data.Record import Data.String -import Data.Text.Arbitrary () import Data.These import Patch import Prologue hiding (fst, snd) @@ -24,8 +23,8 @@ import Syntax import Term import Test.Hspec (Spec, describe, it, parallel) import Test.Hspec.Expectations.Pretty -import Test.Hspec.QuickCheck -import Test.QuickCheck +import Test.Hspec.LeanCheck +import Test.LeanCheck import GHC.Show (Show(..)) spec :: Spec @@ -48,7 +47,7 @@ spec = parallel $ do prop "covers every input line" $ \ elements -> let (_, children, ranges) = toAlignBranchInputs elements in - join <$> (traverse (modifyJoin (fromThese [] []) . fmap (pure . Prologue.fst)) (alignBranch Prologue.snd children ranges)) `shouldBe` ranges + join <$> traverse (modifyJoin (fromThese [] []) . fmap (pure . Prologue.fst)) (alignBranch Prologue.snd children ranges) `shouldBe` ranges prop "covers every input child" $ \ elements -> let (_, children, ranges) = toAlignBranchInputs elements in @@ -195,7 +194,7 @@ spec = parallel $ do describe "numberedRows" $ do prop "counts only non-empty values" $ - \ xs -> counts (numberedRows (xs :: [Join These Char])) `shouldBe` length . catMaybes <$> Join (unalign (runJoin <$> xs)) + \ xs -> counts (numberedRows (unListableF <$> xs :: [Join These Char])) `shouldBe` length . catMaybes <$> Join (unalign (runJoin . unListableF <$> xs)) data BranchElement = Child String (Join These String) @@ -236,23 +235,23 @@ keysOfAlignedChildren lines = lines >>= these identity identity (<>) . runJoin . joinCrosswalk :: Bicrosswalk p => Align f => (a -> f b) -> Join p a -> f (Join p b) joinCrosswalk f = fmap Join . bicrosswalk f f . runJoin -instance Arbitrary BranchElement where - arbitrary = oneof [ key >>= \ key -> Child key <$> joinTheseOf (contents key) - , Margin <$> joinTheseOf margin ] - where key = listOf1 (elements (['a'..'z'] <> ['A'..'Z'] <> ['0'..'9'])) - contents key = wrap key <$> listOf (padding '*') +instance Listable BranchElement where + tiers = oneof [ (\ key -> Child key `mapT` joinTheseOf (contents key)) `concatMapT` key + , Margin `mapT` joinTheseOf (pure `mapT` padding '-') ] + where key = pure `mapT` [['a'..'z'] <> ['A'..'Z'] <> ['0'..'9']] + contents key = (wrap key . pure) `mapT` padding '*' wrap key contents = "(" <> key <> contents <> ")" :: String - margin = listOf (padding '-') - padding char = frequency [ (10, pure char) - , (1, pure '\n') ] - joinTheseOf g = oneof [ Join . This <$> g - , Join . That <$> g - , (Join .) . These <$> g <*> g ] + padding :: Char -> [Tier Char] + padding char = frequency [ (10, [[char]]) + , (1, [['\n']]) ] + joinTheseOf g = oneof [ (Join . This) `mapT` g + , (Join . That) `mapT` g + , productWith ((Join .) . These) g g ] + frequency :: [(Int, [Tier a])] -> [Tier a] + frequency = concatT . foldr ((\/) . pure . uncurry replicate) [] + oneof :: [[[a]]] -> [[a]] + oneof = frequency . fmap ((,) 1) - shrink (Child key contents) = Child key <$> joinCrosswalk shrinkContents contents - where shrinkContents string = (++ suffix) . (prefix ++) <$> shrinkList (const []) (drop (length prefix) (take (length string - length suffix) string)) - (prefix, suffix) = ('(' : key, ")" :: String) - shrink (Margin contents) = Margin <$> joinCrosswalk (shrinkList (const [])) contents counts :: [Join These (Int, a)] -> Both Int counts numbered = fromMaybe 0 . getLast . mconcat . fmap Last <$> Join (unalign (runJoin . fmap Prologue.fst <$> numbered)) diff --git a/test/Data/Mergeable/Spec.hs b/test/Data/Mergeable/Spec.hs index f03092baa..8aba89d5c 100644 --- a/test/Data/Mergeable/Spec.hs +++ b/test/Data/Mergeable/Spec.hs @@ -2,52 +2,62 @@ module Data.Mergeable.Spec where import Data.Functor.Identity +import Data.Functor.Listable import Data.Mergeable +import Data.String (String) +import GHC.Show import Prologue import Syntax import Test.Hspec -import Test.Hspec.QuickCheck -import Test.QuickCheck +import Test.Hspec.LeanCheck +import Test.LeanCheck spec :: Spec spec = parallel $ do describe "[]" $ do - let gen = scale (`div` 25) arbitrary :: Gen [Char] - withAlternativeInstances sequenceAltLaws gen - withAlternativeInstances mergeLaws gen + withAlternativeInstances sequenceAltLaws (tiers :: [Tier String]) + withAlternativeInstances mergeLaws (tiers :: [Tier String]) describe "Maybe" $ do - withAlternativeInstances sequenceAltLaws (arbitrary :: Gen (Maybe Char)) - withAlternativeInstances mergeLaws (arbitrary :: Gen (Maybe Char)) + withAlternativeInstances sequenceAltLaws (tiers :: [Tier (Maybe Char)]) + withAlternativeInstances mergeLaws (tiers :: [Tier (Maybe Char)]) describe "Identity" $ do - withAlternativeInstances sequenceAltLaws (Identity <$> arbitrary :: Gen (Identity Char)) - withAlternativeInstances mergeLaws (Identity <$> arbitrary :: Gen (Identity Char)) + withAlternativeInstances sequenceAltLaws (Identity `mapT` tiers :: [Tier (Identity Char)]) + withAlternativeInstances mergeLaws (Identity `mapT` tiers :: [Tier (Identity Char)]) describe "Syntax" $ do - withAlternativeInstances sequenceAltLaws (sized (syntaxOfSize (const arbitrary)) :: Gen (Syntax Char Char)) - withAlternativeInstances mergeLaws (sized (syntaxOfSize (const arbitrary)) :: Gen (Syntax Char Char)) + withAlternativeInstances sequenceAltLaws (tiers :: [Tier (Syntax Char Char)]) + withAlternativeInstances mergeLaws (tiers :: [Tier (Syntax Char Char)]) - prop "subsumes catMaybes/Just" $ do + prop "subsumes catMaybes/Just" $ \ a -> sequenceAlt a `shouldBe` pure (catMaybes (a :: [Maybe Char])) -mergeLaws :: forall f g a. (Mergeable f, Alternative g, Eq (g (f a)), Show (f a), Show (g (f a))) => Gen (f a) -> Gen (Blind (a -> g a)) -> Spec +mergeLaws :: forall f g a. (Mergeable f, Alternative g, Eq (g (f a)), Show (f a), Show (g (f a))) => [Tier (f a)] -> [Tier (Blind (a -> g a))] -> Spec mergeLaws value function = describe "merge" $ do prop "identity" . forAll value $ \ a -> merge pure a `shouldNotBe` (empty :: g (f a)) - let pair = (,) <$> value <*> function - prop "relationship with sequenceAlt" . forAll pair $ + prop "relationship with sequenceAlt" . forAll (value >< function) $ \ (a, f) -> merge (getBlind f) a `shouldBe` sequenceAlt (fmap (getBlind f) a) -sequenceAltLaws :: forall f g a. (Mergeable f, Alternative g, Eq (g (f a)), Show (f a), Show (g (f a))) => Gen (f a) -> Gen (Blind (a -> g a)) -> Spec -sequenceAltLaws value function = do - describe "sequenceAlt" $ do - prop "identity" . forAll value $ - \ a -> sequenceAlt (pure <$> a) `shouldNotBe` (empty :: g (f a)) +sequenceAltLaws :: forall f g a. (Mergeable f, Alternative g, Eq (g (f a)), Show (f a), Show (g (f a))) => [Tier (f a)] -> [Tier (Blind (a -> g a))] -> Spec +sequenceAltLaws value function = describe "sequenceAlt" $ do + prop "identity" . forAll value $ + \ a -> sequenceAlt (pure <$> a) `shouldNotBe` (empty :: g (f a)) - prop "relationship with merge" . forAll (Blind <$> (fmap . getBlind <$> function <*> value) :: Gen (Blind (f (g a)))) $ - \ a -> sequenceAlt (getBlind a) `shouldBe` merge identity (getBlind a) + prop "relationship with merge" . forAll (productWith ((Blind .) . fmap . getBlind) function value :: [Tier (Blind (f (g a)))]) $ + \ a -> sequenceAlt (getBlind a) `shouldBe` merge identity (getBlind a) -withAlternativeInstances :: forall f a. (Arbitrary a, CoArbitrary a, Eq (f a), Show (f a)) => (forall g. (Alternative g, Eq (g (f a)), Show (g (f a))) => Gen (f a) -> Gen (Blind (a -> g a)) -> Spec) -> Gen (f a) -> Spec +withAlternativeInstances :: forall f a. (Listable a, Eq (f a), Show (f a)) => (forall g. (Alternative g, Eq (g (f a)), Show (g (f a))) => [Tier (f a)] -> [Tier (Blind (a -> g a))] -> Spec) -> [Tier (f a)] -> Spec withAlternativeInstances laws gen = do - describe "[]" $ laws gen (scale (`div` 25) (arbitrary :: Gen (Blind (a -> [a])))) - describe "Maybe" $ laws gen (arbitrary :: Gen (Blind (a -> Maybe a))) + describe "[]" $ laws gen (fmap const `mapT` tiers :: [Tier (Blind (a -> [a]))]) + describe "Maybe" $ laws gen (fmap const `mapT` tiers :: [Tier (Blind (a -> Maybe a))]) + + +newtype Blind a = Blind { getBlind :: a } + deriving Functor + +instance Listable a => Listable (Blind a) where + tiers = Blind `mapT` tiers + +instance Show (Blind a) where + showsPrec _ _ = showString "*" diff --git a/test/Data/RandomWalkSimilarity/Spec.hs b/test/Data/RandomWalkSimilarity/Spec.hs index e8d758a2e..e25680b43 100644 --- a/test/Data/RandomWalkSimilarity/Spec.hs +++ b/test/Data/RandomWalkSimilarity/Spec.hs @@ -1,9 +1,12 @@ {-# LANGUAGE DataKinds #-} module Data.RandomWalkSimilarity.Spec where +import Category import Data.Functor.Both +import Data.Functor.Listable import Data.RandomWalkSimilarity import Data.Record +import Data.String import qualified Data.Vector as Vector import Diff import Info @@ -12,30 +15,27 @@ import Prologue import Syntax import Term import Diffing (getLabel) -import Term.Arbitrary import Test.Hspec -import Test.Hspec.QuickCheck -import Test.QuickCheck +import Test.Hspec.LeanCheck spec :: Spec spec = parallel $ do let positively = succ . abs describe "pqGramDecorator" $ do prop "produces grams with stems of the specified length" $ - \ (term, p, q) -> pqGramDecorator (rhead . headF) (positively p) (positively q) (toTerm term :: Term (Syntax Text) (Record '[Text])) `shouldSatisfy` all ((== positively p) . length . stem . rhead) + \ (term, p, q) -> pqGramDecorator (rhead . headF) (positively p) (positively q) (unListableF term :: SyntaxTerm String '[Category]) `shouldSatisfy` all ((== positively p) . length . stem . rhead) prop "produces grams with bases of the specified width" $ - \ (term, p, q) -> pqGramDecorator (rhead . headF) (positively p) (positively q) (toTerm term :: Term (Syntax Text) (Record '[Text])) `shouldSatisfy` all ((== positively q) . length . base . rhead) + \ (term, p, q) -> pqGramDecorator (rhead . headF) (positively p) (positively q) (unListableF term :: SyntaxTerm String '[Category]) `shouldSatisfy` all ((== positively q) . length . base . rhead) describe "featureVectorDecorator" $ do prop "produces a vector of the specified dimension" $ - \ (term, p, q, d) -> featureVectorDecorator (rhead . headF) (positively p) (positively q) (positively d) (toTerm term :: Term (Syntax Text) (Record '[Text])) `shouldSatisfy` all ((== positively d) . length . rhead) + \ (term, p, q, d) -> featureVectorDecorator (rhead . headF) (positively p) (positively q) (positively d) (unListableF term :: SyntaxTerm String '[Category]) `shouldSatisfy` all ((== positively d) . length . rhead) describe "rws" $ do - let toTerm' = decorate . toTerm - prop "produces correct diffs" . forAll (scale (`div` 4) arbitrary) $ - \ (as, bs) -> let tas = toTerm' <$> (as :: [ArbitraryTerm Text (Record '[Category])]) - tbs = toTerm' <$> (bs :: [ArbitraryTerm Text (Record '[Category])]) + prop "produces correct diffs" $ + \ (as, bs) -> let tas = decorate <$> (unListableF <$> as :: [SyntaxTerm String '[Category]]) + tbs = decorate <$> (unListableF <$> bs :: [SyntaxTerm String '[Category]]) root = cofree . ((Program .: RNil) :<) . Indexed diff = wrap (pure (Program .: RNil) :< Indexed (stripDiff <$> rws compare getLabel tas tbs)) in (beforeTerm diff, afterTerm diff) `shouldBe` (Just (root (stripTerm <$> tas)), Just (root (stripTerm <$> tbs))) diff --git a/test/Diff/Spec.hs b/test/Diff/Spec.hs index 7b5ec59bc..103d992f7 100644 --- a/test/Diff/Spec.hs +++ b/test/Diff/Spec.hs @@ -2,41 +2,37 @@ module Diff.Spec where import Category -import Data.RandomWalkSimilarity -import Data.Record -import Data.Text.Arbitrary () +import Data.Bifunctor.Join +import Data.Functor.Listable +import Data.String import Diff -import Diff.Arbitrary import Diffing (getLabel) -import Info import Interpreter +import Patch import Prologue -import Term.Arbitrary +import Term import Test.Hspec -import Test.Hspec.QuickCheck -import Test.QuickCheck +import Test.Hspec.LeanCheck spec :: Spec spec = parallel $ do - let toTerm' = defaultFeatureVectorDecorator (category . headF) . toTerm prop "equality is reflexive" $ - \ a b -> let diff = diffTerms wrap (==) diffCost getLabel (toTerm' a) (toTerm' (b :: ArbitraryTerm Text (Record '[Category]))) in + \ a -> let diff = unListableDiff a :: SyntaxDiff String '[Category] in diff `shouldBe` diff prop "equal terms produce identity diffs" $ - \ a -> let term = toTerm' (a :: ArbitraryTerm Text (Record '[Category])) in + \ a -> let term = unListableF a :: SyntaxTerm String '[Category] in diffCost (diffTerms wrap (==) diffCost getLabel term term) `shouldBe` 0 describe "beforeTerm" $ do prop "recovers the before term" $ - \ a b -> let diff = diffTerms wrap (==) diffCost getLabel (toTerm' a) (toTerm' (b :: ArbitraryTerm Text (Record '[Category]))) in - beforeTerm diff `shouldBe` Just (toTerm' a) + \ a b -> let diff = diffTerms wrap (==) diffCost getLabel (unListableF a) (unListableF b :: SyntaxTerm String '[Category]) in + beforeTerm diff `shouldBe` Just (unListableF a) describe "afterTerm" $ do prop "recovers the after term" $ - \ a b -> let diff = diffTerms wrap (==) diffCost getLabel (toTerm' a) (toTerm' (b :: ArbitraryTerm Text (Record '[Category]))) in - afterTerm diff `shouldBe` Just (toTerm' b) + \ a b -> let diff = diffTerms wrap (==) diffCost getLabel (unListableF a) (unListableF b :: SyntaxTerm String '[Category]) in + afterTerm diff `shouldBe` Just (unListableF b) - describe "ArbitraryDiff" $ do - prop "generates diffs of a specific size" . forAll ((arbitrary >>= \ n -> (,) n <$> diffOfSize n) `suchThat` ((> 0) . fst)) $ - \ (n, diff) -> arbitraryDiffSize (diff :: ArbitraryDiff Text ()) `shouldBe` n +unListableDiff :: Functor f => ListableF (Free (TermF f (ListableF (Join (,)) annotation))) (Patch (ListableF (Term f) annotation)) -> Diff f annotation +unListableDiff diff = transFreeT (first unListableF) $ fmap unListableF <$> unListableF diff diff --git a/test/DiffSummarySpec.hs b/test/DiffSummarySpec.hs index 3a69499a1..5f7e54d57 100644 --- a/test/DiffSummarySpec.hs +++ b/test/DiffSummarySpec.hs @@ -3,11 +3,12 @@ module DiffSummarySpec where import Category import Data.Functor.Both +import Data.Functor.Listable import Data.List (partition) import Data.RandomWalkSimilarity import Data.Record +import Data.String import Diff -import Diff.Arbitrary import DiffSummary import Info import Interpreter @@ -16,10 +17,9 @@ import Prologue import Source import Syntax import Term -import Term.Arbitrary import Test.Hspec (Spec, describe, it, parallel) import Test.Hspec.Expectations.Pretty -import Test.Hspec.QuickCheck +import Test.Hspec.LeanCheck import Data.These import Diffing (getLabel) @@ -51,13 +51,13 @@ spec = parallel $ do diffSummaries blobs testDiff `shouldBe` [ JSONSummary "Added the \"a\" string" (SourceSpans . That $ sourceSpanBetween (1, 2) (1, 4)) ] prop "equal terms produce identity diffs" $ - \ a -> let term = defaultFeatureVectorDecorator (category . headF) (toTerm (a :: ArbitraryTerm Text (Record '[Category, Range, SourceSpan]))) in + \ a -> let term = defaultFeatureVectorDecorator (category . headF) (unListableF a :: SyntaxTerm String '[Category, Range, SourceSpan]) in diffSummaries blobs (diffTerms wrap (==) diffCost getLabel term term) `shouldBe` [] describe "DiffInfo" $ do prop "patches in summaries match the patches in diffs" $ \a -> let - diff = (toDiff (a :: ArbitraryDiff Text (Record '[Category, Cost, Range, SourceSpan]))) + diff = unListableDiff a :: SyntaxDiff String '[Category, Cost, Range, SourceSpan] summaries = diffToDiffSummaries (source <$> blobs) diff patches = toList diff in @@ -66,14 +66,14 @@ spec = parallel $ do (() <$ branchPatches, () <$ otherPatches) `shouldBe` (() <$ branchDiffPatches, () <$ otherDiffPatches) prop "generates one LeafInfo for each child in an arbitrary branch patch" $ \a -> let - diff = (toDiff (a :: ArbitraryDiff Text (Record '[Category, Range, SourceSpan]))) + diff = unListableDiff a :: SyntaxDiff String '[Category, Range, SourceSpan] diffInfoPatches = patch <$> diffToDiffSummaries (source <$> blobs) diff syntaxPatches = toList diff extractLeaves :: DiffInfo -> [DiffInfo] extractLeaves (BranchInfo children _ _) = join $ extractLeaves <$> children extractLeaves leaf = [ leaf ] - extractDiffLeaves :: Term (Syntax Text) (Record '[Category, Range, SourceSpan]) -> [ Term (Syntax Text) (Record '[Category, Range, SourceSpan]) ] + extractDiffLeaves :: SyntaxTerm String '[Category, Range, SourceSpan] -> [ SyntaxTerm String '[Category, Range, SourceSpan] ] extractDiffLeaves term = case unwrap term of (Indexed children) -> join $ extractDiffLeaves <$> children (Fixed children) -> join $ extractDiffLeaves <$> children @@ -98,3 +98,6 @@ isIndexedOrFixed' syntax = case syntax of isBranchNode :: Patch DiffInfo -> Bool isBranchNode = any isBranchInfo + +unListableDiff :: Functor f => ListableF (Free (TermF f (ListableF (Join (,)) annotation))) (Patch (ListableF (Term f) annotation)) -> Diff f annotation +unListableDiff diff = transFreeT (first unListableF) $ fmap unListableF <$> unListableF diff diff --git a/test/InterpreterSpec.hs b/test/InterpreterSpec.hs index 6addda544..8435314b3 100644 --- a/test/InterpreterSpec.hs +++ b/test/InterpreterSpec.hs @@ -3,8 +3,10 @@ module InterpreterSpec where import Category import Data.Functor.Foldable +import Data.Functor.Listable import Data.RandomWalkSimilarity import Data.Record +import Data.String import Diff import Diffing import Info @@ -12,27 +14,27 @@ import Interpreter import Patch import Prologue import Syntax -import Term.Arbitrary +import Term import Test.Hspec (Spec, describe, it, parallel) import Test.Hspec.Expectations.Pretty -import Test.Hspec.QuickCheck +import Test.Hspec.LeanCheck spec :: Spec spec = parallel $ do describe "interpret" $ do let decorate = defaultFeatureVectorDecorator (category . headF) - let compare = ((==) `on` category . extract) + let compare = (==) `on` category . extract it "returns a replacement when comparing two unicode equivalent terms" $ - let termA = cofree $ (StringLiteral .: RNil) :< Leaf ("t\776" :: Text) + let termA = cofree $ (StringLiteral .: RNil) :< Leaf ("t\776" :: String) termB = cofree $ (StringLiteral .: RNil) :< Leaf "\7831" in stripDiff (diffTerms wrap compare diffCost getLabel (decorate termA) (decorate termB)) `shouldBe` replacing termA termB prop "produces correct diffs" $ - \ a b -> let diff = stripDiff $ diffTerms wrap compare diffCost getLabel (decorate (toTerm a)) (decorate (toTerm (b :: ArbitraryTerm Text (Record '[Category])))) in - (beforeTerm diff, afterTerm diff) `shouldBe` (Just (toTerm a), Just (toTerm b)) + \ a b -> let diff = stripDiff $ diffTerms wrap compare diffCost getLabel (decorate (unListableF a)) (decorate (unListableF b :: SyntaxTerm String '[Category])) in + (beforeTerm diff, afterTerm diff) `shouldBe` (Just (unListableF a), Just (unListableF b)) prop "constructs zero-cost diffs of equal terms" $ - \ a -> let term = decorate (toTerm (a :: ArbitraryTerm Text (Record '[Category]))) + \ a -> let term = decorate (unListableF a :: SyntaxTerm String '[Category]) diff = diffTerms wrap compare diffCost getLabel term term in diffCost diff `shouldBe` 0 diff --git a/test/TermSpec.hs b/test/TermSpec.hs index e6651ea15..14dddfa75 100644 --- a/test/TermSpec.hs +++ b/test/TermSpec.hs @@ -1,19 +1,17 @@ +{-# LANGUAGE DataKinds #-} module TermSpec where -import Data.Text.Arbitrary () +import Category +import Data.Functor.Listable +import Data.String (String) import Prologue -import Term.Arbitrary +import Term import Test.Hspec (Spec, describe, parallel) import Test.Hspec.Expectations.Pretty -import Test.Hspec.QuickCheck -import Test.QuickCheck +import Test.Hspec.LeanCheck spec :: Spec spec = parallel $ do describe "Term" $ do prop "equality is reflexive" $ - \ a -> toTerm a `shouldBe` toTerm (a :: ArbitraryTerm Text ()) - - describe "ArbitraryTerm" $ do - prop "generates terms of a specific size" . forAll ((arbitrary >>= \ n -> (,) n <$> termOfSize n) `suchThat` ((> 0) . fst)) $ - \ (n, term) -> arbitraryTermSize (term :: ArbitraryTerm Text ()) `shouldBe` n + \ a -> unListableF a `shouldBe` (unListableF a :: SyntaxTerm String '[Category]) diff --git a/test/Test/Hspec/LeanCheck.hs b/test/Test/Hspec/LeanCheck.hs new file mode 100644 index 000000000..ba4c4d720 --- /dev/null +++ b/test/Test/Hspec/LeanCheck.hs @@ -0,0 +1,76 @@ +{-# LANGUAGE GADTs, TypeFamilies #-} +module Test.Hspec.LeanCheck +( prop +, forAll +) where + +import Control.Exception +import Data.Bifunctor (first) +import Data.String (String) +import GHC.Show as Show (showsPrec) +import Prologue +import Test.Hspec +import Test.Hspec.Core.Spec +import Test.LeanCheck.Core + +data Property where + Property :: IOTestable prop => prop -> Property + +-- | Perform an enumerative test of a property using LeanCheck. +-- +-- 'prop' will typically be a function of one or more 'Listable' arguments, returning either 'Bool' or 'IO ()' (in the latter case, typically via 'shouldBe' and friends). For example: +-- +-- > describe "+" $ do +-- > prop "associativity" $ +-- > \ a b c -> a + (b + c) `shouldBe` (a + b :: Int) + c +prop :: (HasCallStack, IOTestable prop) => String -> prop -> Spec +prop s = it s . Property + +data ForAll a where + ForAll :: IOTestable prop => [[a]] -> (a -> prop) -> ForAll a + +-- | Test a property given by an explicit list of tiers rather than a 'Listable' instance. This can be used to e.g. filter input values for which the property does not hold. +-- +-- > describe "mean" $ do +-- > prop "≥ the minimum" . forAll (not . null `filterT` tiers) $ +-- > \ list -> (mean list :: Int) `shouldSatisfy` (>= min list) +forAll :: IOTestable prop => [[a]] -> (a -> prop) -> ForAll a +forAll = ForAll + +instance Example Property where + type Arg Property = () + evaluateExample (Property prop) (Params _ bound) _ _ = do + result <- iocounterExample bound prop + case result of + Just messages -> pure $ Fail Nothing (concat messages) + Nothing -> pure Success + +class IOTestable t where + -- 'resultiers', lifted into 'IO'. + ioResultTiers :: t -> [[IO ([String], Bool)]] + +instance IOTestable (IO ()) where + ioResultTiers action = [[ (action >> pure ([], True)) `catch` (\ e -> pure ([ displayException (e :: SomeException) ], False)) ]] + +instance (IOTestable b, Show a, Listable a) => IOTestable (a -> b) where + ioResultTiers p = ioconcatMapT resultiersFor tiers + where resultiersFor x = fmap (fmap (first (showsPrec 11 x "":))) <$> ioResultTiers (p x) + +instance IOTestable Bool where + ioResultTiers p = [[ pure ([], p) ]] + +instance IOTestable (ForAll a) where + ioResultTiers (ForAll tiers property) = concatMapT (ioResultTiers . property) tiers + + +-- | 'concatMapT', lifted into 'IO'. +ioconcatMapT :: (a -> [[IO b]]) -> [[a]] -> [[IO b]] +ioconcatMapT f = (>>= (>>= f)) + +-- | 'counterExamples', lifted into 'IO'. +iocounterExamples :: IOTestable a => Int -> a -> IO [[String]] +iocounterExamples n = fmap (fmap fst . filter (not . snd)) . sequenceA . take n . concat . ioResultTiers + +-- | 'counterExample', lifted into 'IO'. +iocounterExample :: IOTestable a => Int -> a -> IO (Maybe [String]) +iocounterExample n = fmap listToMaybe . iocounterExamples n diff --git a/vendor/text-icu b/vendor/text-icu index 75e614795..b851ba283 160000 --- a/vendor/text-icu +++ b/vendor/text-icu @@ -1 +1 @@ -Subproject commit 75e614795840d32440c0f06deeee775015a94bf4 +Subproject commit b851ba283cd1bb02f57f9c939219b75bea69afeb