@tsonic/frontend 0.0.4 → 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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/ir/builder/exports.d.ts.map +1 -1
- package/dist/ir/builder/exports.js +8 -6
- package/dist/ir/builder/exports.js.map +1 -1
- package/dist/ir/builder/imports.d.ts.map +1 -1
- package/dist/ir/builder/imports.js +6 -6
- package/dist/ir/builder/imports.js.map +1 -1
- package/dist/ir/builder/statements.d.ts +7 -1
- package/dist/ir/builder/statements.d.ts.map +1 -1
- package/dist/ir/builder/statements.js +18 -5
- package/dist/ir/builder/statements.js.map +1 -1
- package/dist/ir/builder.test.js +70 -5
- package/dist/ir/builder.test.js.map +1 -1
- package/dist/ir/converters/anonymous-synthesis.d.ts +76 -0
- package/dist/ir/converters/anonymous-synthesis.d.ts.map +1 -0
- package/dist/ir/converters/anonymous-synthesis.js +307 -0
- package/dist/ir/converters/anonymous-synthesis.js.map +1 -0
- package/dist/ir/converters/expressions/access.d.ts.map +1 -1
- package/dist/ir/converters/expressions/access.js +65 -2
- package/dist/ir/converters/expressions/access.js.map +1 -1
- package/dist/ir/converters/expressions/calls.d.ts.map +1 -1
- package/dist/ir/converters/expressions/calls.js +96 -1
- package/dist/ir/converters/expressions/calls.js.map +1 -1
- package/dist/ir/converters/expressions/collections.d.ts +3 -0
- package/dist/ir/converters/expressions/collections.d.ts.map +1 -1
- package/dist/ir/converters/expressions/collections.js +43 -2
- package/dist/ir/converters/expressions/collections.js.map +1 -1
- package/dist/ir/converters/expressions/functions.d.ts.map +1 -1
- package/dist/ir/converters/expressions/functions.js +52 -5
- package/dist/ir/converters/expressions/functions.js.map +1 -1
- package/dist/ir/converters/expressions/helpers.d.ts +10 -0
- package/dist/ir/converters/expressions/helpers.d.ts.map +1 -1
- package/dist/ir/converters/expressions/helpers.js +173 -7
- package/dist/ir/converters/expressions/helpers.js.map +1 -1
- package/dist/ir/converters/expressions/index.d.ts +1 -1
- package/dist/ir/converters/expressions/index.d.ts.map +1 -1
- package/dist/ir/converters/expressions/index.js +1 -1
- package/dist/ir/converters/expressions/index.js.map +1 -1
- package/dist/ir/converters/expressions/literals.d.ts.map +1 -1
- package/dist/ir/converters/expressions/literals.js +2 -1
- package/dist/ir/converters/expressions/literals.js.map +1 -1
- package/dist/ir/converters/expressions/numeric-recovery.test.d.ts +14 -0
- package/dist/ir/converters/expressions/numeric-recovery.test.d.ts.map +1 -0
- package/dist/ir/converters/expressions/numeric-recovery.test.js +286 -0
- package/dist/ir/converters/expressions/numeric-recovery.test.js.map +1 -0
- package/dist/ir/converters/expressions/operators.d.ts.map +1 -1
- package/dist/ir/converters/expressions/operators.js +16 -2
- package/dist/ir/converters/expressions/operators.js.map +1 -1
- package/dist/ir/converters/expressions/other.d.ts.map +1 -1
- package/dist/ir/converters/expressions/other.js +4 -1
- package/dist/ir/converters/expressions/other.js.map +1 -1
- package/dist/ir/converters/statements/control/conditionals.d.ts.map +1 -1
- package/dist/ir/converters/statements/control/conditionals.js +4 -6
- package/dist/ir/converters/statements/control/conditionals.js.map +1 -1
- package/dist/ir/converters/statements/control/loops.d.ts.map +1 -1
- package/dist/ir/converters/statements/control/loops.js +6 -5
- package/dist/ir/converters/statements/control/loops.js.map +1 -1
- package/dist/ir/converters/statements/declarations/interfaces.d.ts +4 -3
- package/dist/ir/converters/statements/declarations/interfaces.d.ts.map +1 -1
- package/dist/ir/converters/statements/declarations/interfaces.js +55 -1
- package/dist/ir/converters/statements/declarations/interfaces.js.map +1 -1
- package/dist/ir/converters/statements/declarations/type-aliases.d.ts +8 -3
- package/dist/ir/converters/statements/declarations/type-aliases.d.ts.map +1 -1
- package/dist/ir/converters/statements/declarations/type-aliases.js +12 -2
- package/dist/ir/converters/statements/declarations/type-aliases.js.map +1 -1
- package/dist/ir/converters/synthetic-types.d.ts +38 -0
- package/dist/ir/converters/synthetic-types.d.ts.map +1 -0
- package/dist/ir/converters/synthetic-types.js +123 -0
- package/dist/ir/converters/synthetic-types.js.map +1 -0
- package/dist/ir/expression-converter.d.ts.map +1 -1
- package/dist/ir/expression-converter.js +81 -9
- package/dist/ir/expression-converter.js.map +1 -1
- package/dist/ir/statement-converter.d.ts +16 -1
- package/dist/ir/statement-converter.d.ts.map +1 -1
- package/dist/ir/statement-converter.js +32 -0
- package/dist/ir/statement-converter.js.map +1 -1
- package/dist/ir/type-converter/arrays.d.ts.map +1 -1
- package/dist/ir/type-converter/arrays.js +1 -0
- package/dist/ir/type-converter/arrays.js.map +1 -1
- package/dist/ir/type-converter/index.d.ts +1 -1
- package/dist/ir/type-converter/index.d.ts.map +1 -1
- package/dist/ir/type-converter/index.js +1 -1
- package/dist/ir/type-converter/index.js.map +1 -1
- package/dist/ir/type-converter/inference.d.ts +12 -0
- package/dist/ir/type-converter/inference.d.ts.map +1 -1
- package/dist/ir/type-converter/inference.js +172 -23
- package/dist/ir/type-converter/inference.js.map +1 -1
- package/dist/ir/type-converter/literals.d.ts.map +1 -1
- package/dist/ir/type-converter/literals.js +2 -1
- package/dist/ir/type-converter/literals.js.map +1 -1
- package/dist/ir/type-converter/objects.d.ts.map +1 -1
- package/dist/ir/type-converter/objects.js +2 -1
- package/dist/ir/type-converter/objects.js.map +1 -1
- package/dist/ir/type-converter/orchestrator.d.ts +2 -2
- package/dist/ir/type-converter/orchestrator.d.ts.map +1 -1
- package/dist/ir/type-converter/orchestrator.js +55 -1
- package/dist/ir/type-converter/orchestrator.js.map +1 -1
- package/dist/ir/type-converter/references.d.ts.map +1 -1
- package/dist/ir/type-converter/references.js +14 -18
- package/dist/ir/type-converter/references.js.map +1 -1
- package/dist/ir/type-converter.d.ts +1 -1
- package/dist/ir/type-converter.d.ts.map +1 -1
- package/dist/ir/type-converter.js +1 -1
- package/dist/ir/type-converter.js.map +1 -1
- package/dist/ir/types/expressions.d.ts +110 -1
- package/dist/ir/types/expressions.d.ts.map +1 -1
- package/dist/ir/types/guards.d.ts.map +1 -1
- package/dist/ir/types/guards.js +2 -0
- package/dist/ir/types/guards.js.map +1 -1
- package/dist/ir/types/index.d.ts +5 -3
- package/dist/ir/types/index.d.ts.map +1 -1
- package/dist/ir/types/index.js +1 -0
- package/dist/ir/types/index.js.map +1 -1
- package/dist/ir/types/ir-types.d.ts +33 -1
- package/dist/ir/types/ir-types.d.ts.map +1 -1
- package/dist/ir/types/numeric-kind.d.ts +68 -0
- package/dist/ir/types/numeric-kind.d.ts.map +1 -0
- package/dist/ir/types/numeric-kind.js +170 -0
- package/dist/ir/types/numeric-kind.js.map +1 -0
- package/dist/ir/types/statements.d.ts +38 -1
- package/dist/ir/types/statements.d.ts.map +1 -1
- package/dist/ir/types.d.ts +2 -1
- package/dist/ir/types.d.ts.map +1 -1
- package/dist/ir/types.js +2 -0
- package/dist/ir/types.js.map +1 -1
- package/dist/ir/validation/index.d.ts +7 -0
- package/dist/ir/validation/index.d.ts.map +1 -0
- package/dist/ir/validation/index.js +7 -0
- package/dist/ir/validation/index.js.map +1 -0
- package/dist/ir/validation/numeric-invariants.test.d.ts +15 -0
- package/dist/ir/validation/numeric-invariants.test.d.ts.map +1 -0
- package/dist/ir/validation/numeric-invariants.test.js +598 -0
- package/dist/ir/validation/numeric-invariants.test.js.map +1 -0
- package/dist/ir/validation/numeric-proof-pass.d.ts +37 -0
- package/dist/ir/validation/numeric-proof-pass.d.ts.map +1 -0
- package/dist/ir/validation/numeric-proof-pass.js +889 -0
- package/dist/ir/validation/numeric-proof-pass.js.map +1 -0
- package/dist/ir/validation/soundness-gate.d.ts +26 -0
- package/dist/ir/validation/soundness-gate.d.ts.map +1 -0
- package/dist/ir/validation/soundness-gate.js +551 -0
- package/dist/ir/validation/soundness-gate.js.map +1 -0
- package/dist/ir/validation/soundness-gate.test.d.ts +13 -0
- package/dist/ir/validation/soundness-gate.test.d.ts.map +1 -0
- package/dist/ir/validation/soundness-gate.test.js +315 -0
- package/dist/ir/validation/soundness-gate.test.js.map +1 -0
- package/dist/ir/validation/yield-lowering-pass.d.ts +40 -0
- package/dist/ir/validation/yield-lowering-pass.d.ts.map +1 -0
- package/dist/ir/validation/yield-lowering-pass.js +548 -0
- package/dist/ir/validation/yield-lowering-pass.js.map +1 -0
- package/dist/ir/validation/yield-lowering-pass.test.d.ts +12 -0
- package/dist/ir/validation/yield-lowering-pass.test.d.ts.map +1 -0
- package/dist/ir/validation/yield-lowering-pass.test.js +761 -0
- package/dist/ir/validation/yield-lowering-pass.test.js.map +1 -0
- package/dist/program/bindings.d.ts +5 -0
- package/dist/program/bindings.d.ts.map +1 -1
- package/dist/program/bindings.js +12 -1
- package/dist/program/bindings.js.map +1 -1
- package/dist/program/dependency-graph.d.ts +3 -0
- package/dist/program/dependency-graph.d.ts.map +1 -1
- package/dist/program/dependency-graph.js +28 -4
- package/dist/program/dependency-graph.js.map +1 -1
- package/dist/program/index.d.ts +1 -1
- package/dist/program/index.d.ts.map +1 -1
- package/dist/program/index.js.map +1 -1
- package/dist/program.d.ts +1 -0
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js.map +1 -1
- package/dist/resolver/clr-bindings-resolver.d.ts +9 -0
- package/dist/resolver/clr-bindings-resolver.d.ts.map +1 -1
- package/dist/resolver/clr-bindings-resolver.js +52 -3
- package/dist/resolver/clr-bindings-resolver.js.map +1 -1
- package/dist/types/diagnostic.d.ts +1 -1
- package/dist/types/diagnostic.d.ts.map +1 -1
- package/dist/types/diagnostic.js.map +1 -1
- package/dist/validation/static-safety.d.ts +6 -1
- package/dist/validation/static-safety.d.ts.map +1 -1
- package/dist/validation/static-safety.js +144 -36
- package/dist/validation/static-safety.js.map +1 -1
- package/dist/validation/unsupported-utility-types.d.ts +38 -0
- package/dist/validation/unsupported-utility-types.d.ts.map +1 -0
- package/dist/validation/unsupported-utility-types.js +53 -0
- package/dist/validation/unsupported-utility-types.js.map +1 -0
- package/dist/validator.test.js +304 -14
- package/dist/validator.test.js.map +1 -1
- package/package.json +1 -1
- package/dist/types/parameter-modifiers.d.ts +0 -55
- package/dist/types/parameter-modifiers.d.ts.map +0 -1
- package/dist/types/parameter-modifiers.js +0 -148
- 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
|