feat(borrow): CFG-join for ExprHandle + ExprTry catch arms (CORE-01 pt3 Slice C-light, Refs #177)#358
Merged
Merged
Conversation
🔍 Hypatia Security ScanFindings: 112 issues detected
View findings[
{
"reason": "Stray AI.a2ml in root -- use 0-AI-MANIFEST.a2ml only",
"type": "banned",
"file": "AI.a2ml",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "high"
},
{
"reason": "Superseded by 0-AI-MANIFEST.a2ml",
"type": "banned",
"file": "AI.djot",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "high"
},
{
"reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Action actions/checkout@v6 needs attention",
"type": "unpinned_action",
"file": "publish-jsr.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action denoland/setup-deno@v2 needs attention",
"type": "unpinned_action",
"file": "publish-jsr.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/compile.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/runner.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
31397e1 to
9ff097f
Compare
🔍 Hypatia Security ScanFindings: 108 issues detected
View findings[
{
"reason": "Stray AI.a2ml in root -- use 0-AI-MANIFEST.a2ml only",
"type": "banned",
"file": "AI.a2ml",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "high"
},
{
"reason": "Superseded by 0-AI-MANIFEST.a2ml",
"type": "banned",
"file": "AI.djot",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "high"
},
{
"reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Action actions/checkout@v6 needs attention",
"type": "unpinned_action",
"file": "publish-jsr.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action denoland/setup-deno@v2 needs attention",
"type": "unpinned_action",
"file": "publish-jsr.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/compile.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/runner.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
9ff097f to
56a251b
Compare
🔍 Hypatia Security ScanFindings: 108 issues detected
View findings[
{
"reason": "Stray AI.a2ml in root -- use 0-AI-MANIFEST.a2ml only",
"type": "banned",
"file": "AI.a2ml",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "high"
},
{
"reason": "Superseded by 0-AI-MANIFEST.a2ml",
"type": "banned",
"file": "AI.djot",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "high"
},
{
"reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Action actions/checkout@v6 needs attention",
"type": "unpinned_action",
"file": "publish-jsr.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action denoland/setup-deno@v2 needs attention",
"type": "unpinned_action",
"file": "publish-jsr.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/compile.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/runner.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
…t3 Slice C-light, Refs #177) Until now, `ExprHandle` handler arms and `ExprTry` catch arms were checked *sequentially* against a shared state — so moves and borrows from arm i polluted the state seen by arm i+1, even though only one arm runs at runtime. This produced spurious UseAfterMove on the second of two catch arms that independently move the same value, etc. Slice C-light closes this for handlers and try/catch by mirroring the snapshot-restore-merge pattern that `ExprMatch` already does inline. The merge logic — intersection-on-borrows, union-on- moves, first-error propagation, dedupe against the base — is now factored into a single helper, `merge_arm_results`, so all CFG- join sites agree on "what state survives the join": - Errors: propagate the first error seen, left-to-right. - Borrows: intersection across arms (a borrow survives the join only if it survived every arm; branch-local borrows naturally die at arm exit via `check_block`'s borrows_at_entry restore). - Moves: union across arms, deduplicated against the base — a place is reported as moved post-join if any arm moved it (conservative-sound). `ExprMatch`'s inlined logic is *deliberately left untouched* — same semantics as the new helper, but its tests have been stable since PR #240; refactoring it would add risk without ergonomic upside on this PR. A future cleanup can converge them. ExprHandle (`handle body { return(_) => ..., op(p1, p2) => ... }`): mutually exclusive continuations dispatched on whether the body returned normally or performed an effect-op. Body runs against the current state, then each arm runs against the post-body state independently via the helper. ExprTry (`try { body } catch { p => arm; ... } finally { ... }`): body runs first via `check_block` (so its block-local borrows are cleaned at block exit; its moves persist). The snapshot is taken *after* check_block — so the catch arms run against a state that already includes the body's moves (conservatively assuming body might have moved before throwing). Catch arms merge via the helper; `finally` then runs deterministically against the merged post-catch state. Tests (E2E Borrow Graph, +2): - `slice_c_catch_arm_isolation.affine` (positive): three catch arms, two of which independently `drop_int(y)`. Pre-Slice-C this was rejected — arm 2's `drop_int(y)` saw arm 1's move and fired UseAfterMove. Post-Slice-C every arm starts from the post-body state and succeeds independently. - `slice_c_body_move_persists.affine` (anti-regression): the try-body moves `y`; the catch arm matches `_` and ignores `y`; after the `try`, a `read_int(y)` must still fail UseAfterMove because the body might have moved before throwing. This pins that the helper's dedupe-against-base correctly preserves body-side moves through the merge. The current `state.moved` propagation does this for free (the snapshot *captures* body's moves into base_moved, and the merge sets `state.moved <- base_moved @ unique_new`), but the test pins the invariant so a future refactor cannot drop it. ExprHandle is implemented in parallel but has no dedicated test fixture: the AffineScript test corpus has no live `handle` expressions outside the parser's own grammar tests, so an end-to-end borrow-graph fixture for it would be the first. The code path is exercised whenever a real `handle` appears; until then, soundness is by symmetry-with-ExprTry. Anti-regression sweep: all existing borrow / linear-arrow / quantity fixtures audited. The `try_catch_*` and `try_finally` fixtures already in the gate (e/Try semantics smoke tests) are single-arm and don't touch moves, so they go through the new path unchanged. Move-or-borrow scenarios crossing `ExprHandle`/ `ExprTry` are net-new fixtures here. Docs updated: - `STATE.a2ml` borrow-checker → "phase-3-parts-1-3-Slices-A-B-C-light-landed" - `CAPABILITY-MATRIX.adoc` borrow-checker row records Slice C-light - `TECH-DEBT-alt.adoc` CORE-01 row narrows residual to C-full (origin variables, ADR-gated), C' (loop soundness), D (quantity integration), plus ref-to-ref binding. NOTE: container has no OCaml toolchain; `dune build` / `dune runtest` not run locally. CI is the source of truth. Mechanically scoped to two arms of `check_expr` plus one new helper.
56a251b to
770be85
Compare
🔍 Hypatia Security ScanFindings: 103 issues detected
View findings[
{
"reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Action actions/checkout@v6 needs attention",
"type": "unpinned_action",
"file": "publish-jsr.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action denoland/setup-deno@v2 needs attention",
"type": "unpinned_action",
"file": "publish-jsr.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/compile.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/runner.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/discover.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/packages/affine-js/types.d.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CORE-01 Phase 3 Slice C-light — CFG-join semantics for
ExprHandlehandler arms andExprTrycatch arms. Both were previously checked sequentially against a shared state, so moves/borrows from arm i polluted the state seen by arm i+1 — even though only one arm runs at runtime. This PR mirrors the snapshot-restore-merge pattern thatExprMatchalready does inline, and factors the merge logic into amerge_arm_resultshelper that all CFG-join sites can share.Before (sequential)
After (isolated)
Each arm runs against the post-body state independently; the merge takes intersection of borrows and union of moves.
Mechanics
New helper near
check_exprinlib/borrow.ml:Wired into
ExprHandleandExprTry.ExprMatch's inlined logic is deliberately untouched — same semantics, but PR-scope discipline says don't refactor a stable code path here.For
ExprTry:check_block(body)runs first (its block-local borrows are cleaned at block exit; moves persist). The snapshot is taken after check_block, so catch arms see the body's moves inbase_moved. The merge's dedupe-against-base preserves them through the join.finallyruns deterministically after the merge.Tests (E2E Borrow Graph, +2)
slice_c_catch_arm_isolation.affiney, currently rejected with spurious UseAfterMoveslice_c_body_move_persists.affineread_int(y)after the body movedy, even with a_ =>catch armScope of this PR
ExprHandleis implemented in parallel but has no dedicated test fixture — the AffineScript test corpus has no livehandleexpressions outside the parser's grammar tests, so an end-to-end borrow-graph fixture for it would be the first. The code path is exercised whenever a realhandleappears; until then, soundness is by symmetry withExprTry.Deferred (Slices C-full / C' / D):
TyRef/TyMutwith subset constraints + datalog-style loan-live-at-point solver. Architectural change to the type system; ADR-gated.StmtWhile/StmtFor. Coupled to aStmtAssignclear-on-rewrite fix (assignment is currently treated as a read of LHS, so the naive 2-iter spuriously fails on legitimate re-assignment loops).r = some_other_ref_var(RHS not a direct&place) still leaves the ref-binding stale. Symmetric let/assign limitation.Docs
STATE.a2mlborrow-checker→phase-3-parts-1-3-Slices-A-B-C-light-landedCAPABILITY-MATRIX.adocborrow-checker row records Slice C-lightTECH-DEBT-alt.adocCORE-01 row narrows residual to C-full / C' / D + ref-to-ref bindingTest plan
build(opam exec -- dune build) greenRun tests(opam exec -- dune runtest) —E2E Borrow Graphcount +2try_catch_*/try_finallyfixtures (single-arm Int expressions, no move/borrow) untouchedLocal-build caveat: container has no OCaml toolchain; CI is the source of truth.
Refs #177
Generated by Claude Code