@tsonic/frontend 0.0.11 → 0.0.13

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 (125) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ir/converters/expressions/access.d.ts.map +1 -1
  3. package/dist/ir/converters/expressions/access.js +61 -1
  4. package/dist/ir/converters/expressions/access.js.map +1 -1
  5. package/dist/ir/converters/expressions/calls.d.ts.map +1 -1
  6. package/dist/ir/converters/expressions/calls.js +293 -24
  7. package/dist/ir/converters/expressions/calls.js.map +1 -1
  8. package/dist/ir/converters/expressions/helpers.js +4 -4
  9. package/dist/ir/converters/expressions/helpers.js.map +1 -1
  10. package/dist/ir/converters/expressions/literals.d.ts +14 -0
  11. package/dist/ir/converters/expressions/literals.d.ts.map +1 -1
  12. package/dist/ir/converters/expressions/literals.js +22 -2
  13. package/dist/ir/converters/expressions/literals.js.map +1 -1
  14. package/dist/ir/converters/expressions/numeric-recovery.test.js +3 -2
  15. package/dist/ir/converters/expressions/numeric-recovery.test.js.map +1 -1
  16. package/dist/ir/converters/statements/helpers.d.ts.map +1 -1
  17. package/dist/ir/converters/statements/helpers.js +10 -4
  18. package/dist/ir/converters/statements/helpers.js.map +1 -1
  19. package/dist/ir/expression-converter.d.ts.map +1 -1
  20. package/dist/ir/expression-converter.js +38 -5
  21. package/dist/ir/expression-converter.js.map +1 -1
  22. package/dist/ir/index.d.ts +1 -1
  23. package/dist/ir/index.d.ts.map +1 -1
  24. package/dist/ir/index.js +1 -1
  25. package/dist/ir/index.js.map +1 -1
  26. package/dist/ir/statement-converter.d.ts.map +1 -1
  27. package/dist/ir/statement-converter.js +12 -0
  28. package/dist/ir/statement-converter.js.map +1 -1
  29. package/dist/ir/type-converter/inference.d.ts.map +1 -1
  30. package/dist/ir/type-converter/inference.js +50 -7
  31. package/dist/ir/type-converter/inference.js.map +1 -1
  32. package/dist/ir/type-converter/primitives.d.ts +26 -4
  33. package/dist/ir/type-converter/primitives.d.ts.map +1 -1
  34. package/dist/ir/type-converter/primitives.js +36 -2
  35. package/dist/ir/type-converter/primitives.js.map +1 -1
  36. package/dist/ir/type-converter/references.d.ts.map +1 -1
  37. package/dist/ir/type-converter/references.js +324 -11
  38. package/dist/ir/type-converter/references.js.map +1 -1
  39. package/dist/ir/type-converter/utility-types.d.ts +93 -0
  40. package/dist/ir/type-converter/utility-types.d.ts.map +1 -0
  41. package/dist/ir/type-converter/utility-types.js +528 -0
  42. package/dist/ir/type-converter/utility-types.js.map +1 -0
  43. package/dist/ir/type-converter/utility-types.test.d.ts +10 -0
  44. package/dist/ir/type-converter/utility-types.test.d.ts.map +1 -0
  45. package/dist/ir/type-converter/utility-types.test.js +1030 -0
  46. package/dist/ir/type-converter/utility-types.test.js.map +1 -0
  47. package/dist/ir/types/expressions.d.ts +40 -2
  48. package/dist/ir/types/expressions.d.ts.map +1 -1
  49. package/dist/ir/types/helpers.d.ts +3 -1
  50. package/dist/ir/types/helpers.d.ts.map +1 -1
  51. package/dist/ir/types/index.d.ts +2 -1
  52. package/dist/ir/types/index.d.ts.map +1 -1
  53. package/dist/ir/types/index.js +2 -0
  54. package/dist/ir/types/index.js.map +1 -1
  55. package/dist/ir/types/ir-types.d.ts +69 -11
  56. package/dist/ir/types/ir-types.d.ts.map +1 -1
  57. package/dist/ir/types/numeric-helpers.d.ts +46 -0
  58. package/dist/ir/types/numeric-helpers.d.ts.map +1 -0
  59. package/dist/ir/types/numeric-helpers.js +105 -0
  60. package/dist/ir/types/numeric-helpers.js.map +1 -0
  61. package/dist/ir/types/statements.d.ts +11 -1
  62. package/dist/ir/types/statements.d.ts.map +1 -1
  63. package/dist/ir/types.d.ts +2 -2
  64. package/dist/ir/types.d.ts.map +1 -1
  65. package/dist/ir/types.js +3 -1
  66. package/dist/ir/types.js.map +1 -1
  67. package/dist/ir/validation/anonymous-type-lowering-pass.d.ts +32 -0
  68. package/dist/ir/validation/anonymous-type-lowering-pass.d.ts.map +1 -0
  69. package/dist/ir/validation/anonymous-type-lowering-pass.js +854 -0
  70. package/dist/ir/validation/anonymous-type-lowering-pass.js.map +1 -0
  71. package/dist/ir/validation/attribute-collection-pass.d.ts +37 -0
  72. package/dist/ir/validation/attribute-collection-pass.d.ts.map +1 -0
  73. package/dist/ir/validation/attribute-collection-pass.js +282 -0
  74. package/dist/ir/validation/attribute-collection-pass.js.map +1 -0
  75. package/dist/ir/validation/attribute-collection-pass.test.d.ts +5 -0
  76. package/dist/ir/validation/attribute-collection-pass.test.d.ts.map +1 -0
  77. package/dist/ir/validation/attribute-collection-pass.test.js +215 -0
  78. package/dist/ir/validation/attribute-collection-pass.test.js.map +1 -0
  79. package/dist/ir/validation/index.d.ts +3 -0
  80. package/dist/ir/validation/index.d.ts.map +1 -1
  81. package/dist/ir/validation/index.js +3 -0
  82. package/dist/ir/validation/index.js.map +1 -1
  83. package/dist/ir/validation/numeric-coercion-pass.d.ts +77 -0
  84. package/dist/ir/validation/numeric-coercion-pass.d.ts.map +1 -0
  85. package/dist/ir/validation/numeric-coercion-pass.js +686 -0
  86. package/dist/ir/validation/numeric-coercion-pass.js.map +1 -0
  87. package/dist/ir/validation/numeric-invariants.test.js +130 -14
  88. package/dist/ir/validation/numeric-invariants.test.js.map +1 -1
  89. package/dist/ir/validation/numeric-proof-pass.d.ts.map +1 -1
  90. package/dist/ir/validation/numeric-proof-pass.js +68 -108
  91. package/dist/ir/validation/numeric-proof-pass.js.map +1 -1
  92. package/dist/ir/validation/soundness-gate.d.ts.map +1 -1
  93. package/dist/ir/validation/soundness-gate.js +23 -12
  94. package/dist/ir/validation/soundness-gate.js.map +1 -1
  95. package/dist/ir/validation/yield-lowering-pass.test.js +21 -12
  96. package/dist/ir/validation/yield-lowering-pass.test.js.map +1 -1
  97. package/dist/program/bindings.d.ts +20 -9
  98. package/dist/program/bindings.d.ts.map +1 -1
  99. package/dist/program/bindings.js +98 -36
  100. package/dist/program/bindings.js.map +1 -1
  101. package/dist/program/bindings.test.js +3 -3
  102. package/dist/program/dependency-graph.d.ts.map +1 -1
  103. package/dist/program/dependency-graph.js +31 -5
  104. package/dist/program/dependency-graph.js.map +1 -1
  105. package/dist/resolver/import-resolution.d.ts +4 -0
  106. package/dist/resolver/import-resolution.d.ts.map +1 -1
  107. package/dist/resolver/import-resolution.js +18 -7
  108. package/dist/resolver/import-resolution.js.map +1 -1
  109. package/dist/resolver.test.js +2 -2
  110. package/dist/resolver.test.js.map +1 -1
  111. package/dist/types/diagnostic.d.ts +1 -1
  112. package/dist/types/diagnostic.d.ts.map +1 -1
  113. package/dist/types/diagnostic.js.map +1 -1
  114. package/dist/validation/generics.d.ts.map +1 -1
  115. package/dist/validation/generics.js +133 -1
  116. package/dist/validation/generics.js.map +1 -1
  117. package/dist/validation/static-safety.js +13 -0
  118. package/dist/validation/static-safety.js.map +1 -1
  119. package/dist/validation/unsupported-utility-types.d.ts +10 -8
  120. package/dist/validation/unsupported-utility-types.d.ts.map +1 -1
  121. package/dist/validation/unsupported-utility-types.js +12 -19
  122. package/dist/validation/unsupported-utility-types.js.map +1 -1
  123. package/dist/validator.test.js +133 -28
  124. package/dist/validator.test.js.map +1 -1
  125. package/package.json +1 -1
@@ -0,0 +1,686 @@
1
+ /**
2
+ * Numeric Coercion Pass - STRICT CONTRACT enforcement
3
+ *
4
+ * This pass detects cases where an integer expression is used where a double is expected,
5
+ * and requires explicit user intent for the conversion.
6
+ *
7
+ * STRICT RULE: int → double requires explicit user intent
8
+ *
9
+ * Intent sites (where widening is checked):
10
+ * 1. Variable initialization with explicit type: `const x: number = 42` → ERROR
11
+ * 2. Parameter passing: `foo(42)` where foo expects `number` → ERROR
12
+ * 3. Return statements: `return 42` where function returns `number` → ERROR
13
+ * 4. Array elements: `[1, 2, 3]` in `number[]` context → ERROR (each element)
14
+ * 5. Object properties: `{ x: 42 }` where type has `x: number` → ERROR
15
+ * 6. Ternary branches: `cond ? 1 : 2` where expected is `number` → ERROR
16
+ * 7. Default parameters: `function f(x: number = 42)` → ERROR
17
+ * 8. Tuple elements: `[1, 2]` in `[number, number]` context → ERROR
18
+ *
19
+ * How to satisfy the contract:
20
+ * - Use double literal: `const x: number = 42.0` ✓
21
+ * - Use explicit cast: `const x: number = 42 as number` ✓
22
+ *
23
+ * This pass runs AFTER the IR is built, BEFORE emission.
24
+ * It is a HARD GATE - any errors prevent emission.
25
+ */
26
+ import { createDiagnostic, } from "../../types/diagnostic.js";
27
+ import { getBinaryResultKind, } from "../types.js";
28
+ /**
29
+ * Arithmetic operators that produce numeric results.
30
+ * Used to classify binary expression results.
31
+ */
32
+ const ARITHMETIC_OPERATORS = new Set(["+", "-", "*", "/", "%"]);
33
+ /**
34
+ * Classify an expression's numeric kind.
35
+ *
36
+ * This function propagates numeric kind through:
37
+ * - Literals (via numericIntent)
38
+ * - Identifiers with primitiveType(name="int") or primitiveType(name="number")
39
+ * - Arithmetic operations (uses C# promotion rules)
40
+ * - Unary +/- (preserves operand kind)
41
+ * - Ternary (requires both branches to have same kind)
42
+ * - Parentheses (pass through)
43
+ * - numericNarrowing expressions (uses targetKind)
44
+ *
45
+ * Returns "Unknown" for expressions that cannot be classified.
46
+ */
47
+ export const classifyNumericExpr = (expr) => {
48
+ switch (expr.kind) {
49
+ case "literal": {
50
+ // Check numericIntent on literal expressions
51
+ if (typeof expr.value === "number" && expr.numericIntent) {
52
+ return expr.numericIntent === "Int32" ? "Int32" : "Double";
53
+ }
54
+ // Non-numeric literals
55
+ return "Unknown";
56
+ }
57
+ case "identifier": {
58
+ // Check inferredType on identifiers
59
+ if (expr.inferredType?.kind === "primitiveType") {
60
+ if (expr.inferredType.name === "int")
61
+ return "Int32";
62
+ if (expr.inferredType.name === "number")
63
+ return "Double";
64
+ }
65
+ // Also check for CLR numeric reference types
66
+ if (expr.inferredType?.kind === "referenceType") {
67
+ const name = expr.inferredType.name;
68
+ if (name === "Int32" || name === "int")
69
+ return "Int32";
70
+ if (name === "Double" || name === "double")
71
+ return "Double";
72
+ }
73
+ return "Unknown";
74
+ }
75
+ case "unary": {
76
+ // Unary +/- preserves operand kind
77
+ if (expr.operator === "+" || expr.operator === "-") {
78
+ return classifyNumericExpr(expr.expression);
79
+ }
80
+ // Bitwise NOT (~) produces Int32
81
+ if (expr.operator === "~") {
82
+ return "Int32";
83
+ }
84
+ return "Unknown";
85
+ }
86
+ case "binary": {
87
+ // Only classify arithmetic operators
88
+ if (!ARITHMETIC_OPERATORS.has(expr.operator)) {
89
+ return "Unknown";
90
+ }
91
+ const leftKind = classifyNumericExpr(expr.left);
92
+ const rightKind = classifyNumericExpr(expr.right);
93
+ // If either is Unknown, we can't classify
94
+ if (leftKind === "Unknown" || rightKind === "Unknown") {
95
+ return "Unknown";
96
+ }
97
+ // Use C# binary promotion rules
98
+ // Note: getBinaryResultKind returns NumericKind, we map to our simplified type
99
+ const resultKind = getBinaryResultKind(leftKind === "Int32" ? "Int32" : "Double", rightKind === "Int32" ? "Int32" : "Double");
100
+ // Map back to our simplified kind
101
+ if (resultKind === "Double" || resultKind === "Single")
102
+ return "Double";
103
+ return "Int32"; // All integer promotions end up as at least Int32
104
+ }
105
+ case "conditional": {
106
+ // Ternary: both branches must have same kind
107
+ const trueKind = classifyNumericExpr(expr.whenTrue);
108
+ const falseKind = classifyNumericExpr(expr.whenFalse);
109
+ if (trueKind === falseKind)
110
+ return trueKind;
111
+ // Mismatched branches - use promotion
112
+ if (trueKind === "Double" || falseKind === "Double")
113
+ return "Double";
114
+ return "Unknown";
115
+ }
116
+ case "numericNarrowing": {
117
+ // numericNarrowing has explicit targetKind
118
+ if (expr.targetKind === "Int32")
119
+ return "Int32";
120
+ if (expr.targetKind === "Double")
121
+ return "Double";
122
+ // Other numeric kinds (Byte, Int64, etc.) - treat as Unknown for this pass
123
+ return "Unknown";
124
+ }
125
+ case "call": {
126
+ // Check return type
127
+ if (expr.inferredType?.kind === "primitiveType") {
128
+ if (expr.inferredType.name === "int")
129
+ return "Int32";
130
+ if (expr.inferredType.name === "number")
131
+ return "Double";
132
+ }
133
+ return "Unknown";
134
+ }
135
+ case "memberAccess": {
136
+ // Check inferredType for member access results
137
+ if (expr.inferredType?.kind === "primitiveType") {
138
+ if (expr.inferredType.name === "int")
139
+ return "Int32";
140
+ if (expr.inferredType.name === "number")
141
+ return "Double";
142
+ }
143
+ return "Unknown";
144
+ }
145
+ case "update": {
146
+ // ++/-- on int produces int
147
+ const operandKind = classifyNumericExpr(expr.expression);
148
+ return operandKind;
149
+ }
150
+ default:
151
+ return "Unknown";
152
+ }
153
+ };
154
+ /**
155
+ * Check if an expression has explicit double intent.
156
+ * This is true when:
157
+ * - It's a numericNarrowing with targetKind "Double" (i.e., `42 as number`)
158
+ * - It's a literal with numericIntent "Double" (i.e., `42.0`)
159
+ *
160
+ * Used to exempt explicit casts from TSN5110 errors.
161
+ */
162
+ export const hasExplicitDoubleIntent = (expr) => {
163
+ // Case 1: numericNarrowing targeting Double (e.g., `42 as number`, `42 as double`)
164
+ if (expr.kind === "numericNarrowing" && expr.targetKind === "Double") {
165
+ return true;
166
+ }
167
+ // Case 2: Literal with double lexeme (e.g., `42.0`)
168
+ if (expr.kind === "literal" &&
169
+ typeof expr.value === "number" &&
170
+ expr.numericIntent === "Double") {
171
+ return true;
172
+ }
173
+ return false;
174
+ };
175
+ /**
176
+ * Create a source location for a module
177
+ */
178
+ const moduleLocation = (ctx) => ({
179
+ file: ctx.filePath,
180
+ line: 1,
181
+ column: 1,
182
+ length: 1,
183
+ });
184
+ /**
185
+ * Check if a type is "number" (which ALWAYS means double)
186
+ *
187
+ * INVARIANT A: "number" always means C# "double". No exceptions.
188
+ * INVARIANT B: "int" is a distinct primitive type, NOT number with numericIntent.
189
+ */
190
+ const isNumberType = (type) => {
191
+ if (!type)
192
+ return false;
193
+ // primitiveType(name="number") is ALWAYS double
194
+ return type.kind === "primitiveType" && type.name === "number";
195
+ };
196
+ /**
197
+ * Extract the expected type of a property from a structural type.
198
+ *
199
+ * Used for validating object literal properties against their expected types.
200
+ * Returns undefined if the property type cannot be determined (conservative).
201
+ *
202
+ * Handles:
203
+ * - objectType: inline object types like `{ x: number }`
204
+ * - referenceType with structuralMembers: interfaces and type aliases
205
+ */
206
+ const tryGetObjectPropertyType = (expectedType, propName) => {
207
+ if (!expectedType)
208
+ return undefined;
209
+ // Structural object type: objectType has members directly
210
+ if (expectedType.kind === "objectType") {
211
+ const member = expectedType.members.find((m) => m.kind === "propertySignature" && m.name === propName);
212
+ return member?.type;
213
+ }
214
+ // Reference type with structural members (interfaces, type aliases)
215
+ if (expectedType.kind === "referenceType" && expectedType.structuralMembers) {
216
+ const member = expectedType.structuralMembers.find((m) => m.kind === "propertySignature" && m.name === propName);
217
+ return member?.type;
218
+ }
219
+ return undefined;
220
+ };
221
+ /**
222
+ * Extract the expected type of a tuple element at a given index.
223
+ *
224
+ * Used for validating tuple literal elements against their expected types.
225
+ * Returns undefined if the element type cannot be determined.
226
+ */
227
+ const tryGetTupleElementType = (expectedType, index) => {
228
+ if (!expectedType)
229
+ return undefined;
230
+ if (expectedType.kind === "tupleType") {
231
+ return expectedType.elementTypes[index];
232
+ }
233
+ return undefined;
234
+ };
235
+ /**
236
+ * Check if an expression is an integer expression (Int32).
237
+ * Uses the expression classifier to handle composed expressions.
238
+ */
239
+ const isIntegerExpression = (expr) => {
240
+ return classifyNumericExpr(expr) === "Int32";
241
+ };
242
+ /**
243
+ * Check if an expression needs coercion to match an expected double type.
244
+ * Returns true if:
245
+ * - Expected type is "number" (double)
246
+ * - Expression classifies as Int32
247
+ * - Expression does NOT have explicit double intent (cast exemption)
248
+ */
249
+ const needsCoercion = (expr, expectedType) => {
250
+ // Only check if expected type is unadorned "number" (meaning double)
251
+ if (!isNumberType(expectedType)) {
252
+ return false;
253
+ }
254
+ // Check for explicit double intent (e.g., `42 as number`, `42.0`)
255
+ // These are explicitly allowed even though they classify as Int32
256
+ if (hasExplicitDoubleIntent(expr)) {
257
+ return false;
258
+ }
259
+ // Check if the expression classifies as Int32
260
+ return isIntegerExpression(expr);
261
+ };
262
+ /**
263
+ * Get a human-readable description of an expression for error messages.
264
+ */
265
+ const describeExpression = (expr) => {
266
+ switch (expr.kind) {
267
+ case "literal":
268
+ return `literal '${expr.raw ?? String(expr.value)}'`;
269
+ case "identifier":
270
+ return `identifier '${expr.name}'`;
271
+ case "binary":
272
+ return `arithmetic expression`;
273
+ case "unary":
274
+ return `unary expression`;
275
+ case "conditional":
276
+ return `ternary expression`;
277
+ case "call":
278
+ return `call result`;
279
+ case "memberAccess":
280
+ return typeof expr.property === "string"
281
+ ? `property '${expr.property}'`
282
+ : `computed property`;
283
+ default:
284
+ return `expression`;
285
+ }
286
+ };
287
+ /**
288
+ * Emit an error diagnostic for int→double coercion
289
+ */
290
+ const emitCoercionError = (expr, ctx, context) => {
291
+ const location = expr.sourceSpan ?? moduleLocation(ctx);
292
+ const description = describeExpression(expr);
293
+ ctx.diagnostics.push(createDiagnostic("TSN5110", "error", `Integer ${description} cannot be implicitly converted to 'number' (double) ${context}`, location, expr.kind === "literal"
294
+ ? `Use a double literal (e.g., '${expr.raw ?? expr.value}.0') or explicit cast ('${expr.raw ?? expr.value} as number').`
295
+ : `Use an explicit cast (e.g., 'expr as number') to convert to double.`));
296
+ };
297
+ /**
298
+ * Validate an expression in a context where a specific type is expected.
299
+ * This is the core of the strict coercion check.
300
+ */
301
+ const validateExpression = (expr, expectedType, ctx, context) => {
302
+ // Check for direct int→double coercion
303
+ if (needsCoercion(expr, expectedType)) {
304
+ emitCoercionError(expr, ctx, context);
305
+ return;
306
+ }
307
+ // Recursively check sub-expressions based on kind
308
+ switch (expr.kind) {
309
+ case "array": {
310
+ // For tuple types, validate each element against its specific expected type
311
+ if (expectedType?.kind === "tupleType") {
312
+ expr.elements.forEach((el, i) => {
313
+ if (el && el.kind !== "spread") {
314
+ const tupleElementType = tryGetTupleElementType(expectedType, i);
315
+ validateExpression(el, tupleElementType, ctx, `in tuple element ${i}`);
316
+ }
317
+ });
318
+ }
319
+ else {
320
+ // For array types, check each element against the element type
321
+ const elementType = expectedType?.kind === "arrayType"
322
+ ? expectedType.elementType
323
+ : undefined;
324
+ expr.elements.forEach((el, i) => {
325
+ if (el && el.kind !== "spread") {
326
+ validateExpression(el, elementType, ctx, `in array element ${i}`);
327
+ }
328
+ });
329
+ }
330
+ break;
331
+ }
332
+ case "object": {
333
+ // For object literals, check each property against expected property type
334
+ // Uses contextual expectedType only - no guessing
335
+ expr.properties.forEach((prop) => {
336
+ if (prop.kind === "spread") {
337
+ // For spreads, scan for nested call expressions
338
+ scanExpressionForCalls(prop.expression, ctx);
339
+ }
340
+ else {
341
+ // Only handle string keys (not computed expressions)
342
+ if (typeof prop.key === "string") {
343
+ // Get expected type for this property from contextual type
344
+ const expectedPropType = tryGetObjectPropertyType(expectedType, prop.key);
345
+ if (expectedPropType) {
346
+ validateExpression(prop.value, expectedPropType, ctx, `in property '${prop.key}'`);
347
+ }
348
+ else {
349
+ // Can't determine property type - scan for nested calls
350
+ scanExpressionForCalls(prop.value, ctx);
351
+ }
352
+ }
353
+ else {
354
+ // Computed property key - can't resolve type, scan for calls
355
+ scanExpressionForCalls(prop.value, ctx);
356
+ }
357
+ }
358
+ });
359
+ break;
360
+ }
361
+ case "conditional": {
362
+ // Check both branches
363
+ validateExpression(expr.whenTrue, expectedType, ctx, context);
364
+ validateExpression(expr.whenFalse, expectedType, ctx, context);
365
+ break;
366
+ }
367
+ case "logical": {
368
+ // For ?? and ||, the result could be either operand
369
+ if (expr.operator === "??" || expr.operator === "||") {
370
+ validateExpression(expr.left, expectedType, ctx, context);
371
+ validateExpression(expr.right, expectedType, ctx, context);
372
+ }
373
+ break;
374
+ }
375
+ case "call": {
376
+ // Check each argument against expected parameter type
377
+ if (expr.parameterTypes) {
378
+ expr.arguments.forEach((arg, i) => {
379
+ if (arg.kind !== "spread" && expr.parameterTypes?.[i]) {
380
+ validateExpression(arg, expr.parameterTypes[i], ctx, `in argument ${i + 1}`);
381
+ }
382
+ });
383
+ }
384
+ break;
385
+ }
386
+ // Other expression kinds don't need recursive checking for this pass
387
+ }
388
+ };
389
+ /**
390
+ * Scan an expression tree for call expressions and validate their arguments.
391
+ * This is used for expressions without an explicit type context.
392
+ */
393
+ const scanExpressionForCalls = (expr, ctx) => {
394
+ switch (expr.kind) {
395
+ case "call": {
396
+ // Validate call arguments against parameter types
397
+ if (expr.parameterTypes) {
398
+ expr.arguments.forEach((arg, i) => {
399
+ if (arg.kind !== "spread" && expr.parameterTypes?.[i]) {
400
+ validateExpression(arg, expr.parameterTypes[i], ctx, `in argument ${i + 1}`);
401
+ }
402
+ });
403
+ }
404
+ // Also scan the callee for nested calls
405
+ scanExpressionForCalls(expr.callee, ctx);
406
+ // Scan arguments for nested calls
407
+ expr.arguments.forEach((arg) => {
408
+ if (arg.kind !== "spread") {
409
+ scanExpressionForCalls(arg, ctx);
410
+ }
411
+ });
412
+ break;
413
+ }
414
+ case "array": {
415
+ expr.elements.forEach((el) => {
416
+ if (el && el.kind !== "spread") {
417
+ scanExpressionForCalls(el, ctx);
418
+ }
419
+ });
420
+ break;
421
+ }
422
+ case "object": {
423
+ expr.properties.forEach((prop) => {
424
+ if (prop.kind !== "spread") {
425
+ scanExpressionForCalls(prop.value, ctx);
426
+ }
427
+ });
428
+ break;
429
+ }
430
+ case "binary":
431
+ scanExpressionForCalls(expr.left, ctx);
432
+ scanExpressionForCalls(expr.right, ctx);
433
+ break;
434
+ case "unary":
435
+ scanExpressionForCalls(expr.expression, ctx);
436
+ break;
437
+ case "update":
438
+ scanExpressionForCalls(expr.expression, ctx);
439
+ break;
440
+ case "conditional":
441
+ scanExpressionForCalls(expr.condition, ctx);
442
+ scanExpressionForCalls(expr.whenTrue, ctx);
443
+ scanExpressionForCalls(expr.whenFalse, ctx);
444
+ break;
445
+ case "logical":
446
+ scanExpressionForCalls(expr.left, ctx);
447
+ scanExpressionForCalls(expr.right, ctx);
448
+ break;
449
+ case "memberAccess":
450
+ scanExpressionForCalls(expr.object, ctx);
451
+ // For computed access, property is an expression
452
+ if (expr.isComputed && typeof expr.property !== "string") {
453
+ scanExpressionForCalls(expr.property, ctx);
454
+ }
455
+ break;
456
+ case "arrowFunction":
457
+ // Arrow function body can be expression or block
458
+ if ("kind" in expr.body && expr.body.kind !== "blockStatement") {
459
+ scanExpressionForCalls(expr.body, ctx);
460
+ }
461
+ break;
462
+ case "new":
463
+ expr.arguments.forEach((arg) => {
464
+ if (arg.kind !== "spread") {
465
+ scanExpressionForCalls(arg, ctx);
466
+ }
467
+ });
468
+ break;
469
+ case "await":
470
+ scanExpressionForCalls(expr.expression, ctx);
471
+ break;
472
+ case "assignment":
473
+ scanExpressionForCalls(expr.right, ctx);
474
+ break;
475
+ case "numericNarrowing":
476
+ scanExpressionForCalls(expr.expression, ctx);
477
+ break;
478
+ case "yield":
479
+ if (expr.expression) {
480
+ scanExpressionForCalls(expr.expression, ctx);
481
+ }
482
+ break;
483
+ // Leaf expressions: literal, identifier, this - no nested calls
484
+ default:
485
+ break;
486
+ }
487
+ };
488
+ /**
489
+ * Process a statement, checking for int→double coercion at intent sites.
490
+ */
491
+ const processStatement = (stmt, ctx) => {
492
+ switch (stmt.kind) {
493
+ case "variableDeclaration": {
494
+ for (const decl of stmt.declarations) {
495
+ if (decl.initializer) {
496
+ // Check if there's an explicit type annotation
497
+ if (decl.type) {
498
+ validateExpression(decl.initializer, decl.type, ctx, "in variable initialization");
499
+ }
500
+ else {
501
+ // Even without explicit type, scan for call expressions
502
+ // to check their arguments
503
+ scanExpressionForCalls(decl.initializer, ctx);
504
+ }
505
+ }
506
+ }
507
+ break;
508
+ }
509
+ case "returnStatement": {
510
+ // We'd need function context to know expected return type
511
+ // For now, skip - this requires threading function return type through
512
+ break;
513
+ }
514
+ case "expressionStatement": {
515
+ // Check call expressions for parameter coercion
516
+ if (stmt.expression.kind === "call") {
517
+ const call = stmt.expression;
518
+ // Check each argument against expected parameter type
519
+ if (call.parameterTypes) {
520
+ call.arguments.forEach((arg, i) => {
521
+ if (arg.kind !== "spread" && call.parameterTypes?.[i]) {
522
+ validateExpression(arg, call.parameterTypes[i], ctx, `in argument ${i + 1}`);
523
+ }
524
+ });
525
+ }
526
+ }
527
+ break;
528
+ }
529
+ case "functionDeclaration": {
530
+ // Check default parameters for int→double coercion
531
+ for (const param of stmt.parameters) {
532
+ if (param.initializer && param.type) {
533
+ validateExpression(param.initializer, param.type, ctx, "in default parameter");
534
+ }
535
+ }
536
+ // Process function body with return type context
537
+ processStatementWithReturnType(stmt.body, stmt.returnType, ctx);
538
+ break;
539
+ }
540
+ case "classDeclaration": {
541
+ for (const member of stmt.members) {
542
+ if (member.kind === "methodDeclaration") {
543
+ // Check default parameters for int→double coercion
544
+ for (const param of member.parameters) {
545
+ if (param.initializer && param.type) {
546
+ validateExpression(param.initializer, param.type, ctx, "in default parameter");
547
+ }
548
+ }
549
+ if (member.body) {
550
+ processStatementWithReturnType(member.body, member.returnType, ctx);
551
+ }
552
+ }
553
+ if (member.kind === "propertyDeclaration" && member.initializer) {
554
+ validateExpression(member.initializer, member.type, ctx, "in property initialization");
555
+ }
556
+ }
557
+ break;
558
+ }
559
+ case "blockStatement": {
560
+ for (const s of stmt.statements) {
561
+ processStatement(s, ctx);
562
+ }
563
+ break;
564
+ }
565
+ case "ifStatement": {
566
+ processStatement(stmt.thenStatement, ctx);
567
+ if (stmt.elseStatement) {
568
+ processStatement(stmt.elseStatement, ctx);
569
+ }
570
+ break;
571
+ }
572
+ case "whileStatement":
573
+ case "forStatement":
574
+ case "forOfStatement": {
575
+ processStatement(stmt.body, ctx);
576
+ break;
577
+ }
578
+ case "tryStatement": {
579
+ processStatement(stmt.tryBlock, ctx);
580
+ if (stmt.catchClause) {
581
+ processStatement(stmt.catchClause.body, ctx);
582
+ }
583
+ if (stmt.finallyBlock) {
584
+ processStatement(stmt.finallyBlock, ctx);
585
+ }
586
+ break;
587
+ }
588
+ case "switchStatement": {
589
+ for (const caseClause of stmt.cases) {
590
+ for (const s of caseClause.statements) {
591
+ processStatement(s, ctx);
592
+ }
593
+ }
594
+ break;
595
+ }
596
+ }
597
+ };
598
+ /**
599
+ * Process a statement with return type context for checking return statements
600
+ */
601
+ const processStatementWithReturnType = (stmt, returnType, ctx) => {
602
+ switch (stmt.kind) {
603
+ case "returnStatement": {
604
+ if (stmt.expression && returnType) {
605
+ validateExpression(stmt.expression, returnType, ctx, "in return statement");
606
+ }
607
+ break;
608
+ }
609
+ case "blockStatement": {
610
+ for (const s of stmt.statements) {
611
+ processStatementWithReturnType(s, returnType, ctx);
612
+ }
613
+ break;
614
+ }
615
+ case "ifStatement": {
616
+ processStatementWithReturnType(stmt.thenStatement, returnType, ctx);
617
+ if (stmt.elseStatement) {
618
+ processStatementWithReturnType(stmt.elseStatement, returnType, ctx);
619
+ }
620
+ break;
621
+ }
622
+ case "tryStatement": {
623
+ processStatementWithReturnType(stmt.tryBlock, returnType, ctx);
624
+ if (stmt.catchClause) {
625
+ processStatementWithReturnType(stmt.catchClause.body, returnType, ctx);
626
+ }
627
+ if (stmt.finallyBlock) {
628
+ processStatementWithReturnType(stmt.finallyBlock, returnType, ctx);
629
+ }
630
+ break;
631
+ }
632
+ case "switchStatement": {
633
+ for (const caseClause of stmt.cases) {
634
+ for (const s of caseClause.statements) {
635
+ processStatementWithReturnType(s, returnType, ctx);
636
+ }
637
+ }
638
+ break;
639
+ }
640
+ default:
641
+ // For other statements, use regular processing
642
+ processStatement(stmt, ctx);
643
+ }
644
+ };
645
+ /**
646
+ * Run numeric coercion pass on a module.
647
+ */
648
+ const processModule = (module) => {
649
+ const ctx = {
650
+ filePath: module.filePath,
651
+ diagnostics: [],
652
+ };
653
+ // Process module body
654
+ for (const stmt of module.body) {
655
+ processStatement(stmt, ctx);
656
+ }
657
+ // Process exports
658
+ for (const exp of module.exports) {
659
+ if (exp.kind === "declaration") {
660
+ processStatement(exp.declaration, ctx);
661
+ }
662
+ }
663
+ return {
664
+ ok: ctx.diagnostics.length === 0,
665
+ module,
666
+ diagnostics: ctx.diagnostics,
667
+ };
668
+ };
669
+ /**
670
+ * Run numeric coercion validation on all modules.
671
+ *
672
+ * HARD GATE: If any diagnostics are returned, the emitter MUST NOT run.
673
+ */
674
+ export const runNumericCoercionPass = (modules) => {
675
+ const allDiagnostics = [];
676
+ for (const module of modules) {
677
+ const result = processModule(module);
678
+ allDiagnostics.push(...result.diagnostics);
679
+ }
680
+ return {
681
+ ok: allDiagnostics.length === 0,
682
+ modules, // Pass through unchanged - this pass only validates, doesn't transform
683
+ diagnostics: allDiagnostics,
684
+ };
685
+ };
686
+ //# sourceMappingURL=numeric-coercion-pass.js.map