* Box guard sub-expressions once per rule at load time (wildcards as typed * symbols, declared by the caller's scope); `.subs()`-instantiated per match.
( ce: IComputeEngine, guards: ReadonlyArray<GuardSpec> )
| 129 | * symbols, declared by the caller's scope); `.subs()`-instantiated per match. |
| 130 | */ |
| 131 | function buildGuardClosures( |
| 132 | ce: IComputeEngine, |
| 133 | guards: ReadonlyArray<GuardSpec> |
| 134 | ): GuardClosure[] { |
| 135 | const boxGuardExpr = (x: FungrimMathJson): Expression => { |
| 136 | try { |
| 137 | const b = ce.expr(x as ExpressionInput); |
| 138 | if (b.isValid) return b; |
| 139 | } catch { |
| 140 | /* fall through to raw boxing */ |
| 141 | } |
| 142 | return ce.expr(x as ExpressionInput, { form: 'raw' }); |
| 143 | }; |
| 144 | |
| 145 | return guards.map((g): GuardClosure => { |
| 146 | switch (g.k) { |
| 147 | case 'type': { |
| 148 | if (g.t === 'complex') { |
| 149 | // Fungrim CC = FINITE complex numbers. Literal fast path via the |
| 150 | // type lattice; symbols go through the boxed Element evaluation, |
| 151 | // which mirrors `ComplexNumbers.contains` and consults the Track-3 |
| 152 | // type refinements made by `assume(Element(z, ComplexNumbers))`. |
| 153 | const pred = boxGuardExpr(['Element', g.wc, 'ComplexNumbers']); |
| 154 | return (sub) => { |
| 155 | const v = sub[g.wc]; |
| 156 | if (v === undefined) return false; |
| 157 | // Fungrim CC = FINITE complex numbers. Under D10 (2026-07-02) |
| 158 | // `real ⊂ complex`, so a symbol declared complex — or |
| 159 | // real/rational/integer (or a finite_ variant, all subtypes of |
| 160 | // `real ⊂ complex`) — satisfies a complex guard through the normal |
| 161 | // subtype path (`type.matches('complex')`). Fungrim's CC is finite, |
| 162 | // so exclude a PROVABLY-infinite value (a ±∞ literal has |
| 163 | // isFinite === false; a plain real/complex symbol's is undefined |
| 164 | // and is accepted). This replaces the pre-D10 shim that had to |
| 165 | // special-case `type.matches('real')` because `real ⊄ complex`. |
| 166 | if (v.isFinite !== false && v.type.matches('complex')) return true; |
| 167 | try { |
| 168 | const r = pred.subs(sub).evaluate().json; |
| 169 | if (r === 'True') return true; |
| 170 | if (r === 'False') return false; |
| 171 | return undefined; // symbolic residue: undecided |
| 172 | } catch { |
| 173 | return undefined; |
| 174 | } |
| 175 | }; |
| 176 | } |
| 177 | return (sub) => { |
| 178 | const v = sub[g.wc]; |
| 179 | if (v === undefined) return false; |
| 180 | // Fungrim's declared domains (ZZ/QQ/RR) are FINITE, matching the |
| 181 | // 'complex' guard's finiteness gate above (SYM P3-7). A value |
| 182 | // PROVABLY non-finite (a ±∞ or ~∞ literal has `isFinite === false`; |
| 183 | // note `(+∞).isReal === true`, so without this gate a real guard |
| 184 | // would fail-open at infinity) is blocked. Unknown finiteness |
| 185 | // (`isFinite === undefined`, e.g. a plain declared-real symbol) |
| 186 | // still passes — `!== false` — so ordinary symbols discharge as |
| 187 | // before; only a known-infinite instance is rejected. |
| 188 | if (v.isFinite === false) return false; |
no test coverage detected