Skip to content

Merge dev into new-type-hole-inference (elastatics)#2290

Open
cyrus- wants to merge 1577 commits into
new-type-hole-inferencefrom
dev-into-new-type-hole-inference
Open

Merge dev into new-type-hole-inference (elastatics)#2290
cyrus- wants to merge 1577 commits into
new-type-hole-inferencefrom
dev-into-new-type-hole-inference

Conversation

@cyrus-
Copy link
Copy Markdown
Member

@cyrus- cyrus- commented May 11, 2026

Brings PR #1872 (new-type-hole-inference) up to date with dev. The big delta on dev is the elastatics refactor (#2213) — combined statics + elaborator, replaced Self.re with Mark.t / Warning.t / Message.t, added Mod / Sig / MPat sorts. This branch reconciles those with the THI work (annotated provenances, unification constraints, type-hole projector) so the build is green and the inference pipeline is wired end-to-end.

The 9 commits are sequenced for review:

  1. 18af1c8 — merge commit (conflict resolution per the strategy in commit message: dev wholesale for Statics.re / Info.re / StaticsBase.re / CustomStatics.re / test files; THI grammar kept for TPat, annotated provenance, Typ.equivalence; deletions accepted for Self.re, Elaborator.re, helpful-assistant/, Test_Info.re, Test_Evaluator_Probe.re)
  2. dff435c — WIP build fixes (intermediate; could be squashed with the next on merge)
  3. 3ee4f51 — post-merge build fixes: project compiles
  4. fd09895TypeHole.cycle_hole factory + Test_Grammar exhaustiveness
  5. eaad5e2annotate_static_errors tolerates inner-Prov IDs
  6. 9471fff — wire constraint emission and inference_map end-to-end (Info.exp / Info.pat gain constraints field; Statics.mk_with_inference runs Inference.go; CachedStatics populated)
  7. 3bab547Test_Inference smoke tests
  8. a33b6c4 — emit MatchedTyp and branch constraints into the inference pipeline (Arrow / Prod / List / Poly refinements; pairwise If / Match / ListLit branch constraints)
  9. 8e0ff2d — thread label_cons through all four TupLabel sites; clean up warnings

Non-obvious decisions

  • TPat grammar kept from this branch (Unknown(prov) | Var) rather than dev's flat Invalid | EmptyHole | MultiHole | Var. The whole "tpats use type provenances" thing depends on it. Consequence: every dev-side TPat pattern (EmptyHole, MultiHole, etc.) was rewritten to Unknown({term: Hole(_), _}).
  • Annotated type_provenance_t wrapper kept (every Unknown wraps an annotated prov, not bare). Consequence: every dev-side Unknown(SynSwitch) / Unknown(Internal) / Unknown(Hole(_)) was rewritten — Unknown({term: ..., _}) in pattern position, Unknown(... |> Prov.fresh) in expression position. Inner Prov annotations carry their own IDs that statics doesn't populate; Test_Statics_Prelude.annotate_static_errors now treats missing-info lookups for those IDs as "no error" instead of failing.
  • Typ.meet returns tuple (t, list(equivalence)) from this branch — every dev call site (fixed_typ, expectation_mismatch_mark, ascription transition, etc.) was updated to unpack the constraint list.
  • MatchedTyp rewritten to return constraints from every shape (arrow, poly_pair, prod, list_strict, label). Refinable Unknown(SynSwitch) inputs delegate to Typ.matched_*_of_prov and emit Con(ty, Shape(...)) constraints.
  • Dropped the branch's f_prov parameter from map_term signatures. Nothing outside TermBase.re / Grammar.re ever called it, and keeping it would have forced rewrites of every dev-added Mod / Sig / MPat case. The Prov module still has its own internal traversal.
  • Exercise.re taken wholesale from dev (47 lines, thin dispatcher). The branch's zipper_of_code / transition / stitching code referenced types (state, transitionary_spec, hint, pos, Editor) that no longer exist after dev's exercise reorganization into CodeExercise.re / DerivationExercise.re / TheoremExercise.re.
  • Dropped branch's analyze_hazel CLI command. It used Info.show_error and Statics.Map.errors which don't exist post-elastatics. Re-add when needed.
  • Added TypeHole to Language.ProjectorKind (dev moved Kind to the language library; this branch had renamed dev's Info projector to Statics and added TypeHole inline in ProjectorCore). Kept dev's Statics name, added TypeHole alongside.
  • ProjectorInfo.mk_info now takes both ~sample_focus (dev) and ~inference (this branch).
  • Constraint aggregation strategy: rather than threading constraint lists by hand through every Statics handler return signature, each Info entry stores its own constraints list at add time. mk_with_inference walks the finalized info_map and aggregates them (deduped by user-term rep id). Lower risk than rewriting every handler signature.

Constraint surface

What now produces constraints into the inference pipeline:

  • Subsumption + Typ.meet structural constraints — automatic in add
  • Arrow refinement — Ap (no-custom path), DeferredAp (no-custom path), Fun
  • Poly refinement — TypAp, TypFun
  • Prod refinement — Tuple (exp + pat)
  • List refinement — ListLit, Cons, ListConcat (exp + pat)
  • TupLabel refinement — all four TupLabel sites (exp + pat, inside-tuple + standalone)
  • Pairwise branch equivalence — If, Match, ListLit
  • Builtin custom statics — to_lvs_statics, omit_all_labels_statics

Tests

test/Test_Inference.re adds 6 smoke tests directly exercising the constraint paths:

  • 1 + 2 and let x : Int = 1 in x + 2 — no-unknowns no-crash
  • (? : ? -> ?)(1) — arrow refinement
  • if true then 1 else ?If branch constraint
  • [1, ?, ?]ListLit branch constraint
  • ((a=1, b=?) : (a=Int, b=?)) — TupLabel refinement

Build green, all 2694 tests pass.

🤖 Generated with Claude Code

7h3kk1d and others added 30 commits March 25, 2026 09:57
Use mkdir -p so parent directories are created when deploying branches
with slashes (e.g., feature/my-fix).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…simplify pattern

- Ctx.add_ctrs: remove unused `id` parameter and dead `[] => id` fallback
  (ann.ids is always non-empty from parsed tiles and builtins); add assert
- CoCtx.union: replace List.concat intermediate allocation with nested fold
- CodeWithStatics.re: remove redundant `dynamics: _` from pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bumps  and [picomatch](https://github.com/micromatch/picomatch). These dependencies needed to be updated together.

Updates `picomatch` from 4.0.2 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](micromatch/picomatch@4.0.2...4.0.4)

Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](micromatch/picomatch@4.0.2...4.0.4)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: 7h3kk1d <995429+7h3kk1d@users.noreply.github.com>
The Makefile test targets ran node without --require idb_stub.js,
causing IDBKeyRange ReferenceError in Node.js. The dune test action
already included this flag, which is why CI (dune runtest) passed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The context_menu state in CodeEditable.Model.t was being discarded in
two places: the update path created a fresh model (losing the state),
and the calculate path hardcoded context_menu: None on every cycle.
Now both paths preserve context_menu from the existing cell state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add mk_zipper() to Test_Editing.re: builds a Zipper with selection
  from a string using markers. Uses PointToPoint selection via Measured
  coordinates to handle multi-line code with indentation correctly.
- Add printer_indented() for tests that need Points to match layout.
- Add 4 mk_zipper round-trip tests (single-line and multi-line).
- Add 2 FAILING tests in Test_Reassociate.re reproducing the deep
  reassociation bug: cut+paste of cross-scope selections leaves
  incomplete tiles. Reassociate.go does not fully repair the structure.
  - Multi-line: mapi/fun/if nested code (user exact scenario)
  - Single-line: if true then if false then 1 else 2 else 3

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
expand_scope stopped flattening ancestors once the initial request was
satisfied, but completing an inner multi-delimiter tile via rescan can
reveal that outer ancestors also need work. After the first expand +
crack + rescan pass, if incomplete multi-delimiter tiles remain and
there are unflattened outer ancestors, flatten them all and retry.

Regression was introduced in a370067 (targeted ancestor flatten).

Add 4 cut+paste round-trip tests: doubly nested if, triply nested if,
if containing let, and fun containing if containing let.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
expand_scope stopped flattening ancestors once the initial request was
satisfied, but completing an inner multi-delimiter tile via rescan can
reveal that outer ancestors also need work. After the first expand +
crack + rescan pass, if incomplete multi-delimiter tiles remain and
there are unflattened outer ancestors, flatten them all and retry.

Regression was introduced in a370067 (targeted ancestor flatten).

Add 4 cut+paste round-trip tests: doubly nested if, triply nested if,
if containing let, and fun containing if containing let.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment.toggle_multi uses insert_text (character-by-character Insert.go)
which does rescan_reassemble but not Reassociate.go. When commented lines
were inside nested ancestors (cross-scope selection), the re-inserted text
creates incomplete multi-delimiter tiles needing deep reassociation.

Call Reassociate.go after insert_text in both comment and uncomment
branches of toggle_multi, before reselect_lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wrap_balanced creates a new ancestor tile around the selection content,
then does remold_regrout and re-selects. But Reassociate.go was only
called after re-selection (via Perform.go maybe_reassoc), at which
point the selection hides incomplete tiles from the reassociation scan.

Call Reassociate.go after remold_regrout and before re-selection, when
the zipper has no active selection and the siblings are visible.

Thread ~deep_reassociate from Perform.go settings through Insert.go,
try_wrap_selection, and wrap_balanced.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds `code` field to Key.t (reads DOM evt.code) to handle Option+N on
Mac, which is a dead key (evt.key="Dead"). Key matching logic lives in
Keyboard.is_new_slide, dispatched from ScratchMode. Works for both
Scratch and Documentation modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When cutting a selection that spans a delimiter (e.g. `=` in
`let...=...in`) and pasting it back, the re-inserted delimiter gets a
fresh ID from Insert.go and can't reconnect with the ancestor tile.
Siblings.rescan only looks at flat siblings, not ancestors.

Add rescan_parent_shards: walks the ancestor chain converting standalone
monotiles whose tokens match a parent ancestor's missing shards, then
absorbs them into the parent using reassemble_parent-style logic
(split_by_matching + restructure). This preserves intermediate ancestors
(e.g. parens) unlike the simpler delete_parent approach.

Wire into rescan_reassemble via ~with_parent parameter (default false).
Parser.to_zipper uses ~with_parent=true so paste gets the fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Brings in rescan_parent_shards fix for cut-paste across delimiter
boundaries, plus cross-boundary paste test.

Conflict resolution: kept probes-III's mk_zipper-based Test_Reassociate
tests (superset of deep-reassociate-clean's sel_l-based versions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EOF
)
Adds a new Errors tab to the sidebar that lists all errors in the editor,
organized into collapsible Syntax Errors and Static Errors sections.
Features include: error rows sorted by document order, line numbers,
active error highlighting with auto-scroll, color-coded legend,
and a Grouped/Flat view toggle.

Adapted from post-table-study branch error sidebar work (12 commits)
for the dev branch, excluding live typing errors which require
dynamic statics infrastructure not yet on dev.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add collapsible Warnings section with orange styling matching
  editor warning decorations (--warning-hole-stroke/fill)
- Gate warnings on display_warnings setting
- Add warning legend entry
- Include warnings in flat view mode
- Fix gamma (Γ) showing in error rows by calling exp_view/pat_view
  directly instead of view_of_info which wraps with term_view

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lication

- Extract is_syntax_error to Language.Info alongside is_error/is_warning
- Unify error_row and syntax_error_row into a single function taking content node
- Extract data-gathering logic from view into collect_errors pure helper
- Guard scroll_active_error_into_view on Errors panel visibility
- Nest error-specific settings under errors sub-record in SidebarModel
- Make legend dynamic (only shows categories with items)
- Remove duplicated CSS selectors for error type colors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace repetitive per-category code (three toggle actions, three
settings fields, three section blocks, three legend items) with a
single error_category variant and metadata functions. Adding a new
error category now means adding a variant constructor, not editing
multiple functions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce problem/problem_source types to decouple data collection from
Node.t rendering. Partition error_ids into syntax/static in a single pass
in make_error_context, then sort per-category inside collect_category.
Replace monolithic collect_errors with make_error_context + collect_category
(lazy Seq.t) + a single problem_row rendering function.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The sidebar was using raw row indices (origin.row + 1) which included
empty rows added by projectors (e.g., Block(n)), causing line numbers
to disagree with the editor gutter. Build a row→display-line mapping
using the same skip-empty-rows logic as LineNumbers.re.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move error data types and collection logic into a standalone
ErrorCollection module with no UI dependencies. Add from_string
entry point for testing and 6 test cases covering clean programs,
syntax errors, type mismatches, and trailing expressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MultiHole terms (broken expressions like trailing "f" in "1\nf")
were mapped to NotInHole by status_common, so they never appeared
in error_ids or the error sidebar. Add a MultiHole variant to
error_no_type and map IsMulti to InHole(NoType(MultiHole)) so
these are correctly flagged as syntax errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show a colored circular badge instead of the magnifying glass icon:
green checkmark for no issues, yellow with count for warnings only,
red with count for errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The error/warning status is now shown on the Errors sidebar tab,
so the bottom bar status indicator is no longer needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cyrus- and others added 30 commits April 26, 2026 01:42
Resolves conflict in ProbeSidebar.re by accepting dev's Probearium /
Printarium panel-mode switch (#2230).

Made-with: Cursor
The global concurrency group `deploy-to-build-repo` caused queued
deploys on other branches to be cancelled with "higher priority waiting
request" when a third run arrived. GitHub's per-group queue depth is 1,
so cancel-in-progress: false only protects the running job, not the
queued one — additional arrivals evict whatever was waiting.

Switch to a per-branch group so cross-branch deploys don't fight over a
single queue slot. The push-retry-with-rebase loop already handles the
cross-branch race on hazelgrove/build's main ref (each branch writes to
a disjoint directory, so the rebase is conflict-free). Set
cancel-in-progress: true so a newer commit on the same branch supersedes
an older in-flight build instead of deploying stale output.
With per-branch concurrency, cross-branch deploys now run in parallel
and contend for hazelgrove/build's main ref via the retry loop. 3
attempts only covers ~4 concurrent branches worst-case; 10 attempts
with a 1-3s jittered sleep gives generous headroom for bursty pushes
without meaningfully slowing the common case.
Elastatics refactor (PR #2213) combined statics+elaborator, replaced
Self.re/Mode with Mark/Warning/Message, and added Mod/Sig/MPat sorts.

Strategy: took dev wholesale for the heavily-rewritten Statics.re,
Info.re, StaticsBase.re, CustomStatics.re, and the test suite; preserved
the branch's inference machinery (inference/, Prov.re, type_provenance_t
annotated wrapper, TPat = Unknown(prov) | Var, Typ.equivalence
constraints). Build is expected to be broken; re-port follow-up commits
will thread constraints back into the new combined Statics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Started porting dev-side Unknown(prov) call sites to the annotated
type_provenance_t wrapper that the branch's inference work introduced.
Fixed: Equality.tpat callers, Typ.equivalence type, MatchedTyp Unknown
patterns, Typ.re internal hole construction, ProjectorKind TypeHole,
ProjectorCore/Init wiring, CachedSyntax inference_map field, ProjectorInfo
mk_info signature, several files' Unknown(Internal) -> Unknown(Internal |> Prov.fresh).

Build still broken: dev's elastatics rules (Statics.re, CursorInspector.re,
many test files) reference Unknown(SynSwitch) and similar flat-prov patterns
that need rewriting against the annotated wrapper, and the inference work
has not yet been re-threaded into the combined Statics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Threaded the annotated provenance wrapper, constraint-returning Typ.meet,
and remaining structural changes through the dev-side code that the
elastatics merge brought in. Build is now green across language, haz3lcore,
web, CLI, and tests.

Key changes:
- Statics.re, StaticsBase.re, CustomStatics.re, LabeledTupleStaticsHelpers,
  ConstructorStaticsHelpers, BuiltinsList: every Unknown(SynSwitch)/Internal/
  Hole(_) expression wraps the bare prov with Prov.fresh; pattern matches
  use Unknown({term: ..., _}).
- StaticsBase, ConstructorStaticsHelpers: Typ.meet/meet_all callers unpack
  the new (Typ.t, list(equivalence)) tuple.
- CanonicalConstramnot, PossibleProvTypesMap, Inference, Solution: added
  DrvQuoteTy/Projector/Sig cases (Solution treats them as opaque holes
  since they don't participate in inference).
- Exercise.re: took dev's 47-line version wholesale; the branch's
  zipper_of_code/transition/stitching code referenced types (state,
  transitionary_spec, hint, pos, Editor) that no longer exist after dev's
  exercise reorganization.
- ProjectorKind: added TypeHole; ContextMenu: added Type Hole display name.
- CachedSyntax: stored inference_map field on the cache.
- ProjectorInfo.mk_info: now takes both ~sample_focus (dev) and ~inference
  (branch); call sites updated.
- TermBase.Typ: re-added equivalence = Con(t, t).
- Tests: open Language in test files that need Prov.fresh; FIError/FTemp
  factory call sites use TypeProvenance.internal/syn_switch etc.
- Misc TPat dev-style patterns (EmptyHole|MultiHole|Invalid) rewritten as
  Unknown({term: Hole(_), _}) in MultiProbe, HighLevelNodeMap, ExpToSegment,
  Statics utpat handling.

Still pending: the inference work itself (constraint generation through
the new combined Statics, populating CachedStatics.inference_map). The
inference module compiles and is wired through ProjectorInfo, but
inference_map remains empty until that re-port happens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds TypeHole.cycle_hole() factory to Grammar and adds the new
SynSwitch/Internal/LArrow/RArrow/NProduct/MList/RForall/TupLabel*/Meet/
TypeSubstitution Prov.cls cases to Test_Grammar's sample_tpat. Brings
test failures from 5 down to 3 (Evaluator.Properties property test
plus Builtins.group_by_label.009/010 and Functions.011 info-map gaps
that need separate investigation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rors

The branch wraps type_provenance in an annotated wrapper (type_provenance_t)
so Provs can be identified for unification. fresh() assigns each annotation
a unique id, including the inner Prov annotations, but Statics only adds
info entries for the outer typ_t nodes. The test helper's lookup was failing
on those inner Prov ids.

Treat lookup misses as 'no error' instead of asserting. Inner Provs are
metadata for inference and don't carry user-facing diagnostics, so this is
semantically correct. All 2688 tests now pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The branch's inference pipeline (Solution / Inference / PossibleProvTypesMap /
CanonicalConstramnot) was compiling but never being fed real input — every
Inference.TypSolutionMap.empty was a stub. This wires the constraint flow
back end-to-end:

- Info.exp / Info.pat: add `constraints: list(Typ.equivalence)` field.
- Statics.add (both exp and pat variants): each call now records the
  subsumption equivalence Con(ana, elab_syn_ty) when those types meet, plus
  the structural constraints returned by Typ.meet, plus any constraints the
  handler passes in (via the new optional ~constraints parameter). The
  ExpressionStatics module type gets the same ~constraints param.
- collect_all_constraints: walks the info_map and pulls every InfoExp /
  InfoPat constraint list into one flat list, deduplicating by user-term
  representative id so equivalent-id shards (which share an Info entry)
  don't duplicate constraints.
- Statics.mk_with_inference: new 3-tuple version that returns the
  inference_map after running Inference.go on the aggregated constraints.
  Legacy Statics.mk wraps it for callers that only want (info_map, elab).
- CachedStatics.init_from_term: switched to mk_with_inference; the
  TODO/empty stub is gone.

Constraint sources covered automatically (no per-handler edits required):
- subsumption Con(ana, elab_syn_ty)
- structural Typ.meet constraints (Arrow head/tail, Prod elements, etc.)

Constraint sources NOT yet covered (room for follow-up if inference quality
needs more constraints):
- Per-handler constraints emitted by Typ.matched_arrow / matched_prod /
  matched_list / matched_label / matched_poly inside MatchedTyp / Statics
  handlers — these currently return the constraint list but the new
  Statics handlers discard them.
- constrain_branches across match arms / list literals.
- Custom-statics builtin constraints from CustomStatics handlers.

All 2688 tests pass. ProjectorInfo.mk_info already looks up the inference
map for hole projectors, so the TypeHole projector should now show solved
types when inference can resolve them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three quick checks that the constraint-emission pipeline is actually
firing end-to-end:
- (? : ? -> ?)(1): hole-typed function applied to Int — checks the
  inference map gets at least one solution.
- 1 + 2: fully-typed, no inference work to do; just verifies no crash.
- let x : Int = 1 in x + 2: annotation flow; verifies no crash.

These are smoke tests, not correctness tests — they don't pin specific
solutions, just confirm the wiring works. All three pass (2691 tests total).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the inference pipeline only saw subsumption Con(ana, syn) and
the structural constraints from Typ.meet inside add(). Per-handler
constraints emitted by MatchedTyp.arrow / prod / list / poly_pair, plus
pairwise branch constraints for If / Match / ListLit (exp + pat), were
being discarded. This rewires them so inference gets the full picture.

MatchedTyp.re overhauled to return constraint lists alongside the
extracted shape. Every variant delegates to the branch's
Typ.matched_*_of_prov helpers when the input is a refinable Unknown, so
the inference pipeline learns Con(ty, Arrow(L,R)) / Con(ty, Prod(...)) /
Con(ty, List(_)) / Con(ty, TupLabel(_,_)) / Con(ty, Poly(_,_)) refinements:

- arrow / arrow_tolerant: 3-tuple (in, out, cons)
- poly_pair / poly_pair_tolerant: 3-tuple (name?, body, cons)
- prod / prod_strict: triple with cons
- list_strict / list_tolerant: pair with cons
- label: option with cons

constrain_branches helper added at the top of Statics.re. Used at:
- ListLit (exp side)
- ListLit (pat side)
- If
- Match

Per-handler constraints threaded into add(~constraints=...) at:
- Ap (No-custom-statics path): arrow_cons
- DeferredAp (No-custom-statics path): arrow_cons
- TypAp: poly_cons
- Fun: arrow_cons
- TypFun: poly_cons
- Tuple (exp side): prod_cons
- Tuple (pat side): prod_cons
- Cons (exp + pat): list_cons
- ListConcat: list constraints for ana + each arg
- CustomStatics.to_lvs_statics: arrow_cons (all branches)
- CustomStatics.omit_all_labels_statics: arrow_cons (all branches)

Also taught LabeledTupleStaticsHelpers.decompose_label_mode to return
constraints (not yet threaded at the Tuple-element call sites; the
information is consumed by the outer MatchedTyp.prod which already
emits the refinement).

Smoke tests for if-branch and list-branch hole inference, both pass.
2693 tests total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
decompose_label_mode (in LabeledTupleStaticsHelpers) already returned a
constraint list, but at every call site the constraints were being
dropped into _label_cons. Now they're passed to the TupLabel add() so
the inference pipeline sees the TupLabel refinement on the parent ana
prov.

Four call sites covered:
- Exp Tuple's per-element TupLabel handler
- Exp standalone TupLabel handler
- Pat standalone TupLabel handler
- Pat Tuple's per-element TupLabel handler

Added a labeled-tuple inference smoke test against `((a=1, b=?) : (a=Int, b=?))`
which exercises the TupLabel constraint emission. Also dropped the unused
local empty_hole binding in Test_Grammar.sample_type — the shadowing was
hiding it from use, so the body was using Typ.empty_hole via the local
open all along.

2694 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Local runs take ~7-8m on a fast Mac; CI runs were timing out at 20m.
The inference smoke tests add a few seconds but don't account for the
gap — give the suite headroom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

7 participants