Skip to content

feat(#92): widths need not agree for ==, and match arms join on width - #95

Merged
hyperpolymath merged 1 commit into
mainfrom
feat/width-widening-92
Jul 29, 2026
Merged

feat(#92): widths need not agree for ==, and match arms join on width#95
hyperpolymath merged 1 commit into
mainfrom
feat/width-widening-92

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Implements the #92 ruling across both engines — plus two bugs found while verifying it.

The rule

T-Eq-Word required both operands at the same width. Four ways that was inconsistent with the language itself (evidence in #92):

  • tComposeWord already widens to max n m — you could compose two braids you were then forbidden to compare.
  • ~ accepted differing widths and, since TG-7, evaluates through the same Braid_equiv.equiv. Identical operands, identical answer, one rejected by the typechecker.
  • eqIdBraid/eqBraidId decide "is this braid trivial?" against identity : word 0, so under the old rule they could only fire when the braid was empty. The step relation had rules for a question the typing rule forbade asking.
  • braid_equiv.equiv has no width parameter at all.

Lean's tEqWord now takes (n m); infer's .eq case drops the if n = m; OCaml Eq mirrors it. Match arms join on width for words and still require exact agreement otherwise.

The metatheory needed no patching

Progress, Preservation, Determinism, TypeSafety, infer_sound, infer_complete all still holdlean Tangle.lean reports 0 errors, sorry/axiom gate passes.

infer_sound's proof got simpler: with no if n = m there's no inner split to case on. A restriction whose removal shortens the proof was carrying no weight.

TG-3 regenerated and kernel-checked: 496 obligations, 0 errors. 46 flipped from = none to = some .bool — exactly the identity == braid shape that was unreachable before.

Two bugs found while verifying

1. Statement order. parse_file_recovering did stmts := prog @ !stmts then List.rev on the flattened result. That reversal is correct for an accumulator built by prepending single items (as diagnostics is) — but whole segments were prepended, so every program came out backwards:

def x = 5     parses to    def z = 7
def y = 6      ------->    def y = 6
def z = 7                  def x = 5

Any program whose statements depend on order died with "Unbound variable". The test suites never caught it because they call Tangle.Parser.program directly — only the CLI goes through the recovering path.

2. A false assertion in examples/trefoil.tangle:

assert reversed == braid[s1, s1, s1]     # reversed = reverse(trefoil)

reverse reverses the word and negates exponents, yielding the inverse braid. Verified by invariant: writhe(trefoil) = 3, writhe(reversed) = -3, and writhe is invariant under the braid relations — so they cannot be equal. The assertion had been wrong since it was written; nothing ran the examples until #89. Corrected to braid[s1^-1, s1^-1, s1^-1], which passes.

Result

before after
examples evaluating 2/7 7/7
lib/stdlib.tangle did not typecheck typechecks
conformance 18/19 18/19 (v11 is the Harvard sub-language, #94)

All seven examples are now in the corpus gate's must-run set, so none can regress.

⚠ Trusted base — registered as A-TG-92.1

The embedding Bₙ ↪ Bₙ₊₁ that justifies cross-width comparison is standard mathematics, but it is asserted in prose, not mechanised — no Lean lemma states it.

What is machine-checked: that the metatheory holds under the widened rule, and that OCaml infer_expr still agrees with Lean infer on the corpus. That distinction is recorded in ASSUMPTIONS.md rather than left implicit.

Tests

6 new typecheck cases, including two negatives — mismatched kinds still rejected, match arms of different kinds still rejected — so widening cannot quietly become "anything compares to anything". Three test_check cases repointed at an error that is still an error (and an assertion helper left unused by the change was put back to work rather than deleted).

🤖 Generated with Claude Code

Implements the #92 ruling across both engines, plus two bugs found while
verifying it.

## The rule

`T-Eq-Word` required both operands at the same width. That was inconsistent
with the rest of the language in four ways, all recorded in #92:

  * `tComposeWord` already WIDENS to max n m — a program could compose two
    braids it was then forbidden to compare;
  * `~` accepted differing widths and, since TG-7, evaluates through the SAME
    `Braid_equiv.equiv` — identical operands, identical answer, one rejected;
  * `eqIdBraid`/`eqBraidId` decide "is this braid trivial?" against
    `identity : word 0`, so under the old rule they could only ever fire when
    the braid was empty — the step relation had rules for a question the
    typing rule forbade asking;
  * `braid_equiv.equiv` has no width parameter at all.

Lean `tEqWord` now takes (n m); `infer`'s `.eq` case drops the `if n = m`.
OCaml `Eq` mirrors it. Match arms join on width for words (max) and still
require exact agreement for everything else.

## The metatheory did not need patching

Progress, Preservation, Determinism, TypeSafety, infer_sound and
infer_complete all still hold — `lean Tangle.lean` reports 0 errors, and the
sorry/axiom gate passes. The `infer_sound` proof got SIMPLER: with no `if
n = m` there is no inner `split` left to case on, which is a decent sign the
restriction was load-bearing for nothing.

TG-3 regenerated and kernel-checked: 496 obligations, 0 errors. 46 of them
flipped from `= none` to `= some .bool` — precisely the `identity == braid`
shape that was previously unreachable.

## Two bugs found while verifying

1. STATEMENT ORDER. `parse_file_recovering` in bin/main.ml did
   `stmts := prog @ !stmts` and then `List.rev` on the flattened result. That
   reversal is right for an accumulator built by prepending single items — as
   `diagnostics` is — but whole SEGMENTS were prepended, so every program came
   out backwards: `def x; def y; def z` parsed to [z; y; x]. Any program whose
   statements depend on order failed at evaluation with "Unbound variable".
   The test suites never caught it because they call `Tangle.Parser.program`
   directly; only the CLI goes through the recovering path. Now accumulates
   segments and flattens in order.

2. A FALSE ASSERTION in examples/trefoil.tangle:
       assert reversed == braid[s1, s1, s1]
   `reverse` reverses the word AND negates exponents, so it yields the INVERSE
   braid. writhe(trefoil) = 3 but writhe(reversed) = -3, and writhe is
   invariant under the braid relations, so they cannot be equal. The assertion
   had been wrong since it was written; nothing ran the examples until #89.
   Corrected to braid[s1^-1, s1^-1, s1^-1], which passes.

## Result

  examples evaluating : 2/7 -> 7/7   (all now in the must-run set)
  lib/stdlib.tangle   : did not typecheck -> typechecks
  conformance         : 18/19 (v11 add{} is the Harvard sub-language, #94)

## Trusted base

Registered as A-TG-92.1: the embedding Bn -> Bn+1 that justifies cross-width
comparison is standard mathematics but is asserted IN PROSE, not mechanised.
What is machine-checked is that the metatheory holds under the widened rule
and that OCaml still agrees with Lean on the corpus.

Tests: 6 new typecheck cases including two negatives (mismatched KINDS still
rejected; match arms of different kinds still rejected) so widening cannot
degrade into "anything compares to anything". Three test_check cases
repointed at an error that is still an error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hyperpolymath
hyperpolymath merged commit 478857f into main Jul 29, 2026
26 checks passed
@hyperpolymath
hyperpolymath deleted the feat/width-widening-92 branch July 29, 2026 01:20
@gitar-bot

gitar-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

Note

Automatic reviews are paused because your trial's included automatic processing has been used for this period. Upgrade now, or comment "Gitar review" to run a review anytime.
Learn more

Code Review ✅ Approved

Removes strict width agreement requirements for equality comparisons and adds match arm width joining across both engines, enabling all 7 example programs to evaluate successfully. No issues found.

Auto-approved and auto-merge armed: No blocking issues found.
Please see Auto-approve Docs for details on setting custom approval criteria. — merges when pipeline and required approvals pass.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@gitar-bot

gitar-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

⚠️ Gitar auto-approved this PR but could not enable auto-merge: auto-merge is disabled for this repository — enable "Allow auto-merge" in the repository settings.

@gitar-bot gitar-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gitar has auto-approved this PR and enabled auto-merge (configure)

@gitar-bot gitar-bot Bot added the gitar-approved Added by Gitar label Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gitar-approved Added by Gitar

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant