Skip to content

fix(typecheck): implement [T-Twist-Strand] — (~a) on a named strand (#96) - #101

Merged
hyperpolymath merged 1 commit into
mainfrom
fix/twist-in-weave-96
Jul 29, 2026
Merged

fix(typecheck): implement [T-Twist-Strand] — (~a) on a named strand (#96)#101
hyperpolymath merged 1 commit into
mainfrom
fix/twist-in-weave-96

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

conformance/valid/v09_twist.tangle parsed but failed to typecheck:

def twisted =
  weave strands a:Q into
    (~a)
  yield strands a:Q
Strand name 'a' cannot be used as a standalone expression

The spec licenses it explicitly

FORMAL-SEMANTICS.md §3.10:

Σ(a) = (i, T)
───────────────────────────────────  [T-Twist-Strand]
Γ; Σ ⊢ (~a) : Tangle[[T], [T]]

and spec/grammar.ebnf: "In weave context: (~a) twists named strand a."

The Twist case inferred its operand as an ordinary expression, and the Var rule rejects strand names outright — so the strand-name guard fired before the twist rule could apply. [T-Twist-Strand] is now tried first, leaving [T-Twist-Word]/[T-Twist-Tangle] to the standalone forms.

One of a matched pair was missing

The spec presents this rule immediately alongside [T-Self-Cross]:

Σ(a) = (i, T)                          Σ(a) = (i, T)
─────────────────────────────────      ─────────────────────────────────
Γ; Σ ⊢ (~a) : Tangle[[T], [T]]         Γ; Σ ⊢ (a > a) : Tangle[[T], [T]]

[T-Self-Cross] was already implemented; its twin was not. A test now pins that the two agree, so they can't drift apart again.

Evaluation

Mirrors Crossing: structural, yielding the empty word. Weave bodies only became evaluable in #93, so this construct had never run.

That's also defensible mathematically — a single-strand twist is invisible to the braid word: it's a framing change (Reidemeister I) that the braid group doesn't see.

Detecting it as "a Var the environment doesn't bind" is safe because typechecking runs first and admits Twist (Var a) only when a is a strand.

Result

conformance 16/19 → 17/19 evaluating (18/19 parse). The corpus ratchet demanded the manifest update — exactly what it's for. v11_add_block (#94, the Harvard sub-language) is now the only remaining gap.

Tests: the rule itself; agreement with [T-Self-Cross]; a regression guard that standalone twist on a Word is unchanged; and that a nonsense twist still fails.

All suites green; corpus and RSR gates pass.

🤖 Generated with Claude Code

)

conformance/valid/v09_twist.tangle:

    def twisted =
      weave strands a:Q into
        (~a)
      yield strands a:Q

parsed but failed to typecheck with

    Strand name 'a' cannot be used as a standalone expression

The spec licenses it explicitly. FORMAL-SEMANTICS section 3.10:

    Sigma(a) = (i, T)
    -----------------------------------
    Gamma; Sigma |- (~a) : Tangle[[T], [T]]        [T-Twist-Strand]

and spec/grammar.ebnf: "In weave context: (~a) twists named strand a".

The `Twist` case inferred its operand as an ordinary expression, and the `Var`
rule rejects strand names outright — so the strand-name guard fired before the
twist rule could apply. [T-Twist-Strand] is now tried FIRST, leaving
[T-Twist-Word] / [T-Twist-Tangle] to the standalone forms.

The spec presents this rule alongside [T-Self-Cross], which types `(a > a)`
identically and was already implemented. One of a matched pair was missing; a
test now pins that the two agree, so they cannot drift.

Evaluation mirrors `Crossing`: structural, yielding the empty word. Weave
bodies only became evaluable in #93, so this construct had never run. A
single-strand twist is in any case invisible to the braid WORD — it is a
framing change (Reidemeister I) that the braid group does not see. Detecting
it as "a Var the environment does not bind" is safe because typechecking runs
first and admits `Twist (Var a)` only when `a` is a strand.

conformance: 16/19 -> 17/19 evaluating (18/19 parse). The corpus ratchet
demanded the manifest update, which is what it is for. v11_add_block (#94, the
Harvard sub-language) is now the only remaining gap.

Tests: the rule itself, agreement with [T-Self-Cross], a regression guard that
standalone twist on a Word is unchanged, and that a nonsense twist still fails.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread compiler/lib/eval.ml
Comment on lines +440 to +441
| Twist (Var a) when env_lookup env a = None ->
ignore a; VBraid []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Edge Case: Twist eval guard diverges from typecheck under name shadowing

In eval.ml the [T-Twist-Strand] case is detected by Twist (Var a) when env_lookup env a = None, whereas the typechecker prioritizes the strand context (strand_lookup sigma a) over gamma. If a weave strand a shares a name with a top-level def a, typecheck classifies (~a) as a strand twist (Tangle[[T],[T]]) but eval finds a bound in env and falls through to the general Twist e1 branch, twisting the def's braid/tangle value instead of producing the structural empty word. The two phases then disagree on the meaning of the same expression. The neighboring Crossing case avoids this by ignoring env entirely; consider mirroring that (or checking the strand context) so eval and typecheck agree regardless of shadowing.

Was this helpful? React with 👍 / 👎

@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 with suggestions 0 resolved / 1 findings

Implements the [T-Twist-Strand] typechecking and evaluation rule for named strands in weave contexts, advancing the conformance suite to 17/19. Consider aligning the twist evaluation guard with typechecking to prevent divergence under name shadowing.

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.

💡 Edge Case: Twist eval guard diverges from typecheck under name shadowing

📄 compiler/lib/eval.ml:440-441 📄 compiler/lib/typecheck.ml:390-393

In eval.ml the [T-Twist-Strand] case is detected by Twist (Var a) when env_lookup env a = None, whereas the typechecker prioritizes the strand context (strand_lookup sigma a) over gamma. If a weave strand a shares a name with a top-level def a, typecheck classifies (~a) as a strand twist (Tangle[[T],[T]]) but eval finds a bound in env and falls through to the general Twist e1 branch, twisting the def's braid/tangle value instead of producing the structural empty word. The two phases then disagree on the meaning of the same expression. The neighboring Crossing case avoids this by ignoring env entirely; consider mirroring that (or checking the strand context) so eval and typecheck agree regardless of shadowing.

🤖 Prompt for agents
Code Review: Implements the [T-Twist-Strand] typechecking and evaluation rule for named strands in weave contexts, advancing the conformance suite to 17/19. Consider aligning the twist evaluation guard with typechecking to prevent divergence under name shadowing.

1. 💡 Edge Case: Twist eval guard diverges from typecheck under name shadowing
   Files: compiler/lib/eval.ml:440-441, compiler/lib/typecheck.ml:390-393

   In eval.ml the [T-Twist-Strand] case is detected by `Twist (Var a) when env_lookup env a = None`, whereas the typechecker prioritizes the strand context (`strand_lookup sigma a`) over gamma. If a weave strand `a` shares a name with a top-level `def a`, typecheck classifies `(~a)` as a strand twist (Tangle[[T],[T]]) but eval finds `a` bound in env and falls through to the general `Twist e1` branch, twisting the def's braid/tangle value instead of producing the structural empty word. The two phases then disagree on the meaning of the same expression. The neighboring `Crossing` case avoids this by ignoring env entirely; consider mirroring that (or checking the strand context) so eval and typecheck agree regardless of shadowing.

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

@hyperpolymath
hyperpolymath merged commit c0a362f into main Jul 29, 2026
25 checks passed
@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.

@hyperpolymath
hyperpolymath deleted the fix/twist-in-weave-96 branch July 29, 2026 06:54

@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)

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