(expr: Expression | undefined)
| 199 | * integer parameters a collection handler reads (a `Take` count, a |
| 200 | * `RotateLeft` offset, an `InsertAt` position). |
| 201 | * |
| 202 | * Collection handlers are consulted on the *canonical* expression, not on an |
| 203 | * evaluated one: `.at()`/`.each()`/`.count` are public on any canonical |
| 204 | * expression, and the pre-evaluation broadcast in |
| 205 | * `BoxedFunction._computeValue` zips raw operands. A parameter spelled `N-1` |
| 206 | * is therefore still an `Add` node at that point, where a bare `toInteger` |
| 207 | * answers `null` — which every call site turned into its own DEFAULT |
| 208 | * (`?? 1`, `?? 0`), silently substituting a different collection: |
| 209 | * `RotateLeft(S, N-1) + RotateLeft(S, N-2)` answered `2·RotateLeft(S, 1)` |
| 210 | * (Tycho item 107). |
| 211 | * |
| 212 | * `toInteger` runs first, so a literal operand — the hot path of a drain — |
| 213 | * costs nothing extra; only a symbolic operand pays one `evaluate()`. An |
| 214 | * operand that is already a number literal is never re-evaluated: if it is |
| 215 | * not an integer, no amount of evaluation will make it one. |
| 216 | */ |
| 217 | export function toIntegerOperand(expr: Expression | undefined): number | null { |
| 218 | const n = toInteger(expr); |
| 219 | if (n !== null) return n; |
| 220 | if (expr === undefined || isNumber(expr)) return null; |
| 221 | return toInteger(expr.evaluate()); |
| 222 | } |
| 223 | |
| 224 | /** Convert a boxed expression to a bigint. |
| 225 | * Returns null if the expression cannot be converted to a bigint. |
no test coverage detected