1c8c1a0441
Polymorphism is now opt-in via Type::Forall at top-level def types.
Implementation is the textbook ML rule: peel the Forall when checking
the def body, instantiate fresh metavars at every use site, unify.
Mechanics:
- New Subst struct + unify(): standard occurs-check unification, with
effects compared as a set.
- Metavars encoded inside the existing Type::Var as Type::Var{name:
"$m<id>"}. The "$" prefix can't collide with source-level identifiers,
so the AST schema stays untouched and existing module hashes don't
shift (verified: sum.ail.json keeps db33f57cb329935e / d9a916a0ed10a3d3).
- check_fn peels an outer Forall and installs the rigid vars in
Env.rigid_vars, where check_type_well_formed accepts them. Inner
forall vars unify only with themselves.
- Term::Var lookup runs maybe_instantiate on the resolved type, so
polymorphic globals get fresh metavars at each occurrence. Two
use sites of the same `id` get independent ($m0)→$m0 and ($m1)→$m1
shapes — no bleed.
- All expect_eq calls inside synth replaced with unify; expect_eq is
still used for the App-arity / etc. structural checks where no
metavars can appear.
Constraints kept tight on purpose:
- Const types may not be Forall (rejected outright).
- Forall in ADT field positions still rejected.
- No higher-rank polymorphism: App's callee can be Forall (defensive
instantiate) but the body doesn't introduce new quantifiers.
- No let-generalisation of lambdas — all polymorphism is at top-level
defs only.
Codegen still rejects Forall types, so polymorphic programs typecheck
but don't yet compile. Iter 12b adds monomorphisation.
Tests: 14/14 (was 10/10) — added polymorphic_id_def_typechecks,
polymorphic_id_can_be_used_at_int_and_bool,
polymorphic_id_consistency_is_enforced, polymorphic_apply_with_two_vars.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>