Skip to content

Commit 892a4e0

Browse files
authored
fix: reduce type-level let (TLet) RHS in the interpreter (#1441)
2 parents 13cc01f + de694f3 commit 892a4e0

2 files changed

Lines changed: 43 additions & 1 deletion

File tree

primer/src/Primer/EvalFullInterp.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,11 @@ interpTy env = \case
380380
TForall _ v k t ->
381381
let v' = freshLikeTy v env
382382
in TForall () v' k (interpTy (extendTyEnv' v (TVar () v') env) t)
383-
TLet _ v s t -> interpTy (extendTyEnv' v s env) t
383+
-- We must reduce the RHS before binding it, just as every other binding site
384+
-- does: the 'TVar' case returns stored values verbatim, relying on the
385+
-- environment holding normal forms. (Type evaluation always terminates, so
386+
-- this cannot loop.)
387+
TLet _ v s t -> interpTy (extendTyEnv' v (interpTy env s) env) t
384388

385389
extendTmEnv ::
386390
Either GVarName LVarName ->

primer/test/Tests/EvalFullInterp.hs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,44 @@ unit_tlet_self_capture = do
367367
r <- evalFullTest mempty mempty Syn expr
368368
r @?= Right expected
369369

370+
-- tlet a = C in (tlet v = a in v)
371+
-- ==>
372+
-- C
373+
--
374+
-- The RHS of a type-level @tlet@ must be reduced before it is bound, just as at
375+
-- every other binding site in the interpreter. The inner binding @v = a@ has the
376+
-- in-scope variable @a@ as its RHS; binding it unreduced makes the lookup of @v@
377+
-- return @a@ verbatim, leaving a dangling @TVar "a"@ in the result -- @a@ is out
378+
-- of scope once both @tlet@s have been consumed.
379+
-- https://github.com/hackworthltd/primer/issues/1439
380+
unit_tlet_reduce_rhs :: Assertion
381+
unit_tlet_reduce_rhs =
382+
let (expr, expected) = create2 $ do
383+
e0 <- ann emptyHole $ tlet "a" (tcon' ["M"] "C") $ tlet "v" (tvar "a") $ tvar "v"
384+
e1 <- ann emptyHole $ tcon' ["M"] "C"
385+
pure (e0, e1)
386+
in do
387+
r <- evalFullTest mempty mempty Syn expr
388+
r @?= Right expected
389+
390+
-- tlet v = (tlet w = C in w) in (v -> v)
391+
-- ==>
392+
-- C -> C
393+
--
394+
-- A second witness: when the RHS is itself reducible (here another @tlet@),
395+
-- binding it unreduced leaves residual @tlet@s in the result instead of a
396+
-- normal form.
397+
-- https://github.com/hackworthltd/primer/issues/1439
398+
unit_tlet_reduce_rhs_let :: Assertion
399+
unit_tlet_reduce_rhs_let =
400+
let (expr, expected) = create2 $ do
401+
e0 <- ann emptyHole $ tlet "v" (tlet "w" (tcon' ["M"] "C") $ tvar "w") $ tfun (tvar "v") (tvar "v")
402+
e1 <- ann emptyHole $ tfun (tcon' ["M"] "C") (tcon' ["M"] "C")
403+
pure (e0, e1)
404+
in do
405+
r <- evalFullTest mempty mempty Syn expr
406+
r @?= Right expected
407+
370408
-- This test is mainly for the step evaluator, but we check it here as
371409
-- well just for completeness.
372410
unit_closed_let_beta :: Assertion

0 commit comments

Comments
 (0)