@tsonic/frontend 0.0.5 → 0.0.11

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 (138) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/ir/builder/imports.d.ts.map +1 -1
  7. package/dist/ir/builder/imports.js +0 -5
  8. package/dist/ir/builder/imports.js.map +1 -1
  9. package/dist/ir/builder.test.js +60 -0
  10. package/dist/ir/builder.test.js.map +1 -1
  11. package/dist/ir/converters/expressions/access.d.ts.map +1 -1
  12. package/dist/ir/converters/expressions/access.js +65 -2
  13. package/dist/ir/converters/expressions/access.js.map +1 -1
  14. package/dist/ir/converters/expressions/calls.d.ts.map +1 -1
  15. package/dist/ir/converters/expressions/calls.js +49 -1
  16. package/dist/ir/converters/expressions/calls.js.map +1 -1
  17. package/dist/ir/converters/expressions/collections.d.ts.map +1 -1
  18. package/dist/ir/converters/expressions/collections.js +4 -1
  19. package/dist/ir/converters/expressions/collections.js.map +1 -1
  20. package/dist/ir/converters/expressions/functions.d.ts.map +1 -1
  21. package/dist/ir/converters/expressions/functions.js +3 -1
  22. package/dist/ir/converters/expressions/functions.js.map +1 -1
  23. package/dist/ir/converters/expressions/helpers.d.ts +10 -0
  24. package/dist/ir/converters/expressions/helpers.d.ts.map +1 -1
  25. package/dist/ir/converters/expressions/helpers.js +173 -7
  26. package/dist/ir/converters/expressions/helpers.js.map +1 -1
  27. package/dist/ir/converters/expressions/index.d.ts +1 -1
  28. package/dist/ir/converters/expressions/index.d.ts.map +1 -1
  29. package/dist/ir/converters/expressions/index.js +1 -1
  30. package/dist/ir/converters/expressions/index.js.map +1 -1
  31. package/dist/ir/converters/expressions/literals.d.ts.map +1 -1
  32. package/dist/ir/converters/expressions/literals.js +2 -1
  33. package/dist/ir/converters/expressions/literals.js.map +1 -1
  34. package/dist/ir/converters/expressions/numeric-recovery.test.d.ts +14 -0
  35. package/dist/ir/converters/expressions/numeric-recovery.test.d.ts.map +1 -0
  36. package/dist/ir/converters/expressions/numeric-recovery.test.js +286 -0
  37. package/dist/ir/converters/expressions/numeric-recovery.test.js.map +1 -0
  38. package/dist/ir/converters/expressions/operators.d.ts.map +1 -1
  39. package/dist/ir/converters/expressions/operators.js +16 -2
  40. package/dist/ir/converters/expressions/operators.js.map +1 -1
  41. package/dist/ir/converters/expressions/other.d.ts.map +1 -1
  42. package/dist/ir/converters/expressions/other.js +4 -1
  43. package/dist/ir/converters/expressions/other.js.map +1 -1
  44. package/dist/ir/converters/statements/control/loops.d.ts.map +1 -1
  45. package/dist/ir/converters/statements/control/loops.js +1 -0
  46. package/dist/ir/converters/statements/control/loops.js.map +1 -1
  47. package/dist/ir/expression-converter.d.ts.map +1 -1
  48. package/dist/ir/expression-converter.js +81 -9
  49. package/dist/ir/expression-converter.js.map +1 -1
  50. package/dist/ir/type-converter/arrays.d.ts.map +1 -1
  51. package/dist/ir/type-converter/arrays.js +1 -0
  52. package/dist/ir/type-converter/arrays.js.map +1 -1
  53. package/dist/ir/type-converter/inference.d.ts.map +1 -1
  54. package/dist/ir/type-converter/inference.js +31 -20
  55. package/dist/ir/type-converter/inference.js.map +1 -1
  56. package/dist/ir/type-converter/literals.d.ts.map +1 -1
  57. package/dist/ir/type-converter/literals.js +2 -1
  58. package/dist/ir/type-converter/literals.js.map +1 -1
  59. package/dist/ir/type-converter/objects.d.ts.map +1 -1
  60. package/dist/ir/type-converter/objects.js +2 -1
  61. package/dist/ir/type-converter/objects.js.map +1 -1
  62. package/dist/ir/type-converter/orchestrator.d.ts.map +1 -1
  63. package/dist/ir/type-converter/orchestrator.js +18 -1
  64. package/dist/ir/type-converter/orchestrator.js.map +1 -1
  65. package/dist/ir/type-converter/references.d.ts.map +1 -1
  66. package/dist/ir/type-converter/references.js +14 -18
  67. package/dist/ir/type-converter/references.js.map +1 -1
  68. package/dist/ir/types/expressions.d.ts +102 -1
  69. package/dist/ir/types/expressions.d.ts.map +1 -1
  70. package/dist/ir/types/guards.d.ts.map +1 -1
  71. package/dist/ir/types/guards.js +2 -0
  72. package/dist/ir/types/guards.js.map +1 -1
  73. package/dist/ir/types/index.d.ts +4 -2
  74. package/dist/ir/types/index.d.ts.map +1 -1
  75. package/dist/ir/types/index.js +1 -0
  76. package/dist/ir/types/index.js.map +1 -1
  77. package/dist/ir/types/ir-types.d.ts +19 -0
  78. package/dist/ir/types/ir-types.d.ts.map +1 -1
  79. package/dist/ir/types/numeric-kind.d.ts +68 -0
  80. package/dist/ir/types/numeric-kind.d.ts.map +1 -0
  81. package/dist/ir/types/numeric-kind.js +170 -0
  82. package/dist/ir/types/numeric-kind.js.map +1 -0
  83. package/dist/ir/types/statements.d.ts +38 -1
  84. package/dist/ir/types/statements.d.ts.map +1 -1
  85. package/dist/ir/types.d.ts +2 -1
  86. package/dist/ir/types.d.ts.map +1 -1
  87. package/dist/ir/types.js +2 -0
  88. package/dist/ir/types.js.map +1 -1
  89. package/dist/ir/validation/index.d.ts +7 -0
  90. package/dist/ir/validation/index.d.ts.map +1 -0
  91. package/dist/ir/validation/index.js +7 -0
  92. package/dist/ir/validation/index.js.map +1 -0
  93. package/dist/ir/validation/numeric-invariants.test.d.ts +15 -0
  94. package/dist/ir/validation/numeric-invariants.test.d.ts.map +1 -0
  95. package/dist/ir/validation/numeric-invariants.test.js +598 -0
  96. package/dist/ir/validation/numeric-invariants.test.js.map +1 -0
  97. package/dist/ir/validation/numeric-proof-pass.d.ts +37 -0
  98. package/dist/ir/validation/numeric-proof-pass.d.ts.map +1 -0
  99. package/dist/ir/validation/numeric-proof-pass.js +889 -0
  100. package/dist/ir/validation/numeric-proof-pass.js.map +1 -0
  101. package/dist/ir/validation/soundness-gate.d.ts +26 -0
  102. package/dist/ir/validation/soundness-gate.d.ts.map +1 -0
  103. package/dist/ir/validation/soundness-gate.js +551 -0
  104. package/dist/ir/validation/soundness-gate.js.map +1 -0
  105. package/dist/ir/validation/soundness-gate.test.d.ts +13 -0
  106. package/dist/ir/validation/soundness-gate.test.d.ts.map +1 -0
  107. package/dist/ir/validation/soundness-gate.test.js +315 -0
  108. package/dist/ir/validation/soundness-gate.test.js.map +1 -0
  109. package/dist/ir/validation/yield-lowering-pass.d.ts +40 -0
  110. package/dist/ir/validation/yield-lowering-pass.d.ts.map +1 -0
  111. package/dist/ir/validation/yield-lowering-pass.js +548 -0
  112. package/dist/ir/validation/yield-lowering-pass.js.map +1 -0
  113. package/dist/ir/validation/yield-lowering-pass.test.d.ts +12 -0
  114. package/dist/ir/validation/yield-lowering-pass.test.d.ts.map +1 -0
  115. package/dist/ir/validation/yield-lowering-pass.test.js +761 -0
  116. package/dist/ir/validation/yield-lowering-pass.test.js.map +1 -0
  117. package/dist/program/bindings.d.ts +5 -0
  118. package/dist/program/bindings.d.ts.map +1 -1
  119. package/dist/program/bindings.js +12 -1
  120. package/dist/program/bindings.js.map +1 -1
  121. package/dist/program/dependency-graph.d.ts +3 -0
  122. package/dist/program/dependency-graph.d.ts.map +1 -1
  123. package/dist/program/dependency-graph.js +28 -4
  124. package/dist/program/dependency-graph.js.map +1 -1
  125. package/dist/program/index.d.ts +1 -1
  126. package/dist/program/index.d.ts.map +1 -1
  127. package/dist/program/index.js.map +1 -1
  128. package/dist/program.d.ts +1 -0
  129. package/dist/program.d.ts.map +1 -1
  130. package/dist/program.js.map +1 -1
  131. package/dist/types/diagnostic.d.ts +1 -1
  132. package/dist/types/diagnostic.d.ts.map +1 -1
  133. package/dist/types/diagnostic.js.map +1 -1
  134. package/package.json +1 -1
  135. package/dist/types/parameter-modifiers.d.ts +0 -55
  136. package/dist/types/parameter-modifiers.d.ts.map +0 -1
  137. package/dist/types/parameter-modifiers.js +0 -148
  138. package/dist/types/parameter-modifiers.js.map +0 -1
@@ -0,0 +1,889 @@
1
+ /**
2
+ * Numeric Proof Pass - HARD GATE for numeric type narrowings
3
+ *
4
+ * This pass runs before emission and:
5
+ * 1. Validates all numeric narrowings are provable
6
+ * 2. Attaches NumericProof to validated expressions
7
+ * 3. Emits error diagnostics for unprovable narrowings
8
+ *
9
+ * CRITICAL: If this pass emits ANY errors, the emitter MUST NOT run.
10
+ * Unlike the previous implementation, this pass does NOT allow fallback casts.
11
+ * If we cannot prove a narrowing, compilation FAILS.
12
+ *
13
+ * This follows Alice's "Provably-Sound Numeric Types" specification:
14
+ * "If Tsonic cannot prove a numeric narrowing is sound, it must fail compilation."
15
+ */
16
+ import { createDiagnostic, } from "../../types/diagnostic.js";
17
+ import { NUMERIC_RANGES, getBinaryResultKind, isIntegerKind, TSONIC_TO_NUMERIC_KIND, } from "../types.js";
18
+ /**
19
+ * Create a source location for a module
20
+ */
21
+ const moduleLocation = (ctx) => ({
22
+ file: ctx.filePath,
23
+ line: 1,
24
+ column: 1,
25
+ length: 1,
26
+ });
27
+ /**
28
+ * Extract NumericKind from an IrType if it has numericIntent or is a known numeric type name
29
+ */
30
+ const getNumericKindFromType = (type) => {
31
+ if (type === undefined) {
32
+ return undefined;
33
+ }
34
+ // Check for primitiveType with numericIntent (from as-expressions)
35
+ if (type.kind === "primitiveType" && type.name === "number") {
36
+ return type.numericIntent;
37
+ }
38
+ // Check for referenceType with known numeric type name (e.g., int, long, float)
39
+ // Use the TSONIC_TO_NUMERIC_KIND map from ir/types
40
+ if (type.kind === "referenceType") {
41
+ return TSONIC_TO_NUMERIC_KIND.get(type.name);
42
+ }
43
+ return undefined;
44
+ };
45
+ /**
46
+ * Check if a raw lexeme represents a valid integer (no decimal point, no exponent).
47
+ * This is critical for correctness - we must validate the SOURCE text, not the JS number.
48
+ * Supports numeric separators (underscores) per JS/TS syntax: 1_000_000, 0xFF_FF, etc.
49
+ *
50
+ * INVARIANT: This function MUST be called before any parsing (BigInt conversion).
51
+ * The order is: validate → normalize → parse. Never parse unvalidated raw strings.
52
+ *
53
+ * Invalid underscore placement (rejected):
54
+ * - Double underscores: 1__2
55
+ * - Leading underscore: _123
56
+ * - Trailing underscore: 123_
57
+ * - Underscore after prefix: 0x_FF, 0b_10, 0o_77
58
+ */
59
+ const isValidIntegerLexeme = (raw) => {
60
+ // Must not contain decimal point or exponent
61
+ if (raw.includes(".") || raw.includes("e") || raw.includes("E")) {
62
+ return false;
63
+ }
64
+ // Explicit early rejection of invalid underscore placement
65
+ // (also enforced by regex below, but these are clear invariants)
66
+ if (raw.includes("__")) {
67
+ return false; // Double underscore: 1__2
68
+ }
69
+ if (raw.startsWith("_") || raw.startsWith("-_")) {
70
+ return false; // Leading underscore: _123 or -_123
71
+ }
72
+ if (raw.endsWith("_")) {
73
+ return false; // Trailing underscore: 123_
74
+ }
75
+ // Underscore immediately after prefix: 0x_FF, 0b_10, 0o_77
76
+ if (/^-?0[xXoObB]_/.test(raw)) {
77
+ return false;
78
+ }
79
+ // Must be a valid integer pattern (optional sign, digits with optional underscores)
80
+ // Handle hex (0x), octal (0o), binary (0b) prefixes
81
+ // Underscores can appear between digits but not at start/end or adjacent
82
+ return /^-?(?:0[xX][\da-fA-F]+(?:_[\da-fA-F]+)*|0[oO][0-7]+(?:_[0-7]+)*|0[bB][01]+(?:_[01]+)*|\d+(?:_\d+)*)$/.test(raw);
83
+ };
84
+ /**
85
+ * Parse a raw lexeme as a BigInt for precise range checking.
86
+ * Returns undefined if parsing fails.
87
+ * Strips numeric separators (underscores) before parsing.
88
+ *
89
+ * INVARIANT: Only call this AFTER isValidIntegerLexeme(raw) returns true.
90
+ * The order is: validate → normalize → parse. This function does the normalize + parse steps.
91
+ * Calling with unvalidated input may produce incorrect results (e.g., "1__2" → 12).
92
+ */
93
+ const parseBigIntFromRaw = (raw) => {
94
+ try {
95
+ // Step 1: Normalize - strip numeric separators (underscores)
96
+ // This is safe because isValidIntegerLexeme already validated underscore placement
97
+ const normalized = raw.replace(/_/g, "");
98
+ // Handle different bases
99
+ if (normalized.startsWith("0x") || normalized.startsWith("0X")) {
100
+ return BigInt(normalized);
101
+ }
102
+ if (normalized.startsWith("0o") || normalized.startsWith("0O")) {
103
+ return BigInt(normalized);
104
+ }
105
+ if (normalized.startsWith("0b") || normalized.startsWith("0B")) {
106
+ return BigInt(normalized);
107
+ }
108
+ // Handle negative numbers
109
+ if (normalized.startsWith("-")) {
110
+ return -BigInt(normalized.slice(1));
111
+ }
112
+ return BigInt(normalized);
113
+ }
114
+ catch {
115
+ return undefined;
116
+ }
117
+ };
118
+ /**
119
+ * Check if a BigInt value fits within the range of a numeric kind.
120
+ */
121
+ const bigIntFitsInKind = (value, kind) => {
122
+ const range = NUMERIC_RANGES.get(kind);
123
+ if (range === undefined) {
124
+ return false;
125
+ }
126
+ return value >= range.min && value <= range.max;
127
+ };
128
+ /**
129
+ * Try to infer the numeric kind of an expression.
130
+ * Returns undefined if the expression's numeric kind cannot be determined.
131
+ *
132
+ * For literals: follows C# semantics where integer-looking literals default to int.
133
+ * A literal is Int32 if: no decimal point, no exponent, fits in Int32 range.
134
+ * Otherwise it's Double.
135
+ */
136
+ const inferNumericKind = (expr, ctx) => {
137
+ switch (expr.kind) {
138
+ case "literal": {
139
+ if (typeof expr.value === "number") {
140
+ // Check if inferred type has explicit numeric intent (from annotation)
141
+ const typeKind = getNumericKindFromType(expr.inferredType);
142
+ if (typeKind !== undefined) {
143
+ return typeKind;
144
+ }
145
+ // For bare literals, follow C# semantics:
146
+ // Integer-looking literals (no decimal, no exponent) are Int32 if in range
147
+ // Otherwise, they're Double
148
+ if (expr.raw !== undefined && isValidIntegerLexeme(expr.raw)) {
149
+ const bigValue = parseBigIntFromRaw(expr.raw);
150
+ if (bigValue !== undefined && bigIntFitsInKind(bigValue, "Int32")) {
151
+ return "Int32";
152
+ }
153
+ }
154
+ // Floating-point or out-of-range integer literals are Double
155
+ return "Double";
156
+ }
157
+ return undefined;
158
+ }
159
+ case "identifier": {
160
+ // Check if this variable is proven
161
+ const varKind = ctx.provenVariables.get(expr.name);
162
+ if (varKind !== undefined) {
163
+ return varKind;
164
+ }
165
+ // Check if this is a parameter
166
+ const paramKind = ctx.provenParameters.get(expr.name);
167
+ if (paramKind !== undefined) {
168
+ return paramKind;
169
+ }
170
+ // Check inferredType for CLR-typed identifiers
171
+ return getNumericKindFromType(expr.inferredType);
172
+ }
173
+ case "numericNarrowing": {
174
+ // The target kind of the narrowing is the proven kind (if proof exists)
175
+ if (expr.proof) {
176
+ return expr.proof.kind;
177
+ }
178
+ // Fall back to targetKind - the narrowing declares intent even without proof
179
+ // This handles cases where we're inferring before proof is attached
180
+ return expr.targetKind;
181
+ }
182
+ case "binary": {
183
+ // Binary operators follow C# promotion rules
184
+ const leftKind = inferNumericKind(expr.left, ctx);
185
+ const rightKind = inferNumericKind(expr.right, ctx);
186
+ if (leftKind !== undefined && rightKind !== undefined) {
187
+ return getBinaryResultKind(leftKind, rightKind);
188
+ }
189
+ return undefined;
190
+ }
191
+ case "unary": {
192
+ if (expr.operator === "-" ||
193
+ expr.operator === "+" ||
194
+ expr.operator === "~") {
195
+ return inferNumericKind(expr.expression, ctx);
196
+ }
197
+ return undefined;
198
+ }
199
+ case "conditional": {
200
+ const trueKind = inferNumericKind(expr.whenTrue, ctx);
201
+ const falseKind = inferNumericKind(expr.whenFalse, ctx);
202
+ if (trueKind === falseKind) {
203
+ return trueKind;
204
+ }
205
+ if (trueKind !== undefined && falseKind !== undefined) {
206
+ return getBinaryResultKind(trueKind, falseKind);
207
+ }
208
+ return undefined;
209
+ }
210
+ case "call": {
211
+ // Check if the call has a numeric return type from CLR metadata
212
+ return getNumericKindFromType(expr.inferredType);
213
+ }
214
+ case "memberAccess": {
215
+ return getNumericKindFromType(expr.inferredType);
216
+ }
217
+ default:
218
+ return undefined;
219
+ }
220
+ };
221
+ /**
222
+ * Attempt to prove that a literal fits in a target numeric kind.
223
+ * Uses the raw lexeme for integer validation (NOT the JS number).
224
+ */
225
+ const proveLiteral = (value, raw, targetKind, ctx, sourceSpan) => {
226
+ const location = sourceSpan ?? moduleLocation(ctx);
227
+ // For floating-point targets, any finite number works
228
+ if (!isIntegerKind(targetKind)) {
229
+ if (!Number.isFinite(value)) {
230
+ ctx.diagnostics.push(createDiagnostic("TSN5102", "error", `Literal ${value} cannot be proven as ${targetKind}: not a finite number`, location, "Only finite numbers can be used as floating-point types."));
231
+ return undefined;
232
+ }
233
+ return {
234
+ kind: targetKind,
235
+ source: { type: "literal", value },
236
+ };
237
+ }
238
+ // For integer targets, we MUST have the raw lexeme
239
+ if (raw === undefined) {
240
+ ctx.diagnostics.push(createDiagnostic("TSN5102", "error", `Cannot prove literal as ${targetKind}: raw lexeme not available`, location, "Integer literal proofs require the source lexeme for precision."));
241
+ return undefined;
242
+ }
243
+ // Check that the lexeme represents an integer (no decimal, no exponent)
244
+ if (!isValidIntegerLexeme(raw)) {
245
+ ctx.diagnostics.push(createDiagnostic("TSN5102", "error", `Literal '${raw}' cannot be proven as ${targetKind}: not an integer lexeme`, location, "Use an integer literal (no decimal point or exponent), or use float/double."));
246
+ return undefined;
247
+ }
248
+ // Parse as BigInt for precise range checking
249
+ const bigValue = parseBigIntFromRaw(raw);
250
+ if (bigValue === undefined) {
251
+ ctx.diagnostics.push(createDiagnostic("TSN5102", "error", `Cannot parse literal '${raw}' as integer`, location, "The literal could not be parsed as an integer value."));
252
+ return undefined;
253
+ }
254
+ // JS Safe Integer check: Literals > 2^53-1 lose precision in JavaScript
255
+ // This is a representation soundness check - the TypeScript parser may have
256
+ // already lost precision by the time we see the JS number value.
257
+ const JS_MAX_SAFE_INTEGER = BigInt("9007199254740991"); // 2^53 - 1
258
+ const JS_MIN_SAFE_INTEGER = BigInt("-9007199254740991"); // -(2^53 - 1)
259
+ if (bigValue > JS_MAX_SAFE_INTEGER || bigValue < JS_MIN_SAFE_INTEGER) {
260
+ ctx.diagnostics.push(createDiagnostic("TSN5108", "error", `Literal ${raw} exceeds JavaScript safe integer range (±${JS_MAX_SAFE_INTEGER})`, location, "Values outside ±2^53-1 cannot be exactly represented in JavaScript. " +
261
+ "Use BigInt literals (e.g., 9007199254740992n) or string parsing."));
262
+ return undefined;
263
+ }
264
+ // Check range
265
+ if (!bigIntFitsInKind(bigValue, targetKind)) {
266
+ const range = NUMERIC_RANGES.get(targetKind);
267
+ ctx.diagnostics.push(createDiagnostic("TSN5102", "error", `Literal ${raw} is out of range for type ${targetKind} (valid range: ${range?.min} to ${range?.max})`, location, `Value must be in range ${range?.min} to ${range?.max} for ${targetKind}.`));
268
+ return undefined;
269
+ }
270
+ return {
271
+ kind: targetKind,
272
+ source: { type: "literal", value: bigValue },
273
+ };
274
+ };
275
+ /**
276
+ * Attempt to prove a numeric narrowing expression.
277
+ * If proof fails, emits an error diagnostic - NO FALLBACK TO CAST.
278
+ */
279
+ const proveNarrowing = (expr, ctx) => {
280
+ const innerExpr = expr.expression;
281
+ const targetKind = expr.targetKind;
282
+ // Use expression's sourceSpan if available, otherwise fall back to module location
283
+ const location = expr.sourceSpan ?? innerExpr.sourceSpan ?? moduleLocation(ctx);
284
+ // Case 1: Inner expression is a literal
285
+ if (innerExpr.kind === "literal" && typeof innerExpr.value === "number") {
286
+ const proof = proveLiteral(innerExpr.value, innerExpr.raw, targetKind, ctx, innerExpr.sourceSpan ?? location);
287
+ if (proof === undefined) {
288
+ // proveLiteral already pushed the diagnostic
289
+ return undefined;
290
+ }
291
+ return proof;
292
+ }
293
+ // Case 2: Inner expression is an identifier that is already proven
294
+ if (innerExpr.kind === "identifier") {
295
+ const varKind = ctx.provenVariables.get(innerExpr.name);
296
+ const paramKind = ctx.provenParameters.get(innerExpr.name);
297
+ const sourceKind = varKind ?? paramKind;
298
+ if (sourceKind !== undefined) {
299
+ if (sourceKind === targetKind) {
300
+ return {
301
+ kind: targetKind,
302
+ source: { type: "variable", name: innerExpr.name },
303
+ };
304
+ }
305
+ // Different kind - this is an error, not a cast
306
+ ctx.diagnostics.push(createDiagnostic("TSN5104", "error", `Cannot narrow '${innerExpr.name}' from ${sourceKind} to ${targetKind}`, innerExpr.sourceSpan ?? location, `Variable '${innerExpr.name}' is proven as ${sourceKind}. ` +
307
+ `Cast operands to ${targetKind} first if needed.`));
308
+ return undefined;
309
+ }
310
+ // Identifier not in proven scope - check if it has numeric intent from CLR
311
+ const identKind = getNumericKindFromType(innerExpr.inferredType);
312
+ if (identKind !== undefined && identKind === targetKind) {
313
+ return {
314
+ kind: targetKind,
315
+ source: { type: "variable", name: innerExpr.name },
316
+ };
317
+ }
318
+ // Cannot prove this identifier
319
+ ctx.diagnostics.push(createDiagnostic("TSN5101", "error", `Cannot prove narrowing of '${innerExpr.name}' to ${targetKind}`, innerExpr.sourceSpan ?? location, `Expression '${innerExpr.name}' has unknown numeric kind. ` +
320
+ `Narrow using \`${innerExpr.name} as ${targetKind}\` at assignment, ` +
321
+ `or ensure it originates from a proven ${targetKind} source.`));
322
+ return undefined;
323
+ }
324
+ // Case 3: Inner expression is a binary operation
325
+ if (innerExpr.kind === "binary") {
326
+ const leftKind = inferNumericKind(innerExpr.left, ctx);
327
+ const rightKind = inferNumericKind(innerExpr.right, ctx);
328
+ if (leftKind !== undefined && rightKind !== undefined) {
329
+ const resultKind = getBinaryResultKind(leftKind, rightKind);
330
+ if (resultKind === targetKind) {
331
+ return {
332
+ kind: targetKind,
333
+ source: {
334
+ type: "binaryOp",
335
+ operator: innerExpr.operator,
336
+ leftKind,
337
+ rightKind,
338
+ },
339
+ };
340
+ }
341
+ // Result kind doesn't match target - error
342
+ ctx.diagnostics.push(createDiagnostic("TSN5103", "error", `Binary '${leftKind} ${innerExpr.operator} ${rightKind}' produces ${resultKind}, not ${targetKind}`, innerExpr.sourceSpan ?? location, `C# promotion rules: ${leftKind} ${innerExpr.operator} ${rightKind} = ${resultKind}. ` +
343
+ `Cast operands to ${targetKind} first if needed.`));
344
+ return undefined;
345
+ }
346
+ // Cannot determine operand kinds
347
+ ctx.diagnostics.push(createDiagnostic("TSN5101", "error", `Cannot prove narrowing of binary expression to ${targetKind}`, innerExpr.sourceSpan ?? location, "One or both operands have unknown numeric kind. " +
348
+ "Ensure all operands are proven numeric types."));
349
+ return undefined;
350
+ }
351
+ // Case 4: Inner expression is a unary operation
352
+ if (innerExpr.kind === "unary") {
353
+ if (innerExpr.operator === "-" ||
354
+ innerExpr.operator === "+" ||
355
+ innerExpr.operator === "~") {
356
+ const operandKind = inferNumericKind(innerExpr.expression, ctx);
357
+ if (operandKind !== undefined && operandKind === targetKind) {
358
+ return {
359
+ kind: targetKind,
360
+ source: {
361
+ type: "unaryOp",
362
+ operator: innerExpr.operator,
363
+ operandKind,
364
+ },
365
+ };
366
+ }
367
+ }
368
+ ctx.diagnostics.push(createDiagnostic("TSN5101", "error", `Cannot prove narrowing of unary expression to ${targetKind}`, innerExpr.sourceSpan ?? location, "Unary expression has unknown or mismatched numeric kind. " +
369
+ "Ensure the operand is a proven numeric type."));
370
+ return undefined;
371
+ }
372
+ // Case 5: Inner expression is another numeric narrowing (nested)
373
+ if (innerExpr.kind === "numericNarrowing") {
374
+ if (innerExpr.proof !== undefined && innerExpr.proof.kind === targetKind) {
375
+ return {
376
+ kind: targetKind,
377
+ source: { type: "narrowing", from: innerExpr.proof.kind },
378
+ };
379
+ }
380
+ ctx.diagnostics.push(createDiagnostic("TSN5101", "error", `Cannot prove nested narrowing to ${targetKind}`, innerExpr.sourceSpan ?? location, "Inner narrowing is unproven or has different kind."));
381
+ return undefined;
382
+ }
383
+ // Case 6: Inner expression is a call with known return type from CLR
384
+ if (innerExpr.kind === "call") {
385
+ const returnKind = getNumericKindFromType(innerExpr.inferredType);
386
+ if (returnKind !== undefined && returnKind === targetKind) {
387
+ const methodName = innerExpr.callee.kind === "memberAccess" &&
388
+ typeof innerExpr.callee.property === "string"
389
+ ? innerExpr.callee.property
390
+ : "unknown";
391
+ return {
392
+ kind: targetKind,
393
+ source: { type: "dotnetReturn", method: methodName, returnKind },
394
+ };
395
+ }
396
+ }
397
+ // Case 7: Inner expression is a member access with known type
398
+ if (innerExpr.kind === "memberAccess") {
399
+ const memberKind = getNumericKindFromType(innerExpr.inferredType);
400
+ if (memberKind !== undefined && memberKind === targetKind) {
401
+ const propName = typeof innerExpr.property === "string"
402
+ ? innerExpr.property
403
+ : "computed";
404
+ return {
405
+ kind: targetKind,
406
+ source: {
407
+ type: "dotnetReturn",
408
+ method: propName,
409
+ returnKind: memberKind,
410
+ },
411
+ };
412
+ }
413
+ }
414
+ // HARD GATE: Cannot prove - emit error, DO NOT fallback to cast
415
+ ctx.diagnostics.push(createDiagnostic("TSN5101", "error", `Cannot prove narrowing to ${targetKind}`, location, `Expression of kind '${innerExpr.kind}' cannot be proven to produce ${targetKind}. ` +
416
+ `Ensure the expression is a literal in range, a variable with known numeric type, ` +
417
+ `or a binary/unary operation on proven numeric operands.`));
418
+ return undefined;
419
+ };
420
+ /**
421
+ * Process an expression, proving numeric narrowings and returning
422
+ * the expression with proofs attached.
423
+ */
424
+ const processExpression = (expr, ctx) => {
425
+ switch (expr.kind) {
426
+ case "numericNarrowing": {
427
+ const processedInner = processExpression(expr.expression, ctx);
428
+ const proof = proveNarrowing({ ...expr, expression: processedInner }, ctx);
429
+ // Note: proof may be undefined if proveNarrowing failed (diagnostic pushed)
430
+ // We still return the expression but without proof - emitter will fail if it sees this
431
+ return {
432
+ ...expr,
433
+ expression: processedInner,
434
+ proof,
435
+ };
436
+ }
437
+ case "identifier": {
438
+ // Annotate identifiers with their proven numeric kind
439
+ // This allows the emitter to know that x is Int32 when used in binary expressions
440
+ const varKind = ctx.provenVariables.get(expr.name);
441
+ const paramKind = ctx.provenParameters.get(expr.name);
442
+ const numericKind = varKind ?? paramKind;
443
+ if (numericKind !== undefined) {
444
+ // Update inferredType to include numericIntent
445
+ return {
446
+ ...expr,
447
+ inferredType: {
448
+ kind: "primitiveType",
449
+ name: "number",
450
+ numericIntent: numericKind,
451
+ },
452
+ };
453
+ }
454
+ return expr;
455
+ }
456
+ case "array":
457
+ return {
458
+ ...expr,
459
+ elements: expr.elements.map((e) => e !== undefined ? processExpression(e, ctx) : undefined),
460
+ };
461
+ case "object":
462
+ return {
463
+ ...expr,
464
+ properties: expr.properties.map((p) => {
465
+ if (p.kind === "property") {
466
+ return {
467
+ ...p,
468
+ key: typeof p.key === "string"
469
+ ? p.key
470
+ : processExpression(p.key, ctx),
471
+ value: processExpression(p.value, ctx),
472
+ };
473
+ }
474
+ return {
475
+ ...p,
476
+ expression: processExpression(p.expression, ctx),
477
+ };
478
+ }),
479
+ };
480
+ case "binary": {
481
+ const processedLeft = processExpression(expr.left, ctx);
482
+ const processedRight = processExpression(expr.right, ctx);
483
+ // Infer numeric kind from processed operands
484
+ const leftKind = inferNumericKind(processedLeft, ctx);
485
+ const rightKind = inferNumericKind(processedRight, ctx);
486
+ // If both operands have numeric kinds, annotate the binary result
487
+ if (leftKind !== undefined && rightKind !== undefined) {
488
+ const resultKind = getBinaryResultKind(leftKind, rightKind);
489
+ return {
490
+ ...expr,
491
+ left: processedLeft,
492
+ right: processedRight,
493
+ inferredType: {
494
+ kind: "primitiveType",
495
+ name: "number",
496
+ numericIntent: resultKind,
497
+ },
498
+ };
499
+ }
500
+ return {
501
+ ...expr,
502
+ left: processedLeft,
503
+ right: processedRight,
504
+ };
505
+ }
506
+ case "logical":
507
+ return {
508
+ ...expr,
509
+ left: processExpression(expr.left, ctx),
510
+ right: processExpression(expr.right, ctx),
511
+ };
512
+ case "unary":
513
+ case "update":
514
+ case "await":
515
+ case "spread":
516
+ return {
517
+ ...expr,
518
+ expression: processExpression(expr.expression, ctx),
519
+ };
520
+ case "yield":
521
+ return {
522
+ ...expr,
523
+ expression: expr.expression
524
+ ? processExpression(expr.expression, ctx)
525
+ : undefined,
526
+ };
527
+ case "conditional":
528
+ return {
529
+ ...expr,
530
+ condition: processExpression(expr.condition, ctx),
531
+ whenTrue: processExpression(expr.whenTrue, ctx),
532
+ whenFalse: processExpression(expr.whenFalse, ctx),
533
+ };
534
+ case "assignment":
535
+ return {
536
+ ...expr,
537
+ left: expr.left.kind === "identifierPattern" ||
538
+ expr.left.kind === "arrayPattern" ||
539
+ expr.left.kind === "objectPattern"
540
+ ? expr.left
541
+ : processExpression(expr.left, ctx),
542
+ right: processExpression(expr.right, ctx),
543
+ };
544
+ case "memberAccess": {
545
+ const processedObject = processExpression(expr.object, ctx);
546
+ const processedProperty = typeof expr.property === "string"
547
+ ? expr.property
548
+ : processExpression(expr.property, ctx);
549
+ // ============================================================================
550
+ // CONTRACT: Proof pass is the ONLY source of numeric proofs.
551
+ // The emitter ONLY consumes markers (numericIntent); it never re-derives proofs.
552
+ // If this pass doesn't annotate an index, the emitter will ICE.
553
+ // ============================================================================
554
+ // ARRAY INDEX VALIDATION: Use accessKind tag (set during IR build) to determine
555
+ // whether Int32 proof is required. This is COMPILER-GRADE: no heuristic name matching.
556
+ if (typeof processedProperty !== "string" && expr.isComputed) {
557
+ const accessKind = expr.accessKind;
558
+ // HARD GATE: accessKind MUST be set for computed access.
559
+ // If missing or "unknown", this is a compiler bug - fail early with clear diagnostic.
560
+ if (accessKind === undefined || accessKind === "unknown") {
561
+ // Build debug info for diagnosis
562
+ const objectType = processedObject.inferredType;
563
+ const typeInfo = objectType
564
+ ? `objectType.kind=${objectType.kind}` +
565
+ (objectType.kind === "referenceType"
566
+ ? `, name=${objectType.name}, resolvedClrType=${objectType.resolvedClrType ?? "undefined"}`
567
+ : "")
568
+ : "objectType=undefined";
569
+ ctx.diagnostics.push(createDiagnostic("TSN5109", "error", `Computed access kind was not classified during IR build (accessKind=${accessKind ?? "undefined"}, ${typeInfo})`, expr.sourceSpan ??
570
+ processedProperty.sourceSpan ??
571
+ moduleLocation(ctx), "This is a compiler bug: cannot validate index proof without access classification. " +
572
+ "Report this issue with the source code that triggered it."));
573
+ // Return without annotation - emitter will ICE if it reaches this
574
+ return {
575
+ ...expr,
576
+ object: processedObject,
577
+ property: processedProperty,
578
+ };
579
+ }
580
+ // Require Int32 proof for:
581
+ // - clrIndexer: CLR collection indexers (List<T>, Array, etc.)
582
+ // - jsRuntimeArray: Tsonic.JSRuntime.Array.get()
583
+ // - stringChar: string character access
584
+ // Dictionary access does NOT require Int32 proof (key is typed K, usually string)
585
+ const requiresInt32 = accessKind === "clrIndexer" ||
586
+ accessKind === "jsRuntimeArray" ||
587
+ accessKind === "stringChar";
588
+ if (requiresInt32) {
589
+ const indexKind = inferNumericKind(processedProperty, ctx);
590
+ if (indexKind !== "Int32") {
591
+ ctx.diagnostics.push(createDiagnostic("TSN5107", "error", `Array index must be Int32, got ${indexKind ?? "unknown"}`, processedProperty.sourceSpan ?? moduleLocation(ctx), "Use 'index as int' to narrow, or ensure index is derived from Int32 source."));
592
+ // Return without annotation - emitter will ICE if it reaches this
593
+ return {
594
+ ...expr,
595
+ object: processedObject,
596
+ property: processedProperty,
597
+ };
598
+ }
599
+ // Annotate the index expression with numericIntent so emitter can check it
600
+ // without re-deriving the proof. This is the ONLY place proof markers should be set.
601
+ const annotatedProperty = {
602
+ ...processedProperty,
603
+ inferredType: {
604
+ kind: "primitiveType",
605
+ name: "number",
606
+ numericIntent: "Int32",
607
+ },
608
+ };
609
+ return {
610
+ ...expr,
611
+ object: processedObject,
612
+ property: annotatedProperty,
613
+ };
614
+ }
615
+ // For dictionary access (accessKind === "dictionary"): no Int32 requirement
616
+ // Pass through without annotation - key type is handled by the dictionary itself
617
+ }
618
+ return {
619
+ ...expr,
620
+ object: processedObject,
621
+ property: processedProperty,
622
+ };
623
+ }
624
+ case "call":
625
+ return {
626
+ ...expr,
627
+ callee: processExpression(expr.callee, ctx),
628
+ arguments: expr.arguments.map((a) => processExpression(a, ctx)),
629
+ };
630
+ case "new":
631
+ return {
632
+ ...expr,
633
+ callee: processExpression(expr.callee, ctx),
634
+ arguments: expr.arguments.map((a) => processExpression(a, ctx)),
635
+ };
636
+ case "templateLiteral":
637
+ return {
638
+ ...expr,
639
+ expressions: expr.expressions.map((e) => processExpression(e, ctx)),
640
+ };
641
+ case "arrowFunction":
642
+ return {
643
+ ...expr,
644
+ body: expr.body.kind === "blockStatement"
645
+ ? processStatement(expr.body, ctx)
646
+ : processExpression(expr.body, ctx),
647
+ };
648
+ case "functionExpression":
649
+ return {
650
+ ...expr,
651
+ body: processStatement(expr.body, ctx),
652
+ };
653
+ default:
654
+ return expr;
655
+ }
656
+ };
657
+ /**
658
+ * Process a statement, proving numeric narrowings in expressions.
659
+ */
660
+ const processStatement = (stmt, ctx) => {
661
+ switch (stmt.kind) {
662
+ case "variableDeclaration": {
663
+ const processedDeclarations = stmt.declarations.map((d) => {
664
+ const processedInit = d.initializer
665
+ ? processExpression(d.initializer, ctx)
666
+ : undefined;
667
+ // Track proven variables from declarations with known numeric kind
668
+ // This includes:
669
+ // 1. numericNarrowing with proof (explicit `as int`)
670
+ // 2. Binary/unary expressions that produce known numeric kinds
671
+ // 3. Identifiers with known numeric types
672
+ // We track both const and let (let can be reassigned, but at init we know the type)
673
+ if (d.name.kind === "identifierPattern" &&
674
+ processedInit !== undefined) {
675
+ // First check for explicit numericNarrowing
676
+ if (processedInit.kind === "numericNarrowing" &&
677
+ processedInit.proof !== undefined) {
678
+ ctx.provenVariables.set(d.name.name, processedInit.proof.kind);
679
+ }
680
+ else {
681
+ // Otherwise, try to infer the numeric kind from the expression
682
+ const inferredKind = inferNumericKind(processedInit, ctx);
683
+ if (inferredKind !== undefined) {
684
+ ctx.provenVariables.set(d.name.name, inferredKind);
685
+ }
686
+ }
687
+ }
688
+ return {
689
+ ...d,
690
+ initializer: processedInit,
691
+ };
692
+ });
693
+ return { ...stmt, declarations: processedDeclarations };
694
+ }
695
+ case "functionDeclaration": {
696
+ const paramCtx = {
697
+ ...ctx,
698
+ provenParameters: new Map(ctx.provenParameters),
699
+ provenVariables: new Map(ctx.provenVariables),
700
+ };
701
+ for (const param of stmt.parameters) {
702
+ if (param.pattern.kind === "identifierPattern") {
703
+ const numericKind = getNumericKindFromType(param.type);
704
+ if (numericKind !== undefined) {
705
+ paramCtx.provenParameters.set(param.pattern.name, numericKind);
706
+ }
707
+ }
708
+ }
709
+ return {
710
+ ...stmt,
711
+ body: processStatement(stmt.body, paramCtx),
712
+ };
713
+ }
714
+ case "classDeclaration": {
715
+ const processedMembers = stmt.members.map((m) => {
716
+ if (m.kind === "methodDeclaration" && m.body) {
717
+ const methodCtx = {
718
+ ...ctx,
719
+ provenParameters: new Map(ctx.provenParameters),
720
+ provenVariables: new Map(ctx.provenVariables),
721
+ };
722
+ for (const param of m.parameters) {
723
+ if (param.pattern.kind === "identifierPattern") {
724
+ const numericKind = getNumericKindFromType(param.type);
725
+ if (numericKind !== undefined) {
726
+ methodCtx.provenParameters.set(param.pattern.name, numericKind);
727
+ }
728
+ }
729
+ }
730
+ return { ...m, body: processStatement(m.body, methodCtx) };
731
+ }
732
+ if (m.kind === "propertyDeclaration" && m.initializer) {
733
+ return { ...m, initializer: processExpression(m.initializer, ctx) };
734
+ }
735
+ if (m.kind === "constructorDeclaration" && m.body) {
736
+ return { ...m, body: processStatement(m.body, ctx) };
737
+ }
738
+ return m;
739
+ });
740
+ return { ...stmt, members: processedMembers };
741
+ }
742
+ case "expressionStatement":
743
+ return {
744
+ ...stmt,
745
+ expression: processExpression(stmt.expression, ctx),
746
+ };
747
+ case "returnStatement":
748
+ return {
749
+ ...stmt,
750
+ expression: stmt.expression
751
+ ? processExpression(stmt.expression, ctx)
752
+ : undefined,
753
+ };
754
+ case "ifStatement":
755
+ return {
756
+ ...stmt,
757
+ condition: processExpression(stmt.condition, ctx),
758
+ thenStatement: processStatement(stmt.thenStatement, ctx),
759
+ elseStatement: stmt.elseStatement
760
+ ? processStatement(stmt.elseStatement, ctx)
761
+ : undefined,
762
+ };
763
+ case "whileStatement":
764
+ return {
765
+ ...stmt,
766
+ condition: processExpression(stmt.condition, ctx),
767
+ body: processStatement(stmt.body, ctx),
768
+ };
769
+ case "forStatement":
770
+ return {
771
+ ...stmt,
772
+ initializer: stmt.initializer
773
+ ? stmt.initializer.kind === "variableDeclaration"
774
+ ? processStatement(stmt.initializer, ctx)
775
+ : processExpression(stmt.initializer, ctx)
776
+ : undefined,
777
+ condition: stmt.condition
778
+ ? processExpression(stmt.condition, ctx)
779
+ : undefined,
780
+ update: stmt.update ? processExpression(stmt.update, ctx) : undefined,
781
+ body: processStatement(stmt.body, ctx),
782
+ };
783
+ case "forOfStatement":
784
+ return {
785
+ ...stmt,
786
+ expression: processExpression(stmt.expression, ctx),
787
+ body: processStatement(stmt.body, ctx),
788
+ };
789
+ case "switchStatement":
790
+ return {
791
+ ...stmt,
792
+ expression: processExpression(stmt.expression, ctx),
793
+ cases: stmt.cases.map((c) => ({
794
+ ...c,
795
+ test: c.test ? processExpression(c.test, ctx) : undefined,
796
+ statements: c.statements.map((s) => processStatement(s, ctx)),
797
+ })),
798
+ };
799
+ case "throwStatement":
800
+ return {
801
+ ...stmt,
802
+ expression: processExpression(stmt.expression, ctx),
803
+ };
804
+ case "tryStatement":
805
+ return {
806
+ ...stmt,
807
+ tryBlock: processStatement(stmt.tryBlock, ctx),
808
+ catchClause: stmt.catchClause
809
+ ? {
810
+ ...stmt.catchClause,
811
+ body: processStatement(stmt.catchClause.body, ctx),
812
+ }
813
+ : undefined,
814
+ finallyBlock: stmt.finallyBlock
815
+ ? processStatement(stmt.finallyBlock, ctx)
816
+ : undefined,
817
+ };
818
+ case "blockStatement":
819
+ return {
820
+ ...stmt,
821
+ statements: stmt.statements.map((s) => processStatement(s, ctx)),
822
+ };
823
+ case "yieldStatement":
824
+ return {
825
+ ...stmt,
826
+ output: stmt.output ? processExpression(stmt.output, ctx) : undefined,
827
+ };
828
+ case "generatorReturnStatement":
829
+ return {
830
+ ...stmt,
831
+ expression: stmt.expression
832
+ ? processExpression(stmt.expression, ctx)
833
+ : undefined,
834
+ };
835
+ default:
836
+ return stmt;
837
+ }
838
+ };
839
+ /**
840
+ * Run numeric proof pass on a module.
841
+ */
842
+ const processModule = (module) => {
843
+ const ctx = {
844
+ filePath: module.filePath,
845
+ diagnostics: [],
846
+ provenVariables: new Map(),
847
+ provenParameters: new Map(),
848
+ };
849
+ const processedBody = module.body.map((stmt) => processStatement(stmt, ctx));
850
+ const processedExports = module.exports.map((exp) => {
851
+ if (exp.kind === "default") {
852
+ return { ...exp, expression: processExpression(exp.expression, ctx) };
853
+ }
854
+ if (exp.kind === "declaration") {
855
+ return { ...exp, declaration: processStatement(exp.declaration, ctx) };
856
+ }
857
+ return exp;
858
+ });
859
+ return {
860
+ ok: ctx.diagnostics.length === 0,
861
+ module: {
862
+ ...module,
863
+ body: processedBody,
864
+ exports: processedExports,
865
+ },
866
+ diagnostics: ctx.diagnostics,
867
+ };
868
+ };
869
+ /**
870
+ * Run numeric proof validation on all modules.
871
+ *
872
+ * HARD GATE: If any diagnostics are returned, the emitter MUST NOT run.
873
+ * This is not a warning system - unprovable narrowings are compilation errors.
874
+ */
875
+ export const runNumericProofPass = (modules) => {
876
+ const processedModules = [];
877
+ const allDiagnostics = [];
878
+ for (const module of modules) {
879
+ const result = processModule(module);
880
+ processedModules.push(result.module);
881
+ allDiagnostics.push(...result.diagnostics);
882
+ }
883
+ return {
884
+ ok: allDiagnostics.length === 0,
885
+ modules: processedModules,
886
+ diagnostics: allDiagnostics,
887
+ };
888
+ };
889
+ //# sourceMappingURL=numeric-proof-pass.js.map