-
Notifications
You must be signed in to change notification settings - Fork 1
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
fix: EvalFull duplicated IDs and captures variables in BETA reduction #511
Conversation
primer/src/Primer/EvalFull.hs
Outdated
-- We would like to say (Λa.t : ∀b.T) S ~> (letType a = S in t) : (letType b = S in T) | ||
-- but we do not have letTypes inside types, so the best we can do is | ||
-- (Λa.t : ∀b.T) S ~> (letType b = S in (letType a = S in t) : T | ||
-- We need to be careful if a /= b: as this can capture a 'b' inside 'S' or 't'. | ||
-- Thus if necessary we do some renaming | ||
-- (Λa.t : ∀b.T) S ~> letType c = b in letType b = c in (Λa.t : ∀b.T) S for b free in t or S, and fresh c | ||
-- We then ensure the delicate property that we reduce the b=c first, then the BETA, then the c=b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe at some point in the future we can undo this change by adding let
s in types. This may be necessary to implement #44
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we expect that adding such let
s would be difficult? I suspect the symmetry would make interactive eval a lot easier to follow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, there's a missing closing parenthesis on line 369.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we expect that adding such
let
s would be difficult? I suspect the symmetry would make interactive eval a lot easier to follow.
I would expect it to be the same solution as supporting term-level let type
in the typechecker #337. The issue is how to display types that may refer to the locally-bound name or its expansion.
-- Previously EvalFull reducing a BETA expression could result in variable | ||
-- capture. We would reduce (Λa.t : ∀b.T) S to | ||
-- let b = S in (let a = S in t) : T | ||
-- The outer let binding could capture references within S or t. | ||
unit_type_preservation_BETA_regression :: Assertion | ||
unit_type_preservation_BETA_regression = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The property tests we have "should have caught it eventually", but not in a reasonable timeframe. I would like to improve the generators (or add different tests) to improve the situation, but I don't have any good ideas on how to do this.
One thing I explored is to have a generator of BETA
redexes, and test type preservation for that on its own. However, this rather targeted test still only picked up the bug roughly one test in 5000. For posterity, the code is pasted below
-- This test: generate random BETA redex in an environment with a typevar, check for type preservation
--
-- Bug was (Λa. t : ∀b.T) S ~~> letType b=S in (letType a=S in t) : T
-- which can capture a 'b' in 'S' or 't'
hprop_tmp :: Property
hprop_tmp = withTests 1000 $
withDiscards 2000 $ propertyWT [] $ do
k <- forAllT genWTKind
v <- forAllT genTyVarName
(t, tyBeta) <- local (extendLocalCxtTy (v,k)) genBETA
annotateShow t
annotateShow tyBeta
t' <- generateIDs $ LAM () v t
let tyOrig = TForall () v k tyBeta
_ <- checkTest tyOrig t'
let chk e = do
annotateShow e
when (null [() | LetType{} <- universe e]) $ void $ checkTest tyOrig e
let go i e | i >= 10 = pure ()
| otherwise = do
annotateShow i
e' <- evalFull mempty mempty 1 Chk e
case e' of
Left (TimedOut e'') -> chk e'' >> go (i +1) e''
Right e'' -> chk e''
go 0 t'
where
genBETA = do
k <- forAllT genWTKind
a <- forAllT genTyVarName
ty <- forAllT $ local (extendLocalCxtTy (a,k)) $ genWTType KType
b <- forAllT $ genTyVarNameAvoiding ty
ty' <- substTy a (TVar () b) ty
t <- forAllT $ local (extendLocalCxtTy (b,k)) $ genChk ty'
ty2 <- forAllT $ genWTType k
tyRet <- substTy a ty2 ty
pure (APP () (Ann () (LAM () b t) (TForall () a k ty)) ty2, tyRet)
Yikes, how did you find this one? |
3ebbff3
to
2f17fbd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I confess that I don't understand the fix well enough to say anything particularly useful about it. I'm approving this so it can proceed, but also feel free to wait for @georgefst to have a look, if you prefer.
Part of my search for shadowing and capture problems. I noticed when looking at the documented evaluation rule again. |
I labeled this "Do not merge" as a note to myself until @georgefst has a chance to look at it upon his return. |
primer/src/Primer/EvalFull.hs
Outdated
-- We would like to say (Λa.t : ∀b.T) S ~> (letType a = S in t) : (letType b = S in T) | ||
-- but we do not have letTypes inside types, so the best we can do is | ||
-- (Λa.t : ∀b.T) S ~> (letType b = S in (letType a = S in t) : T | ||
-- We need to be careful if a /= b: as this can capture a 'b' inside 'S' or 't'. | ||
-- Thus if necessary we do some renaming | ||
-- (Λa.t : ∀b.T) S ~> letType c = b in letType b = c in (Λa.t : ∀b.T) S for b free in t or S, and fresh c | ||
-- We then ensure the delicate property that we reduce the b=c first, then the BETA, then the c=b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we expect that adding such let
s would be difficult? I suspect the symmetry would make interactive eval a lot easier to follow.
primer/src/Primer/EvalFull.hs
Outdated
-- We would like to say (Λa.t : ∀b.T) S ~> (letType a = S in t) : (letType b = S in T) | ||
-- but we do not have letTypes inside types, so the best we can do is | ||
-- (Λa.t : ∀b.T) S ~> (letType b = S in (letType a = S in t) : T | ||
-- We need to be careful if a /= b: as this can capture a 'b' inside 'S' or 't'. | ||
-- Thus if necessary we do some renaming | ||
-- (Λa.t : ∀b.T) S ~> letType c = b in letType b = c in (Λa.t : ∀b.T) S for b free in t or S, and fresh c | ||
-- We then ensure the delicate property that we reduce the b=c first, then the BETA, then the c=b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, there's a missing closing parenthesis on line 369.
2f17fbd
to
81e929f
Compare
We previously were testing this in some unit tests but had no property test. We add a property test to prevent any regression in the future.
We add a unit test to avoid regressions in the future. This test is rather detailed, testing multiple intermediate steps of the reduction to illustrate the slightly subtle process.
81e929f
to
716cbb5
Compare
No description provided.