Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PIR Generators #4559

Closed
wants to merge 135 commits into from
Closed
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
135 commits
Select commit Hold shift + click to select a range
669707c
first stab at generator move
MaximilianAlgehed Apr 20, 2022
7e79cf7
wip
MaximilianAlgehed Apr 20, 2022
8fa0859
WIP make generator port compile
MaximilianAlgehed Apr 20, 2022
242984a
WIP generator porting
MaximilianAlgehed Apr 20, 2022
faf11cd
WIP generator porting
MaximilianAlgehed Apr 21, 2022
45978d1
more generator porting
MaximilianAlgehed Apr 21, 2022
1990886
wip
MaximilianAlgehed Apr 21, 2022
fb1b37d
port
MaximilianAlgehed Apr 21, 2022
08a9ec7
getting rid of more unnecessary things
MaximilianAlgehed Apr 21, 2022
a0f84e3
wip
MaximilianAlgehed Apr 22, 2022
7b961e6
prettyprinter and generator first compiling port
MaximilianAlgehed Apr 22, 2022
dd35f47
rip out prettyprinters
MaximilianAlgehed May 5, 2022
794a7e1
pretty generators wip
MaximilianAlgehed May 5, 2022
23b8c1d
wip make generators build
MaximilianAlgehed May 6, 2022
d0b9f8e
minor
MaximilianAlgehed May 6, 2022
f00254f
fine.
MaximilianAlgehed May 6, 2022
c48edcb
WIP generator cleanup
MaximilianAlgehed May 9, 2022
536642e
more cleanup
MaximilianAlgehed May 9, 2022
6ee2d0f
more wip polish
MaximilianAlgehed May 9, 2022
6cf273e
more wip polish
MaximilianAlgehed May 9, 2022
d05a210
more polish
MaximilianAlgehed May 9, 2022
f06f47d
more polish
MaximilianAlgehed May 9, 2022
7cea94f
more polish
MaximilianAlgehed May 9, 2022
37e2b89
more polish
MaximilianAlgehed May 9, 2022
876b07e
move generator tests to their own module
MaximilianAlgehed May 9, 2022
890ff91
wip refactor
MaximilianAlgehed May 9, 2022
1a392dd
hscleanup
MaximilianAlgehed May 10, 2022
50df298
more wip
MaximilianAlgehed May 10, 2022
d0a6e10
more wip
MaximilianAlgehed May 10, 2022
7249c77
custom generator plugin
MaximilianAlgehed May 10, 2022
50ffcea
Update plutus-core/plutus-ir/test/GeneratorSpec.hs
MaximilianAlgehed May 23, 2022
d40e57e
wip
MaximilianAlgehed May 23, 2022
adc8415
wip
MaximilianAlgehed May 23, 2022
78c592b
wip
MaximilianAlgehed May 23, 2022
f16f958
wip
MaximilianAlgehed May 23, 2022
bfb7d07
wip
MaximilianAlgehed May 23, 2022
a10454f
wip
MaximilianAlgehed May 23, 2022
a52a16a
wip
MaximilianAlgehed May 23, 2022
8a2c44e
wip
MaximilianAlgehed May 23, 2022
f29dcbb
wip
MaximilianAlgehed May 23, 2022
cda5770
wip
MaximilianAlgehed May 23, 2022
a0de3e2
wip
MaximilianAlgehed May 24, 2022
4377c2f
wip
MaximilianAlgehed May 24, 2022
fd13cb4
wip
MaximilianAlgehed May 24, 2022
108e665
wip
MaximilianAlgehed May 24, 2022
b30f947
wip
MaximilianAlgehed May 24, 2022
87908f4
wip
MaximilianAlgehed May 24, 2022
a4d1bff
wip
MaximilianAlgehed May 24, 2022
2a42fc6
wip
MaximilianAlgehed May 24, 2022
2898705
wip
MaximilianAlgehed May 24, 2022
723fda2
wip
MaximilianAlgehed May 24, 2022
b841156
wip
MaximilianAlgehed May 24, 2022
6610af0
wip
MaximilianAlgehed May 24, 2022
d4e43bd
Update plutus-core/testlib/PlutusCore/Generators/PIR.hs
MaximilianAlgehed May 24, 2022
a1a6382
Update plutus-core/testlib/PlutusCore/Generators/PIR.hs
MaximilianAlgehed May 30, 2022
c4765df
Update plutus-core/testlib/PlutusCore/Generators/PIR.hs
MaximilianAlgehed May 30, 2022
2389328
Update plutus-core/testlib/PlutusCore/Generators/PIR.hs
MaximilianAlgehed Jun 1, 2022
69e6065
refactor GenTm to its own module
MaximilianAlgehed Jun 1, 2022
cd90088
wip
MaximilianAlgehed Jun 1, 2022
a8e8afd
wip
MaximilianAlgehed Jun 1, 2022
aeea83b
more refactoring
MaximilianAlgehed Jun 1, 2022
0956890
refactor properties etc
MaximilianAlgehed Jun 1, 2022
ccd9484
cleanup
MaximilianAlgehed Jun 1, 2022
75d1fa3
more cleanup
MaximilianAlgehed Jun 1, 2022
c866f28
wip
MaximilianAlgehed Jun 2, 2022
f3ca510
wip
MaximilianAlgehed Jun 2, 2022
b3887c3
wip
MaximilianAlgehed Jun 2, 2022
9662aa2
wip
MaximilianAlgehed Jun 2, 2022
0f50120
wip
MaximilianAlgehed Jun 2, 2022
7a69139
wip
MaximilianAlgehed Jun 2, 2022
dd9b95c
some incomplete refactoring
MaximilianAlgehed Jun 9, 2022
7caa634
remove unnecessary shrinking
MaximilianAlgehed Jun 9, 2022
99124c8
wip
MaximilianAlgehed Jun 9, 2022
a087635
wip
MaximilianAlgehed Jun 9, 2022
122630d
wip
MaximilianAlgehed Jun 9, 2022
6b29e28
wip
MaximilianAlgehed Jun 9, 2022
85c40d7
wip
MaximilianAlgehed Jun 9, 2022
2e9068a
wip
MaximilianAlgehed Jun 9, 2022
e25eec6
wip
MaximilianAlgehed Jun 9, 2022
74c4b46
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully Jun 14, 2022
d1f3e78
Drop a couple of calls to 'runQuoteT'
effectfully Jun 14, 2022
a9f872b
Provide support for builtins in their full glory
effectfully Jun 15, 2022
004587e
Add a bit of docs to 'Builtin.hs'
effectfully Jun 16, 2022
8272249
Use 'quickcheck-transformer'
effectfully Jun 22, 2022
180d8e5
un-CPS 'sizeSplit*' functions
effectfully Jun 22, 2022
025a495
Add 'argsKind' and refactor 'minimalType'
effectfully Jun 22, 2022
5db7792
Handle 'TyIFix' in the type generator
effectfully Jun 23, 2022
a8a2919
comments
MaximilianAlgehed Jun 27, 2022
5359389
Keep error messages
effectfully Jun 29, 2022
6f17b92
Merge branch 'PR-PIR-generators' of https://github.com/Quviq/plutus i…
effectfully Jun 29, 2022
01cf59c
Debug mode for type generators
MaximilianAlgehed Jun 29, 2022
2f1899c
invariant checking for terms + type error in previous commit (oops)
MaximilianAlgehed Jun 29, 2022
2073a2d
stricter checkInvariants
MaximilianAlgehed Jun 29, 2022
3068a4a
Return generation of types back to normal, make pretty-printing debug…
effectfully Jul 6, 2022
cae4fb5
Run a million 'prop_genTypeCorrect' tests
effectfully Jul 6, 2022
bd6bbbc
Merge branch 'PR-PIR-generators' of https://github.com/Quviq/plutus i…
effectfully Jul 6, 2022
3eed522
debug options so that finding the failing seed isn't slow
MaximilianAlgehed Jul 7, 2022
c643347
run more tests
MaximilianAlgehed Jul 7, 2022
d4582f6
wip
MaximilianAlgehed Jul 7, 2022
47a4227
test factor to easily be able to change the number of tests
MaximilianAlgehed Jul 7, 2022
b4b54d4
Remove negativity check
MaximilianAlgehed Jul 11, 2022
c71d964
update golden tests
MaximilianAlgehed Jul 11, 2022
560a377
Documentation for subtle behaviour in substitution generator
MaximilianAlgehed Jul 12, 2022
d1cf5a6
shrink order documentation
MaximilianAlgehed Jul 12, 2022
c0cf301
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully Jul 14, 2022
fd751ff
Merge branch 'PR-PIR-generators' of https://github.com/Quviq/plutus i…
effectfully Jul 14, 2022
33e154d
Fix semantic merge conflicts
effectfully Jul 14, 2022
84286f4
Disable 'editorconfig' in all the generator files
effectfully Jul 14, 2022
5afc967
change names for util functions to be consistent
MaximilianAlgehed Aug 31, 2022
4c76ad2
Fix todo
MaximilianAlgehed Aug 31, 2022
88bec79
Address some minor comments
effectfully Sep 6, 2022
44de8c4
Merge branch 'PR-PIR-generators' of https://github.com/Quviq/plutus i…
effectfully Sep 6, 2022
bb64ff1
Move substitution functions to 'PlutusCore.Subst'
effectfully Sep 7, 2022
b27a07e
Pull type shrinker into its own module
effectfully Sep 7, 2022
dd0048c
Merge branch 'master' of https://github.com/input-output-hk/plutus in…
effectfully Sep 8, 2022
ba134a6
Address comments about stuff from 'Substitution.hs'
effectfully Sep 12, 2022
c20e48d
Restructure and document 'unifyType' a little
effectfully Sep 13, 2022
e824886
Use monad transformers in 'unifyType'
effectfully Sep 13, 2022
b1691fa
Comment on 'minimalType' in the type shrinker for binders
effectfully Sep 13, 2022
d7eb16d
Fix a warning
effectfully Sep 14, 2022
1a4abe2
Do a bit more in the shrinker
effectfully Sep 14, 2022
05a1f36
Minor tweaks and comments
effectfully Sep 15, 2022
2559022
Use strict 'Map's out of paranoia
effectfully Sep 15, 2022
b2cfc24
Give up on pattern synonyms
effectfully Sep 19, 2022
4425a58
Drop redundant pragmas, remove almost all name shadowing
effectfully Sep 19, 2022
1b6f144
Minor tweaks
effectfully Sep 19, 2022
6a74dc3
Use the compiler's internals in 'matchType'
effectfully Sep 19, 2022
ef9e328
Proper generation and shrinking of built-in types
effectfully Sep 20, 2022
ef4a379
Recalibrate the amounts of test cases
effectfully Sep 20, 2022
7e794a7
Fix a typo
effectfully Sep 20, 2022
4033f34
Move 'GenTm' functions from 'Utils' to 'GenTm'
effectfully Sep 20, 2022
fd778e9
Define 'fvTypeBag' in terms of 'multiSetOf'
effectfully Sep 20, 2022
4ef3ca6
Refactor and document 'genSubst'
effectfully Sep 21, 2022
abdf1d2
Define 'substEscape' in terms of the general substitution worker
effectfully Sep 22, 2022
d855182
Add Note [The unification algorithm]
effectfully Sep 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions plutus-core/plutus-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ library plutus-core-testlib
exposed-modules:
PlutusCore.Test
PlutusCore.Generators
PlutusCore.Generators.PIR
PlutusCore.Generators.AST
PlutusCore.Generators.Interesting
PlutusCore.Generators.NEAT.Common
Expand Down Expand Up @@ -360,8 +361,10 @@ library plutus-core-testlib
tasty-golden -any,
tasty-hedgehog -any,
tasty-hunit -any,
QuickCheck -any,
text -any,
transformers -any
transformers -any,
pretty -any

test-suite satint-test
import: lang
Expand Down Expand Up @@ -431,6 +434,7 @@ test-suite plutus-ir-test
ParserSpec
TransformSpec
TypeSpec
GeneratorSpec
build-depends:
base >=4.9 && <5,
plutus-core -any,
Expand All @@ -440,7 +444,11 @@ test-suite plutus-ir-test
megaparsec -any,
tasty -any,
tasty-hedgehog -any,
text -any
tasty-quickcheck -any,
text -any,
containers -any,
QuickCheck -any,
mtl -any

test-suite untyped-plutus-core-test
import: lang
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
module PlutusIR.Core.Instance.Pretty.Readable
( prettyPirReadable
, PrettyPir
) where

import PlutusCore.Default.Universe
Expand Down
316 changes: 316 additions & 0 deletions plutus-core/plutus-ir/test/GeneratorSpec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
module GeneratorSpec where

import PlutusCore.Generators.PIR

import Control.Monad.Reader

import Data.Map qualified as Map
import Data.Set qualified as Set

import Data.Char
import Data.List hiding (insert)
import Data.List.NonEmpty (NonEmpty (..))
import Text.Printf

import PlutusCore.Name
import PlutusCore.Quote (runQuote)
import PlutusCore.Rename
import PlutusIR
import PlutusIR.Core.Instance.Pretty.Readable

import Data.Maybe
import Data.String

import Test.QuickCheck
import Test.Tasty
import Test.Tasty.Extras
import Test.Tasty.QuickCheck

-- | Check that a list of potential counterexamples is empty and display the
-- list as a QuickCheck counterexample if its not.
assertNoCounterexamples :: PrettyPir a => [a] -> Property
assertNoCounterexamples [] = property True
assertNoCounterexamples bad = ceDoc (prettyPirReadable bad) False

-- CODE REVIEW: The property below checks that when we run a generated PIR term through the compiler
MaximilianAlgehed marked this conversation as resolved.
Show resolved Hide resolved
-- we actually get something out. As the generators are supposed to generate reasonable stuff this is
-- a test of the compiler. I think we
-- 1. Want this somewhere
-- 2. Don't want it here
-- Where do we want something like this?
-- Also, the code below is a giant hack to "conenct to" the compiler at the "right" place (as judged
MaximilianAlgehed marked this conversation as resolved.
Show resolved Hide resolved
-- by us when we were ripping the compiler apart to extract something that did something reasonable-ish)
--
-- TODO: we want this property somewhere!
-- compile :: Term TyName Name DefaultUni DefaultFun ()
-- -> Either (CompileError DefaultUni DefaultFun) (CompiledCode a)
-- compile _tm = either Left Right $ runQuoteT $ do
-- -- Make sure that names are unique (that's not guaranteed by QuickCheck)
-- tm <- rename _tm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michaelpj Is the compiler supposed to do renaming itself? It's weird to impose this requirement on the caller for any not for-internal-consumption-only function.

-- plcTcConfig <- PLC.getDefTypeCheckConfig PIR.noProvenance
-- let hints = UPLC.InlineHints $ \a _ -> case a of
MaximilianAlgehed marked this conversation as resolved.
Show resolved Hide resolved
-- PIR.DatatypeComponent PIR.Destructor _ -> True
-- _ -> False
-- pirCtx = PIR.toDefaultCompilationCtx plcTcConfig
-- & set (PIR.ccOpts . PIR.coOptimize) True
-- & set (PIR.ccOpts . PIR.coPedantic) False
-- & set (PIR.ccOpts . PIR.coVerbose) False
-- & set (PIR.ccOpts . PIR.coDebug) False
-- & set (PIR.ccOpts . PIR.coMaxSimplifierIterations)
-- (PIR.defaultCompilationOpts ^. PIR.coMaxSimplifierIterations)
-- & set PIR.ccTypeCheckConfig Nothing
-- uplcSimplOpts = UPLC.defaultSimplifyOpts
-- & set UPLC.soMaxSimplifierIterations (PIR.defaultCompilationOpts ^. PIR.coMaxSimplifierIterations)
-- & set UPLC.soInlineHints hints
--
-- plcT <- flip runReaderT pirCtx $ PIR.compileReadableToPlc $ fmap Original tm
MaximilianAlgehed marked this conversation as resolved.
Show resolved Hide resolved
-- plcTcError <- runExceptT @(PLC.Error _ _ _)
MaximilianAlgehed marked this conversation as resolved.
Show resolved Hide resolved
-- $ UPLC.deBruijnTerm =<< UPLC.simplifyTerm uplcSimplOpts (UPLC.erase plcT)
-- case plcTcError of
-- Left _ -> error "wrong"
-- Right cc -> return $ DeserializedCode (UPLC.Program () (PLC.defaultVersion ()) $ void cc) Nothing mempty
--
-- prop_compile :: Property
-- prop_compile =
-- forAllDoc "_,tm" genTypeAndTermNoHelp_ shrinkClosedTypedTerm $ \ (_, tm) ->
-- isRight $ compile tm

generators :: TestNested
generators = return $ testGroup "generators"
[ testProperty "prop_genKindCorrect" $ withMaxSuccess 1000 prop_genKindCorrect
, testProperty "prop_shrinkTypeSound" $ prop_shrinkTypeSound
, testProperty "prop_genTypeCorrect" $ withMaxSuccess 1000 prop_genTypeCorrect
, testProperty "prop_shrinkTermSound" $ withMaxSuccess 20 prop_shrinkTermSound
]

-- * Core properties for PIR generators

-- | Check that the types we generate are kind-correct
-- See Note [Debugging generators that don't generate well-typed/kinded terms/types]
-- when this property fails.
prop_genKindCorrect :: Property
prop_genKindCorrect =
-- Context minimality doesn't help readability, so no shrinking here
forAllDoc "ctx" genCtx (const []) $ \ ctx ->
MaximilianAlgehed marked this conversation as resolved.
Show resolved Hide resolved
-- Note, no shrinking here because shrinking relies on well-kindedness.
forAllDoc "k,ty" genKindAndType (const []) $ \ (k, ty) ->
checkKind ctx ty k

-- | Check that shrinking types maintains kinds
prop_shrinkTypeSound :: Property
prop_shrinkTypeSound =
forAllDoc "ctx" genCtx (const []) $ \ ctx ->
forAllDoc "k,ty" (genKindAndTypeWithCtx ctx) (shrinkKindAndType ctx) $ \ (k, ty) ->
-- See discussion about the same trick in `prop_shrinkTermSound`
checkKind ctx ty k ==>
assertNoCounterexamples [ (k, ty) | (k, ty) <- shrinkKindAndType ctx (k, ty)
, not $ checkKind ctx ty k ]

-- | Test that our generators only result in well-typed terms.
-- Note, the counterexamples from this property are not shrunk (see why below).
-- See Note [Debugging generators that don't generate well-typed/kinded terms/types]
-- when this property fails.
prop_genTypeCorrect :: Property
prop_genTypeCorrect =
-- Note, we don't shrink this term here because a precondition of shrinking is that
-- the term we are shrinking is well-typed. If it is not, the counterexample we get
-- from shrinking will be nonsene.
forAllDoc "ty,tm" genTypeAndTerm_ (const []) $ \ (ty, tm) -> typeCheckTerm tm ty
MaximilianAlgehed marked this conversation as resolved.
Show resolved Hide resolved

-- | Test that when we generate a fully applied term we end up
-- with a well-typed term.
prop_genWellTypedFullyApplied :: Property
prop_genWellTypedFullyApplied =
forAllDoc "ty, tm" genTypeAndTerm_ shrinkClosedTypedTerm $ \ (ty, tm) ->
-- No shrinking here because if `genFullyApplied` is wrong then the shrinking
-- will be wrong too. See `prop_genTypeCorrect`.
forAllDoc "ty', tm'" (genFullyApplied ty tm) (const []) $ \ (ty', tm') ->
typeCheckTerm tm' ty'

-- | Test that shrinking a well-typed term results in a well-typed term
prop_shrinkTermSound :: Property
prop_shrinkTermSound =
forAllDoc "ty,tm" genTypeAndTerm_ shrinkClosedTypedTerm $ \ (ty, tm) ->
let shrinks = shrinkClosedTypedTerm (ty, tm) in
-- While we generate well-typed terms we still need this check here for
-- shrinking counterexamples to *this* property. If we find a term whose
-- shrinks aren't well-typed we want to find smaller *well-typed* terms
-- whose shrinks aren't well typed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, and in particular this property only becomes interesting when the shrinker isn't working, at which point you can't rely on shrinks being well-typed!

Copy link
Contributor

@effectfully effectfully May 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super clever.

I'm confused about something, though. So let's pretend those pairs are numbers and "type checking" a number fails when that number is, say, 17.

We start running tests:

  • 0: no shrinks, skip
  • 1: all shrinks are well-typed
  • 3: all shrinks are well-typed
  • ...
  • 25: one of the shrinks is ill-typed, 17

Now we start shrinking 25.

  • 0: all shrinks are well-typed
  • 2: all shrinks are well-typed
  • 4: all shrinks are well-typed
  • ...
  • 17: uh-oh, ill-typed itself, skip
  • 20: found a smaller number that shrinks to an ill-typed one

Now reduce 20:

  • 0: all shrinks are well-typed
  • ...

So in total: any counterexample will require us to go through a cubic (?) number of boring test cases. Is this correct?

Of course type-and-term pairs are not numbers, so we aren't going to test the same thing over and over again, but it does seem like we're mostly stuck in the corner with little examples.

Or am I misunderstanding it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unresolved this comment, because I still have the questions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I don't think this was meant to get resolved. I was intending to answer here. Yes, you're right that - in the worst case - you get cubic shrinking. There are two things you can do about this:

  1. Ignore it! You're about to report a bug to the developer - they can afford to wait!
  2. Do something clever about the shrinking order to avoid this worst-case as often as possible. There are various things you can do - like trying to shrinks in reverse order etc. But it's not clear what the right heuristic is in this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore it! You're about to report a bug to the developer - they can afford to wait!

Will they see that shrinking is being done? I don't remember how QuickCheck handles that.

Do something clever about the shrinking order to avoid this worst-case as often as possible. There are various things you can do - like trying to shrinks in reverse order etc. But it's not clear what the right heuristic is in this case.

Yeah, I'm not trying to suggest this needs to be fixed in this PR, seems like a rather non-trivial problem. It's not even clear to me if anything needs to be fixed at all, maybe we get diverse small shrinks that way.

But could you leave a note about shrinking being cubic in the worst case and potentially running through the same shrinks over and over again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the user sees that shrinking is taking place. The reason I hesitate to put a comment in about this is that this is almost always the case for all QuickCheck properties - not just this one.

-- Importantly, this property is only interesting when
-- shrinking itself is broken, so we can only use the
-- parts of shrinking that happen to be OK.
typeCheckTerm tm ty ==>
-- We don't want to let the shrinker get away with being empty, so we ignore empty shrinks. QuickCheck will give
-- up and print an error if the shrinker returns the empty list too often.
not (null shrinks) ==>
MaximilianAlgehed marked this conversation as resolved.
Show resolved Hide resolved
assertNoCounterexamples [ (ty, tm, scopeCheckTyVars Map.empty (ty, tm))
| (ty, tm) <- shrinks, not $ typeCheckTerm tm ty ]

-- * Utility tests for debugging generators that behave weirdly

-- | Test that `typeInstTerm` results in a well-typed instantiation.
prop_typeInstTerm :: Property
prop_typeInstTerm =
forAllDoc "ctx" genCtx (const []) $ \ ctx ->
forAllDoc "ty" (genTypeWithCtx ctx $ Star) (shrinkType ctx) $ \ ty ->
forAllDoc "target" (genTypeWithCtx ctx $ Star) (shrinkType ctx) $ \ target ->
assertNoCounterexamples [ (n, insts)
| n <- [0..arity ty+3]
, Just insts <- [typeInstTerm ctx n target ty]
, not $ checkInst ctx x ty insts target
]
where
x = Name "x" (toEnum 0)
arity (TyForall _ _ _ a) = arity a
arity (TyFun _ _ b) = 1 + arity b
arity _ = 0

-- Check that building a "minimal" term that performs the instantiations in
-- `insts` produces a well-typed term.
checkInst ctx x ty insts target = typeCheckTermInContext ctx tmCtx tm target
where
-- Build a term and a context from `insts` that consists of
-- `tm @ty` for every `InstApp ty` in `insts` and `tm y` for
-- a fresh variable `y : ty` for every `InstArg ty` in `insts`.
(tmCtx, tm) = go (toEnum 1) (Map.singleton x ty) (Var () x) insts
go _ tmCtx tm [] = (tmCtx, tm)
go i tmCtx tm (InstApp ty : insts) = go i tmCtx (TyInst () tm ty) insts
go i tmCtx tm (InstArg ty : insts) = go (succ i) (Map.insert y ty tmCtx)
(Apply () tm (Var () y)) insts
where y = Name "y" i

-- | Check what's in the leaves of the generated data
prop_stats_leaves :: Property
prop_stats_leaves =
-- No shrinking here because we are only collecting stats
forAllDoc "_,tm" genTypeAndTerm_ (const []) $ \ (_, tm) ->
tabulate "leaves" (map (filter isAlpha . show . prettyPirReadable) $ leaves tm) $ property True
where
-- Figure out what's at the leaves of the AST,
-- including variable names, error, and builtins.
leaves (Var _ x) = [x]
leaves (TyInst _ a _) = leaves a
leaves (Let _ _ _ b) = leaves b
leaves (LamAbs _ _ _ b) = leaves b
leaves (Apply _ a b) = leaves a ++ leaves b
leaves Error{} = [Name "error" $ toEnum 0]
leaves Builtin{} = [Name "builtin" $ toEnum 0]
leaves _ = []

-- | Check the ratio of duplicate shrinks
prop_stats_numShrink :: Property
prop_stats_numShrink =
-- No shrinking here because we are only collecting stats
forAllDoc "ty,tm" genTypeAndTerm_ (const []) $ \ (ty, tm) ->
let shrinks = shrinkClosedTypedTerm (ty, tm)
n = fromIntegral (length shrinks)
u = fromIntegral (length $ nub shrinks)
r | n > 0 = (n - u) / n :: Double
| otherwise = 0
in tabulate "r" [printf "%0.1f" r] True

-- | Specific test that `inhabitType` returns well-typed things
prop_inhabited :: Property
prop_inhabited =
-- No shrinking here because if the generator
-- generates nonsense shrinking will be nonsense.
forAllDoc "ty,tm" (genInhab mempty) (const []) $ \ (ty, tm) -> typeCheckTerm tm ty
where
-- Generate some datatypes and then immediately call
-- `inhabitType` to test `inhabitType` directly instead
-- of through the whole term generator. Quick-ish way
-- of debugging "clever hacks" in `inhabitType`.
genInhab ctx = runGenTm $ local (\ e -> e { geTypes = ctx }) $
MaximilianAlgehed marked this conversation as resolved.
Show resolved Hide resolved
genDatatypeLets $ \ dats -> do
ty <- genType Star
tm <- inhabitType ty
return (ty, foldr (\ dat -> Let () NonRec (DatatypeBind () dat :| [])) tm dats)

-- | Test that shrinking types results in smaller types. Useful for debugging shrinking.
prop_shrinkTypeSmallerKind :: Property
prop_shrinkTypeSmallerKind =
forAllDoc "k,ty" genKindAndType (shrinkKindAndType Map.empty) $ \ (k, ty) ->
assertNoCounterexamples [ (k', ty')
| (k', ty') <- shrinkKindAndType Map.empty (k, ty)
, not $ leKind k' k ]

-- | Test that shrinking kinds generates smaller kinds
prop_shrinkKindSmaller :: Property
prop_shrinkKindSmaller =
forAllDoc "k" arbitrary shrink $ \ k ->
assertNoCounterexamples [ k' | k' <- shrink k, not $ leKind k' k ]

-- | Test that fixKind actually gives you something of the right kind
prop_fixKind :: Property
prop_fixKind =
forAllDoc "ctx" genCtx (const []) $ \ ctx ->
forAllDoc "k,ty" genKindAndType (shrinkKindAndType ctx) $ \ (k, ty) ->
-- Note, fixKind only works on smaller kinds, so we use shrink to get a definitely smaller kind
assertNoCounterexamples [ (ty', k') | k' <- shrink k
, let ty' = fixKind ctx ty k'
, not $ checkKind ctx ty' k' ]

-- * Tests for unification and substitution

-- | Check that unification does the right thing.
prop_unify :: Property
prop_unify =
MaximilianAlgehed marked this conversation as resolved.
Show resolved Hide resolved
forAllDoc "n" arbitrary shrink $ \ (NonNegative n) ->
forAllDoc "m" (choose (0, n)) shrink $ \ m ->
letCE "xs" (take n allTheVarsCalledX) $ \ xs ->
forAllDoc "ks"
(vectorOf n arbitrary)
(filter ((== n) . length) . shrink) $ \ ks ->
letCE "ctx" (Map.fromList $ zip xs ks) $ \ ctx ->
forAllDoc "ty1"
(genTypeWithCtx ctx $ Star)
(shrinkType ctx) $ \ ty1 ->
forAllDoc "ty2"
(genTypeWithCtx ctx $ Star)
(shrinkType ctx) $ \ ty2 ->
letCE "nty1" (normalizeTy ty1) $ \ _ ->
letCE "nty2" (normalizeTy ty2) $ \ _ ->
letCE "res" (unifyType ctx (Set.fromList $ take m xs) Map.empty ty1 ty2) $ \ res ->
isJust res ==>
let sub = fromJust res
checkSub (x, ty) = letCE "x,ty" (x, ty) $ \ _ ->
letCE "k" (ctx Map.! x) $ \ k -> checkKind ctx ty k
in
letCE "sty1" (substType sub ty1) $ \ sty1 ->
letCE "sty2" (substType sub ty2) $ \ sty2 ->
letCE "nsty1" (normalizeTy sty1) $ \ nsty1 ->
letCE "nsty2" (normalizeTy sty2) $ \ nsty2 ->
tabulate "sizes" [show $ min (Set.size $ fvType ty1) (Set.size $ fvType ty2)] $
foldr (.&&.) (property $ nsty1 == nsty2) (map checkSub (Map.toList sub))
where
allTheVarsCalledX = [ TyName $ Name (fromString $ "x" ++ show i) (toEnum i) | i <- [1..] ]

-- | Check that a type unifies with a renaming of itself
prop_unifyRename :: Property
prop_unifyRename =
forAllDoc "_, ty" genKindAndType (shrinkKindAndType mempty) $ \ (_, ty) ->
letCE "rename ty" (runQuote $ rename ty) $ \ rnty ->
isJust $ unifyType mempty mempty mempty ty rnty

-- | Check that substitution gets rid of all the right variables
prop_substType :: Property
prop_substType =
-- No shrinking because every nested shrink makes properties
-- harder to shrink and context minimality doesn't help readability very much.
forAllDoc "ctx" genCtx (const []) $ \ ctx ->
forAllDoc "ty" (genTypeWithCtx ctx Star) (shrinkType ctx) $ \ ty ->
forAllDoc "sub" (genSubst ctx) (shrinkSubst ctx) $ \ sub ->
letCE "res" (substType sub ty) $ \ res ->
fvTypeR sub ty == fvType res && checkKind ctx res Star

-- | Check that there are no one-step shrink loops
prop_noTermShrinkLoops :: Property
prop_noTermShrinkLoops =
-- Note that we need to remove x from the shrinks of x here because
-- a counterexample to this property is otherwise guaranteed to
-- go into a shrink loop.
forAllDoc "ty,tm" genTypeAndTerm_ (\x -> filter (/= x) $ shrinkClosedTypedTerm x) $ \ tytm ->
tytm `notElem` shrinkClosedTypedTerm tytm
2 changes: 2 additions & 0 deletions plutus-core/plutus-ir/test/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Main (main) where

import PlutusPrelude

import GeneratorSpec
import NamesSpec
import ParserSpec
import TransformSpec
Expand Down Expand Up @@ -42,6 +43,7 @@ tests = testGroup "plutus-ir" <$> sequence
, transform
, types
, typeErrors
, generators
]

prettyprinting :: TestNested
Expand Down
Loading