Skip to content

fix(check): harden type checker — occurs check + type schemes#61

Merged
hyperpolymath merged 2 commits into
mainfrom
claude/sharp-heisenberg-2ZsZv
Jun 14, 2026
Merged

fix(check): harden type checker — occurs check + type schemes#61
hyperpolymath merged 2 commits into
mainfrom
claude/sharp-heisenberg-2ZsZv

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Harden the bet-check type checker

The follow-up to the echo-types work (and the "harden the checker" item from that session). Two parts:

1. Occurs check (landed) ✅

unify bound a type variable to any type without checking whether the variable occurred inside it — so 'a = 'a -> Int (or 'a = Echo 'a) was accepted and made resolve recurse forever. Added CheckEnv::occurs(id, ty) (walks the resolved type through every former: Fun, Dist, List, Set, Option, Map, Result, Tuple, Echo, EchoR) and both Var bindings now reject occurrence with a clear error.

  • 3 new tests (infinite via Fun, infinite via Echo, distinct-vars-still-unify). 41 bet-check tests pass; lib clippy-clean.

2. Real type schemes (in progress) ⏳

Replacing the shared-variable pseudo-polymorphism (the header comment claims "let-polymorphism via generalization at let-boundaries" but the code didn't do it) with proper generalize/instantiate. Will land as a follow-up commit on this branch.

https://claude.ai/code/session_01QGi8GND5yNWgDyfReVEPYs


Generated by Claude Code

claude added 2 commits June 14, 2026 16:05
unify previously bound a type variable to any type without checking whether
the variable occurred inside it, so `'a = 'a -> Int` (or `'a = Echo 'a`) was
accepted and made `resolve` recurse forever. Adds `CheckEnv::occurs(id, ty)`,
which walks the resolved type through every former (Fun, Dist, List, Set,
Option, Map, Result, Tuple, Echo, EchoR), and refuses the binding with a clear
error when the variable occurs.

3 new tests (infinite via Fun, infinite via Echo, distinct-vars-still-unify);
41 bet-check tests pass; lib is clippy-clean.

First half of the checker-hardening pass; real type schemes (generalise /
instantiate at let-boundaries) follow.

https://claude.ai/code/session_01QGi8GND5yNWgDyfReVEPYs
…antiate

Replaces the shared-variable pseudo-polymorphism (the module header claimed
let-polymorphism, but the code pinned a single type var across all uses of a
binding) with proper Hindley-Milner generalization:

- new `Scheme { vars, ty }`; env bindings are now schemes. `bind()` still takes
  a `Type` and wraps a monomorphic scheme, so its many callers are unchanged;
  `bind_scheme()` is added for polymorphic bindings.
- `generalize(ty)` quantifies the type vars free in `ty` but not free in the
  environment; `instantiate(scheme)` freshens the quantified vars at each use.
- top-level `Item::Let` generalizes before binding; `Expr::Var` instantiates on
  lookup; the recursive-let placeholder reads the scheme body.
- `to_string` is now seeded as `forall a. a -> String` (genuinely polymorphic).

2 new tests (independent instantiation; `to_string` at Int and String in one
scope — the old approximation would reject the second). 43 bet-check tests
pass; lib clippy-clean. Completes the checker-hardening pass (occurs check +
type schemes).

https://claude.ai/code/session_01QGi8GND5yNWgDyfReVEPYs
@hyperpolymath
hyperpolymath marked this pull request as ready for review June 14, 2026 16:53
@hyperpolymath
hyperpolymath merged commit 77ba191 into main Jun 14, 2026
17 checks passed
@hyperpolymath
hyperpolymath deleted the claude/sharp-heisenberg-2ZsZv branch June 14, 2026 16:53
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.

2 participants