@invinite-org/chartlang-compiler 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +254 -0
  2. package/dist/analysis/extractAlertConditions.js.map +1 -1
  3. package/dist/analysis/extractCapabilities.js.map +1 -1
  4. package/dist/analysis/extractDependencyGraph.js.map +1 -1
  5. package/dist/analysis/extractInputs.js.map +1 -1
  6. package/dist/analysis/extractMaxLookback.d.ts +2 -1
  7. package/dist/analysis/extractMaxLookback.d.ts.map +1 -1
  8. package/dist/analysis/extractMaxLookback.js +90 -6
  9. package/dist/analysis/extractMaxLookback.js.map +1 -1
  10. package/dist/analysis/extractRequestedIntervals.d.ts +43 -1
  11. package/dist/analysis/extractRequestedIntervals.d.ts.map +1 -1
  12. package/dist/analysis/extractRequestedIntervals.js +95 -10
  13. package/dist/analysis/extractRequestedIntervals.js.map +1 -1
  14. package/dist/analysis/extractRequiresIntervals.js.map +1 -1
  15. package/dist/analysis/forbiddenConstructs.d.ts.map +1 -1
  16. package/dist/analysis/forbiddenConstructs.js +2 -41
  17. package/dist/analysis/forbiddenConstructs.js.map +1 -1
  18. package/dist/analysis/index.d.ts +3 -1
  19. package/dist/analysis/index.d.ts.map +1 -1
  20. package/dist/analysis/index.js +2 -1
  21. package/dist/analysis/index.js.map +1 -1
  22. package/dist/analysis/loopBounds.d.ts +91 -0
  23. package/dist/analysis/loopBounds.d.ts.map +1 -0
  24. package/dist/analysis/loopBounds.js +132 -0
  25. package/dist/analysis/loopBounds.js.map +1 -0
  26. package/dist/analysis/resolveIndexBound.d.ts +73 -0
  27. package/dist/analysis/resolveIndexBound.d.ts.map +1 -0
  28. package/dist/analysis/resolveIndexBound.js +336 -0
  29. package/dist/analysis/resolveIndexBound.js.map +1 -0
  30. package/dist/analysis/statefulCallInLoop.js.map +1 -1
  31. package/dist/analysis/structuralChecks.js.map +1 -1
  32. package/dist/analysis/validateLowerTfIntervals.js.map +1 -1
  33. package/dist/analysis/validateSecurityExpr.d.ts +25 -0
  34. package/dist/analysis/validateSecurityExpr.d.ts.map +1 -0
  35. package/dist/analysis/validateSecurityExpr.js +154 -0
  36. package/dist/analysis/validateSecurityExpr.js.map +1 -0
  37. package/dist/api.d.ts.map +1 -1
  38. package/dist/api.js +13 -3
  39. package/dist/api.js.map +1 -1
  40. package/dist/bundle.js.map +1 -1
  41. package/dist/dependency/index.js.map +1 -1
  42. package/dist/dependency/resolveProducer.js.map +1 -1
  43. package/dist/diagnostics.d.ts +4 -2
  44. package/dist/diagnostics.d.ts.map +1 -1
  45. package/dist/diagnostics.js.map +1 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/manifest.d.ts +2 -1
  48. package/dist/manifest.d.ts.map +1 -1
  49. package/dist/manifest.js +7 -0
  50. package/dist/manifest.js.map +1 -1
  51. package/dist/program.d.ts.map +1 -1
  52. package/dist/program.js +91 -14
  53. package/dist/program.js.map +1 -1
  54. package/dist/transformers/callsiteIdInjection.d.ts +21 -0
  55. package/dist/transformers/callsiteIdInjection.d.ts.map +1 -1
  56. package/dist/transformers/callsiteIdInjection.js +26 -3
  57. package/dist/transformers/callsiteIdInjection.js.map +1 -1
  58. package/dist/transformers/index.js.map +1 -1
  59. package/dist/transformers/plotKindFromCallsite.js.map +1 -1
  60. package/dist/transformers/resolveCallee.d.ts +21 -0
  61. package/dist/transformers/resolveCallee.d.ts.map +1 -1
  62. package/dist/transformers/resolveCallee.js +14 -1
  63. package/dist/transformers/resolveCallee.js.map +1 -1
  64. package/dist/transformers/rewriteDependencyAccessors.js.map +1 -1
  65. package/dist/typesEmit.js.map +1 -1
  66. package/package.json +2 -2
@@ -0,0 +1,91 @@
1
+ import ts from "typescript";
2
+ /**
3
+ * The comparison operators a legal chartlang `for` condition may use.
4
+ * Shared by `parseBoundedForLoop` (which captures the operator so a sizer
5
+ * can derive the loop's max index) and `forbiddenConstructs` (which rejects
6
+ * any other condition shape) so the two passes recognise the same set.
7
+ *
8
+ * @since 0.1
9
+ * @stable
10
+ * @example
11
+ * COMPARISON_OPS.has(ts.SyntaxKind.LessThanToken); // → true
12
+ */
13
+ export declare const COMPARISON_OPS: ReadonlySet<ts.SyntaxKind>;
14
+ /**
15
+ * The parsed shape of a legal chartlang `for` loop.
16
+ *
17
+ * @since 0.1
18
+ * @stable
19
+ * @example
20
+ * const loop: BoundedForLoop = {
21
+ * varName: "i",
22
+ * start: 0,
23
+ * op: ts.SyntaxKind.LessThanToken,
24
+ * limit: 5,
25
+ * };
26
+ * void loop;
27
+ */
28
+ export type BoundedForLoop = Readonly<{
29
+ /** The induction variable name (the `i` in `for (let i = …)`). */
30
+ varName: string;
31
+ /** The literal initial value (`for (let i = <start>; …)`). */
32
+ start: number;
33
+ /** The comparison operator token used in the condition. */
34
+ op: ts.SyntaxKind;
35
+ /** The literal right-hand bound (`… i <op> <limit>; …`). */
36
+ limit: number;
37
+ }>;
38
+ /**
39
+ * Parse a `ts.ForStatement` into its `BoundedForLoop` shape, or `null`
40
+ * when it is not the one legal chartlang loop form
41
+ * (`for (let i = <numLit>; i <comparison> <numLit>; i++)` — single
42
+ * `let` init, id-on-left/literal-on-right condition, postfix `i++`).
43
+ * The single source of truth for "what is a bounded loop"; both
44
+ * `forbiddenConstructs` (reject everything else) and
45
+ * `resolveIndexUpperBound` (size the index range) call it so the two
46
+ * passes can never disagree.
47
+ *
48
+ * @since 0.1
49
+ * @stable
50
+ * @example
51
+ * // for (let i = 0; i < 5; i++) → { varName: "i", start: 0,
52
+ * // op: LessThanToken, limit: 5 }
53
+ * const fn: typeof parseBoundedForLoop = parseBoundedForLoop;
54
+ * void fn;
55
+ */
56
+ export declare function parseBoundedForLoop(node: ts.ForStatement): BoundedForLoop | null;
57
+ /**
58
+ * The induction variable's **declaration** identifier of the single legal
59
+ * chartlang loop *initializer* shape, or `null` otherwise. A sizer calls
60
+ * this directly when it needs the declaration node (not just the `varName`
61
+ * text) to ask the type checker whether an index use resolves to this
62
+ * loop's own binding rather than a nested shadow of the same name. Shares
63
+ * `parseBoundedForLoop`'s initializer acceptance via `parseLoopInit`.
64
+ *
65
+ * @since 0.1
66
+ * @stable
67
+ * @example
68
+ * // for (let i = 0; i < 5; i++) → the `i` declaration identifier
69
+ * const fn: typeof boundedLoopVarId = boundedLoopVarId;
70
+ * void fn;
71
+ */
72
+ export declare function boundedLoopVarId(node: ts.ForStatement): ts.Identifier | null;
73
+ /**
74
+ * Unwrap any number of nested parentheses around an expression. The Pine
75
+ * converter emits a historical bar offset as the parenthesised form
76
+ * `bar.point(-(N), …)` (see the converter's `anchorToWorldPoint`), so the
77
+ * lookback recogniser must peel the parens before matching the literal;
78
+ * the index-bound resolver does the same before matching a numeric leaf.
79
+ * Housed here — a leaf module with no analysis-package imports — so both
80
+ * `extractMaxLookback` and `resolveIndexBound` can share it without a
81
+ * circular import.
82
+ *
83
+ * @since 0.1
84
+ * @stable
85
+ * @example
86
+ * // unwrapParens of `((7))` → the `7` numeric-literal node
87
+ * const fn: typeof unwrapParens = unwrapParens;
88
+ * void fn;
89
+ */
90
+ export declare function unwrapParens(node: ts.Expression): ts.Expression;
91
+ //# sourceMappingURL=loopBounds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopBounds.d.ts","sourceRoot":"","sources":["../../src/analysis/loopBounds.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,EAAE,CAAC,UAAU,CAKpD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IAClC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC;IAClB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,GAAG,cAAc,GAAG,IAAI,CAoBhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,GAAG,EAAE,CAAC,UAAU,GAAG,IAAI,CAE5E;AAsBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAI/D"}
@@ -0,0 +1,132 @@
1
+ // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
+ // See the LICENSE file in the repo root for full license text.
3
+ import ts from "typescript";
4
+ /**
5
+ * The comparison operators a legal chartlang `for` condition may use.
6
+ * Shared by `parseBoundedForLoop` (which captures the operator so a sizer
7
+ * can derive the loop's max index) and `forbiddenConstructs` (which rejects
8
+ * any other condition shape) so the two passes recognise the same set.
9
+ *
10
+ * @since 0.1
11
+ * @stable
12
+ * @example
13
+ * COMPARISON_OPS.has(ts.SyntaxKind.LessThanToken); // → true
14
+ */
15
+ export const COMPARISON_OPS = new Set([
16
+ ts.SyntaxKind.LessThanToken,
17
+ ts.SyntaxKind.LessThanEqualsToken,
18
+ ts.SyntaxKind.GreaterThanToken,
19
+ ts.SyntaxKind.GreaterThanEqualsToken,
20
+ ]);
21
+ /**
22
+ * Parse a `ts.ForStatement` into its `BoundedForLoop` shape, or `null`
23
+ * when it is not the one legal chartlang loop form
24
+ * (`for (let i = <numLit>; i <comparison> <numLit>; i++)` — single
25
+ * `let` init, id-on-left/literal-on-right condition, postfix `i++`).
26
+ * The single source of truth for "what is a bounded loop"; both
27
+ * `forbiddenConstructs` (reject everything else) and
28
+ * `resolveIndexUpperBound` (size the index range) call it so the two
29
+ * passes can never disagree.
30
+ *
31
+ * @since 0.1
32
+ * @stable
33
+ * @example
34
+ * // for (let i = 0; i < 5; i++) → { varName: "i", start: 0,
35
+ * // op: LessThanToken, limit: 5 }
36
+ * const fn: typeof parseBoundedForLoop = parseBoundedForLoop;
37
+ * void fn;
38
+ */
39
+ export function parseBoundedForLoop(node) {
40
+ const init = parseLoopInit(node);
41
+ if (init === null)
42
+ return null;
43
+ const condition = node.condition;
44
+ const incrementor = node.incrementor;
45
+ if (!condition || !incrementor)
46
+ return null;
47
+ if (!ts.isBinaryExpression(condition))
48
+ return null;
49
+ if (!COMPARISON_OPS.has(condition.operatorToken.kind))
50
+ return null;
51
+ if (!ts.isNumericLiteral(condition.right))
52
+ return null;
53
+ if (!ts.isIdentifier(condition.left))
54
+ return null;
55
+ if (condition.left.text !== init.varId.text)
56
+ return null;
57
+ if (!ts.isPostfixUnaryExpression(incrementor))
58
+ return null;
59
+ if (!ts.isIdentifier(incrementor.operand))
60
+ return null;
61
+ if (incrementor.operand.text !== init.varId.text)
62
+ return null;
63
+ return {
64
+ varName: init.varId.text,
65
+ start: Number(init.start.text),
66
+ op: condition.operatorToken.kind,
67
+ limit: Number(condition.right.text),
68
+ };
69
+ }
70
+ /**
71
+ * The induction variable's **declaration** identifier of the single legal
72
+ * chartlang loop *initializer* shape, or `null` otherwise. A sizer calls
73
+ * this directly when it needs the declaration node (not just the `varName`
74
+ * text) to ask the type checker whether an index use resolves to this
75
+ * loop's own binding rather than a nested shadow of the same name. Shares
76
+ * `parseBoundedForLoop`'s initializer acceptance via `parseLoopInit`.
77
+ *
78
+ * @since 0.1
79
+ * @stable
80
+ * @example
81
+ * // for (let i = 0; i < 5; i++) → the `i` declaration identifier
82
+ * const fn: typeof boundedLoopVarId = boundedLoopVarId;
83
+ * void fn;
84
+ */
85
+ export function boundedLoopVarId(node) {
86
+ return parseLoopInit(node)?.varId ?? null;
87
+ }
88
+ /**
89
+ * The accepted `for (let i = <numLit>; …)` initializer — a single-
90
+ * declaration `let`/`const` list whose name is an identifier with a
91
+ * numeric-literal start value — captured as both nodes, or `null`. The one
92
+ * place the initializer shape is recognised; `parseBoundedForLoop` and
93
+ * `boundedLoopVarId` both build on it (no narrowing casts in either).
94
+ */
95
+ function parseLoopInit(node) {
96
+ const init = node.initializer;
97
+ if (!init || !ts.isVariableDeclarationList(init))
98
+ return null;
99
+ if (init.declarations.length !== 1)
100
+ return null;
101
+ const declaration = init.declarations[0];
102
+ if (!declaration || !ts.isIdentifier(declaration.name))
103
+ return null;
104
+ const start = declaration.initializer;
105
+ if (!start || !ts.isNumericLiteral(start))
106
+ return null;
107
+ return { varId: declaration.name, start };
108
+ }
109
+ /**
110
+ * Unwrap any number of nested parentheses around an expression. The Pine
111
+ * converter emits a historical bar offset as the parenthesised form
112
+ * `bar.point(-(N), …)` (see the converter's `anchorToWorldPoint`), so the
113
+ * lookback recogniser must peel the parens before matching the literal;
114
+ * the index-bound resolver does the same before matching a numeric leaf.
115
+ * Housed here — a leaf module with no analysis-package imports — so both
116
+ * `extractMaxLookback` and `resolveIndexBound` can share it without a
117
+ * circular import.
118
+ *
119
+ * @since 0.1
120
+ * @stable
121
+ * @example
122
+ * // unwrapParens of `((7))` → the `7` numeric-literal node
123
+ * const fn: typeof unwrapParens = unwrapParens;
124
+ * void fn;
125
+ */
126
+ export function unwrapParens(node) {
127
+ let current = node;
128
+ while (ts.isParenthesizedExpression(current))
129
+ current = current.expression;
130
+ return current;
131
+ }
132
+ //# sourceMappingURL=loopBounds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopBounds.js","sourceRoot":"","sources":["../../src/analysis/loopBounds.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport ts from \"typescript\";\n\n/**\n * The comparison operators a legal chartlang `for` condition may use.\n * Shared by `parseBoundedForLoop` (which captures the operator so a sizer\n * can derive the loop's max index) and `forbiddenConstructs` (which rejects\n * any other condition shape) so the two passes recognise the same set.\n *\n * @since 0.1\n * @stable\n * @example\n * COMPARISON_OPS.has(ts.SyntaxKind.LessThanToken); // → true\n */\nexport const COMPARISON_OPS: ReadonlySet<ts.SyntaxKind> = new Set<ts.SyntaxKind>([\n ts.SyntaxKind.LessThanToken,\n ts.SyntaxKind.LessThanEqualsToken,\n ts.SyntaxKind.GreaterThanToken,\n ts.SyntaxKind.GreaterThanEqualsToken,\n]);\n\n/**\n * The parsed shape of a legal chartlang `for` loop.\n *\n * @since 0.1\n * @stable\n * @example\n * const loop: BoundedForLoop = {\n * varName: \"i\",\n * start: 0,\n * op: ts.SyntaxKind.LessThanToken,\n * limit: 5,\n * };\n * void loop;\n */\nexport type BoundedForLoop = Readonly<{\n /** The induction variable name (the `i` in `for (let i = …)`). */\n varName: string;\n /** The literal initial value (`for (let i = <start>; …)`). */\n start: number;\n /** The comparison operator token used in the condition. */\n op: ts.SyntaxKind;\n /** The literal right-hand bound (`… i <op> <limit>; …`). */\n limit: number;\n}>;\n\n/**\n * Parse a `ts.ForStatement` into its `BoundedForLoop` shape, or `null`\n * when it is not the one legal chartlang loop form\n * (`for (let i = <numLit>; i <comparison> <numLit>; i++)` — single\n * `let` init, id-on-left/literal-on-right condition, postfix `i++`).\n * The single source of truth for \"what is a bounded loop\"; both\n * `forbiddenConstructs` (reject everything else) and\n * `resolveIndexUpperBound` (size the index range) call it so the two\n * passes can never disagree.\n *\n * @since 0.1\n * @stable\n * @example\n * // for (let i = 0; i < 5; i++) → { varName: \"i\", start: 0,\n * // op: LessThanToken, limit: 5 }\n * const fn: typeof parseBoundedForLoop = parseBoundedForLoop;\n * void fn;\n */\nexport function parseBoundedForLoop(node: ts.ForStatement): BoundedForLoop | null {\n const init = parseLoopInit(node);\n if (init === null) return null;\n const condition = node.condition;\n const incrementor = node.incrementor;\n if (!condition || !incrementor) return null;\n if (!ts.isBinaryExpression(condition)) return null;\n if (!COMPARISON_OPS.has(condition.operatorToken.kind)) return null;\n if (!ts.isNumericLiteral(condition.right)) return null;\n if (!ts.isIdentifier(condition.left)) return null;\n if (condition.left.text !== init.varId.text) return null;\n if (!ts.isPostfixUnaryExpression(incrementor)) return null;\n if (!ts.isIdentifier(incrementor.operand)) return null;\n if (incrementor.operand.text !== init.varId.text) return null;\n return {\n varName: init.varId.text,\n start: Number(init.start.text),\n op: condition.operatorToken.kind,\n limit: Number(condition.right.text),\n };\n}\n\n/**\n * The induction variable's **declaration** identifier of the single legal\n * chartlang loop *initializer* shape, or `null` otherwise. A sizer calls\n * this directly when it needs the declaration node (not just the `varName`\n * text) to ask the type checker whether an index use resolves to this\n * loop's own binding rather than a nested shadow of the same name. Shares\n * `parseBoundedForLoop`'s initializer acceptance via `parseLoopInit`.\n *\n * @since 0.1\n * @stable\n * @example\n * // for (let i = 0; i < 5; i++) → the `i` declaration identifier\n * const fn: typeof boundedLoopVarId = boundedLoopVarId;\n * void fn;\n */\nexport function boundedLoopVarId(node: ts.ForStatement): ts.Identifier | null {\n return parseLoopInit(node)?.varId ?? null;\n}\n\n/**\n * The accepted `for (let i = <numLit>; …)` initializer — a single-\n * declaration `let`/`const` list whose name is an identifier with a\n * numeric-literal start value — captured as both nodes, or `null`. The one\n * place the initializer shape is recognised; `parseBoundedForLoop` and\n * `boundedLoopVarId` both build on it (no narrowing casts in either).\n */\nfunction parseLoopInit(\n node: ts.ForStatement,\n): Readonly<{ varId: ts.Identifier; start: ts.NumericLiteral }> | null {\n const init = node.initializer;\n if (!init || !ts.isVariableDeclarationList(init)) return null;\n if (init.declarations.length !== 1) return null;\n const declaration = init.declarations[0];\n if (!declaration || !ts.isIdentifier(declaration.name)) return null;\n const start = declaration.initializer;\n if (!start || !ts.isNumericLiteral(start)) return null;\n return { varId: declaration.name, start };\n}\n\n/**\n * Unwrap any number of nested parentheses around an expression. The Pine\n * converter emits a historical bar offset as the parenthesised form\n * `bar.point(-(N), …)` (see the converter's `anchorToWorldPoint`), so the\n * lookback recogniser must peel the parens before matching the literal;\n * the index-bound resolver does the same before matching a numeric leaf.\n * Housed here — a leaf module with no analysis-package imports — so both\n * `extractMaxLookback` and `resolveIndexBound` can share it without a\n * circular import.\n *\n * @since 0.1\n * @stable\n * @example\n * // unwrapParens of `((7))` → the `7` numeric-literal node\n * const fn: typeof unwrapParens = unwrapParens;\n * void fn;\n */\nexport function unwrapParens(node: ts.Expression): ts.Expression {\n let current = node;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n"]}
@@ -0,0 +1,73 @@
1
+ import ts from "typescript";
2
+ /**
3
+ * Compile-time context for resolving a series index's upper bound.
4
+ *
5
+ * @since 0.1
6
+ * @stable
7
+ * @example
8
+ * const ctx: IndexBoundContext = {
9
+ * constEnv: new Map([["k", 3]]),
10
+ * checker, // ts.TypeChecker
11
+ * };
12
+ * void ctx;
13
+ */
14
+ export type IndexBoundContext = Readonly<{
15
+ /** `const <id> = <numeric literal>` bindings visible at the index use site. */
16
+ constEnv: ReadonlyMap<string, number>;
17
+ /** Checker used to avoid resolving loop variables through a shadowed name. */
18
+ checker: ts.TypeChecker;
19
+ }>;
20
+ /**
21
+ * The provable maximum non-negative integer a series-index expression
22
+ * can reach at runtime, or `null` when no sound upper bound exists.
23
+ * Over-approximates: a result is always `>=` the true max index, so the
24
+ * runtime buffer (sized `maxLookback + 1`) never under-sizes. `null`
25
+ * signals the caller to fall back to the 5000-slot dynamic buffer.
26
+ *
27
+ * Resolves any expression built from numeric literals, `const`
28
+ * numeric-literal bindings (`ctx.constEnv`), bounded-loop induction
29
+ * variables (resolved to their full range), the binary operators `+`,
30
+ * `−`, `*`, unary `±`, and parentheses, by computing its integer
31
+ * interval and returning the **upper** endpoint. Any other node (another
32
+ * identifier, a call, `/`, `%`, `**`, a bitwise op, a non-numeric
33
+ * literal) collapses the containing interval — and thus the whole
34
+ * index — to `null`.
35
+ *
36
+ * @since 0.1
37
+ * @stable
38
+ * @example
39
+ * // for (let i = 0; i < 5; i++) { series[i + 1]; }
40
+ * // resolveIndexUpperBound(<the `i + 1` arg>, <access node>, ctx) → 5
41
+ * const fn: typeof resolveIndexUpperBound = resolveIndexUpperBound;
42
+ * void fn;
43
+ */
44
+ export declare function resolveIndexUpperBound(argument: ts.Expression, node: ts.Node, ctx: IndexBoundContext): number | null;
45
+ /**
46
+ * The `const <id> = <numeric literal>` bindings lexically visible at a
47
+ * specific series-index expression. Only `const` initialised with a
48
+ * numeric literal — or a unary `+`/`-` on one — is included (mirroring
49
+ * `extractInputs.readLiteral`'s numeric handling); a binary initialiser
50
+ * is left for Task 2's interval evaluator and is simply omitted. The walk
51
+ * runs from `useSite` outward through its lexical containers up to
52
+ * `scopeRoot`, collecting only declarations that occur before
53
+ * `useSite.pos` within each container, so it never sees a declaration
54
+ * after the read, inside a sibling block, or in a nested function/class
55
+ * that does not contain `useSite`. The innermost visible binding for a
56
+ * name wins (normal shadowing) — including binders that are not
57
+ * `var`/`let`/`const` statements: a `for`-init induction variable and a
58
+ * function parameter shadow an outer numeric `const` of the same name
59
+ * (`markContainerBinders`), so a reassigned `for (let i …)` index or a
60
+ * `request.security((k) => series[k])` callback parameter can never leak
61
+ * an unrelated outer `const k`'s value into the bound (which would
62
+ * under-size the buffer).
63
+ *
64
+ * @since 0.1
65
+ * @stable
66
+ * @example
67
+ * // const k = 3; series[k];
68
+ * // collectConstNumberEnv(<the `k` arg>, scope).get("k") → 3
69
+ * const fn: typeof collectConstNumberEnv = collectConstNumberEnv;
70
+ * void fn;
71
+ */
72
+ export declare function collectConstNumberEnv(useSite: ts.Node, scopeRoot: ts.Node): ReadonlyMap<string, number>;
73
+ //# sourceMappingURL=resolveIndexBound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveIndexBound.d.ts","sourceRoot":"","sources":["../../src/analysis/resolveIndexBound.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAS5B;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACrC,+EAA+E;IAC/E,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,8EAA8E;IAC9E,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC;CAC3B,CAAC,CAAC;AAUH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CAClC,QAAQ,EAAE,EAAE,CAAC,UAAU,EACvB,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,GAAG,EAAE,iBAAiB,GACvB,MAAM,GAAG,IAAI,CAGf;AAqLD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CACjC,OAAO,EAAE,EAAE,CAAC,IAAI,EAChB,SAAS,EAAE,EAAE,CAAC,IAAI,GACnB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CA+B7B"}
@@ -0,0 +1,336 @@
1
+ // Copyright (c) 2026 Invinite. Licensed under the MIT License.
2
+ // See the LICENSE file in the repo root for full license text.
3
+ import ts from "typescript";
4
+ import { boundedLoopVarId, parseBoundedForLoop, unwrapParens, } from "./loopBounds.js";
5
+ /**
6
+ * The provable maximum non-negative integer a series-index expression
7
+ * can reach at runtime, or `null` when no sound upper bound exists.
8
+ * Over-approximates: a result is always `>=` the true max index, so the
9
+ * runtime buffer (sized `maxLookback + 1`) never under-sizes. `null`
10
+ * signals the caller to fall back to the 5000-slot dynamic buffer.
11
+ *
12
+ * Resolves any expression built from numeric literals, `const`
13
+ * numeric-literal bindings (`ctx.constEnv`), bounded-loop induction
14
+ * variables (resolved to their full range), the binary operators `+`,
15
+ * `−`, `*`, unary `±`, and parentheses, by computing its integer
16
+ * interval and returning the **upper** endpoint. Any other node (another
17
+ * identifier, a call, `/`, `%`, `**`, a bitwise op, a non-numeric
18
+ * literal) collapses the containing interval — and thus the whole
19
+ * index — to `null`.
20
+ *
21
+ * @since 0.1
22
+ * @stable
23
+ * @example
24
+ * // for (let i = 0; i < 5; i++) { series[i + 1]; }
25
+ * // resolveIndexUpperBound(<the `i + 1` arg>, <access node>, ctx) → 5
26
+ * const fn: typeof resolveIndexUpperBound = resolveIndexUpperBound;
27
+ * void fn;
28
+ */
29
+ export function resolveIndexUpperBound(argument, node, ctx) {
30
+ const interval = evalInterval(argument, node, ctx);
31
+ return interval === null ? null : interval.hi;
32
+ }
33
+ /**
34
+ * The integer interval an index sub-expression spans, or `null` when any
35
+ * leaf or operator cannot be soundly bounded. The single evaluator that
36
+ * subsumes the leaf cases (literal / bounded-loop var / `const` number)
37
+ * and their affine combinations (`+`, `−`, `*`, unary `±`, parens).
38
+ */
39
+ function evalInterval(expr, node, ctx) {
40
+ const inner = unwrapParens(expr);
41
+ if (ts.isNumericLiteral(inner)) {
42
+ const value = Number(inner.text);
43
+ return finiteInterval(value, value);
44
+ }
45
+ if (ts.isIdentifier(inner)) {
46
+ const loopInterval = resolveLoopVarInterval(inner, node, ctx.checker);
47
+ if (loopInterval !== null)
48
+ return loopInterval;
49
+ const constValue = ctx.constEnv.get(inner.text);
50
+ return constValue === undefined ? null : finiteInterval(constValue, constValue);
51
+ }
52
+ if (ts.isPrefixUnaryExpression(inner)) {
53
+ if (inner.operator === ts.SyntaxKind.PlusToken) {
54
+ return evalInterval(inner.operand, node, ctx);
55
+ }
56
+ if (inner.operator === ts.SyntaxKind.MinusToken) {
57
+ const operand = evalInterval(inner.operand, node, ctx);
58
+ return operand === null ? null : finiteInterval(-operand.hi, -operand.lo);
59
+ }
60
+ return null;
61
+ }
62
+ if (ts.isBinaryExpression(inner)) {
63
+ return evalBinaryInterval(inner, node, ctx);
64
+ }
65
+ return null;
66
+ }
67
+ /**
68
+ * The interval of a `+`/`−`/`*` over two sub-intervals, or `null` when
69
+ * either operand is unbounded or the operator is unsupported (`/`, `%`,
70
+ * `**`, bitwise, …). Multiplication takes the min/max of the four
71
+ * endpoint products so the bound is correct for any sign combination.
72
+ */
73
+ function evalBinaryInterval(expr, node, ctx) {
74
+ const left = evalInterval(expr.left, node, ctx);
75
+ if (left === null)
76
+ return null;
77
+ const right = evalInterval(expr.right, node, ctx);
78
+ if (right === null)
79
+ return null;
80
+ switch (expr.operatorToken.kind) {
81
+ case ts.SyntaxKind.PlusToken:
82
+ return finiteInterval(left.lo + right.lo, left.hi + right.hi);
83
+ case ts.SyntaxKind.MinusToken:
84
+ return finiteInterval(left.lo - right.hi, left.hi - right.lo);
85
+ case ts.SyntaxKind.AsteriskToken: {
86
+ const products = [
87
+ left.lo * right.lo,
88
+ left.lo * right.hi,
89
+ left.hi * right.lo,
90
+ left.hi * right.hi,
91
+ ];
92
+ return finiteInterval(Math.min(...products), Math.max(...products));
93
+ }
94
+ default:
95
+ return null;
96
+ }
97
+ }
98
+ /**
99
+ * An interval with finite endpoints, or `null` when either endpoint is
100
+ * non-finite. A defensive guard against a pathological literal
101
+ * (`1e400` → `Infinity`) or an overflow product feeding a non-finite
102
+ * `hi` into `maxLookback`; integer-ness needs no check (see `Interval`).
103
+ */
104
+ function finiteInterval(lo, hi) {
105
+ if (!Number.isFinite(lo) || !Number.isFinite(hi))
106
+ return null;
107
+ // Normalise `-0` (e.g. `-2 * 0`) to `0` so a resolved bound is never the
108
+ // negative zero a downstream `Object.is`/strict consumer would distinguish.
109
+ return { lo: lo + 0, hi: hi + 0 };
110
+ }
111
+ /**
112
+ * The index range a bare bounded-loop induction variable can span, or
113
+ * `null` when the identifier is not the induction variable of an
114
+ * enclosing legal `for`, is reassigned in the body, runs a
115
+ * non-terminating `>`/`>=` loop, or is a shadowed name that does not
116
+ * resolve to the loop's own declaration. Affine forms need the full
117
+ * range (`K - i` is largest when `i` is smallest); a bare loop-var read
118
+ * still takes `interval.hi`, identical to the leaf max.
119
+ */
120
+ function resolveLoopVarInterval(id, node, checker) {
121
+ const idSymbol = checker.getSymbolAtLocation(id);
122
+ let current = node;
123
+ while (current) {
124
+ if (ts.isForStatement(current)) {
125
+ const loopVarId = boundedLoopVarId(current);
126
+ if (loopVarId && loopVarId.text === id.text) {
127
+ // Confirm the use refers to THIS loop's induction variable
128
+ // and not a nested binding that shadows the same text.
129
+ const loopSymbol = checker.getSymbolAtLocation(loopVarId);
130
+ if (!idSymbol || !loopSymbol || idSymbol !== loopSymbol)
131
+ return null;
132
+ const loop = parseBoundedForLoop(current);
133
+ if (loop === null)
134
+ return null;
135
+ if (isLoopVarReassigned(current, loop.varName))
136
+ return null;
137
+ return loopVarInterval(loop);
138
+ }
139
+ }
140
+ current = current.parent;
141
+ }
142
+ return null;
143
+ }
144
+ /**
145
+ * The full index range a terminating `for` reaches (`[start, max]`), or
146
+ * `null` for a non-terminating (`>`/`>=` with `i++`) header the resolver
147
+ * cannot bound. `<` reaches `limit - 1`; `<=` reaches `limit`.
148
+ */
149
+ function loopVarInterval(loop) {
150
+ if (loop.op === ts.SyntaxKind.LessThanToken) {
151
+ return finiteInterval(loop.start, loop.limit - 1);
152
+ }
153
+ if (loop.op === ts.SyntaxKind.LessThanEqualsToken) {
154
+ return finiteInterval(loop.start, loop.limit);
155
+ }
156
+ return null;
157
+ }
158
+ /**
159
+ * Whether the loop body reassigns `varName` beyond the header `i++`
160
+ * incrementor (a plain `=`, a compound assignment, or an extra `++`/`--`
161
+ * whose target is the induction variable). Such a body breaks the
162
+ * `limit`-based bound, so the resolver refuses to size the read.
163
+ */
164
+ function isLoopVarReassigned(loop, varName) {
165
+ const body = loop.statement;
166
+ let reassigned = false;
167
+ const visit = (node) => {
168
+ if (reassigned)
169
+ return;
170
+ if (ts.isBinaryExpression(node) &&
171
+ isAssignmentOperator(node.operatorToken.kind) &&
172
+ ts.isIdentifier(node.left) &&
173
+ node.left.text === varName) {
174
+ reassigned = true;
175
+ return;
176
+ }
177
+ if ((ts.isPostfixUnaryExpression(node) || ts.isPrefixUnaryExpression(node)) &&
178
+ (node.operator === ts.SyntaxKind.PlusPlusToken ||
179
+ node.operator === ts.SyntaxKind.MinusMinusToken) &&
180
+ ts.isIdentifier(node.operand) &&
181
+ node.operand.text === varName) {
182
+ reassigned = true;
183
+ return;
184
+ }
185
+ ts.forEachChild(node, visit);
186
+ };
187
+ visit(body);
188
+ return reassigned;
189
+ }
190
+ /** Whether a binary operator token writes to its left operand. */
191
+ function isAssignmentOperator(kind) {
192
+ return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment;
193
+ }
194
+ /**
195
+ * The `const <id> = <numeric literal>` bindings lexically visible at a
196
+ * specific series-index expression. Only `const` initialised with a
197
+ * numeric literal — or a unary `+`/`-` on one — is included (mirroring
198
+ * `extractInputs.readLiteral`'s numeric handling); a binary initialiser
199
+ * is left for Task 2's interval evaluator and is simply omitted. The walk
200
+ * runs from `useSite` outward through its lexical containers up to
201
+ * `scopeRoot`, collecting only declarations that occur before
202
+ * `useSite.pos` within each container, so it never sees a declaration
203
+ * after the read, inside a sibling block, or in a nested function/class
204
+ * that does not contain `useSite`. The innermost visible binding for a
205
+ * name wins (normal shadowing) — including binders that are not
206
+ * `var`/`let`/`const` statements: a `for`-init induction variable and a
207
+ * function parameter shadow an outer numeric `const` of the same name
208
+ * (`markContainerBinders`), so a reassigned `for (let i …)` index or a
209
+ * `request.security((k) => series[k])` callback parameter can never leak
210
+ * an unrelated outer `const k`'s value into the bound (which would
211
+ * under-size the buffer).
212
+ *
213
+ * @since 0.1
214
+ * @stable
215
+ * @example
216
+ * // const k = 3; series[k];
217
+ * // collectConstNumberEnv(<the `k` arg>, scope).get("k") → 3
218
+ * const fn: typeof collectConstNumberEnv = collectConstNumberEnv;
219
+ * void fn;
220
+ */
221
+ export function collectConstNumberEnv(useSite, scopeRoot) {
222
+ const env = new Map();
223
+ // Every `var`/`let`/`const` name bound at or inside a nearer container,
224
+ // numeric or not. A nearer binding shadows an outer one even when it is
225
+ // a `let` or a non-numeric `const`, so an outer `const k = 5` must not
226
+ // leak through it — once a name is `seen`, no outer container can set it.
227
+ const seen = new Set();
228
+ let container = useSite.parent;
229
+ while (container) {
230
+ // A `for`-init variable / function parameter introduced by this
231
+ // container shadows any same-named outer `const`, even though it is
232
+ // not a `var`/`let`/`const` statement `variableDeclarationsIn` scans.
233
+ markContainerBinders(container, seen);
234
+ for (const declaration of variableDeclarationsIn(container)) {
235
+ if (declaration.pos >= useSite.pos)
236
+ continue;
237
+ if (!ts.isIdentifier(declaration.name))
238
+ continue;
239
+ const name = declaration.name.text;
240
+ if (seen.has(name))
241
+ continue;
242
+ seen.add(name);
243
+ const list = declaration.parent;
244
+ if (ts.isVariableDeclarationList(list) && (list.flags & ts.NodeFlags.Const) !== 0) {
245
+ const value = readNumericLiteralInit(declaration);
246
+ if (value !== null)
247
+ env.set(name, value);
248
+ }
249
+ }
250
+ if (container === scopeRoot)
251
+ break;
252
+ container = container.parent;
253
+ }
254
+ return env;
255
+ }
256
+ /**
257
+ * Mark every binding name a container introduces at its OWN level — a
258
+ * `for` initializer's induction variable and a function-like's parameters
259
+ * — as `seen`, so an outer numeric `const` of the same name cannot leak
260
+ * past it. These binders are never numeric `const`s the resolver trusts,
261
+ * so marking them only blocks unsound shadow leaks: a reassigned
262
+ * `for (let i …)` index or a callback parameter (`(k) => series[k]`) that
263
+ * collides with an outer `const i`/`const k`. Only identifier binders
264
+ * matter here (a numeric series index is an identifier); destructured
265
+ * parameter/loop patterns bind no numeric index and are skipped.
266
+ */
267
+ function markContainerBinders(container, seen) {
268
+ if (ts.isForStatement(container)) {
269
+ const init = container.initializer;
270
+ if (init && ts.isVariableDeclarationList(init)) {
271
+ for (const declaration of init.declarations) {
272
+ if (ts.isIdentifier(declaration.name))
273
+ seen.add(declaration.name.text);
274
+ }
275
+ }
276
+ return;
277
+ }
278
+ if (ts.isFunctionLike(container)) {
279
+ for (const parameter of container.parameters) {
280
+ if (ts.isIdentifier(parameter.name))
281
+ seen.add(parameter.name.text);
282
+ }
283
+ }
284
+ }
285
+ /**
286
+ * The direct `var`/`let`/`const` `VariableDeclaration`s of a container —
287
+ * of every declaration kind, so the caller can detect a nearer binding
288
+ * that shadows an outer numeric `const` — without descending into nested
289
+ * functions, classes, or blocks (those are handled by their own
290
+ * enclosing-container pass when they actually contain the use site).
291
+ * `Block`, `SourceFile`, function bodies, and case clauses hold their
292
+ * declarations as `statements`/`clauses` we scan directly.
293
+ */
294
+ function variableDeclarationsIn(container) {
295
+ const declarations = [];
296
+ const statements = statementsOf(container);
297
+ for (const statement of statements) {
298
+ if (!ts.isVariableStatement(statement))
299
+ continue;
300
+ for (const declaration of statement.declarationList.declarations) {
301
+ declarations.push(declaration);
302
+ }
303
+ }
304
+ return declarations;
305
+ }
306
+ /** The lexical statement list a container exposes, or `[]` when it holds none. */
307
+ function statementsOf(container) {
308
+ if (ts.isSourceFile(container) || ts.isBlock(container) || ts.isModuleBlock(container)) {
309
+ return container.statements;
310
+ }
311
+ if (ts.isCaseClause(container) || ts.isDefaultClause(container)) {
312
+ return container.statements;
313
+ }
314
+ return [];
315
+ }
316
+ /**
317
+ * The numeric value of a `const k = <numeric literal>` /
318
+ * `const k = <unary ± numeric literal>` initialiser, or `null` for any
319
+ * other initialiser (no binary folding here — that is Task 2).
320
+ */
321
+ function readNumericLiteralInit(declaration) {
322
+ const initializer = declaration.initializer;
323
+ if (!initializer)
324
+ return null;
325
+ const expr = unwrapParens(initializer);
326
+ if (ts.isNumericLiteral(expr))
327
+ return Number(expr.text);
328
+ if (ts.isPrefixUnaryExpression(expr) &&
329
+ (expr.operator === ts.SyntaxKind.MinusToken || expr.operator === ts.SyntaxKind.PlusToken) &&
330
+ ts.isNumericLiteral(expr.operand)) {
331
+ const value = Number(expr.operand.text);
332
+ return expr.operator === ts.SyntaxKind.MinusToken ? -value : value;
333
+ }
334
+ return null;
335
+ }
336
+ //# sourceMappingURL=resolveIndexBound.js.map