@scriptc/compiler 0.3.3 → 0.3.4
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/ambient/scriptc-node-fallback.d.ts +713 -26
- package/dist/backend/cycle-mutability.d.ts +5 -0
- package/dist/backend/cycle-mutability.js +62 -3
- package/dist/backend/cycle-mutability.js.map +1 -1
- package/dist/backend/executable-features.js +1 -0
- package/dist/backend/executable-features.js.map +1 -1
- package/dist/backend/library-pack.js +1 -0
- package/dist/backend/library-pack.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.d.ts +5 -1
- package/dist/backend/llvm/call-lifetimes.js +11 -3
- package/dist/backend/llvm/call-lifetimes.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.test.js +31 -1
- package/dist/backend/llvm/call-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +5 -0
- package/dist/backend/llvm/common.js +7 -0
- package/dist/backend/llvm/common.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.js +2 -2
- package/dist/backend/llvm/dense-array-access.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +40 -1
- package/dist/backend/llvm/dyn.js +178 -27
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +12 -0
- package/dist/backend/llvm/emitter.js +164 -27
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.d.ts +9 -0
- package/dist/backend/llvm/expr-bytes.js +158 -63
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-calls.d.ts +3 -1
- package/dist/backend/llvm/expr-calls.js +49 -1
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +16 -5
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/intern-tables.test.d.ts +1 -0
- package/dist/backend/llvm/intern-tables.test.js +77 -0
- package/dist/backend/llvm/intern-tables.test.js.map +1 -0
- package/dist/backend/llvm/lib-datagram.js +10 -0
- package/dist/backend/llvm/lib-datagram.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +92 -1
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +51 -0
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-network.js +16 -0
- package/dist/backend/llvm/lib-network.js.map +1 -1
- package/dist/backend/llvm/lib-shared.d.ts +5 -0
- package/dist/backend/llvm/lib-shared.js +80 -1
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/lib-stream.js +87 -0
- package/dist/backend/llvm/lib-stream.js.map +1 -1
- package/dist/backend/llvm/lib-test.js +9 -0
- package/dist/backend/llvm/lib-test.js.map +1 -1
- package/dist/backend/llvm/map-number-lookup.d.ts +4 -0
- package/dist/backend/llvm/map-number-lookup.js +22 -0
- package/dist/backend/llvm/map-number-lookup.js.map +1 -1
- package/dist/backend/llvm/map-number-lookup.test.js +56 -0
- package/dist/backend/llvm/map-number-lookup.test.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.d.ts +9 -0
- package/dist/backend/llvm/map-read-lifetimes.js +89 -1
- package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +129 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/publish.d.ts +5 -0
- package/dist/backend/llvm/publish.js +22 -3
- package/dist/backend/llvm/publish.js.map +1 -1
- package/dist/backend/llvm/reference-writes.js +5 -2
- package/dist/backend/llvm/reference-writes.js.map +1 -1
- package/dist/backend/llvm/regions.d.ts +4 -1
- package/dist/backend/llvm/regions.js +12 -3
- package/dist/backend/llvm/regions.js.map +1 -1
- package/dist/backend/llvm/string-reads.js +2 -2
- package/dist/backend/llvm/string-reads.js.map +1 -1
- package/dist/backend/llvm/string-slices.js +2 -2
- package/dist/backend/llvm/string-slices.js.map +1 -1
- package/dist/backend/may-throw.js +4 -0
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/native/contracts.d.ts +3 -0
- package/dist/backend/native/executable.js +13 -5
- package/dist/backend/native/executable.js.map +1 -1
- package/dist/backend/native/runtime-inputs.d.ts +1 -1
- package/dist/backend/native/runtime-inputs.js +1 -0
- package/dist/backend/native/runtime-inputs.js.map +1 -1
- package/dist/backend/native-codegen-core.d.ts +4 -4
- package/dist/backend/native-codegen-core.js +4 -6
- package/dist/backend/native-codegen-core.js.map +1 -1
- package/dist/backend/native-codegen.test.js +7 -8
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-link-info-core.d.ts +3 -0
- package/dist/backend/native-link-info-core.js.map +1 -1
- package/dist/backend/publication.js +3 -1
- package/dist/backend/publication.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.js +1 -0
- package/dist/backend/runtime-pack-targets.test.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +1 -0
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/target-diagnostics.js +7 -0
- package/dist/backend/target-diagnostics.js.map +1 -1
- package/dist/backend/warm-cache.js +1 -0
- package/dist/backend/warm-cache.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +1 -0
- package/dist/executable/executable-cache.js +1 -0
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +1 -0
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/frontend/experimental-import.d.ts +5 -1
- package/dist/frontend/experimental-import.js +28 -3
- package/dist/frontend/experimental-import.js.map +1 -1
- package/dist/frontend/lowering/assert-instances.d.ts +38 -0
- package/dist/frontend/lowering/assert-instances.js +269 -0
- package/dist/frontend/lowering/assert-instances.js.map +1 -0
- package/dist/frontend/lowering/builtins/compression.d.ts +18 -0
- package/dist/frontend/lowering/builtins/compression.js +175 -2
- package/dist/frontend/lowering/builtins/compression.js.map +1 -1
- package/dist/frontend/lowering/builtins/constants.js +7 -2
- package/dist/frontend/lowering/builtins/constants.js.map +1 -1
- package/dist/frontend/lowering/builtins/crypto.d.ts +6 -0
- package/dist/frontend/lowering/builtins/crypto.js +111 -1
- package/dist/frontend/lowering/builtins/crypto.js.map +1 -1
- package/dist/frontend/lowering/builtins/errors.js +23 -0
- package/dist/frontend/lowering/builtins/errors.js.map +1 -1
- package/dist/frontend/lowering/builtins/timers.d.ts +3 -0
- package/dist/frontend/lowering/builtins/timers.js +109 -23
- package/dist/frontend/lowering/builtins/timers.js.map +1 -1
- package/dist/frontend/lowering/call-signatures.js +6 -0
- package/dist/frontend/lowering/call-signatures.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.js +66 -12
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/intern-tables.d.ts +13 -0
- package/dist/frontend/lowering/intern-tables.js +536 -0
- package/dist/frontend/lowering/intern-tables.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.js +700 -61
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-async-iteration.js +11 -2
- package/dist/frontend/lowering/lower-async-iteration.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +17 -3
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +3 -0
- package/dist/frontend/lowering/lower-classes.js +36 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.js +77 -52
- package/dist/frontend/lowering/lower-dgram.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.js +12 -0
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.d.ts +6 -0
- package/dist/frontend/lowering/lower-inspect.js +112 -39
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-mock.d.ts +11 -0
- package/dist/frontend/lowering/lower-mock.js +504 -0
- package/dist/frontend/lowering/lower-mock.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.js +7 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +3 -0
- package/dist/frontend/lowering/lower-server.js +380 -30
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +23 -2
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.d.ts +10 -6
- package/dist/frontend/lowering/lower-stream.js +786 -27
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lower-test.js +86 -8
- package/dist/frontend/lowering/lower-test.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +22 -0
- package/dist/frontend/lowering/lowerer.js +72 -7
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/pairs-snapshot.d.ts +6 -0
- package/dist/frontend/lowering/pairs-snapshot.js +49 -0
- package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +4 -0
- package/dist/frontend/lowering/surfaces.js +61 -15
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/lowering/zlib-constants.d.ts +1 -0
- package/dist/frontend/lowering/zlib-constants.js +181 -0
- package/dist/frontend/lowering/zlib-constants.js.map +1 -0
- package/dist/frontend/mock-shapes.d.ts +10 -0
- package/dist/frontend/mock-shapes.js +76 -0
- package/dist/frontend/mock-shapes.js.map +1 -0
- package/dist/frontend/pipeline.js +5 -3
- package/dist/frontend/pipeline.js.map +1 -1
- package/dist/frontend/program.js +6 -1
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.d.ts +4 -0
- package/dist/frontend/resolve.js +14 -0
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/tsc-codes.js +1 -0
- package/dist/frontend/tsc-codes.js.map +1 -1
- package/dist/frontend/type-mapper.d.ts +5 -0
- package/dist/frontend/type-mapper.js +102 -9
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/ir/builtin-effects.js +36 -0
- package/dist/ir/builtin-effects.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +147 -8
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/ir.d.ts +20 -1
- package/dist/ir/ir.js +17 -0
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/runtime-features.d.ts +1 -0
- package/dist/ir/runtime-features.js +29 -2
- package/dist/ir/runtime-features.js.map +1 -1
- package/dist/ir/validate.js +195 -5
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/ir-policy.js +1 -0
- package/dist/library/ir-policy.js.map +1 -1
- package/dist/native/cli.js +1 -0
- package/dist/native/cli.js.map +1 -1
- package/dist/native/compiler.js +1 -3
- package/dist/native/compiler.js.map +1 -1
- package/dist/native/prepare.js +1 -0
- package/dist/native/prepare.js.map +1 -1
- package/dist/native/sanitizer.test.js +1 -0
- package/dist/native/sanitizer.test.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +869 -48
|
@@ -27,10 +27,10 @@ import * as ts from "../ts7/adapter.js";
|
|
|
27
27
|
import { resultIsDiscarded } from "./call-position.js";
|
|
28
28
|
import { jsFuncNameOf, own } from "./lowerer.js";
|
|
29
29
|
import { NARROW_FIRST } from "./surfaces.js";
|
|
30
|
-
import { inspectExpr, typeReachesItself } from "./lower-inspect.js";
|
|
30
|
+
import { inspectExpr, typeReachesCycle, typeReachesFunction, typeReachesItself, } from "./lower-inspect.js";
|
|
31
31
|
import { abstractEqualityExpr, abstractEqualitySupported } from "./abstract-equality.js";
|
|
32
32
|
import { BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, REGEX, RUNTIME_ERROR_CLASSES, STRING, VOID, isUnitType, typeEquals, typeKey, } from "../../ir/ir.js";
|
|
33
|
-
import { boolLit, countedFor, numLit, strLit, varRef } from "../../ir/build.js";
|
|
33
|
+
import { boolLit, countedFor, dynUndefinedExpr, numLit, strLit, varRef } from "../../ir/build.js";
|
|
34
34
|
/** node:assert/strict binds the loose NAMES to the strict comparisons —
|
|
35
35
|
* Node's own aliasing (`strict.equal === assert.strictEqual`). */
|
|
36
36
|
const STRICT_ALIASES = {
|
|
@@ -46,6 +46,68 @@ function canonicalAssertMember(module, member) {
|
|
|
46
46
|
return own(STRICT_ALIASES, member) ?? member;
|
|
47
47
|
return member;
|
|
48
48
|
}
|
|
49
|
+
/* Node's AssertionError carries actual, expected and operator. Where the
|
|
50
|
+
* verdict is computed in compiled code, the failure branch records them
|
|
51
|
+
* (assert.record) before the throwing call, so a passing assertion boxes
|
|
52
|
+
* nothing. Values without a checked-dynamic form record undefined, and so
|
|
53
|
+
* do values whose type can be cyclic (the checked-dynamic form is a deep
|
|
54
|
+
* copy, which a cyclic value does not have) or can hold functions (their
|
|
55
|
+
* boxes carry no source names, which the failure message prints). */
|
|
56
|
+
/** A value as the dyn the record carries. */
|
|
57
|
+
function recordedValue(lowerer, value, loc) {
|
|
58
|
+
if (value === null)
|
|
59
|
+
return dynUndefinedExpr(loc);
|
|
60
|
+
if (value.type.kind === "dyn")
|
|
61
|
+
return value;
|
|
62
|
+
if (value.kind === "unitLit" ||
|
|
63
|
+
(lowerer.dynConvertible(value.type) &&
|
|
64
|
+
!typeReachesCycle(lowerer, value.type) &&
|
|
65
|
+
!typeReachesFunction(lowerer, value.type))) {
|
|
66
|
+
return { kind: "dynFrom", value, type: DYN, loc };
|
|
67
|
+
}
|
|
68
|
+
return dynUndefinedExpr(loc);
|
|
69
|
+
}
|
|
70
|
+
/** `if (failing) assert.record(actual, expected, operator)`. */
|
|
71
|
+
function recordOnFailure(lowerer, failing, actual, expected, operator, loc) {
|
|
72
|
+
return {
|
|
73
|
+
kind: "if",
|
|
74
|
+
cond: failing,
|
|
75
|
+
then: [
|
|
76
|
+
{
|
|
77
|
+
kind: "exprStmt",
|
|
78
|
+
expr: {
|
|
79
|
+
kind: "libCall",
|
|
80
|
+
fn: "assert.record",
|
|
81
|
+
args: [
|
|
82
|
+
recordedValue(lowerer, actual, loc),
|
|
83
|
+
recordedValue(lowerer, expected, loc),
|
|
84
|
+
strLit(operator, loc),
|
|
85
|
+
],
|
|
86
|
+
type: VOID,
|
|
87
|
+
loc,
|
|
88
|
+
},
|
|
89
|
+
loc,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
else_: null,
|
|
93
|
+
loc,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** A value held in a hidden local, so the verdict and the record read it once. */
|
|
97
|
+
function stableValue(lowerer, stmts, value, name, loc) {
|
|
98
|
+
if (value.kind === "varRef" ||
|
|
99
|
+
value.kind === "unitLit" ||
|
|
100
|
+
value.kind === "boolLit" ||
|
|
101
|
+
value.kind === "numLit" ||
|
|
102
|
+
value.kind === "strLit")
|
|
103
|
+
return value;
|
|
104
|
+
const local = lowerer.declareHiddenLocal(name, value.type);
|
|
105
|
+
stmts.push({ kind: "varDecl", localId: local.id, init: value, loc });
|
|
106
|
+
return varRef(local.id, value.type, loc);
|
|
107
|
+
}
|
|
108
|
+
function notExpr(value, loc) {
|
|
109
|
+
return { kind: "unary", op: "!", operand: value, type: BOOL, loc };
|
|
110
|
+
}
|
|
49
111
|
/** VOID/never results are usable as statements and concise arrow bodies
|
|
50
112
|
* only (the lower-dgram stance): nothing downstream can consume them. */
|
|
51
113
|
function requireStatementPosition(lowerer, call, what) {
|
|
@@ -90,6 +152,12 @@ export function lowerAssertModuleCall(lowerer, expr, bi, loc) {
|
|
|
90
152
|
return lowerAssertEqual(lowerer, expr, loc, false, true);
|
|
91
153
|
case "notDeepStrictEqual":
|
|
92
154
|
return lowerAssertEqual(lowerer, expr, loc, true, true);
|
|
155
|
+
case "deepEqual":
|
|
156
|
+
return lowerAssertLooseDeep(lowerer, expr, loc, false);
|
|
157
|
+
case "notDeepEqual":
|
|
158
|
+
return lowerAssertLooseDeep(lowerer, expr, loc, true);
|
|
159
|
+
case "partialDeepStrictEqual":
|
|
160
|
+
return lowerAssertPartialDeep(lowerer, expr, loc);
|
|
93
161
|
case "fail":
|
|
94
162
|
return lowerAssertFail(lowerer, expr, loc);
|
|
95
163
|
case "match":
|
|
@@ -164,9 +232,20 @@ function lowerAssertLooseEqual(lowerer, expr, loc, negated) {
|
|
|
164
232
|
};
|
|
165
233
|
const a = stable(left, "%assertLooseA");
|
|
166
234
|
const b = stable(right, "%assertLooseB");
|
|
167
|
-
const equal = abstractEqualityExpr(lowerer, a, b, loc, true);
|
|
168
|
-
const
|
|
169
|
-
|
|
235
|
+
const equal = stableValue(lowerer, stmts, abstractEqualityExpr(lowerer, a, b, loc, true), "%assertLooseEqual", loc);
|
|
236
|
+
const failing = negated ? equal : notExpr(equal, loc);
|
|
237
|
+
stmts.push(recordOnFailure(lowerer, failing, a, b, negated ? "!=" : "==", loc));
|
|
238
|
+
// The operands are inspected only for the failure message.
|
|
239
|
+
const inspectOnFailure = (value) => ({
|
|
240
|
+
kind: "ternary",
|
|
241
|
+
cond: failing,
|
|
242
|
+
then: inspectExpr(lowerer, value.type, value, numLit(0, loc), numLit(2, loc), loc),
|
|
243
|
+
else_: strLit("", loc),
|
|
244
|
+
type: STRING,
|
|
245
|
+
loc,
|
|
246
|
+
});
|
|
247
|
+
const actual = inspectOnFailure(a);
|
|
248
|
+
const expected = inspectOnFailure(b);
|
|
170
249
|
const { msg, hasMsg } = lowerMessageArg(lowerer, expr.arguments[2], loc);
|
|
171
250
|
const result = {
|
|
172
251
|
kind: "libCall",
|
|
@@ -177,6 +256,437 @@ function lowerAssertLooseEqual(lowerer, expr, loc, negated) {
|
|
|
177
256
|
};
|
|
178
257
|
return { kind: "seqExpr", stmts, result, type: VOID, loc };
|
|
179
258
|
}
|
|
259
|
+
/** The two compared operands. A context-free EMPTY array literal on
|
|
260
|
+
* either side has no element type of its own (`deepStrictEqual(
|
|
261
|
+
* ee.listeners('x'), [])` — the checker says any[]): it adopts the OTHER
|
|
262
|
+
* side's array type. Literals have no side effects, so lowering order is
|
|
263
|
+
* unobservable. */
|
|
264
|
+
function lowerComparedOperands(lowerer, aNode, bNode, loc) {
|
|
265
|
+
const emptyArrayLit = (n) => ts.isArrayLiteralExpression(n) && n.elements.length === 0;
|
|
266
|
+
const emptyOf = (t) => ({ kind: "arrayLit", elems: [], type: t, loc });
|
|
267
|
+
// A dyn other side adopts number[] — the dyn array a boxed empty
|
|
268
|
+
// literal builds is elementless, so any elem type serves.
|
|
269
|
+
const adoptedEmpty = (other) => other.kind === "array"
|
|
270
|
+
? emptyOf(other)
|
|
271
|
+
: other.kind === "dyn"
|
|
272
|
+
? emptyOf({ kind: "array", elem: F64 })
|
|
273
|
+
: null;
|
|
274
|
+
if (emptyArrayLit(aNode) && !emptyArrayLit(bNode)) {
|
|
275
|
+
const b = lowerer.lowerExpr(bNode);
|
|
276
|
+
return { a: adoptedEmpty(b.type) ?? lowerer.lowerExpr(aNode), b };
|
|
277
|
+
}
|
|
278
|
+
const a = lowerer.lowerExpr(aNode);
|
|
279
|
+
const b = emptyArrayLit(bNode)
|
|
280
|
+
? (adoptedEmpty(a.type) ?? lowerer.lowerExpr(bNode))
|
|
281
|
+
: lowerer.lowerExpr(bNode);
|
|
282
|
+
return { a, b };
|
|
283
|
+
}
|
|
284
|
+
/** True when a union occurs anywhere in `t`'s value structure. */
|
|
285
|
+
function typeHasUnion(lowerer, t, visiting) {
|
|
286
|
+
switch (t.kind) {
|
|
287
|
+
case "union":
|
|
288
|
+
return true;
|
|
289
|
+
case "array":
|
|
290
|
+
case "set":
|
|
291
|
+
return typeHasUnion(lowerer, t.elem, visiting);
|
|
292
|
+
case "map":
|
|
293
|
+
return typeHasUnion(lowerer, t.key, visiting) || typeHasUnion(lowerer, t.value, visiting);
|
|
294
|
+
case "record": {
|
|
295
|
+
if (visiting.has(t.shapeId))
|
|
296
|
+
return false;
|
|
297
|
+
visiting.add(t.shapeId);
|
|
298
|
+
const shape = lowerer.shapes.get(t.shapeId);
|
|
299
|
+
return shape?.fields.some((f) => typeHasUnion(lowerer, f.type, visiting)) ?? false;
|
|
300
|
+
}
|
|
301
|
+
default:
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/** assert.deepEqual / notDeepEqual: Node's loose deep comparison. Two
|
|
306
|
+
* operands of one union-free static type compare through the per-type
|
|
307
|
+
* helper in its loose mode (numbers under ==, typed arrays without the
|
|
308
|
+
* Buffer brand); everything else crosses into the checked-dynamic tree
|
|
309
|
+
* and compares there. Types that can be cyclic are refused: the failure
|
|
310
|
+
* message renders the values, which needs their checked-dynamic copy. */
|
|
311
|
+
function lowerAssertLooseDeep(lowerer, expr, loc, negated) {
|
|
312
|
+
const surface = negated ? "assert.notDeepEqual" : "assert.deepEqual";
|
|
313
|
+
requireStatementPosition(lowerer, expr, surface);
|
|
314
|
+
if (expr.arguments.length < 2 || expr.arguments.length > 3) {
|
|
315
|
+
lowerer.noLowering(`${surface} with ${expr.arguments.length} arguments`, expr);
|
|
316
|
+
}
|
|
317
|
+
const aNode = expr.arguments[0];
|
|
318
|
+
const bNode = expr.arguments[1];
|
|
319
|
+
const { a, b } = lowerComparedOperands(lowerer, aNode, bNode, loc);
|
|
320
|
+
const { msg, hasMsg } = lowerMessageArg(lowerer, expr.arguments[2], loc);
|
|
321
|
+
refuseUnrenderedDeepOperands(lowerer, surface, [
|
|
322
|
+
[a, aNode],
|
|
323
|
+
[b, bNode],
|
|
324
|
+
], "loose");
|
|
325
|
+
if (typeEquals(a.type, b.type) &&
|
|
326
|
+
a.type.kind !== "dyn" &&
|
|
327
|
+
// Functions box with their source names for the message.
|
|
328
|
+
a.type.kind !== "func" &&
|
|
329
|
+
!isUnitType(a.type) &&
|
|
330
|
+
deepUnsupportedReason(lowerer, a.type, new Set()) === null &&
|
|
331
|
+
!typeHasUnion(lowerer, a.type, new Set())) {
|
|
332
|
+
const verdictOf = a.type.kind === "bytes"
|
|
333
|
+
? (x, y) => ({
|
|
334
|
+
kind: "libCall",
|
|
335
|
+
fn: "assert.looseBytesEq",
|
|
336
|
+
args: [x, y],
|
|
337
|
+
type: BOOL,
|
|
338
|
+
loc,
|
|
339
|
+
})
|
|
340
|
+
: (x, y) => ({
|
|
341
|
+
kind: "call",
|
|
342
|
+
callee: deepEqHelper(lowerer, a.type, loc, true),
|
|
343
|
+
args: [x, y],
|
|
344
|
+
type: BOOL,
|
|
345
|
+
loc,
|
|
346
|
+
});
|
|
347
|
+
return deepCheckCall(lowerer, a, b, "loose", verdictOf, negated, msg, hasMsg, loc, "loose");
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
kind: "libCall",
|
|
351
|
+
fn: "assert.looseDeepDyn",
|
|
352
|
+
args: [
|
|
353
|
+
boxDeepOperand(lowerer, surface, a, aNode, a, b),
|
|
354
|
+
boxDeepOperand(lowerer, surface, b, bNode, a, b),
|
|
355
|
+
boolLit(negated, loc),
|
|
356
|
+
msg,
|
|
357
|
+
hasMsg,
|
|
358
|
+
],
|
|
359
|
+
type: VOID,
|
|
360
|
+
loc,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/** The operand types the checked-dynamic deep comparisons refuse: types
|
|
364
|
+
* that can be cyclic, and structures holding functions. Their failure
|
|
365
|
+
* messages render the checked-dynamic copy, which a cyclic value does not
|
|
366
|
+
* have and in which a nested function loses its source name. */
|
|
367
|
+
function refuseUnrenderedDeepOperands(lowerer, surface, operands, hintFor) {
|
|
368
|
+
for (const [value, node] of operands) {
|
|
369
|
+
if (value.type.kind !== "dyn" && typeReachesCycle(lowerer, value.type)) {
|
|
370
|
+
lowerer.noLowering(`${surface} of '${lowerer.fmt(value.type)}' values`, node, `recursive types have no ${hintFor} deep comparison — deepStrictEqual compares them`);
|
|
371
|
+
}
|
|
372
|
+
if (value.type.kind !== "func" && typeReachesFunction(lowerer, value.type)) {
|
|
373
|
+
lowerer.noLowering(`${surface} of '${lowerer.fmt(value.type)}' values`, node, `structures holding functions have no ${hintFor} deep comparison — compare the functions with strictEqual`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/** An operand as the checked-dynamic value the runtime comparisons take;
|
|
378
|
+
* a function boxes with its source name for the message. */
|
|
379
|
+
function boxDeepOperand(lowerer, surface, e, node, a, b) {
|
|
380
|
+
if (e.type.kind === "dyn")
|
|
381
|
+
return e;
|
|
382
|
+
const convertible = e.kind === "unitLit" ||
|
|
383
|
+
(e.type.kind === "object" && e.type.className === "%Error") ||
|
|
384
|
+
lowerer.dynConvertible(e.type);
|
|
385
|
+
if (!convertible) {
|
|
386
|
+
lowerer.noLowering(`${surface} of '${lowerer.fmt(a.type)}' and '${lowerer.fmt(b.type)}' values`, node, "both sides must convert into the checked-dynamic tree (primitives, functions, arrays, records, Maps, Sets, typed arrays and errors)");
|
|
387
|
+
}
|
|
388
|
+
const fnName = e.type.kind === "func" ? jsFuncNameOf(node) : null;
|
|
389
|
+
return {
|
|
390
|
+
kind: "dynFrom",
|
|
391
|
+
value: e,
|
|
392
|
+
...(fnName !== null ? { fnName } : {}),
|
|
393
|
+
type: DYN,
|
|
394
|
+
loc: e.loc,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/** assert.partialDeepStrictEqual: Node's partial deep comparison (the
|
|
398
|
+
* expected value contained in the actual one), over the checked-dynamic
|
|
399
|
+
* copies of both operands. */
|
|
400
|
+
function lowerAssertPartialDeep(lowerer, expr, loc) {
|
|
401
|
+
const surface = "assert.partialDeepStrictEqual";
|
|
402
|
+
requireStatementPosition(lowerer, expr, surface);
|
|
403
|
+
if (expr.arguments.length < 2 || expr.arguments.length > 3) {
|
|
404
|
+
lowerer.noLowering(`${surface} with ${expr.arguments.length} arguments`, expr);
|
|
405
|
+
}
|
|
406
|
+
const aNode = expr.arguments[0];
|
|
407
|
+
const bNode = expr.arguments[1];
|
|
408
|
+
const { a, b } = lowerComparedOperands(lowerer, aNode, bNode, loc);
|
|
409
|
+
const { msg, hasMsg } = lowerMessageArg(lowerer, expr.arguments[2], loc);
|
|
410
|
+
refuseUnrenderedDeepOperands(lowerer, surface, [
|
|
411
|
+
[a, aNode],
|
|
412
|
+
[b, bNode],
|
|
413
|
+
], "partial");
|
|
414
|
+
const boxedA = boxDeepOperand(lowerer, surface, a, aNode, a, b);
|
|
415
|
+
const boxedB = boxDeepOperand(lowerer, surface, b, bNode, a, b);
|
|
416
|
+
// Functions box with their source names for the message.
|
|
417
|
+
if (a.type.kind !== "dyn" &&
|
|
418
|
+
b.type.kind !== "dyn" &&
|
|
419
|
+
a.type.kind !== "func" &&
|
|
420
|
+
b.type.kind !== "func") {
|
|
421
|
+
// Statically typed operands compare through the typed partial
|
|
422
|
+
// comparison; only subtrees it does not cover cross into the
|
|
423
|
+
// checked-dynamic tree.
|
|
424
|
+
return deepCheckCall(lowerer, a, b, "partial", (x, y) => partialEqExpr(lowerer, x, y, loc), false, msg, hasMsg, loc, "partial");
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
kind: "libCall",
|
|
428
|
+
fn: "assert.partialDeepDyn",
|
|
429
|
+
args: [boxedA, boxedB, msg, hasMsg],
|
|
430
|
+
type: VOID,
|
|
431
|
+
loc,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const SCALAR_KINDS = new Set(["f64", "string", "bool", "bigint", "symbol"]);
|
|
435
|
+
/** Node's partial deep comparison of `x` (actual) and `y` (expected), two
|
|
436
|
+
* side-effect-free reads of statically typed values. Scalars compare by
|
|
437
|
+
* Object.is, mismatched primitive kinds are unequal, unions dispatch on
|
|
438
|
+
* their tags, and arrays and records compare through the lifted helper;
|
|
439
|
+
* every other pair compares as checked-dynamic values. */
|
|
440
|
+
function partialEqExpr(lowerer, x, y, loc) {
|
|
441
|
+
const ta = x.type;
|
|
442
|
+
const tb = y.type;
|
|
443
|
+
const helperCall = () => ({
|
|
444
|
+
kind: "call",
|
|
445
|
+
callee: partialEqHelper(lowerer, ta, tb, loc),
|
|
446
|
+
args: [x, y],
|
|
447
|
+
type: BOOL,
|
|
448
|
+
loc,
|
|
449
|
+
});
|
|
450
|
+
// A unit value against a union: equal exactly when the union holds it.
|
|
451
|
+
const unitAgainstUnion = (unit, union, value) => {
|
|
452
|
+
if (!isUnitType(unit) || union.kind !== "union")
|
|
453
|
+
return null;
|
|
454
|
+
const tag = lowerer.armTag(union.unionId, unit);
|
|
455
|
+
if (tag < 0)
|
|
456
|
+
return boolLit(false, loc);
|
|
457
|
+
return {
|
|
458
|
+
kind: "unionIsTag",
|
|
459
|
+
unionId: union.unionId,
|
|
460
|
+
tag,
|
|
461
|
+
negated: false,
|
|
462
|
+
value,
|
|
463
|
+
type: BOOL,
|
|
464
|
+
loc,
|
|
465
|
+
};
|
|
466
|
+
};
|
|
467
|
+
const unitCase = unitAgainstUnion(ta, tb, y) ?? unitAgainstUnion(tb, ta, x);
|
|
468
|
+
if (unitCase)
|
|
469
|
+
return unitCase;
|
|
470
|
+
if (ta.kind === "union" || tb.kind === "union")
|
|
471
|
+
return helperCall();
|
|
472
|
+
if (isUnitType(ta) || isUnitType(tb))
|
|
473
|
+
return boolLit(ta.kind === tb.kind, loc);
|
|
474
|
+
if (SCALAR_KINDS.has(ta.kind) && SCALAR_KINDS.has(tb.kind)) {
|
|
475
|
+
if (ta.kind !== tb.kind)
|
|
476
|
+
return boolLit(false, loc);
|
|
477
|
+
switch (ta.kind) {
|
|
478
|
+
case "f64":
|
|
479
|
+
return { kind: "libCall", fn: "assert.sameValue", args: [x, y], type: BOOL, loc };
|
|
480
|
+
case "bigint":
|
|
481
|
+
return { kind: "libCall", fn: "bigint.eq", args: [x, y], type: BOOL, loc };
|
|
482
|
+
case "string":
|
|
483
|
+
return { kind: "strEq", negated: false, left: x, right: y, type: BOOL, loc };
|
|
484
|
+
default:
|
|
485
|
+
return { kind: "bin", op: "===", left: x, right: y, type: BOOL, loc };
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const composite = (t) => t.kind === "array" || t.kind === "record";
|
|
489
|
+
if ((ta.kind === "array" && tb.kind === "array") ||
|
|
490
|
+
(ta.kind === "record" &&
|
|
491
|
+
tb.kind === "record" &&
|
|
492
|
+
lowerer.shapes.get(ta.shapeId)?.indexValue === undefined &&
|
|
493
|
+
lowerer.shapes.get(tb.shapeId)?.indexValue === undefined)) {
|
|
494
|
+
return helperCall();
|
|
495
|
+
}
|
|
496
|
+
// A primitive never partially equals an array or a record.
|
|
497
|
+
if ((SCALAR_KINDS.has(ta.kind) && composite(tb)) ||
|
|
498
|
+
(composite(ta) && SCALAR_KINDS.has(tb.kind))) {
|
|
499
|
+
return boolLit(false, loc);
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
kind: "libCall",
|
|
503
|
+
fn: "assert.partialEqDyn",
|
|
504
|
+
args: [
|
|
505
|
+
x.type.kind === "dyn" ? x : { kind: "dynFrom", value: x, type: DYN, loc },
|
|
506
|
+
y.type.kind === "dyn" ? y : { kind: "dynFrom", value: y, type: DYN, loc },
|
|
507
|
+
],
|
|
508
|
+
type: BOOL,
|
|
509
|
+
loc,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
/** The interned `%assert.pdeq.<n>(a, b): boolean` for one pair of static
|
|
513
|
+
* types: union tag dispatch, ordered-subsequence arrays (partialArrayEquiv),
|
|
514
|
+
* or records compared over the expected shape's present fields. */
|
|
515
|
+
function partialEqHelper(lowerer, ta, tb, loc) {
|
|
516
|
+
const key = `assert.pdeq:${typeKey(ta)}|${typeKey(tb)}`;
|
|
517
|
+
const existing = lowerer.assertHelpers.get(key);
|
|
518
|
+
if (existing)
|
|
519
|
+
return existing;
|
|
520
|
+
const name = `%assert.pdeq.${lowerer.assertHelpers.size}`;
|
|
521
|
+
lowerer.assertHelpers.set(key, name);
|
|
522
|
+
const a = () => varRef("a.0", ta, loc);
|
|
523
|
+
const b = () => varRef("b.0", tb, loc);
|
|
524
|
+
const ret = (value) => ({ kind: "return", value, loc });
|
|
525
|
+
const not = (operand) => notExpr(operand, loc);
|
|
526
|
+
const bailIf = (cond) => ({
|
|
527
|
+
kind: "if",
|
|
528
|
+
cond,
|
|
529
|
+
then: [ret(boolLit(false, loc))],
|
|
530
|
+
else_: null,
|
|
531
|
+
loc,
|
|
532
|
+
});
|
|
533
|
+
const locals = [
|
|
534
|
+
{ id: "a.0", name: "a", type: ta, mutable: false },
|
|
535
|
+
{ id: "b.0", name: "b", type: tb, mutable: false },
|
|
536
|
+
];
|
|
537
|
+
let body;
|
|
538
|
+
const unionDispatch = (side) => {
|
|
539
|
+
const t = side === "a" ? ta : tb;
|
|
540
|
+
if (t.kind !== "union")
|
|
541
|
+
throw new InternalCompilerError("partial union dispatch over a non-union");
|
|
542
|
+
const def = lowerer.unions.get(t.unionId);
|
|
543
|
+
if (!def)
|
|
544
|
+
throw new InternalCompilerError(`partial deep-equal of unknown union ${t.unionId}`);
|
|
545
|
+
const value = side === "a" ? a() : b();
|
|
546
|
+
const stmts = def.arms.map((arm, tag) => {
|
|
547
|
+
const narrowed = {
|
|
548
|
+
kind: "unionNarrow",
|
|
549
|
+
unionId: t.unionId,
|
|
550
|
+
tag,
|
|
551
|
+
value,
|
|
552
|
+
type: arm,
|
|
553
|
+
loc,
|
|
554
|
+
};
|
|
555
|
+
return {
|
|
556
|
+
kind: "if",
|
|
557
|
+
cond: {
|
|
558
|
+
kind: "unionIsTag",
|
|
559
|
+
unionId: t.unionId,
|
|
560
|
+
tag,
|
|
561
|
+
negated: false,
|
|
562
|
+
value,
|
|
563
|
+
type: BOOL,
|
|
564
|
+
loc,
|
|
565
|
+
},
|
|
566
|
+
then: [
|
|
567
|
+
ret(side === "a"
|
|
568
|
+
? partialEqExpr(lowerer, narrowed, b(), loc)
|
|
569
|
+
: partialEqExpr(lowerer, a(), narrowed, loc)),
|
|
570
|
+
],
|
|
571
|
+
else_: null,
|
|
572
|
+
loc,
|
|
573
|
+
};
|
|
574
|
+
});
|
|
575
|
+
stmts.push(ret(boolLit(false, loc)));
|
|
576
|
+
return stmts;
|
|
577
|
+
};
|
|
578
|
+
if (ta.kind === "union") {
|
|
579
|
+
body = unionDispatch("a");
|
|
580
|
+
}
|
|
581
|
+
else if (tb.kind === "union") {
|
|
582
|
+
body = unionDispatch("b");
|
|
583
|
+
}
|
|
584
|
+
else if (ta.kind === "array" && tb.kind === "array") {
|
|
585
|
+
const len = (arr) => ({
|
|
586
|
+
kind: "arrIntrinsic",
|
|
587
|
+
method: "length",
|
|
588
|
+
receiver: arr,
|
|
589
|
+
args: [],
|
|
590
|
+
type: F64,
|
|
591
|
+
loc,
|
|
592
|
+
});
|
|
593
|
+
const pos = () => varRef("pos.0", F64, loc);
|
|
594
|
+
const bump = {
|
|
595
|
+
kind: "assign",
|
|
596
|
+
localId: "pos.0",
|
|
597
|
+
value: { kind: "bin", op: "+", left: pos(), right: numLit(1, loc), type: F64, loc },
|
|
598
|
+
loc,
|
|
599
|
+
};
|
|
600
|
+
locals.push({ id: "i.0", name: "i", type: F64, mutable: true }, { id: "pos.0", name: "pos", type: F64, mutable: true });
|
|
601
|
+
body = [
|
|
602
|
+
bailIf({ kind: "bin", op: "<", left: len(a()), right: len(b()), type: BOOL, loc }),
|
|
603
|
+
{ kind: "varDecl", localId: "pos.0", init: numLit(0, loc), loc },
|
|
604
|
+
countedFor(loc, len(b()), (i) => [
|
|
605
|
+
{
|
|
606
|
+
kind: "while",
|
|
607
|
+
cond: not(partialEqExpr(lowerer, { kind: "arrayGet", arr: a(), index: pos(), type: ta.elem, loc }, { kind: "arrayGet", arr: b(), index: i, type: tb.elem, loc }, loc)),
|
|
608
|
+
body: [
|
|
609
|
+
bump,
|
|
610
|
+
// pos > la - lb + i: the rest of b no longer fits in a.
|
|
611
|
+
bailIf({
|
|
612
|
+
kind: "bin",
|
|
613
|
+
op: ">",
|
|
614
|
+
left: pos(),
|
|
615
|
+
right: {
|
|
616
|
+
kind: "bin",
|
|
617
|
+
op: "+",
|
|
618
|
+
left: { kind: "bin", op: "-", left: len(a()), right: len(b()), type: F64, loc },
|
|
619
|
+
right: i,
|
|
620
|
+
type: F64,
|
|
621
|
+
loc,
|
|
622
|
+
},
|
|
623
|
+
type: BOOL,
|
|
624
|
+
loc,
|
|
625
|
+
}),
|
|
626
|
+
],
|
|
627
|
+
loc,
|
|
628
|
+
},
|
|
629
|
+
bump,
|
|
630
|
+
]),
|
|
631
|
+
ret(boolLit(true, loc)),
|
|
632
|
+
];
|
|
633
|
+
}
|
|
634
|
+
else if (ta.kind === "record" && tb.kind === "record") {
|
|
635
|
+
const shapeA = lowerer.shapes.get(ta.shapeId);
|
|
636
|
+
const shapeB = lowerer.shapes.get(tb.shapeId);
|
|
637
|
+
if (!shapeA || !shapeB)
|
|
638
|
+
throw new InternalCompilerError("partial deep-equal of an unknown shape");
|
|
639
|
+
body = [];
|
|
640
|
+
for (const fb of shapeB.fields) {
|
|
641
|
+
const presentB = lowerer.recordFieldPresent(b(), tb.shapeId, fb.name, loc);
|
|
642
|
+
const fa = shapeA.fields.find((f) => f.name === fb.name);
|
|
643
|
+
const checks = [];
|
|
644
|
+
if (!fa) {
|
|
645
|
+
checks.push(ret(boolLit(false, loc)));
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
const presentA = lowerer.recordFieldPresent(a(), ta.shapeId, fa.name, loc);
|
|
649
|
+
if (presentA.kind !== "boolLit")
|
|
650
|
+
checks.push(bailIf(not(presentA)));
|
|
651
|
+
checks.push(bailIf(not(partialEqExpr(lowerer, {
|
|
652
|
+
kind: "recordGet",
|
|
653
|
+
obj: a(),
|
|
654
|
+
shapeId: ta.shapeId,
|
|
655
|
+
field: fa.name,
|
|
656
|
+
type: fa.type,
|
|
657
|
+
loc,
|
|
658
|
+
}, {
|
|
659
|
+
kind: "recordGet",
|
|
660
|
+
obj: b(),
|
|
661
|
+
shapeId: tb.shapeId,
|
|
662
|
+
field: fb.name,
|
|
663
|
+
type: fb.type,
|
|
664
|
+
loc,
|
|
665
|
+
}, loc))));
|
|
666
|
+
}
|
|
667
|
+
if (presentB.kind === "boolLit")
|
|
668
|
+
body.push(...checks);
|
|
669
|
+
else
|
|
670
|
+
body.push({ kind: "if", cond: presentB, then: checks, else_: null, loc });
|
|
671
|
+
}
|
|
672
|
+
body.push(ret(boolLit(true, loc)));
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
throw new InternalCompilerError(`partial deep-equal helper over ${typeKey(ta)} and ${typeKey(tb)}`);
|
|
676
|
+
}
|
|
677
|
+
lowerer.liftedFns.push({
|
|
678
|
+
name,
|
|
679
|
+
params: [
|
|
680
|
+
{ localId: "a.0", name: "a", type: ta },
|
|
681
|
+
{ localId: "b.0", name: "b", type: tb },
|
|
682
|
+
],
|
|
683
|
+
returnType: BOOL,
|
|
684
|
+
locals,
|
|
685
|
+
body,
|
|
686
|
+
loc,
|
|
687
|
+
});
|
|
688
|
+
return name;
|
|
689
|
+
}
|
|
180
690
|
/** The callable-module form: `assert(x)` where the callee identifier IS
|
|
181
691
|
* the assert module binding — a default import (`import assert from
|
|
182
692
|
* "node:assert"`) or the CJS `const assert = require("assert")`. Node's
|
|
@@ -208,19 +718,34 @@ function lowerAssertOk(lowerer, expr, loc) {
|
|
|
208
718
|
lowerer.noLowering(`${expr.expression.getText()} with ${expr.arguments.length} arguments`, expr, "the supported forms are assert(value) and assert(value, message)");
|
|
209
719
|
}
|
|
210
720
|
const valueNode = expr.arguments[0];
|
|
211
|
-
const
|
|
721
|
+
const stmts = [];
|
|
722
|
+
const value = stableValue(lowerer, stmts, lowerer.lowerExpr(valueNode), "%assertOkValue", loc);
|
|
723
|
+
const pass = stableValue(lowerer, stmts, lowerer.ensureBool(value, valueNode), "%assertOkPass", loc);
|
|
212
724
|
const msgNode = expr.arguments[1];
|
|
725
|
+
let msg;
|
|
726
|
+
let generated = false;
|
|
213
727
|
if (!msgNode || (ts.isIdentifier(msgNode) && msgNode.text === "undefined")) {
|
|
214
728
|
// Node reads the CALLSITE LINE and slices the expression out of it
|
|
215
729
|
// (internal/errors/error_source.js) — a call spanning lines truncates
|
|
216
730
|
// at the first newline, so the compile-time text does too.
|
|
217
731
|
const source = expr.getText().split("\n", 1)[0];
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
732
|
+
const text = `The expression evaluated to a falsy value:\n\n ${source}\n`;
|
|
733
|
+
msg = { kind: "strLit", value: text, type: STRING, loc };
|
|
734
|
+
generated = true;
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
msg = lowerMessageArg(lowerer, msgNode, loc).msg;
|
|
221
738
|
}
|
|
222
|
-
|
|
223
|
-
|
|
739
|
+
// Node's actual is the value, expected true, operator "==".
|
|
740
|
+
stmts.push(recordOnFailure(lowerer, notExpr(pass, loc), value, boolLit(true, loc), "==", loc));
|
|
741
|
+
const result = {
|
|
742
|
+
kind: "libCall",
|
|
743
|
+
fn: "assert.ok",
|
|
744
|
+
args: [pass, msg, boolLit(generated, loc)],
|
|
745
|
+
type: VOID,
|
|
746
|
+
loc,
|
|
747
|
+
};
|
|
748
|
+
return { kind: "seqExpr", stmts, result, type: VOID, loc };
|
|
224
749
|
}
|
|
225
750
|
/** assert.fail([message]): an unconditional AssertionError — "Failed"
|
|
226
751
|
* when the message is omitted (Node's internal default). Lowers as
|
|
@@ -231,15 +756,36 @@ function lowerAssertFail(lowerer, expr, loc) {
|
|
|
231
756
|
lowerer.noLowering(`assert.fail with ${expr.arguments.length} arguments`, expr, "the supported form is fail(message?) — the deprecated fail(actual, expected, ...) has no lowering");
|
|
232
757
|
}
|
|
233
758
|
let msg;
|
|
759
|
+
let generated;
|
|
234
760
|
const msgNode = expr.arguments[0];
|
|
761
|
+
const stmts = [];
|
|
235
762
|
if (!msgNode || (ts.isIdentifier(msgNode) && msgNode.text === "undefined")) {
|
|
236
763
|
msg = { kind: "strLit", value: "Failed", type: STRING, loc };
|
|
764
|
+
generated = boolLit(true, loc);
|
|
237
765
|
}
|
|
238
766
|
else {
|
|
239
|
-
|
|
767
|
+
// Node's AssertionError sets generatedMessage to !message, so an
|
|
768
|
+
// empty custom message counts as generated.
|
|
769
|
+
msg = stableValue(lowerer, stmts, lowerMessageArg(lowerer, msgNode, loc).msg, "%assertFailMessage", loc);
|
|
770
|
+
generated = {
|
|
771
|
+
kind: "strEq",
|
|
772
|
+
negated: false,
|
|
773
|
+
left: msg,
|
|
774
|
+
right: strLit("", loc),
|
|
775
|
+
type: BOOL,
|
|
776
|
+
loc,
|
|
777
|
+
};
|
|
240
778
|
}
|
|
241
779
|
const falseLit = { kind: "boolLit", value: false, type: BOOL, loc };
|
|
242
|
-
|
|
780
|
+
stmts.push(recordOnFailure(lowerer, boolLit(true, loc), null, null, "fail", loc));
|
|
781
|
+
const result = {
|
|
782
|
+
kind: "libCall",
|
|
783
|
+
fn: "assert.ok",
|
|
784
|
+
args: [falseLit, msg, generated],
|
|
785
|
+
type: VOID,
|
|
786
|
+
loc,
|
|
787
|
+
};
|
|
788
|
+
return { kind: "seqExpr", stmts, result, type: VOID, loc };
|
|
243
789
|
}
|
|
244
790
|
/** The equality quartet. Scalars (number/string/boolean) compare with
|
|
245
791
|
* Object.is in the runtime (strictEqual and deepStrictEqual agree there —
|
|
@@ -253,34 +799,39 @@ function lowerAssertEqual(lowerer, expr, loc, negated, deep) {
|
|
|
253
799
|
if (expr.arguments.length < 2 || expr.arguments.length > 3) {
|
|
254
800
|
lowerer.noLowering(`${surface} with ${expr.arguments.length} arguments`, expr);
|
|
255
801
|
}
|
|
256
|
-
// A context-free EMPTY array literal on either side has no element type
|
|
257
|
-
// of its own (`deepStrictEqual(ee.listeners('x'), [])` — the checker
|
|
258
|
-
// says any[]): it adopts the OTHER side's array type. Literals have no
|
|
259
|
-
// side effects, so lowering order is unobservable.
|
|
260
802
|
const aNode = expr.arguments[0];
|
|
261
803
|
const bNode = expr.arguments[1];
|
|
262
|
-
const
|
|
263
|
-
const emptyOf = (t) => ({ kind: "arrayLit", elems: [], type: t, loc });
|
|
264
|
-
// A dyn other side adopts number[] — the dyn array a boxed empty
|
|
265
|
-
// literal builds is elementless, so any elem type serves.
|
|
266
|
-
const adoptedEmpty = (other) => other.kind === "array"
|
|
267
|
-
? emptyOf(other)
|
|
268
|
-
: other.kind === "dyn"
|
|
269
|
-
? emptyOf({ kind: "array", elem: F64 })
|
|
270
|
-
: null;
|
|
271
|
-
let a;
|
|
272
|
-
let b;
|
|
273
|
-
if (emptyArrayLit(aNode) && !emptyArrayLit(bNode)) {
|
|
274
|
-
b = lowerer.lowerExpr(bNode);
|
|
275
|
-
a = adoptedEmpty(b.type) ?? lowerer.lowerExpr(aNode);
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
a = lowerer.lowerExpr(aNode);
|
|
279
|
-
b = emptyArrayLit(bNode)
|
|
280
|
-
? (adoptedEmpty(a.type) ?? lowerer.lowerExpr(bNode))
|
|
281
|
-
: lowerer.lowerExpr(bNode);
|
|
282
|
-
}
|
|
804
|
+
const { a, b } = lowerComparedOperands(lowerer, aNode, bNode, loc);
|
|
283
805
|
const { msg, hasMsg } = lowerMessageArg(lowerer, expr.arguments[2], loc);
|
|
806
|
+
// An Assert instance with skipPrototype: Node compares own properties
|
|
807
|
+
// without the prototype check, so operands of different static types (two
|
|
808
|
+
// classes, a class and a record) can be equal. They compare as
|
|
809
|
+
// checked-dynamic values, and so do class instances, which have no typed
|
|
810
|
+
// deep comparison; the runtime walk skips the prototype check for the
|
|
811
|
+
// call (assert-instances.ts). Other same-typed operands keep their typed
|
|
812
|
+
// comparison.
|
|
813
|
+
const skipPrototype = deep && lowerer.assertCallOptions?.skipPrototype === true;
|
|
814
|
+
if (skipPrototype &&
|
|
815
|
+
a.type.kind !== "dyn" &&
|
|
816
|
+
b.type.kind !== "dyn" &&
|
|
817
|
+
(!typeEquals(a.type, b.type) || deepUnsupportedReason(lowerer, a.type, new Set()) !== null)) {
|
|
818
|
+
const box = (e, node) => {
|
|
819
|
+
if (e.kind !== "unitLit" &&
|
|
820
|
+
(!lowerer.dynConvertible(e.type) ||
|
|
821
|
+
typeReachesCycle(lowerer, e.type) ||
|
|
822
|
+
typeReachesFunction(lowerer, e.type))) {
|
|
823
|
+
lowerer.noLowering(`${surface} with skipPrototype of '${lowerer.fmt(a.type)}' and '${lowerer.fmt(b.type)}' values`, node, "operands of different static types compare as checked-dynamic values, which this operand does not convert to");
|
|
824
|
+
}
|
|
825
|
+
return { kind: "dynFrom", value: e, type: DYN, loc: e.loc };
|
|
826
|
+
};
|
|
827
|
+
return {
|
|
828
|
+
kind: "libCall",
|
|
829
|
+
fn: "assert.eqDyn",
|
|
830
|
+
args: [box(a, aNode), box(b, bNode), boolLit(negated, loc), boolLit(true, loc), msg, hasMsg],
|
|
831
|
+
type: VOID,
|
|
832
|
+
loc,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
284
835
|
// CHECKED-DYNAMIC operands (dyn-vs-dyn, dyn-vs-static): both sides
|
|
285
836
|
// cross into the checked-dynamic tree and one runtime entry answers the whole quartet —
|
|
286
837
|
// SameValue for the strict pair (boxed-closure identity for
|
|
@@ -321,7 +872,10 @@ function lowerAssertEqual(lowerer, expr, loc, negated, deep) {
|
|
|
321
872
|
// same). The deep walk compares the retained error data without
|
|
322
873
|
// re-encoding and losing identity (SEMANTICS.md).
|
|
323
874
|
(k === "object" && e.type.kind === "object" && e.type.className === "%Error") ||
|
|
324
|
-
(deep && k !== "bytes" && k !== "object" && lowerer.dynConvertible(e.type))
|
|
875
|
+
(deep && k !== "bytes" && k !== "object" && lowerer.dynConvertible(e.type)) ||
|
|
876
|
+
// Under skipPrototype the prototype a class instance or Buffer
|
|
877
|
+
// brand would carry is not compared.
|
|
878
|
+
(skipPrototype && lowerer.dynConvertible(e.type));
|
|
325
879
|
if (!ok) {
|
|
326
880
|
const hint = !deep && (k === "record" || k === "array" || k === "map" || k === "set" || k === "union")
|
|
327
881
|
? "strictEqual on objects is reference equality, which does not survive the unknown boundary (values copy when they cross) — deepStrictEqual compares structure, or narrow the unknown side first"
|
|
@@ -401,23 +955,18 @@ function lowerAssertEqual(lowerer, expr, loc, negated, deep) {
|
|
|
401
955
|
loc,
|
|
402
956
|
};
|
|
403
957
|
}
|
|
404
|
-
if (brandA === null || brandB === null) {
|
|
958
|
+
if (!skipPrototype && (brandA === null || brandB === null)) {
|
|
405
959
|
lowerer.noLowering(`${surface} of typed-array values whose constructor is not in the static type`, brandA === null ? expr.arguments[0] : expr.arguments[1], "Node's deep equality compares prototypes (a Buffer is never deep-equal to a plain Uint8Array) — annotate the value so its constructor is visible");
|
|
406
960
|
}
|
|
407
|
-
|
|
961
|
+
// The brand is the prototype; skipPrototype ignores it.
|
|
962
|
+
const sameBrand = skipPrototype || brandA === brandB;
|
|
963
|
+
return deepCheckCall(lowerer, a, b, `bytes:${sameBrand}`, (x, y) => ({
|
|
408
964
|
kind: "libCall",
|
|
409
965
|
fn: "assert.bytesDeepEq",
|
|
410
|
-
args: [
|
|
966
|
+
args: [x, y, boolLit(sameBrand, loc)],
|
|
411
967
|
type: BOOL,
|
|
412
968
|
loc,
|
|
413
|
-
};
|
|
414
|
-
return {
|
|
415
|
-
kind: "libCall",
|
|
416
|
-
fn: "assert.deepResult",
|
|
417
|
-
args: [verdict, boolLit(negated, loc), msg, hasMsg],
|
|
418
|
-
type: VOID,
|
|
419
|
-
loc,
|
|
420
|
-
};
|
|
969
|
+
}), negated, msg, hasMsg, loc);
|
|
421
970
|
}
|
|
422
971
|
// Function values (ANY two static signatures — a tuple-typed
|
|
423
972
|
// listeners() element against a prefix-declared handler): strictEqual
|
|
@@ -461,11 +1010,62 @@ function lowerAssertEqual(lowerer, expr, loc, negated, deep) {
|
|
|
461
1010
|
lowerer.noLowering(`${surface} of '${lowerer.fmt(a.type)}' values`, expr, unsupported);
|
|
462
1011
|
}
|
|
463
1012
|
const helper = deepEqHelper(lowerer, a.type, loc);
|
|
464
|
-
|
|
1013
|
+
return deepCheckCall(lowerer, a, b, "deep", (x, y) => ({ kind: "call", callee: helper, args: [x, y], type: BOOL, loc }), negated, msg, hasMsg, loc);
|
|
1014
|
+
}
|
|
1015
|
+
/** `assert.deepResult(%assert.dcheck.<n>(a, b), negated, msg, hasMsg)`.
|
|
1016
|
+
* The wrapper returns the verdict and, on failure, records actual and
|
|
1017
|
+
* expected. The operands are its parameters, so the call site passes them
|
|
1018
|
+
* as temporaries and a passing assertion holds no extra reference. */
|
|
1019
|
+
function deepCheckCall(lowerer, a, b, variant, verdictOf, negated, msg, hasMsg, loc, mode = "strict") {
|
|
1020
|
+
const key = `assert.dcheck:${variant}:${negated}:${typeKey(a.type)}:${typeKey(b.type)}`;
|
|
1021
|
+
let name = lowerer.assertHelpers.get(key);
|
|
1022
|
+
if (!name) {
|
|
1023
|
+
name = `%assert.dcheck.${lowerer.assertHelpers.size}`;
|
|
1024
|
+
lowerer.assertHelpers.set(key, name);
|
|
1025
|
+
const pa = varRef("a.0", a.type, loc);
|
|
1026
|
+
const pb = varRef("b.0", b.type, loc);
|
|
1027
|
+
const verdict = varRef("v.0", BOOL, loc);
|
|
1028
|
+
lowerer.liftedFns.push({
|
|
1029
|
+
name,
|
|
1030
|
+
params: [
|
|
1031
|
+
{ localId: "a.0", name: "a", type: a.type },
|
|
1032
|
+
{ localId: "b.0", name: "b", type: b.type },
|
|
1033
|
+
],
|
|
1034
|
+
returnType: BOOL,
|
|
1035
|
+
locals: [
|
|
1036
|
+
{ id: "a.0", name: "a", type: a.type, mutable: false },
|
|
1037
|
+
{ id: "b.0", name: "b", type: b.type, mutable: false },
|
|
1038
|
+
{ id: "v.0", name: "verdict", type: BOOL, mutable: false },
|
|
1039
|
+
],
|
|
1040
|
+
body: [
|
|
1041
|
+
{ kind: "varDecl", localId: "v.0", init: verdictOf(pa, pb), loc },
|
|
1042
|
+
recordOnFailure(lowerer, negated ? verdict : notExpr(verdict, loc), pa, pb, mode === "partial"
|
|
1043
|
+
? "partialDeepStrictEqual"
|
|
1044
|
+
: mode === "loose"
|
|
1045
|
+
? negated
|
|
1046
|
+
? "notDeepEqual"
|
|
1047
|
+
: "deepEqual"
|
|
1048
|
+
: negated
|
|
1049
|
+
? "notDeepStrictEqual"
|
|
1050
|
+
: "deepStrictEqual", loc),
|
|
1051
|
+
{ kind: "return", value: verdict, loc },
|
|
1052
|
+
],
|
|
1053
|
+
loc,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
465
1056
|
return {
|
|
466
1057
|
kind: "libCall",
|
|
467
|
-
fn: "
|
|
468
|
-
|
|
1058
|
+
fn: mode === "partial"
|
|
1059
|
+
? "assert.partialDeepResult"
|
|
1060
|
+
: mode === "loose"
|
|
1061
|
+
? "assert.looseDeepResult"
|
|
1062
|
+
: "assert.deepResult",
|
|
1063
|
+
args: [
|
|
1064
|
+
{ kind: "call", callee: name, args: [a, b], type: BOOL, loc },
|
|
1065
|
+
boolLit(negated, loc),
|
|
1066
|
+
msg,
|
|
1067
|
+
hasMsg,
|
|
1068
|
+
],
|
|
469
1069
|
type: VOID,
|
|
470
1070
|
loc,
|
|
471
1071
|
};
|
|
@@ -893,7 +1493,20 @@ function assertThrowsHelper(lowerer, mode, recvType, recvIsPromise, expected, lo
|
|
|
893
1493
|
let catchBody;
|
|
894
1494
|
if (mode === "dnr" || mode === "dnt") {
|
|
895
1495
|
const actual = () => ({ kind: "caughtToDyn", value: caughtRef(), type: DYN, loc });
|
|
896
|
-
|
|
1496
|
+
// Node's actual is the caught value.
|
|
1497
|
+
const unwanted = () => ({
|
|
1498
|
+
kind: "block",
|
|
1499
|
+
body: [
|
|
1500
|
+
lib("assert.record", [
|
|
1501
|
+
actual(),
|
|
1502
|
+
dynUndefinedExpr(loc),
|
|
1503
|
+
strLit(mode === "dnr" ? "doesNotReject" : "doesNotThrow", loc),
|
|
1504
|
+
]),
|
|
1505
|
+
unwantedCall(),
|
|
1506
|
+
],
|
|
1507
|
+
loc,
|
|
1508
|
+
});
|
|
1509
|
+
const unwantedCall = () => lib("assert.unwantedError", [
|
|
897
1510
|
{
|
|
898
1511
|
kind: "libCall",
|
|
899
1512
|
fn: "util.toUSVString",
|
|
@@ -1002,6 +1615,7 @@ function assertThrowsHelper(lowerer, mode, recvType, recvIsPromise, expected, lo
|
|
|
1002
1615
|
narrowed(),
|
|
1003
1616
|
m(),
|
|
1004
1617
|
hm(),
|
|
1618
|
+
boolLit(mode === "rejects", loc),
|
|
1005
1619
|
]),
|
|
1006
1620
|
]),
|
|
1007
1621
|
// A non-Error thrown value: propagate (SEMANTICS.md 104; the
|
|
@@ -1013,7 +1627,13 @@ function assertThrowsHelper(lowerer, mode, recvType, recvIsPromise, expected, lo
|
|
|
1013
1627
|
case "regex":
|
|
1014
1628
|
catchBody = [
|
|
1015
1629
|
ifStmt(isInstance("%Error"), [
|
|
1016
|
-
lib("assert.throwsRegex", [
|
|
1630
|
+
lib("assert.throwsRegex", [
|
|
1631
|
+
varRef("re.0", REGEX, loc),
|
|
1632
|
+
narrowed(),
|
|
1633
|
+
m(),
|
|
1634
|
+
hm(),
|
|
1635
|
+
boolLit(mode === "rejects", loc),
|
|
1636
|
+
]),
|
|
1017
1637
|
doReturn,
|
|
1018
1638
|
]),
|
|
1019
1639
|
rethrow,
|
|
@@ -1023,7 +1643,7 @@ function assertThrowsHelper(lowerer, mode, recvType, recvIsPromise, expected, lo
|
|
|
1023
1643
|
catchBody = [
|
|
1024
1644
|
ifStmt(isInstance("%Error"), [
|
|
1025
1645
|
...shapeSlotStmts(),
|
|
1026
|
-
lib("assert.shapeEnd", [m(), hm()]),
|
|
1646
|
+
lib("assert.shapeEnd", [m(), hm(), boolLit(mode === "rejects", loc)]),
|
|
1027
1647
|
doReturn,
|
|
1028
1648
|
]),
|
|
1029
1649
|
rethrow,
|
|
@@ -1150,6 +1770,7 @@ function lowerAssertIfError(lowerer, expr, loc) {
|
|
|
1150
1770
|
args: [
|
|
1151
1771
|
{ kind: "boolLit", value: true, type: BOOL, loc },
|
|
1152
1772
|
{ kind: "strLit", value: "", type: STRING, loc },
|
|
1773
|
+
{ kind: "boolLit", value: true, type: BOOL, loc },
|
|
1153
1774
|
],
|
|
1154
1775
|
type: VOID,
|
|
1155
1776
|
loc,
|
|
@@ -1168,6 +1789,7 @@ function lowerAssertIfError(lowerer, expr, loc) {
|
|
|
1168
1789
|
args: [
|
|
1169
1790
|
{ kind: "boolLit", value: true, type: BOOL, loc },
|
|
1170
1791
|
{ kind: "strLit", value: "", type: STRING, loc },
|
|
1792
|
+
{ kind: "boolLit", value: true, type: BOOL, loc },
|
|
1171
1793
|
],
|
|
1172
1794
|
type: VOID,
|
|
1173
1795
|
loc,
|
|
@@ -1367,12 +1989,15 @@ function deepUnsupportedReason(lowerer, t, visiting) {
|
|
|
1367
1989
|
* tag+payload unions, SameValueZero-keyed Maps with deep values, scalar
|
|
1368
1990
|
* Sets by membership. Registered in the cache BEFORE the body builds, so
|
|
1369
1991
|
* recursive record shapes call themselves by name. */
|
|
1370
|
-
function deepEqHelper(lowerer, t, loc) {
|
|
1371
|
-
|
|
1992
|
+
function deepEqHelper(lowerer, t, loc, loose = false) {
|
|
1993
|
+
// The loose mode (deepEqual over one union-free type) differs only in
|
|
1994
|
+
// numbers: == with NaN equal to NaN.
|
|
1995
|
+
const prefix = loose ? "ldeq" : "deq";
|
|
1996
|
+
const key = `assert.${prefix}:${typeKey(t)}`;
|
|
1372
1997
|
const existing = lowerer.assertHelpers.get(key);
|
|
1373
1998
|
if (existing)
|
|
1374
1999
|
return existing;
|
|
1375
|
-
const name = `%assert
|
|
2000
|
+
const name = `%assert.${prefix}.${lowerer.assertHelpers.size}`;
|
|
1376
2001
|
lowerer.assertHelpers.set(key, name);
|
|
1377
2002
|
const a = () => varRef("a.0", t, loc);
|
|
1378
2003
|
const b = () => varRef("b.0", t, loc);
|
|
@@ -1398,7 +2023,13 @@ function deepEqHelper(lowerer, t, loc) {
|
|
|
1398
2023
|
/** Deep-compare two same-typed exprs through the per-type helper. */
|
|
1399
2024
|
const deq = (elemT, x, y) => isUnitType(elemT)
|
|
1400
2025
|
? boolLit(true, loc)
|
|
1401
|
-
: {
|
|
2026
|
+
: {
|
|
2027
|
+
kind: "call",
|
|
2028
|
+
callee: deepEqHelper(lowerer, elemT, loc, loose),
|
|
2029
|
+
args: [x, y],
|
|
2030
|
+
type: BOOL,
|
|
2031
|
+
loc,
|
|
2032
|
+
};
|
|
1402
2033
|
let locals = [
|
|
1403
2034
|
{ id: "a.0", name: "a", type: t, mutable: false },
|
|
1404
2035
|
{ id: "b.0", name: "b", type: t, mutable: false },
|
|
@@ -1406,7 +2037,15 @@ function deepEqHelper(lowerer, t, loc) {
|
|
|
1406
2037
|
let body;
|
|
1407
2038
|
switch (t.kind) {
|
|
1408
2039
|
case "f64":
|
|
1409
|
-
body = [
|
|
2040
|
+
body = [
|
|
2041
|
+
ret({
|
|
2042
|
+
kind: "libCall",
|
|
2043
|
+
fn: loose ? "assert.looseF64Eq" : "assert.sameValue",
|
|
2044
|
+
args: [a(), b()],
|
|
2045
|
+
type: BOOL,
|
|
2046
|
+
loc,
|
|
2047
|
+
}),
|
|
2048
|
+
];
|
|
1410
2049
|
break;
|
|
1411
2050
|
case "bigint":
|
|
1412
2051
|
body = [ret({ kind: "libCall", fn: "bigint.eq", args: [a(), b()], type: BOOL, loc })];
|