Skip to content

fix: reduce type-level let (TLet) RHS in the interpreter#1441

Merged
dhess merged 2 commits into
mainfrom
claude-code/interp-reduce-tlet
Jun 8, 2026
Merged

fix: reduce type-level let (TLet) RHS in the interpreter#1441
dhess merged 2 commits into
mainfrom
claude-code/interp-reduce-tlet

Conversation

@hackworth-ltd-claude-code

Copy link
Copy Markdown

Fixes #1439.

Problem

Primer.EvalFullInterp.interpTy evaluates types in an environment whose values are meant to be normal forms — the TVar case returns a stored value verbatim. But the TLet case bound its right-hand side unevaluated:

TLet _ v s t -> interpTy (extendTyEnv' v s env) t

Every other binding site already interprets first — term-level Let, LetType, BETA/APP, the Case argument types, and TForall. TLet was the lone exception, so type-level lets were left partly unreduced, and could even leak a dangling, out-of-scope type variable into the result.

Structure

As requested on the issue, the PR is split so the bug is demonstrated before it's fixed:

  1. test: adds two regression tests (unit_tlet_reduce_rhs, unit_tlet_reduce_rhs_let). They fail at this commit, demonstrating the bug:
    • tlet a = C in (tlet v = a in v) → reduces to a dangling TVar "a" instead of C.
    • tlet v = (tlet w = C in w) in (v -> v) → leaves residual tlets instead of C -> C.
  2. fix: interprets the RHS before binding it (interpTy (extendTyEnv' v (interpTy env s) env) t), turning both tests green. It's safe and terminating, since type evaluation always terminates.

Notes

A note on the DCO check

I'm an automated agent (Hackworth Ltd Claude Code) and can't certify the Developer Certificate of Origin, so these commits aren't signed off and the check-dco status will be red. A human with authority to certify the sign-off will need to resolve that before merge.

Hackworth Ltd Claude Code added 2 commits June 8, 2026 11:14
`interpTy` binds the right-hand side of a type-level `tlet` into the type
environment without reducing it first, violating the "environment values
are in normal form" invariant that the `TVar` lookup relies on. Add two
regression tests that demonstrate this:

  - unit_tlet_reduce_rhs: `tlet a = C in (tlet v = a in v)` should reduce
    to `C`, but the unreduced RHS leaves a dangling, out-of-scope
    `TVar "a"` in the result.
  - unit_tlet_reduce_rhs_let: `tlet v = (tlet w = C in w) in (v -> v)`
    should reduce to `C -> C`, but the unreduced RHS leaves residual
    `tlet`s rather than a normal form.

Both tests fail until the following commit fixes `interpTy`. See
#1439.
`interpTy` bound the right-hand side of a type-level `tlet` into the type
environment unevaluated, while the `TVar` lookup returns stored values
verbatim -- relying on them being normal forms. Every other binding site
(term-level `Let`, `LetType`, `BETA`/`APP`, the `Case` argument types, and
`TForall`) already interprets its RHS first; `TLet` was the lone exception.

Interpret the RHS before binding it. This is safe and terminating, since
type evaluation always terminates. It fixes both witnesses from the
previous commit: type-level `let`s now fully reduce instead of leaving a
dangling out-of-scope variable or residual `tlet`s.

Fixes #1439.
@hackworth-ltd-claude-code

Copy link
Copy Markdown
Author

The first commit (b4aed7e) is intentionally red: its two tests fail in CI exactly as expected, demonstrating the bug.

  • tlet reduce rhstlet a = C in (tlet v = a in v) reduced to a dangling, out-of-scope TVar "a" instead of C.
  • tlet reduce rhs lettlet v = (tlet w = C in w) in (v -> v) left residual tlets instead of C -> C.

(2 out of 886 tests failed, both in primer-test's EvalFullInterp group.)

The second commit (de694f3) is the fix: interpTy now reduces the TLet right-hand side before binding it, just as every other binding site already does. Both tests then pass and the full suite is green (886/886).

@dhess dhess merged commit 892a4e0 into main Jun 8, 2026
71 of 72 checks passed
@dhess dhess deleted the claude-code/interp-reduce-tlet branch June 8, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Interpreter does not reduce type-level let (TLet) right-hand sides

1 participant