Repository navigation
Which parameters are specialized is a least fixpoint, so --verdict specializes a cycle of items the same in any declaration order (#1179) - #1208
Conversation
…ecializes a cycle of items the same in any declaration order (bendlang#1179) spec_of cached [] for an item while computing it, so an item read before its answer was known (a later declaration, or itself) counted as having nothing specialized, and safe.ts rejected the kind as depending on a run-time value. It now runs a worklist from the item asked for: each item read is added the first time, and an item is computed again whenever an answer it read grows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nly a cycle is redone and one item reading many is not walked once per link The worklist computed an item before the items it read, so a type with N fields T0..T(N-1), specialization reaching up the chain one link per round, re-queued the type N times: quadratic. Now an item is finished before anything reads it unless it is in progress, so only a cycle's items (or one reading itself) are redone. Also names C and D, not A and B, as the two-way cycle in spec_cycle.bend. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Code review of f54dc5f. The fixpoint itself looks correct: answers only grow, a recursive 1. A type that recurses at a growing argument now hangs where main fails cleanly (confirmed). type S<n: Type, m: Type> is Kind(qof(n)):
S1{s: S<List<m>, n>}
2. It builds a
Since const sp = e.spec.get(k);
if (sp !== undefined) return sp;3. Every self-recursive datatype with a specialized parameter is computed twice. A constructor's return type reads the type itself with its own parameters, which can't change the answer. This is the PR table's 301→601 on the chain and 202→403 on the fan-in. Two ways to avoid the second pass for almost every datatype:
4. Each recomputation redoes work that doesn't depend on the fixpoint. It lowers the telescope and every constructor type again, calls 5. The regression test is only guarded by
Note: this review was written by an AI at my request. If anything here is wrong, reply and I will review it myself. |
…hose arguments grow on an instance of the same item above it is out of scope (bendlang#1179) spec_of walks each item's types once, for its seeds (a Quant or ~ parameter, a variable a kind reads) and its flows (a parameter's variable inside an argument at another item's parameter), then spreads the seeds along the flows: the least set the rules close, in any declaration order, with no item computed twice. A specialized parameter can now be passed a bigger argument each time it recurs (S at List<m>, n), which names instances without end. item_ref records which instance named each new one, and a new instance whose arguments hold (homeomorphically embed) those of an instance of the same item above it is out of scope: every endless chain of instances has such a pair, and a finite cycle (S at m, n) or a shrinking index has none. A bit per item on each chain skips the walk when the item is not above. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Picked this up: f77b1c3 (on top of a merge of main 1159f0b) reworks the fix and closes the hang from the review above. What was leftOnce a cycle specializes its parameters correctly, a specialized parameter can recur at a bigger argument each time, and
The reason given is The change
Each item's types are walked once to collect seeds and flows, and the seeds then spread along the flows. The answer can't depend on declaration order, and no item is computed twice. This replaces the redo worklist and covers findings 2–4 above:
Computation counts are back to main's (chain of 300 types 303, fan-in with 400 fields 404).
Tests
Checks
#1180 (binder levels in 🤖 Generated with Claude Code |
… under ∀; --verdict emits generic defs once (#1203) The BendTT kernel expresses bend2's kinds as terms and fits function types by their parts, so --verdict no longer monomorphizes on quantities and trusts bend2 for less. Kernel (bendtt.lean): - Typ carries a quantity term: *(q) for q : <Q0, Q1, Q2>, with *1 and *2 as *(.Q1) and *(.Q2). A type of kind *(q) is Data exactly where q reduces to .Q2. - (a <&> b), the meet of two quantities, reduces as bend2's does (.Q2 its identity, .Q0 absorbing, even next to a stuck side). It is live code: the live check reads both sides, and evaluation steps it to a label. - Σ at any kind *(g), each field at kindof(q, *(g)). - Fits is inductive: conversion; kinds, where *(g) fits *(h) when g is .Q2 wherever h is; enums; ∀s by their parts, the domain reversed and the codomain along, as bend2's LE; and transitivity. Term.fits decides kinds with bend2's rules (qge) and ∀s by their parts. fits_at reads a fit at a former, for subject reduction and progress. - Every claim (consistent, book_check, confluent, sr, progress, halts, empty) checks with no sorry under Lean v4.34.0 and depends only on [propext, Classical.choice, Quot.sound]. 63,985 ttok (cap 64,000). Elaborator (safe.ts): - Kinds and meets go out as bend2 writes them, with no folding, so the kernel computes every quantity itself. - Only template (~) parameters are specialized; a Quant parameter, and any parameter a kind reads, is a kernel variable, and each def goes out once. - The out-of-scope guards for Quant are gone. - uses() counts a meet's sides. - qsig_eq compares binder quantities only, since the kernel fits domains, codomains and kinds itself. Paper: bend2/docs/BendTT/main.typ and paper/BendTT.pdf describe the above. Supersedes #1201 and #1208. Closes #1175 Closes #1179 Closes #1180 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #1179.
Cause
spec_ofinbend2/safe.tsdecides which parameters of an item are specialized. On main it cached[]for an item while that item was still being computed, so any read before the answer was known counted as "nothing specialized". That happens with a later declaration (A before B), with a self-recursive type, and with a two-way cycle. A parameter that only a cycle makes specialized stayed live, and--verdictrejected its kind as "a kind that depends on a run-time value". Reordering can't fix the self-recursive or two-way cases.Once cycles specialize correctly, a second problem shows up. A specialized parameter can recur at a bigger argument each time, and
item_refthen names instances without end:S<n, m>with a fieldS<List<m>, n>T<n>with a fieldT<List<n>>U<n: Nat>with a fieldU<1n+n>A<n>→B<List<n>>→A<m>def f(c: Nat, n: Type, -A: Kind(qof(n)))callingf(p, List<n>, Unit)Fix
Which parameters are specialized is a reachability. The rules are:
~parameter, or a variable that a kind reads.r's parameterjis specialized when its variable sits inside an argument at a specialized parameteriofh.The least set these rules close is a graph reachability.
spec_ofwalks each item's types once to collect its seeds and flows, then spreads the seeds along the flows. The answer can't depend on declaration order, and no item is computed twice: a book makes as many item computations as on main (a chain of 300 types: 303).An instance that grows on one above it is out of scope.
item_refrecords which instance named each new one. A new instance is out of scope (a specialized argument that grows at each instance of S) when its arguments hold the arguments of an instance of the same item above it on that chain.S~nis held inList<S~n>. A number holds a no bigger one, and a string literal holds one it contains in order.S<m, n>has none, and neither does a shrinking index:V<3n>with a fieldV<Nat.sub(n, 1n)>still goes out, as its 4 instances.defs_12800peaks at 485 MB on both).Known limit. The check is conservative and fails closed. A closed counter that cycles back down, such as
M<Nat.mod(1n+n, 3n)>used atM<0n>, is reported out of scope even though it has only 3 instances. Main doesn't finish on that file at all: M's root instance at its opaque constant grows without end.Size:
safe.tsgoes from 21,624 to 22,699 ttok (the cap is 24,000).Checks
All run on this branch merged with main 08a6083.
Tests
tests/check/spec_cycle.bendcovers A-before-B, a self-recursive type and a two-way cycle, each also used at closed arguments. Main givesSOME PROOFS FAIL; this PR givesALL PROOFS CHECK.tests/check/spec_grow.bendcovers every growth case in the table, plus three that must stay in scope:V<3n>, a list of 24 equal heads, and a"ab"/"ba"flip. Main overflows the stack on it, the previous head of this PR times out after 60 s, and this PR finishes in 0.2 s.Corpus
.bendfile in tests, demos, bench and guide (1,629) was emitted with-oon main and on this branch. Exit codes match, the 1,123 files that compile give byte-identical.bendtt, and the rest fail with the same messages.Gates
gates/test.ts:PASS: 1548 / 1548.gates/safe.ts tests: the same row for every existing file as main 08a6083 (992 agree, 1 false rejectagree_32). The new rows arespec_cycle.bendagree andspec_grow.bendout of scope.gates/repo.ts: passes.gates/safe.tsruns these tests under--verdict, and it reports classes without failing, so a regression shows up in its table rather than as a failed gate.Timing
-oruns are within noise of main ondefs_12800,demos/app_pong_game_2d/PROOF.bendand a chain of 300 types.defs_12800, 20 ms on the chain and 40 ms on a type with 400 fields.#1180 (binder levels in
spec_of) is separate and not addressed here.🤖 Generated with Claude Code