@scriptc/compiler 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/LICENSE +202 -0
  2. package/ambient/package.json +3 -0
  3. package/ambient/scriptc-node-fallback.d.ts +3001 -0
  4. package/ambient/scriptc-overrides.d.ts +95 -0
  5. package/ambient/scriptc.d.ts +74 -0
  6. package/dist/backend/cc.d.ts +169 -0
  7. package/dist/backend/cc.js +897 -0
  8. package/dist/backend/cc.js.map +1 -0
  9. package/dist/backend/emission/emit-async.d.ts +145 -0
  10. package/dist/backend/emission/emit-async.js +996 -0
  11. package/dist/backend/emission/emit-async.js.map +1 -0
  12. package/dist/backend/emission/emit-exprs.d.ts +3 -0
  13. package/dist/backend/emission/emit-exprs.js +5949 -0
  14. package/dist/backend/emission/emit-exprs.js.map +1 -0
  15. package/dist/backend/emission/emit-island.d.ts +45 -0
  16. package/dist/backend/emission/emit-island.js +271 -0
  17. package/dist/backend/emission/emit-island.js.map +1 -0
  18. package/dist/backend/emission/emit-shapes.d.ts +142 -0
  19. package/dist/backend/emission/emit-shapes.js +575 -0
  20. package/dist/backend/emission/emit-shapes.js.map +1 -0
  21. package/dist/backend/emission/emit-stmts.d.ts +78 -0
  22. package/dist/backend/emission/emit-stmts.js +960 -0
  23. package/dist/backend/emission/emit-stmts.js.map +1 -0
  24. package/dist/backend/emission/emit-types.d.ts +65 -0
  25. package/dist/backend/emission/emit-types.js +652 -0
  26. package/dist/backend/emission/emit-types.js.map +1 -0
  27. package/dist/backend/emission/emit-walkers.d.ts +154 -0
  28. package/dist/backend/emission/emit-walkers.js +1587 -0
  29. package/dist/backend/emission/emit-walkers.js.map +1 -0
  30. package/dist/backend/emission/emitter.d.ts +452 -0
  31. package/dist/backend/emission/emitter.js +1249 -0
  32. package/dist/backend/emission/emitter.js.map +1 -0
  33. package/dist/backend/emission/may-throw.d.ts +15 -0
  34. package/dist/backend/emission/may-throw.js +250 -0
  35. package/dist/backend/emission/may-throw.js.map +1 -0
  36. package/dist/backend/llvm/blocks.d.ts +21 -0
  37. package/dist/backend/llvm/blocks.js +68 -0
  38. package/dist/backend/llvm/blocks.js.map +1 -0
  39. package/dist/backend/llvm/classes.d.ts +84 -0
  40. package/dist/backend/llvm/classes.js +438 -0
  41. package/dist/backend/llvm/classes.js.map +1 -0
  42. package/dist/backend/llvm/dyn.d.ts +123 -0
  43. package/dist/backend/llvm/dyn.js +2624 -0
  44. package/dist/backend/llvm/dyn.js.map +1 -0
  45. package/dist/backend/llvm/emitter.d.ts +3 -0
  46. package/dist/backend/llvm/emitter.js +10102 -0
  47. package/dist/backend/llvm/emitter.js.map +1 -0
  48. package/dist/backend/llvm/shapes.d.ts +78 -0
  49. package/dist/backend/llvm/shapes.js +754 -0
  50. package/dist/backend/llvm/shapes.js.map +1 -0
  51. package/dist/backend/llvm/unsupported.d.ts +6 -0
  52. package/dist/backend/llvm/unsupported.js +12 -0
  53. package/dist/backend/llvm/unsupported.js.map +1 -0
  54. package/dist/backend/llvm/walkers.d.ts +58 -0
  55. package/dist/backend/llvm/walkers.js +793 -0
  56. package/dist/backend/llvm/walkers.js.map +1 -0
  57. package/dist/backend/mangle.d.ts +124 -0
  58. package/dist/backend/mangle.js +232 -0
  59. package/dist/backend/mangle.js.map +1 -0
  60. package/dist/coverage/report.d.ts +65 -0
  61. package/dist/coverage/report.js +238 -0
  62. package/dist/coverage/report.js.map +1 -0
  63. package/dist/diagnostics/diagnostic.d.ts +140 -0
  64. package/dist/diagnostics/diagnostic.js +458 -0
  65. package/dist/diagnostics/diagnostic.js.map +1 -0
  66. package/dist/diagnostics/render.d.ts +11 -0
  67. package/dist/diagnostics/render.js +58 -0
  68. package/dist/diagnostics/render.js.map +1 -0
  69. package/dist/frontend/cjs-lexer.d.ts +20 -0
  70. package/dist/frontend/cjs-lexer.js +813 -0
  71. package/dist/frontend/cjs-lexer.js.map +1 -0
  72. package/dist/frontend/lowering/http2-constants.d.ts +1 -0
  73. package/dist/frontend/lowering/http2-constants.js +251 -0
  74. package/dist/frontend/lowering/http2-constants.js.map +1 -0
  75. package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
  76. package/dist/frontend/lowering/lib-boundary.js +143 -0
  77. package/dist/frontend/lowering/lib-boundary.js.map +1 -0
  78. package/dist/frontend/lowering/lower-assert.d.ts +18 -0
  79. package/dist/frontend/lowering/lower-assert.js +1269 -0
  80. package/dist/frontend/lowering/lower-assert.js.map +1 -0
  81. package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
  82. package/dist/frontend/lowering/lower-builtins.js +5249 -0
  83. package/dist/frontend/lowering/lower-builtins.js.map +1 -0
  84. package/dist/frontend/lowering/lower-calls.d.ts +581 -0
  85. package/dist/frontend/lowering/lower-calls.js +6286 -0
  86. package/dist/frontend/lowering/lower-calls.js.map +1 -0
  87. package/dist/frontend/lowering/lower-classes.d.ts +727 -0
  88. package/dist/frontend/lowering/lower-classes.js +4701 -0
  89. package/dist/frontend/lowering/lower-classes.js.map +1 -0
  90. package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
  91. package/dist/frontend/lowering/lower-comptime.js +244 -0
  92. package/dist/frontend/lowering/lower-comptime.js.map +1 -0
  93. package/dist/frontend/lowering/lower-containers.d.ts +486 -0
  94. package/dist/frontend/lowering/lower-containers.js +6345 -0
  95. package/dist/frontend/lowering/lower-containers.js.map +1 -0
  96. package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
  97. package/dist/frontend/lowering/lower-dgram.js +319 -0
  98. package/dist/frontend/lowering/lower-dgram.js.map +1 -0
  99. package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
  100. package/dist/frontend/lowering/lower-emitter.js +688 -0
  101. package/dist/frontend/lowering/lower-emitter.js.map +1 -0
  102. package/dist/frontend/lowering/lower-enums.d.ts +22 -0
  103. package/dist/frontend/lowering/lower-enums.js +235 -0
  104. package/dist/frontend/lowering/lower-enums.js.map +1 -0
  105. package/dist/frontend/lowering/lower-expando.d.ts +35 -0
  106. package/dist/frontend/lowering/lower-expando.js +276 -0
  107. package/dist/frontend/lowering/lower-expando.js.map +1 -0
  108. package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
  109. package/dist/frontend/lowering/lower-exprs.js +8111 -0
  110. package/dist/frontend/lowering/lower-exprs.js.map +1 -0
  111. package/dist/frontend/lowering/lower-generators.d.ts +50 -0
  112. package/dist/frontend/lowering/lower-generators.js +421 -0
  113. package/dist/frontend/lowering/lower-generators.js.map +1 -0
  114. package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
  115. package/dist/frontend/lowering/lower-inspect.js +1074 -0
  116. package/dist/frontend/lowering/lower-inspect.js.map +1 -0
  117. package/dist/frontend/lowering/lower-island.d.ts +108 -0
  118. package/dist/frontend/lowering/lower-island.js +650 -0
  119. package/dist/frontend/lowering/lower-island.js.map +1 -0
  120. package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
  121. package/dist/frontend/lowering/lower-mixins.js +534 -0
  122. package/dist/frontend/lowering/lower-mixins.js.map +1 -0
  123. package/dist/frontend/lowering/lower-modules.d.ts +135 -0
  124. package/dist/frontend/lowering/lower-modules.js +1613 -0
  125. package/dist/frontend/lowering/lower-modules.js.map +1 -0
  126. package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
  127. package/dist/frontend/lowering/lower-namespaces.js +706 -0
  128. package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
  129. package/dist/frontend/lowering/lower-server.d.ts +90 -0
  130. package/dist/frontend/lowering/lower-server.js +3471 -0
  131. package/dist/frontend/lowering/lower-server.js.map +1 -0
  132. package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
  133. package/dist/frontend/lowering/lower-stmts.js +5341 -0
  134. package/dist/frontend/lowering/lower-stmts.js.map +1 -0
  135. package/dist/frontend/lowering/lower-stream.d.ts +88 -0
  136. package/dist/frontend/lowering/lower-stream.js +1533 -0
  137. package/dist/frontend/lowering/lower-stream.js.map +1 -0
  138. package/dist/frontend/lowering/lower-test.d.ts +27 -0
  139. package/dist/frontend/lowering/lower-test.js +353 -0
  140. package/dist/frontend/lowering/lower-test.js.map +1 -0
  141. package/dist/frontend/lowering/lowerer.d.ts +1748 -0
  142. package/dist/frontend/lowering/lowerer.js +6257 -0
  143. package/dist/frontend/lowering/lowerer.js.map +1 -0
  144. package/dist/frontend/lowering/surfaces.d.ts +272 -0
  145. package/dist/frontend/lowering/surfaces.js +1096 -0
  146. package/dist/frontend/lowering/surfaces.js.map +1 -0
  147. package/dist/frontend/npm-static.d.ts +55 -0
  148. package/dist/frontend/npm-static.js +300 -0
  149. package/dist/frontend/npm-static.js.map +1 -0
  150. package/dist/frontend/npm.d.ts +330 -0
  151. package/dist/frontend/npm.js +1259 -0
  152. package/dist/frontend/npm.js.map +1 -0
  153. package/dist/frontend/program.d.ts +186 -0
  154. package/dist/frontend/program.js +2255 -0
  155. package/dist/frontend/program.js.map +1 -0
  156. package/dist/frontend/provenance-registry.d.ts +48 -0
  157. package/dist/frontend/provenance-registry.js +87 -0
  158. package/dist/frontend/provenance-registry.js.map +1 -0
  159. package/dist/frontend/provenance.d.ts +6 -0
  160. package/dist/frontend/provenance.js +459 -0
  161. package/dist/frontend/provenance.js.map +1 -0
  162. package/dist/frontend/resolve.d.ts +56 -0
  163. package/dist/frontend/resolve.js +682 -0
  164. package/dist/frontend/resolve.js.map +1 -0
  165. package/dist/frontend/shared.d.ts +67 -0
  166. package/dist/frontend/shared.js +241 -0
  167. package/dist/frontend/shared.js.map +1 -0
  168. package/dist/frontend/ts7/adapter.d.ts +7 -0
  169. package/dist/frontend/ts7/adapter.js +54 -0
  170. package/dist/frontend/ts7/adapter.js.map +1 -0
  171. package/dist/frontend/ts7/ast.d.ts +50 -0
  172. package/dist/frontend/ts7/ast.js +211 -0
  173. package/dist/frontend/ts7/ast.js.map +1 -0
  174. package/dist/frontend/ts7/census-check.d.ts +1 -0
  175. package/dist/frontend/ts7/census-check.js +12 -0
  176. package/dist/frontend/ts7/census-check.js.map +1 -0
  177. package/dist/frontend/ts7/checker.d.ts +140 -0
  178. package/dist/frontend/ts7/checker.js +544 -0
  179. package/dist/frontend/ts7/checker.js.map +1 -0
  180. package/dist/frontend/ts7/enums.d.ts +18 -0
  181. package/dist/frontend/ts7/enums.js +43 -0
  182. package/dist/frontend/ts7/enums.js.map +1 -0
  183. package/dist/frontend/ts7/program.d.ts +99 -0
  184. package/dist/frontend/ts7/program.js +278 -0
  185. package/dist/frontend/ts7/program.js.map +1 -0
  186. package/dist/frontend/ts7/world-check.d.ts +3 -0
  187. package/dist/frontend/ts7/world-check.js +34 -0
  188. package/dist/frontend/ts7/world-check.js.map +1 -0
  189. package/dist/frontend/types.d.ts +205 -0
  190. package/dist/frontend/types.js +2485 -0
  191. package/dist/frontend/types.js.map +1 -0
  192. package/dist/index.d.ts +85 -0
  193. package/dist/index.js +429 -0
  194. package/dist/index.js.map +1 -0
  195. package/dist/ir/nodes.d.ts +4334 -0
  196. package/dist/ir/nodes.js +1906 -0
  197. package/dist/ir/nodes.js.map +1 -0
  198. package/dist/ir/serialize.d.ts +4 -0
  199. package/dist/ir/serialize.js +34 -0
  200. package/dist/ir/serialize.js.map +1 -0
  201. package/dist/ir/validate.d.ts +36 -0
  202. package/dist/ir/validate.js +4885 -0
  203. package/dist/ir/validate.js.map +1 -0
  204. package/package.json +30 -6
  205. package/README.md +0 -3
  206. package/index.js +0 -2
@@ -0,0 +1,1269 @@
1
+ /* The node:assert lowering (a spoke module like lower-dgram.ts): the
2
+ * callable module form (`assert(x)` through a default import or a CJS
3
+ * require binding), ok, the strict/deep equality quartet, fail, throws,
4
+ * and match/doesNotMatch — for BOTH the "assert" and "assert/strict"
5
+ * modules ("assert/strict" additionally binds the loose NAMES to the
6
+ * strict comparisons: equal IS strictEqual, exactly Node's aliasing).
7
+ *
8
+ * Failures throw Node's AssertionError (a runtime %Error whose name is
9
+ * "AssertionError" and whose code slot is "ERR_ASSERTION"), so the
10
+ * catch-side reads (`instanceof Error`, `.name`, `.message`, `.code`)
11
+ * answer exactly like Node's. Generated messages are Node-exact for the
12
+ * scalar comparisons (the runtime reproduces assertion_error.js's scalar
13
+ * forms; scr_assert.c's design note lists the divergences); assert.ok's
14
+ * source-text message bakes at compile time — the frontend HAS the source
15
+ * where Node re-reads the file at runtime.
16
+ *
17
+ * Deep equality is honest, never approximate: deepStrictEqual over
18
+ * composite values synthesizes a per-type structural comparison (Node's
19
+ * strict rules — Object.is for numbers, per-element arrays, per-field
20
+ * records, SameValueZero-keyed Maps/Sets with deep values), and any type
21
+ * it cannot compare faithfully (class instances, typed arrays, index-
22
+ * signature records, dyn/unknown) fences at the call site instead of
23
+ * miscomparing. */
24
+ import * as ts from "../ts7/adapter.js";
25
+ import { jsFuncNameOf, own } from "./lowerer.js";
26
+ import { NARROW_FIRST } from "./surfaces.js";
27
+ import { BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, REGEX, RUNTIME_ERROR_CLASSES, STRING, VOID, isUnitType, typeEquals, typeKey } from "../../ir/nodes.js";
28
+ /** node:assert/strict binds the loose NAMES to the strict comparisons —
29
+ * Node's own aliasing (`strict.equal === assert.strictEqual`). */
30
+ const STRICT_ALIASES = {
31
+ equal: "strictEqual",
32
+ notEqual: "notStrictEqual",
33
+ deepEqual: "deepStrictEqual",
34
+ notDeepEqual: "notDeepStrictEqual",
35
+ };
36
+ /** The canonical member the two modules share ("assert/strict".equal →
37
+ * strictEqual); "assert" members pass through unchanged. */
38
+ function canonicalAssertMember(module, member) {
39
+ if (module === "assert/strict")
40
+ return own(STRICT_ALIASES, member) ?? member;
41
+ return member;
42
+ }
43
+ /** VOID/never results are usable as statements and concise arrow bodies
44
+ * only (the lower-dgram stance): nothing downstream can consume them. */
45
+ function requireStatementPosition(L, call, what) {
46
+ if (ts.isExpressionStatement(call.parent) || ts.isArrowFunction(call.parent))
47
+ return;
48
+ L.noLowering(`using the result of ${what}`, call, "the result is void — call it as its own statement");
49
+ }
50
+ /** The optional trailing message argument: Node accepts a string or an
51
+ * Error (throwing the Error itself) — the STRING form lowers; a literal
52
+ * `undefined` counts as omitted (Node's `message == null` test). Returns
53
+ * the libCall's (msg, hasMsg) pair — a "" dummy when omitted, so the
54
+ * runtime can distinguish an omitted message from an empty one. */
55
+ function lowerMessageArg(L, node, loc) {
56
+ const flag = (value) => ({ kind: "boolLit", value, type: BOOL, loc });
57
+ if (!node || (ts.isIdentifier(node) && node.text === "undefined")) {
58
+ return { msg: { kind: "strLit", value: "", type: STRING, loc }, hasMsg: flag(false) };
59
+ }
60
+ const msg = L.lowerExpr(node);
61
+ if (msg.type.kind !== "string") {
62
+ L.noLowering(`assert messages of type '${L.fmt(msg.type)}'`, node, "only string messages lower (Node also accepts an Error to throw — construct and throw it directly)");
63
+ }
64
+ return { msg, hasMsg: flag(true) };
65
+ }
66
+ /** The module-function dispatch — the spoke's entry, called from both the
67
+ * named-import and namespace/default-import call paths. Null for other
68
+ * modules and for members with no lowering (the module tables' fence
69
+ * takes over, module-qualified with the surfaces.ts hints). */
70
+ export function lowerAssertModuleCall(L, expr, bi, loc) {
71
+ if (bi.module !== "assert" && bi.module !== "assert/strict")
72
+ return null;
73
+ switch (canonicalAssertMember(bi.module, bi.member)) {
74
+ case "ok":
75
+ return lowerAssertOk(L, expr, loc);
76
+ case "strictEqual":
77
+ return lowerAssertEqual(L, expr, loc, false, false);
78
+ case "notStrictEqual":
79
+ return lowerAssertEqual(L, expr, loc, true, false);
80
+ case "deepStrictEqual":
81
+ return lowerAssertEqual(L, expr, loc, false, true);
82
+ case "notDeepStrictEqual":
83
+ return lowerAssertEqual(L, expr, loc, true, true);
84
+ case "fail":
85
+ return lowerAssertFail(L, expr, loc);
86
+ case "match":
87
+ return lowerAssertMatch(L, expr, loc, false);
88
+ case "doesNotMatch":
89
+ return lowerAssertMatch(L, expr, loc, true);
90
+ case "throws":
91
+ return lowerAssertThrows(L, expr, loc);
92
+ case "rejects":
93
+ return lowerAssertRejects(L, expr, loc, false);
94
+ case "doesNotReject":
95
+ return lowerAssertRejects(L, expr, loc, true);
96
+ case "ifError":
97
+ return lowerAssertIfError(L, expr, loc);
98
+ default:
99
+ return null;
100
+ }
101
+ }
102
+ /** The callable-module form: `assert(x)` where the callee identifier IS
103
+ * the assert module binding — a default import (`import assert from
104
+ * "node:assert"`) or the CJS `const assert = require("assert")`. Node's
105
+ * module object is assert.ok itself. A NAMESPACE import binding is not
106
+ * callable in Node (ES namespace objects throw TypeError), so that form
107
+ * fences instead of compiling a crash. Null for non-assert callees. */
108
+ export function lowerAssertDirectCall(L, expr, loc) {
109
+ const callee = expr.expression;
110
+ if (!ts.isIdentifier(callee))
111
+ return null;
112
+ const module = L.builtinNamespaceModuleOf(callee);
113
+ if (module !== "assert" && module !== "assert/strict")
114
+ return null;
115
+ const calleeSym = L.checker.getSymbolAtLocation(callee);
116
+ const decl = calleeSym ? L.checker.declarationsOf(calleeSym)[0] : undefined;
117
+ if (decl && ts.isNamespaceImport(decl)) {
118
+ L.unsupported("SC1013", expr, "calling a module namespace object (Node throws TypeError there — " +
119
+ 'use the default import: import assert from "node:assert")');
120
+ }
121
+ return lowerAssertOk(L, expr, loc);
122
+ }
123
+ /** assert(value[, message]) / assert.ok(value[, message]): the truthiness
124
+ * test over any condition-testable type; the generated message carries the
125
+ * call's SOURCE TEXT (Node re-reads the file at runtime — the frontend has
126
+ * the same bytes at compile time). */
127
+ function lowerAssertOk(L, expr, loc) {
128
+ requireStatementPosition(L, expr, expr.expression.getText());
129
+ if (expr.arguments.length < 1 || expr.arguments.length > 2) {
130
+ L.noLowering(`${expr.expression.getText()} with ${expr.arguments.length} arguments`, expr, "the supported forms are assert(value) and assert(value, message)");
131
+ }
132
+ const valueNode = expr.arguments[0];
133
+ const pass = L.ensureBool(L.lowerExpr(valueNode), valueNode);
134
+ const msgNode = expr.arguments[1];
135
+ if (!msgNode || (ts.isIdentifier(msgNode) && msgNode.text === "undefined")) {
136
+ // Node reads the CALLSITE LINE and slices the expression out of it
137
+ // (internal/errors/error_source.js) — a call spanning lines truncates
138
+ // at the first newline, so the compile-time text does too.
139
+ const source = expr.getText().split("\n", 1)[0];
140
+ const generated = `The expression evaluated to a falsy value:\n\n ${source}\n`;
141
+ const msg = { kind: "strLit", value: generated, type: STRING, loc };
142
+ return { kind: "libCall", fn: "assert.ok", args: [pass, msg], type: VOID, loc };
143
+ }
144
+ const { msg } = lowerMessageArg(L, msgNode, loc);
145
+ return { kind: "libCall", fn: "assert.ok", args: [pass, msg], type: VOID, loc };
146
+ }
147
+ /** assert.fail([message]): an unconditional AssertionError — "Failed"
148
+ * when the message is omitted (Node's internal default). Lowers as
149
+ * assert.ok over a false literal. */
150
+ function lowerAssertFail(L, expr, loc) {
151
+ requireStatementPosition(L, expr, "assert.fail");
152
+ if (expr.arguments.length > 1) {
153
+ L.noLowering(`assert.fail with ${expr.arguments.length} arguments`, expr, "the supported form is fail(message?) — the deprecated fail(actual, expected, ...) has no lowering");
154
+ }
155
+ let msg;
156
+ const msgNode = expr.arguments[0];
157
+ if (!msgNode || (ts.isIdentifier(msgNode) && msgNode.text === "undefined")) {
158
+ msg = { kind: "strLit", value: "Failed", type: STRING, loc };
159
+ }
160
+ else {
161
+ msg = lowerMessageArg(L, msgNode, loc).msg;
162
+ }
163
+ const falseLit = { kind: "boolLit", value: false, type: BOOL, loc };
164
+ return { kind: "libCall", fn: "assert.ok", args: [falseLit, msg], type: VOID, loc };
165
+ }
166
+ /** The equality quartet. Scalars (number/string/boolean) compare with
167
+ * Object.is in the runtime (strictEqual and deepStrictEqual agree there —
168
+ * Node's own rule) and generate Node's exact messages; composite
169
+ * deepStrictEqual synthesizes the per-type structural comparison. Objects
170
+ * under strictEqual (reference equality), mixed static types, and unions
171
+ * fence — honestly, never a miscompare. */
172
+ function lowerAssertEqual(L, expr, loc, negated, deep) {
173
+ const surface = `assert.${deep ? (negated ? "notDeepStrictEqual" : "deepStrictEqual") : negated ? "notStrictEqual" : "strictEqual"}`;
174
+ requireStatementPosition(L, expr, surface);
175
+ if (expr.arguments.length < 2 || expr.arguments.length > 3) {
176
+ L.noLowering(`${surface} with ${expr.arguments.length} arguments`, expr);
177
+ }
178
+ // A context-free EMPTY array literal on either side has no element type
179
+ // of its own (`deepStrictEqual(ee.listeners('x'), [])` — the checker
180
+ // says any[]): it adopts the OTHER side's array type. Literals have no
181
+ // side effects, so lowering order is unobservable.
182
+ const aNode = expr.arguments[0];
183
+ const bNode = expr.arguments[1];
184
+ const emptyArrayLit = (n) => ts.isArrayLiteralExpression(n) && n.elements.length === 0;
185
+ const emptyOf = (t) => ({ kind: "arrayLit", elems: [], type: t, loc });
186
+ // A dyn other side adopts number[] — the DOM array a boxed empty
187
+ // literal builds is elementless, so any elem type serves.
188
+ const adoptedEmpty = (other) => other.kind === "array" ? emptyOf(other) : other.kind === "dyn" ? emptyOf({ kind: "array", elem: F64 }) : null;
189
+ let a;
190
+ let b;
191
+ if (emptyArrayLit(aNode) && !emptyArrayLit(bNode)) {
192
+ b = L.lowerExpr(bNode);
193
+ a = adoptedEmpty(b.type) ?? L.lowerExpr(aNode);
194
+ }
195
+ else {
196
+ a = L.lowerExpr(aNode);
197
+ b = emptyArrayLit(bNode) ? (adoptedEmpty(a.type) ?? L.lowerExpr(bNode)) : L.lowerExpr(bNode);
198
+ }
199
+ const { msg, hasMsg } = lowerMessageArg(L, expr.arguments[2], loc);
200
+ const flag = (value) => ({ kind: "boolLit", value, type: BOOL, loc });
201
+ // CHECKED-DYNAMIC operands (dyn-vs-dyn, dyn-vs-static): both sides
202
+ // cross into the DOM and one runtime entry answers the whole quartet —
203
+ // SameValue for the strict pair (boxed-closure identity for
204
+ // functions), the structural DOM walk for the deep pair, with
205
+ // assertion_error.js's generated messages (scalar forms byte-exact;
206
+ // composites render compact:false/sorted through the DOM). The static
207
+ // side boxes only when the conversion is HONEST for the operator:
208
+ // scalars and unit literals always; boxable functions (the closure
209
+ // crosses by identity); JSON-safe composites under the deep pair only.
210
+ // Under strictEqual a composite fences instead: crossing the unknown
211
+ // boundary COPIES, so a strict compare against a static composite
212
+ // would answer false where Node can answer true (the value's own
213
+ // earlier crossing) — a miscompare, never lowered. Bytes fence both
214
+ // ways (the DOM copy cannot carry the Buffer/Uint8Array brand the
215
+ // checker type knows); class instances and everything non-convertible
216
+ // keep the fence too.
217
+ if (a.type.kind === "dyn" || b.type.kind === "dyn") {
218
+ const box = (e, node) => {
219
+ if (e.type.kind === "dyn")
220
+ return e;
221
+ const k = e.type.kind;
222
+ const ok = e.kind === "unitLit" ||
223
+ k === "f64" ||
224
+ k === "string" ||
225
+ k === "bool" ||
226
+ (k === "func" && L.dynConvertible(e.type)) ||
227
+ // Runtime handles cross by REFERENCE (identity preserved), so the
228
+ // strict compare is honest: assert.strictEqual(this, server) —
229
+ // the ambient-receiver suite shape. The runtime's deep arm
230
+ // answers same-handle only (its documented stance).
231
+ DYN_HANDLE_KINDS.has(k) ||
232
+ // %Error crosses as the DOM's error encoding ({%error, name,
233
+ // message, code?}) through scr_dyn_from_error's IDENTITY CACHE —
234
+ // one error instance is ONE DOM node however it crosses. Strict
235
+ // compares are therefore honest reference equality (a trace
236
+ // context's stamped .error, a caught dyn, and the static error's
237
+ // own boxing are the same node exactly when Node's objects are the
238
+ // same), and the deep walk compares the encoding's name/message/
239
+ // code — Node's deepStrictEqual over Errors. Island round trips
240
+ // re-encode and lose the identity (SEMANTICS.md).
241
+ (k === "object" && e.type.kind === "object" && e.type.className === "%Error") ||
242
+ (deep && k !== "bytes" && k !== "object" && L.dynConvertible(e.type));
243
+ if (!ok) {
244
+ const hint = !deep && (k === "record" || k === "array" || k === "map" || k === "set" || k === "union")
245
+ ? "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"
246
+ : k === "bytes"
247
+ ? "the unknown boundary cannot carry the Buffer-vs-Uint8Array brand Node compares — narrow the unknown side to a typed array and compare those"
248
+ : "the static side must convert into the checked-dynamic DOM (numbers, strings, booleans, null/undefined, boxable functions, or JSON-safe structures under the deep forms) — narrow the unknown side instead";
249
+ L.noLowering(`${surface} of 'unknown' and '${L.fmt(e.type)}' values`, node, hint);
250
+ }
251
+ // A boxed closure takes its best-effort JS name from the source
252
+ // node (the coerceInto convention) — the failure message renders
253
+ // [Function: name] like Node.
254
+ const fnName = k === "func" ? jsFuncNameOf(node) : null;
255
+ return { kind: "dynFrom", value: e, ...(fnName !== null ? { fnName } : {}), type: DYN, loc: e.loc };
256
+ };
257
+ return {
258
+ kind: "libCall",
259
+ fn: "assert.eqDyn",
260
+ args: [box(a, aNode), box(b, bNode), flag(negated), flag(deep), msg, hasMsg],
261
+ type: VOID,
262
+ loc,
263
+ };
264
+ }
265
+ const scalarFn = a.type.kind === b.type.kind
266
+ ? a.type.kind === "f64"
267
+ ? "assert.eqF64"
268
+ : a.type.kind === "string"
269
+ ? "assert.eqStr"
270
+ : a.type.kind === "bool"
271
+ ? "assert.eqBool"
272
+ : a.type.kind === "symbol"
273
+ ? // Symbols are primitives under SameValue: strictEqual IS
274
+ // deepStrictEqual (pointer identity), with v24's
275
+ // "Symbol(desc)" stacked-diff messages.
276
+ "assert.eqSym"
277
+ : null
278
+ : null;
279
+ if (scalarFn) {
280
+ return {
281
+ kind: "libCall",
282
+ fn: scalarFn,
283
+ args: [a, b, flag(negated), flag(deep), msg, hasMsg],
284
+ type: VOID,
285
+ loc,
286
+ };
287
+ }
288
+ // Typed arrays / Buffers, both sides one static bytes type. strictEqual
289
+ // is reference identity (Object.is on objects — the runtime value IS its
290
+ // pointer); deepStrictEqual is brand + content: Node compares prototypes
291
+ // first (Buffer.from([1]) is never deep-equal to new Uint8Array([1])),
292
+ // and the brand lives in each argument's own checker type (the
293
+ // lower-inspect precedent: the runtime representation is one ScrBytes
294
+ // either way). A checker brand this code cannot classify fences — never
295
+ // a miscompare. The one accepted approximation: a bytes value reaching
296
+ // the call through a binding whose DECLARED constructor differs from the
297
+ // value's runtime one compares by the declared brand (SEMANTICS.md).
298
+ if (a.type.kind === "bytes" && typeEquals(a.type, b.type)) {
299
+ const brandA = bytesBrandOf(L, expr.arguments[0]);
300
+ const brandB = bytesBrandOf(L, expr.arguments[1]);
301
+ if (!deep) {
302
+ // Brands shape only the failure HEADER (same-structure vs
303
+ // reference-equal expectation); an unclassifiable side defers to the
304
+ // content probe.
305
+ const brandsEq = flag(brandA === null || brandB === null || brandA === brandB);
306
+ return {
307
+ kind: "libCall",
308
+ fn: "assert.refEqBytes",
309
+ args: [a, b, flag(negated), brandsEq, msg, hasMsg],
310
+ type: VOID,
311
+ loc,
312
+ };
313
+ }
314
+ if (brandA === null || brandB === null) {
315
+ L.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");
316
+ }
317
+ const verdict = {
318
+ kind: "libCall",
319
+ fn: "assert.bytesDeepEq",
320
+ args: [a, b, flag(brandA === brandB)],
321
+ type: BOOL,
322
+ loc,
323
+ };
324
+ return {
325
+ kind: "libCall",
326
+ fn: "assert.deepResult",
327
+ args: [verdict, flag(negated), msg, hasMsg],
328
+ type: VOID,
329
+ loc,
330
+ };
331
+ }
332
+ // Function values (ANY two static signatures — a tuple-typed
333
+ // listeners() element against a prefix-declared handler): strictEqual
334
+ // is reference identity (functions are objects to Object.is), Node's
335
+ // object-comparison headers. The deep forms fall through to the generic
336
+ // deep path below — deep equality over functions IS reference identity
337
+ // (same-typed there; the per-type helper carries it).
338
+ if (!deep && a.type.kind === "func" && b.type.kind === "func") {
339
+ return {
340
+ kind: "libCall",
341
+ fn: "assert.refEqFn",
342
+ args: [a, b, flag(negated), msg, hasMsg],
343
+ type: VOID,
344
+ loc,
345
+ };
346
+ }
347
+ if (!deep) {
348
+ const hint = a.type.kind === "union" || b.type.kind === "union"
349
+ ? `union-typed comparisons need one arm (${NARROW_FIRST})`
350
+ : a.type.kind !== b.type.kind
351
+ ? "both sides must share one static scalar type (number, string, or boolean)"
352
+ : "strictEqual on objects is reference equality — deepStrictEqual compares structure";
353
+ L.noLowering(`${surface} of '${L.fmt(a.type)}' and '${L.fmt(b.type)}' values`, expr, hint);
354
+ }
355
+ // Deep equality: identical static types, structurally comparable.
356
+ if (!typeEquals(a.type, b.type)) {
357
+ L.noLowering(`${surface} of '${L.fmt(a.type)}' and '${L.fmt(b.type)}' values`, expr, "deep equality lowers when both sides have the SAME static type — annotate or narrow one side");
358
+ }
359
+ if (isUnitType(a.type)) {
360
+ // undefined vs undefined / null vs null: trivially deep-equal.
361
+ return {
362
+ kind: "libCall",
363
+ fn: "assert.deepResult",
364
+ args: [flag(true), flag(negated), msg, hasMsg],
365
+ type: VOID,
366
+ loc,
367
+ };
368
+ }
369
+ const unsupported = deepUnsupportedReason(L, a.type, new Set());
370
+ if (unsupported !== null) {
371
+ L.noLowering(`${surface} of '${L.fmt(a.type)}' values`, expr, unsupported);
372
+ }
373
+ const helper = deepEqHelper(L, a.type, loc);
374
+ const verdict = { kind: "call", callee: helper, args: [a, b], type: BOOL, loc };
375
+ return {
376
+ kind: "libCall",
377
+ fn: "assert.deepResult",
378
+ args: [verdict, flag(negated), msg, hasMsg],
379
+ type: VOID,
380
+ loc,
381
+ };
382
+ }
383
+ /** assert.match / assert.doesNotMatch: the regex test with Node's
384
+ * generated message (regex source + getter-ordered flags, the inspected
385
+ * input). */
386
+ function lowerAssertMatch(L, expr, loc, negated) {
387
+ const surface = negated ? "assert.doesNotMatch" : "assert.match";
388
+ requireStatementPosition(L, expr, surface);
389
+ if (expr.arguments.length < 2 || expr.arguments.length > 3) {
390
+ L.noLowering(`${surface} with ${expr.arguments.length} arguments`, expr);
391
+ }
392
+ const s = L.lowerExprExpecting(expr.arguments[0], STRING);
393
+ const re = L.lowerExpr(expr.arguments[1]);
394
+ if (re.type.kind !== "regex") {
395
+ L.noLowering(`${surface} with a non-regex pattern`, expr.arguments[1], "the second argument must be a regular expression");
396
+ }
397
+ const { msg, hasMsg } = lowerMessageArg(L, expr.arguments[2], loc);
398
+ const neg = { kind: "boolLit", value: negated, type: BOOL, loc };
399
+ return { kind: "libCall", fn: "assert.match", args: [s, re, neg, msg, hasMsg], type: VOID, loc };
400
+ }
401
+ const SHAPE_KEY_IDS = { code: 0, message: 1, name: 2 };
402
+ /** inspect renders regex flags in GETTER order (dgimsuvy), not source
403
+ * order — the same reordering scr_regex.c applies at runtime. */
404
+ function regexFlagsGetterOrder(flags) {
405
+ return [..."dgimsuvy"].filter((f) => flags.includes(f)).join("");
406
+ }
407
+ /** Is `className` the runtime %Error root or a class inside its
408
+ * hierarchy (builtin kinds and user subclasses alike)? */
409
+ function inErrorHierarchy(L, className) {
410
+ if (className === "%Error" || RUNTIME_ERROR_CLASSES.has(className))
411
+ return true;
412
+ for (let c = L.classes.get(className)?.base ?? null; c; c = c.base) {
413
+ if (c.def.name === "%Error")
414
+ return true;
415
+ }
416
+ return false;
417
+ }
418
+ /** Classify the expected argument: string → message, regex value → the
419
+ * regex test over String(error), an error-class identifier → instanceof,
420
+ * an object LITERAL over name/message/code (string or regex values) →
421
+ * the per-key shape comparison. Anything else fences honestly. */
422
+ function classifyThrowsExpected(L, surface, node, loc) {
423
+ const expectedT = L.mapTypeOf(L.typeOf(node));
424
+ if (expectedT?.kind === "string") {
425
+ const m = lowerMessageArg(L, node, loc);
426
+ return { form: "message", msg: m.msg, hasMsg: m.hasMsg };
427
+ }
428
+ if (expectedT?.kind === "regex") {
429
+ return { form: "regex", value: L.lowerExpr(node) };
430
+ }
431
+ const sym = ts.isIdentifier(node) ? L.resolveValueSymbol(node) : null;
432
+ const info = (sym && L.builtinErrorInfoOf(sym)) ?? (sym && L.classBySymbol.get(sym)) ?? null;
433
+ // A rebindable decorated name: the runtime expectation is the decoration
434
+ // result's interval, not the declaration's — no static class check.
435
+ if (info?.classDecorators?.valueGlobalId !== undefined) {
436
+ L.unsupported("SC1090", node, "assert.throws/rejects against a decorated class whose decorators may replace it");
437
+ }
438
+ if (info && L.inHierarchy(info)) {
439
+ const rec = RUNTIME_ERROR_CLASSES.get(info.def.name);
440
+ return {
441
+ form: "class",
442
+ className: info.def.name,
443
+ displayName: rec ? rec.lib : info.def.name.replace(/^%/, ""),
444
+ };
445
+ }
446
+ let obj = node;
447
+ while (ts.isParenthesizedExpression(obj))
448
+ obj = obj.expression;
449
+ if (ts.isObjectLiteralExpression(obj)) {
450
+ if (obj.properties.length === 0) {
451
+ L.noLowering(`${surface} with an empty expected object`, obj, "Node rejects this (the error 'may not be an empty object') — name at least one of name/message/code");
452
+ }
453
+ // Duplicate keys keep the LAST value (JS object-literal semantics);
454
+ // property order is preserved for argument evaluation.
455
+ const byId = new Map();
456
+ for (const prop of obj.properties) {
457
+ const named = ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop) ? prop : null;
458
+ if (!named || !(ts.isIdentifier(named.name) || ts.isStringLiteral(named.name))) {
459
+ L.noLowering(`${surface} with this expected-object property form`, prop, "plain `key: value` properties are the lowered form");
460
+ }
461
+ const keyName = named.name.text;
462
+ const id = own(SHAPE_KEY_IDS, keyName);
463
+ if (id === undefined) {
464
+ L.noLowering(`${surface} comparing the '${keyName}' property of the thrown error`, prop, "the static error surface carries name, message, and code — compare other properties in a catch block");
465
+ }
466
+ const valueNode = ts.isPropertyAssignment(named) ? named.initializer : named.name;
467
+ const value = L.lowerExpr(valueNode);
468
+ if (value.type.kind === "string") {
469
+ byId.set(id, { id, kind: "str", value, enameBake: null });
470
+ }
471
+ else if (value.type.kind === "regex") {
472
+ let bake = null;
473
+ if (ts.isRegularExpressionLiteral(valueNode)) {
474
+ const text = valueNode.text;
475
+ const cut = text.lastIndexOf("/");
476
+ bake = `/${text.slice(1, cut)}/${regexFlagsGetterOrder(text.slice(cut + 1))}`;
477
+ }
478
+ byId.set(id, { id, kind: "re", value, enameBake: bake });
479
+ }
480
+ else {
481
+ L.noLowering(`${surface} expected-object values of type '${L.fmt(value.type)}'`, valueNode, "string and regular-expression values are the lowered comparisons");
482
+ }
483
+ }
484
+ return { form: "shape", keys: [...byId.values()] };
485
+ }
486
+ // An error INSTANCE (`assert.rejects(p, expectedError)` — the
487
+ // tracingChannel suite's shape): the value crosses as a DOM error and
488
+ // the runtime compares per Node's expectsError key walk. A
489
+ // checked-dynamic expected rides directly; a static %Error boxes.
490
+ if (expectedT?.kind === "dyn") {
491
+ const value = L.lowerExpr(node);
492
+ if (value.type.kind === "dyn")
493
+ return { form: "errValue", value };
494
+ }
495
+ if (expectedT?.kind === "object" && expectedT.className === "%Error") {
496
+ const value = L.lowerExpr(node);
497
+ if (value.type.kind === "object") {
498
+ return { form: "errValue", value: { kind: "dynFrom", value, type: DYN, loc } };
499
+ }
500
+ }
501
+ L.noLowering(`${surface} with this expected-error shape`, node, "an error CLASS (Error/TypeError/... or a class extending them), an error instance, a message string, " +
502
+ "a regular expression, or an object literal over name/message/code are the lowered forms");
503
+ }
504
+ /** assert.throws(fn[, expected][, message]): the zero-parameter callback
505
+ * runs inside a synthesized try/catch helper. Bare form: any throw
506
+ * passes, a normal return throws "Missing expected exception." — a
507
+ * STRING second argument is Node's message form. An error-CLASS second
508
+ * argument checks `instanceof` (a non-matching Error throws Node's
509
+ * mismatch AssertionError); a REGEX tests String(error); an object
510
+ * LITERAL compares its name/message/code keys (strings deep, regex
511
+ * values by test) with Node's byte-exact Comparison-diff message. A
512
+ * non-Error thrown value under any expectation propagates
513
+ * (SEMANTICS.md 104 — Node builds an AssertionError from its
514
+ * inspection). */
515
+ function lowerAssertThrows(L, expr, loc) {
516
+ requireStatementPosition(L, expr, "assert.throws");
517
+ if (expr.arguments.length < 1 || expr.arguments.length > 3) {
518
+ L.noLowering(`assert.throws with ${expr.arguments.length} arguments`, expr, "the supported forms are throws(fn), throws(fn, expected), and throws(fn, expected, message)");
519
+ }
520
+ const fn = L.lowerExpr(expr.arguments[0]);
521
+ if (fn.type.kind !== "func" || fn.type.params.length !== 0) {
522
+ L.noLowering("assert.throws with this callback shape", expr.arguments[0], "the callback must be a zero-parameter function");
523
+ }
524
+ const { expected, msg, hasMsg } = throwsArguments(L, "assert.throws", expr, loc);
525
+ const helper = assertThrowsHelper(L, "throws", fn.type, false, expected, loc);
526
+ return { kind: "call", callee: helper, args: [fn, msg, hasMsg, ...expectedArgs(expected)], type: VOID, loc };
527
+ }
528
+ /** assert.rejects / assert.doesNotReject: the async twins. The first
529
+ * argument is a zero-parameter promise-returning function (an async
530
+ * function value — a synchronous throw from a non-async one propagates
531
+ * RAW through the helper's own promise, Node's waitForActual) or a
532
+ * promise value. The call answers Promise<void>: `await` it like Node;
533
+ * an un-awaited failing call enters the unhandled-rejection ledger,
534
+ * also like Node. rejects shares assert.throws's expected forms;
535
+ * doesNotReject rethrows a NON-matching rejection and throws Node's
536
+ * "Got unwanted rejection" AssertionError on a match (regex
537
+ * expectations fence — the runtime predicate is throw-shaped). */
538
+ function lowerAssertRejects(L, expr, loc, doesNot) {
539
+ const surface = doesNot ? "assert.doesNotReject" : "assert.rejects";
540
+ if (expr.arguments.length < 1 || expr.arguments.length > 3) {
541
+ L.noLowering(`${surface} with ${expr.arguments.length} arguments`, expr, `the supported forms are ${doesNot ? "doesNotReject" : "rejects"}(fn), (fn, expected), and (fn, expected, message)`);
542
+ }
543
+ const recv = L.lowerExpr(expr.arguments[0]);
544
+ const recvIsPromise = recv.type.kind === "promise";
545
+ if (!recvIsPromise &&
546
+ (recv.type.kind !== "func" || recv.type.params.length !== 0 || recv.type.ret.kind !== "promise")) {
547
+ L.noLowering(`${surface} with this first argument`, expr.arguments[0], "an async (promise-returning) zero-parameter function or a promise are the lowered forms " +
548
+ "(a bare thenable — an object with a then method — has no lowering: wrap it, e.g. Promise.resolve().then(() => thenable))");
549
+ }
550
+ const { expected, msg, hasMsg } = throwsArguments(L, surface, expr, loc);
551
+ if (doesNot && (expected.form === "shape" || expected.form === "errValue")) {
552
+ // Node itself rejects this form (ERR_INVALID_ARG_TYPE: "expected"
553
+ // must be a function or RegExp) — the fence is the compile-time
554
+ // statement of the same rule.
555
+ L.noLowering(`${surface} with an object-shape expectation`, expr.arguments[1], "Node rejects this form (doesNotReject takes a class or RegExp) — use a class, a regex, or a bare call");
556
+ }
557
+ const helper = assertThrowsHelper(L, doesNot ? "dnr" : "rejects", recv.type, recvIsPromise, expected, loc);
558
+ return {
559
+ kind: "call",
560
+ callee: helper,
561
+ args: [recv, msg, hasMsg, ...expectedArgs(expected)],
562
+ type: { kind: "promise", inner: VOID },
563
+ loc,
564
+ };
565
+ }
566
+ /** The shared expected/message argument split (arguments 2 and 3): a
567
+ * string expected IS the message and excludes a third argument (Node
568
+ * throws ERR_INVALID_ARG_TYPE there). */
569
+ function throwsArguments(L, surface, expr, loc) {
570
+ const expectedNode = expr.arguments[1];
571
+ // A literal `undefined` expected counts as omitted (Node reads the
572
+ // message from the THIRD argument in that spelling).
573
+ const expected = expectedNode && !(ts.isIdentifier(expectedNode) && expectedNode.text === "undefined")
574
+ ? classifyThrowsExpected(L, surface, expectedNode, loc)
575
+ : { form: "bare" };
576
+ if (expected.form === "message") {
577
+ if (expr.arguments.length === 3) {
578
+ L.noLowering(`${surface} with a string expected argument AND a message`, expr.arguments[2], "Node rejects this form (the string IS the message) — use an expected shape second");
579
+ }
580
+ return { expected, msg: expected.msg, hasMsg: expected.hasMsg };
581
+ }
582
+ const { msg, hasMsg } = lowerMessageArg(L, expr.arguments[2], loc);
583
+ return { expected, msg, hasMsg };
584
+ }
585
+ /** The extra call-site arguments a form carries into its helper, in the
586
+ * helper's own parameter order. */
587
+ function expectedArgs(expected) {
588
+ if (expected.form === "regex")
589
+ return [expected.value];
590
+ if (expected.form === "shape")
591
+ return expected.keys.map((k) => k.value);
592
+ if (expected.form === "errValue")
593
+ return [expected.value];
594
+ return [];
595
+ }
596
+ /** The interned per-(mode, receiver-type, expected-signature) helper:
597
+ *
598
+ * %assert.throws.<n>(f, m, hm[, re | k0..kN]) {
599
+ * try { f(); } catch (e) { <per-form checks> }
600
+ * <"Missing expected exception" throw>
601
+ * }
602
+ *
603
+ * rejects/doesNotReject synthesize the ASYNC twin — the callback's
604
+ * promise is created OUTSIDE the try (a sync throw from the callback
605
+ * rejects the helper's own promise raw, Node's waitForActual), then
606
+ * awaited inside it:
607
+ *
608
+ * async %assert.rejects.<n>(f, m, hm, ...) {
609
+ * const pr = f(); // or the promise param itself
610
+ * try { await pr; } catch (e) { <per-form checks, return> }
611
+ * <"Missing expected rejection" throw>
612
+ * }
613
+ *
614
+ * Per-form catch bodies (throws/rejects): bare returns; class returns on
615
+ * instanceof, throws the mismatch AssertionError for other Errors;
616
+ * regex/shape run their runtime checks (which throw on mismatch) and
617
+ * return. doesNotReject inverts: a MATCHING Error throws "Got unwanted
618
+ * rejection", everything else rethrows the original reason. A non-Error
619
+ * thrown value under any expectation propagates (SEMANTICS.md 104). */
620
+ function assertThrowsHelper(L, mode, recvType, recvIsPromise, expected, loc) {
621
+ const sig = expected.form === "class"
622
+ ? `class:${expected.className}`
623
+ : expected.form === "regex"
624
+ ? "regex"
625
+ : expected.form === "shape"
626
+ ? "shape:" + expected.keys.map((k) => `${k.id}${k.kind}:${k.enameBake ?? ""}`).join(",")
627
+ : expected.form === "errValue"
628
+ ? "errval"
629
+ : "bare";
630
+ const key = `assert.${mode}:${recvIsPromise ? "p" : "f"}:${typeKey(recvType)}:${sig}`;
631
+ const existing = L.assertHelpers.get(key);
632
+ if (existing)
633
+ return existing;
634
+ const name = `%assert.${mode}.${L.assertHelpers.size}`;
635
+ L.assertHelpers.set(key, name);
636
+ const ref = (localId, type) => ({ kind: "varRef", localId, type, loc });
637
+ const flag = (value) => ({ kind: "boolLit", value, type: BOOL, loc });
638
+ const str = (value) => ({ kind: "strLit", value, type: STRING, loc });
639
+ const num = (value) => ({ kind: "numLit", value, type: F64, loc });
640
+ const caughtRef = () => ref("e.0", CAUGHT);
641
+ const errT = { kind: "object", className: "%Error" };
642
+ const narrowed = () => ({ kind: "caughtNarrow", value: caughtRef(), type: errT, loc });
643
+ const isInstance = (className) => ({
644
+ kind: "caughtTest", value: caughtRef(), test: "instanceof", className, type: BOOL, loc,
645
+ });
646
+ const lib = (fn, args) => ({
647
+ kind: "exprStmt",
648
+ expr: { kind: "libCall", fn, args, type: VOID, loc },
649
+ loc,
650
+ });
651
+ const m = () => ref("m.0", STRING);
652
+ const hm = () => ref("hm.0", BOOL);
653
+ const doReturn = { kind: "return", value: null, loc };
654
+ const rethrow = { kind: "rethrow", localId: "e.0", loc };
655
+ const ifStmt = (cond, then) => ({ kind: "if", cond, then, else_: null, loc });
656
+ const params = [
657
+ { localId: "f.0", name: recvIsPromise ? "p" : "f", type: recvType },
658
+ { localId: "m.0", name: "m", type: STRING },
659
+ { localId: "hm.0", name: "hm", type: BOOL },
660
+ ];
661
+ const shapeParamIds = [];
662
+ if (expected.form === "regex")
663
+ params.push({ localId: "re.0", name: "re", type: REGEX });
664
+ if (expected.form === "errValue")
665
+ params.push({ localId: "ev.0", name: "ev", type: DYN });
666
+ if (expected.form === "shape") {
667
+ expected.keys.forEach((k, i) => {
668
+ const localId = `k${i}.0`;
669
+ shapeParamIds.push(localId);
670
+ params.push({ localId, name: `k${i}`, type: k.kind === "str" ? STRING : REGEX });
671
+ });
672
+ }
673
+ const locals = params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false }));
674
+ locals.push({ id: "e.0", name: "e", type: CAUGHT, mutable: false });
675
+ /** shapeBegin + one slot call per key (never throw; shapeEnd consumes
676
+ * the accumulator). */
677
+ const shapeSlotStmts = () => {
678
+ if (expected.form !== "shape")
679
+ return [];
680
+ const stmts = [lib("assert.shapeBegin", [narrowed()])];
681
+ expected.keys.forEach((k, i) => {
682
+ stmts.push(lib(k.kind === "str" ? "assert.shapeStr" : "assert.shapeRe", [
683
+ num(k.id),
684
+ ref(shapeParamIds[i], k.kind === "str" ? STRING : REGEX),
685
+ ]));
686
+ });
687
+ return stmts;
688
+ };
689
+ // The ` (${expected.name})` detail of the missing-exception message: a
690
+ // class's display name, a shape's name VALUE (its string param, or the
691
+ // compile-time rendering of a regex-literal name); a non-literal regex
692
+ // name drops the detail (a documented sliver).
693
+ const ename = (() => {
694
+ if (expected.form === "class")
695
+ return { e: str(expected.displayName), has: true };
696
+ if (expected.form === "shape") {
697
+ const i = expected.keys.findIndex((k) => k.id === 2);
698
+ if (i >= 0) {
699
+ const k = expected.keys[i];
700
+ if (k.kind === "str")
701
+ return { e: ref(shapeParamIds[i], STRING), has: true };
702
+ if (k.enameBake !== null)
703
+ return { e: str(k.enameBake), has: true };
704
+ }
705
+ }
706
+ return { e: str(""), has: false };
707
+ })();
708
+ let catchBody;
709
+ if (mode === "dnr") {
710
+ switch (expected.form) {
711
+ case "bare":
712
+ case "message":
713
+ catchBody = [
714
+ ifStmt(isInstance("%Error"), [lib("assert.unwantedRejection", [narrowed(), m(), hm()])]),
715
+ rethrow,
716
+ ];
717
+ break;
718
+ case "class":
719
+ catchBody = [
720
+ ifStmt(isInstance(expected.className), [
721
+ lib("assert.unwantedRejection", [narrowed(), m(), hm()]),
722
+ ]),
723
+ rethrow,
724
+ ];
725
+ break;
726
+ case "regex":
727
+ catchBody = [
728
+ ifStmt(isInstance("%Error"), [
729
+ ifStmt({
730
+ kind: "libCall",
731
+ fn: "assert.regexErrTest",
732
+ args: [ref("re.0", REGEX), narrowed()],
733
+ type: BOOL,
734
+ loc,
735
+ }, [lib("assert.unwantedRejection", [narrowed(), m(), hm()])]),
736
+ ]),
737
+ rethrow,
738
+ ];
739
+ break;
740
+ default:
741
+ // Node itself rejects shape expectations here (ERR_INVALID_ARG_TYPE)
742
+ // — the call site fences before reaching this helper.
743
+ throw new Error("assert helper: doesNotReject shape forms fence at the call site");
744
+ }
745
+ }
746
+ else {
747
+ switch (expected.form) {
748
+ case "bare":
749
+ case "message":
750
+ catchBody = [doReturn];
751
+ break;
752
+ case "class":
753
+ catchBody = [
754
+ ifStmt(isInstance(expected.className), [doReturn]),
755
+ ifStmt(isInstance("%Error"), [
756
+ lib("assert.throwsMismatch", [str(expected.displayName), narrowed(), m(), hm()]),
757
+ ]),
758
+ // A non-Error thrown value: propagate (SEMANTICS.md 104; the
759
+ // mismatch throw above never falls through — its pending
760
+ // exception unwinds first).
761
+ rethrow,
762
+ ];
763
+ break;
764
+ case "regex":
765
+ catchBody = [
766
+ ifStmt(isInstance("%Error"), [
767
+ lib("assert.throwsRegex", [ref("re.0", REGEX), narrowed(), m(), hm()]),
768
+ doReturn,
769
+ ]),
770
+ rethrow,
771
+ ];
772
+ break;
773
+ case "shape":
774
+ catchBody = [
775
+ ifStmt(isInstance("%Error"), [
776
+ ...shapeSlotStmts(),
777
+ lib("assert.shapeEnd", [m(), hm()]),
778
+ doReturn,
779
+ ]),
780
+ rethrow,
781
+ ];
782
+ break;
783
+ case "errValue":
784
+ // Node's expectsError over an error-instance expected: the
785
+ // caught value crosses as a DOM value (identity-preserving) and
786
+ // the runtime walks the expected's keys — a mismatch throws the
787
+ // deep-equal AssertionError.
788
+ catchBody = [
789
+ lib("assert.expectsErrDyn", [
790
+ { kind: "caughtToDyn", value: caughtRef(), type: DYN, loc },
791
+ ref("ev.0", DYN),
792
+ m(),
793
+ hm(),
794
+ ]),
795
+ doReturn,
796
+ ];
797
+ break;
798
+ }
799
+ }
800
+ const body = [];
801
+ let tryBody;
802
+ if (mode === "throws") {
803
+ const fnT = recvType;
804
+ tryBody = [
805
+ {
806
+ kind: "exprStmt",
807
+ expr: { kind: "callValue", callee: ref("f.0", fnT), args: [], type: fnT.ret, loc },
808
+ loc,
809
+ },
810
+ ];
811
+ }
812
+ else {
813
+ let awaited;
814
+ if (recvIsPromise) {
815
+ awaited = ref("f.0", recvType);
816
+ }
817
+ else {
818
+ const fnT = recvType;
819
+ const promT = fnT.ret;
820
+ locals.push({ id: "pr.0", name: "pr", type: promT, mutable: false });
821
+ body.push({
822
+ kind: "varDecl",
823
+ localId: "pr.0",
824
+ init: { kind: "callValue", callee: ref("f.0", fnT), args: [], type: promT, loc },
825
+ loc,
826
+ });
827
+ awaited = ref("pr.0", promT);
828
+ }
829
+ const inner = awaited.type.inner;
830
+ tryBody = [
831
+ { kind: "exprStmt", expr: { kind: "awaitExpr", value: awaited, type: inner, loc }, loc },
832
+ ];
833
+ }
834
+ body.push({ kind: "tryCatch", tryBody, catchBody, catchLocalId: "e.0", finallyBody: null, loc });
835
+ if (mode !== "dnr") {
836
+ body.push(lib("assert.throwsNone", [flag(mode === "rejects"), ename.e, flag(ename.has), m(), hm()]));
837
+ }
838
+ body.push(doReturn);
839
+ L.liftedFns.push({
840
+ name,
841
+ params,
842
+ returnType: VOID,
843
+ locals,
844
+ body,
845
+ loc,
846
+ ...(mode === "throws" ? {} : { async: true }),
847
+ });
848
+ return name;
849
+ }
850
+ /* ── assert.ifError ────────────────────────────────────────────────────── */
851
+ /** The typed runtime entry for one non-union ifError argument type:
852
+ * "unit" for null/undefined (can never throw — the evaluated expression
853
+ * alone suffices), a libCall name for scalars and %Error-hierarchy
854
+ * objects, null for everything else (the caller fences). */
855
+ function ifErrorLibFn(L, t) {
856
+ switch (t.kind) {
857
+ case "undefinedT":
858
+ case "nullT":
859
+ return "unit";
860
+ case "f64":
861
+ return "assert.ifErrorF64";
862
+ case "string":
863
+ return "assert.ifErrorStr";
864
+ case "bool":
865
+ return "assert.ifErrorBool";
866
+ case "object":
867
+ return inErrorHierarchy(L, t.className) ? "assert.ifErrorErr" : null;
868
+ default:
869
+ return null;
870
+ }
871
+ }
872
+ /** assert.ifError(value): Node throws for ANY value except null and
873
+ * undefined — falsy scalars included — with "ifError got unwanted
874
+ * exception: " + the error's message (its name when the message is
875
+ * empty) or the value's inspection. Unit-typed arguments lower to the
876
+ * evaluated expression alone; scalars and %Error-hierarchy objects
877
+ * throw through the typed runtime entries; unions dispatch per arm in
878
+ * an interned helper. Anything else fences. */
879
+ function lowerAssertIfError(L, expr, loc) {
880
+ requireStatementPosition(L, expr, "assert.ifError");
881
+ if (expr.arguments.length !== 1) {
882
+ L.noLowering(`assert.ifError with ${expr.arguments.length} arguments`, expr, "the supported form is ifError(value)");
883
+ }
884
+ const argNode = expr.arguments[0];
885
+ const argT = L.mapTypeOf(L.typeOf(argNode));
886
+ if (argT && isUnitType(argT)) {
887
+ // null/undefined can never throw, and a unit-typed expression has no
888
+ // standalone runtime value to evaluate — a passing assert.ok is the
889
+ // void-typed no-op.
890
+ return {
891
+ kind: "libCall",
892
+ fn: "assert.ok",
893
+ args: [
894
+ { kind: "boolLit", value: true, type: BOOL, loc },
895
+ { kind: "strLit", value: "", type: STRING, loc },
896
+ ],
897
+ type: VOID,
898
+ loc,
899
+ };
900
+ }
901
+ const v = L.lowerExpr(argNode);
902
+ const fence = (t) => L.noLowering(`assert.ifError over '${L.fmt(t)}' values`, argNode, "numbers, strings, booleans, Errors, null/undefined, and unions of those are the lowered forms");
903
+ const direct = ifErrorLibFn(L, v.type);
904
+ if (direct === "unit") {
905
+ // A unit-typed value that mapped through some other checker spelling:
906
+ // the same no-op (unreachable in practice — the mapTypeOf branch
907
+ // above catches unit checker types).
908
+ return {
909
+ kind: "libCall",
910
+ fn: "assert.ok",
911
+ args: [
912
+ { kind: "boolLit", value: true, type: BOOL, loc },
913
+ { kind: "strLit", value: "", type: STRING, loc },
914
+ ],
915
+ type: VOID,
916
+ loc,
917
+ };
918
+ }
919
+ if (direct !== null)
920
+ return { kind: "libCall", fn: direct, args: [v], type: VOID, loc };
921
+ // A checked-dynamic argument (test/common's mustSucceed wrapper — the
922
+ // callback's err parameter is untyped): the runtime dispatches over the
923
+ // DOM kind — units pass quietly, %error-marked objects throw with the
924
+ // error's message, everything else with the inspection.
925
+ if (v.type.kind === "dyn") {
926
+ return { kind: "libCall", fn: "assert.ifErrorDyn", args: [v], type: VOID, loc };
927
+ }
928
+ if (v.type.kind !== "union")
929
+ fence(v.type);
930
+ const unionT = v.type;
931
+ const def = L.unions.get(unionT.unionId);
932
+ if (!def)
933
+ fence(v.type);
934
+ for (const arm of def.arms) {
935
+ if (ifErrorLibFn(L, arm) === null)
936
+ fence(arm);
937
+ }
938
+ const helper = ifErrorHelper(L, unionT, loc);
939
+ return { kind: "call", callee: helper, args: [v], type: VOID, loc };
940
+ }
941
+ /** The interned per-union ifError helper `%assert.iferr.<n>(v): void` —
942
+ * one tag test per arm: unit arms return quietly, every other arm
943
+ * narrows and throws through its typed runtime entry. */
944
+ function ifErrorHelper(L, t, loc) {
945
+ const key = `assert.iferr:${typeKey(t)}`;
946
+ const existing = L.assertHelpers.get(key);
947
+ if (existing)
948
+ return existing;
949
+ const name = `%assert.iferr.${L.assertHelpers.size}`;
950
+ L.assertHelpers.set(key, name);
951
+ const def = L.unions.get(t.unionId);
952
+ if (!def)
953
+ throw new Error(`assert.ifError over unknown union ${t.unionId}`);
954
+ const v = () => ({ kind: "varRef", localId: "v.0", type: t, loc });
955
+ const doReturn = { kind: "return", value: null, loc };
956
+ const body = [];
957
+ def.arms.forEach((arm, tag) => {
958
+ const fn = ifErrorLibFn(L, arm);
959
+ if (fn === null)
960
+ throw new Error("assert.ifError helper over an unfenced arm");
961
+ const then = fn === "unit"
962
+ ? [doReturn]
963
+ : [
964
+ {
965
+ kind: "exprStmt",
966
+ expr: {
967
+ kind: "libCall",
968
+ fn,
969
+ args: [{ kind: "unionNarrow", unionId: t.unionId, tag, value: v(), type: arm, loc }],
970
+ type: VOID,
971
+ loc,
972
+ },
973
+ loc,
974
+ },
975
+ doReturn,
976
+ ];
977
+ body.push({
978
+ kind: "if",
979
+ cond: { kind: "unionIsTag", unionId: t.unionId, tag, negated: false, value: v(), type: BOOL, loc },
980
+ then,
981
+ else_: null,
982
+ loc,
983
+ });
984
+ });
985
+ body.push(doReturn); // unreachable: some arm always matches
986
+ L.liftedFns.push({
987
+ name,
988
+ params: [{ localId: "v.0", name: "v", type: t }],
989
+ returnType: VOID,
990
+ locals: [{ id: "v.0", name: "v", type: t, mutable: false }],
991
+ body,
992
+ loc,
993
+ });
994
+ return name;
995
+ }
996
+ /** The TOP-LEVEL checker brand of a bytes-typed argument: the constructor
997
+ * name Node's deep equality compares as the prototype. The runtime
998
+ * representation is one ScrBytes for Buffer AND the typed arrays, so the
999
+ * argument's own checker type is the only carrier (the lower-inspect
1000
+ * precedent). Null when the spelling is not a recognized constructor —
1001
+ * the caller fences. */
1002
+ const BYTES_BRANDS = new Set([
1003
+ "Uint8Array", "Uint8ClampedArray", "Uint16Array", "Uint32Array",
1004
+ "Int8Array", "Int16Array", "Int32Array", "Float32Array", "Float64Array",
1005
+ "BigInt64Array", "BigUint64Array", "DataView",
1006
+ ]);
1007
+ function bytesBrandOf(L, node) {
1008
+ const tname = L.checker.typeToString(L.checker.getBaseTypeOfLiteralType(L.typeOf(node)));
1009
+ const head = /^[A-Za-z_$][A-Za-z0-9_$]*/.exec(tname)?.[0] ?? "";
1010
+ if (head === "Buffer" || head === "NonSharedBuffer")
1011
+ return "Buffer";
1012
+ return BYTES_BRANDS.has(head) ? head : null;
1013
+ }
1014
+ /* ── deep equality (deepStrictEqual / notDeepStrictEqual) ──────────────── */
1015
+ /** Why `t` has no honest deep comparison, or null when it lowers. The
1016
+ * fence names the FIRST unsupported constituent — never a silent
1017
+ * miscompare. Recursive record shapes terminate through `visiting`
1018
+ * (a shape being checked is assumed fine; its own walk decides). */
1019
+ function deepUnsupportedReason(L, t, visiting) {
1020
+ switch (t.kind) {
1021
+ case "f64":
1022
+ case "string":
1023
+ case "bool":
1024
+ case "undefinedT":
1025
+ case "nullT":
1026
+ return null;
1027
+ case "array":
1028
+ return deepUnsupportedReason(L, t.elem, visiting);
1029
+ case "record": {
1030
+ if (visiting.has(t.shapeId))
1031
+ return null;
1032
+ visiting.add(t.shapeId);
1033
+ const shape = L.shapes.get(t.shapeId);
1034
+ if (!shape)
1035
+ return "this record shape has no deep comparison";
1036
+ if (shape.indexValue !== undefined) {
1037
+ return "index-signature records have no deep comparison (their key sets are dynamic)";
1038
+ }
1039
+ for (const f of shape.fields) {
1040
+ const why = deepUnsupportedReason(L, f.type, visiting);
1041
+ if (why !== null)
1042
+ return why;
1043
+ }
1044
+ return null;
1045
+ }
1046
+ case "map": {
1047
+ const keyWhy = deepUnsupportedReason(L, t.key, visiting);
1048
+ if (keyWhy !== null)
1049
+ return keyWhy;
1050
+ return deepUnsupportedReason(L, t.value, visiting);
1051
+ }
1052
+ case "set":
1053
+ return t.elem.kind === "f64" || t.elem.kind === "string" || t.elem.kind === "bool"
1054
+ ? null
1055
+ : "Sets of non-scalar elements have no deep comparison (Node matches object members structurally)";
1056
+ case "union": {
1057
+ const def = L.unions.get(t.unionId);
1058
+ if (!def)
1059
+ return "this union has no deep comparison";
1060
+ for (const arm of def.arms) {
1061
+ const why = deepUnsupportedReason(L, arm, visiting);
1062
+ if (why !== null)
1063
+ return why;
1064
+ }
1065
+ return null;
1066
+ }
1067
+ case "func":
1068
+ // Node's deep equality over functions is reference identity (two
1069
+ // distinct functions are never deep-equal; the same reference is) —
1070
+ // a pointer compare carries it faithfully.
1071
+ return null;
1072
+ case "symbol":
1073
+ // Symbols deep-compare by identity (SameValue primitives) — the
1074
+ // pointer IS the identity.
1075
+ return null;
1076
+ case "bytes":
1077
+ // NESTED bytes (an array element, a record field): the Buffer-vs-
1078
+ // Uint8Array brand Node compares lives in the checker type of a
1079
+ // TOP-LEVEL argument only — no brand survives into shapes.
1080
+ return "typed arrays inside composites have no deep comparison (Node compares constructors, which the static element/field type does not carry)";
1081
+ case "object":
1082
+ return "class instances have no deep comparison (Node compares prototypes and own properties)";
1083
+ default:
1084
+ return `'${L.fmt(t)}' values have no deep comparison`;
1085
+ }
1086
+ }
1087
+ /** The interned per-type deep-equality helper `%assert.deq.<n>(a, b):
1088
+ * boolean` — Node's strict deep rules over the static type: Object.is
1089
+ * numbers, byte-equal strings, per-element arrays, per-field records,
1090
+ * tag+payload unions, SameValueZero-keyed Maps with deep values, scalar
1091
+ * Sets by membership. Registered in the cache BEFORE the body builds, so
1092
+ * recursive record shapes call themselves by name. */
1093
+ function deepEqHelper(L, t, loc) {
1094
+ const key = `assert.deq:${typeKey(t)}`;
1095
+ const existing = L.assertHelpers.get(key);
1096
+ if (existing)
1097
+ return existing;
1098
+ const name = `%assert.deq.${L.assertHelpers.size}`;
1099
+ L.assertHelpers.set(key, name);
1100
+ const ref = (localId, type) => ({ kind: "varRef", localId, type, loc });
1101
+ const a = () => ref("a.0", t);
1102
+ const b = () => ref("b.0", t);
1103
+ const num = (value) => ({ kind: "numLit", value, type: F64, loc });
1104
+ const boolLit = (value) => ({ kind: "boolLit", value, type: BOOL, loc });
1105
+ const ret = (value) => ({ kind: "return", value, loc });
1106
+ const retFalse = ret(boolLit(false));
1107
+ const not = (operand) => ({ kind: "unary", op: "!", operand, type: BOOL, loc });
1108
+ const neq = (left, right) => ({ kind: "bin", op: "!==", left, right, type: BOOL, loc });
1109
+ /** if (cond) return false; */
1110
+ const bailIf = (cond) => ({ kind: "if", cond, then: [retFalse], else_: null, loc });
1111
+ /** Deep-compare two same-typed exprs through the per-type helper. */
1112
+ const deq = (elemT, x, y) => isUnitType(elemT)
1113
+ ? boolLit(true)
1114
+ : { kind: "call", callee: deepEqHelper(L, elemT, loc), args: [x, y], type: BOOL, loc };
1115
+ const locals = [
1116
+ { id: "a.0", name: "a", type: t, mutable: false },
1117
+ { id: "b.0", name: "b", type: t, mutable: false },
1118
+ ];
1119
+ let body;
1120
+ switch (t.kind) {
1121
+ case "f64":
1122
+ body = [ret({ kind: "libCall", fn: "assert.sameValue", args: [a(), b()], type: BOOL, loc })];
1123
+ break;
1124
+ case "string":
1125
+ body = [ret({ kind: "strEq", negated: false, left: a(), right: b(), type: BOOL, loc })];
1126
+ break;
1127
+ case "bool":
1128
+ body = [ret({ kind: "bin", op: "===", left: a(), right: b(), type: BOOL, loc })];
1129
+ break;
1130
+ case "func":
1131
+ case "symbol":
1132
+ // Reference identity — Node's deep equality over functions; a
1133
+ // symbol IS its pointer identity.
1134
+ body = [ret({ kind: "bin", op: "===", left: a(), right: b(), type: BOOL, loc })];
1135
+ break;
1136
+ case "array": {
1137
+ const len = (arr) => ({ kind: "arrIntrinsic", method: "length", receiver: arr, args: [], type: F64, loc });
1138
+ const at = (arr, i) => ({ kind: "arrayGet", arr, index: i, type: t.elem, loc });
1139
+ locals.push({ id: "i.0", name: "i", type: F64, mutable: true });
1140
+ body = [
1141
+ bailIf(neq(len(a()), len(b()))),
1142
+ {
1143
+ kind: "for",
1144
+ init: { kind: "varDecl", localId: "i.0", init: num(0), loc },
1145
+ cond: { kind: "bin", op: "<", left: ref("i.0", F64), right: len(a()), type: BOOL, loc },
1146
+ update: { kind: "assign", localId: "i.0", value: { kind: "bin", op: "+", left: ref("i.0", F64), right: num(1), type: F64, loc }, loc },
1147
+ body: [bailIf(not(deq(t.elem, at(a(), ref("i.0", F64)), at(b(), ref("i.0", F64)))))],
1148
+ loc,
1149
+ },
1150
+ ret(boolLit(true)),
1151
+ ];
1152
+ break;
1153
+ }
1154
+ case "record": {
1155
+ const shape = L.shapes.get(t.shapeId);
1156
+ if (!shape)
1157
+ throw new Error(`assert deep-equal of unknown shape ${t.shapeId}`);
1158
+ const get = (obj, field, type) => ({ kind: "recordGet", obj, shapeId: t.shapeId, field, type, loc });
1159
+ body = [];
1160
+ for (const f of shape.fields) {
1161
+ if (isUnitType(f.type))
1162
+ continue; // a unit field is equal by type
1163
+ body.push(bailIf(not(deq(f.type, get(a(), f.name, f.type), get(b(), f.name, f.type)))));
1164
+ }
1165
+ body.push(ret(boolLit(true)));
1166
+ break;
1167
+ }
1168
+ case "union": {
1169
+ const def = L.unions.get(t.unionId);
1170
+ if (!def)
1171
+ throw new Error(`assert deep-equal of unknown union ${t.unionId}`);
1172
+ const isTag = (v, tag, negated) => ({ kind: "unionIsTag", unionId: t.unionId, tag, negated, value: v, type: BOOL, loc });
1173
+ const narrow = (v, tag, armT) => ({ kind: "unionNarrow", unionId: t.unionId, tag, value: v, type: armT, loc });
1174
+ body = [];
1175
+ def.arms.forEach((arm, tag) => {
1176
+ body.push({
1177
+ kind: "if",
1178
+ cond: isTag(a(), tag, false),
1179
+ then: [
1180
+ bailIf(isTag(b(), tag, true)),
1181
+ ret(deq(arm, narrow(a(), tag, arm), narrow(b(), tag, arm))),
1182
+ ],
1183
+ else_: null,
1184
+ loc,
1185
+ });
1186
+ });
1187
+ body.push(retFalse); // unreachable: some arm always matches
1188
+ break;
1189
+ }
1190
+ case "map": {
1191
+ const mi = (method, receiver, args, type) => ({ kind: "mapIntrinsic", method, receiver, args, type, loc });
1192
+ const valueT = t.value;
1193
+ const hasUndefArm = valueT.kind === "union" &&
1194
+ (L.unions.get(valueT.unionId)?.arms.some((arm) => arm.kind === "undefinedT") ?? false);
1195
+ const getT = hasUndefArm ? valueT : L.withUndefinedArm(valueT);
1196
+ locals.push({ id: "i.0", name: "i", type: F64, mutable: true }, { id: "k.0", name: "k", type: t.key, mutable: false }, { id: "av.0", name: "av", type: valueT, mutable: false }, { id: "bv.0", name: "bv", type: getT, mutable: false });
1197
+ const i = () => ref("i.0", F64);
1198
+ const k = () => ref("k.0", t.key);
1199
+ // The b-side value: get() answers `V | undefined`; has() already
1200
+ // proved presence, so a plain V narrows through the value arm.
1201
+ const bValue = getT === valueT
1202
+ ? ref("bv.0", getT)
1203
+ : {
1204
+ kind: "unionNarrow",
1205
+ unionId: getT.unionId,
1206
+ tag: L.armTag(getT.unionId, valueT),
1207
+ value: ref("bv.0", getT),
1208
+ type: valueT,
1209
+ loc,
1210
+ };
1211
+ body = [
1212
+ bailIf(neq(mi("size", a(), [], F64), mi("size", b(), [], F64))),
1213
+ {
1214
+ kind: "for",
1215
+ init: { kind: "varDecl", localId: "i.0", init: num(0), loc },
1216
+ cond: { kind: "bin", op: "<", left: i(), right: mi("iterCount", a(), [], F64), type: BOOL, loc },
1217
+ update: { kind: "assign", localId: "i.0", value: { kind: "bin", op: "+", left: i(), right: num(1), type: F64, loc }, loc },
1218
+ body: [
1219
+ { kind: "if", cond: not(mi("iterLive", a(), [i()], BOOL)), then: [{ kind: "continue", loc }], else_: null, loc },
1220
+ { kind: "varDecl", localId: "k.0", init: mi("iterKey", a(), [i()], t.key), loc },
1221
+ bailIf(not(mi("has", b(), [k()], BOOL))),
1222
+ { kind: "varDecl", localId: "av.0", init: mi("iterValue", a(), [i()], valueT), loc },
1223
+ { kind: "varDecl", localId: "bv.0", init: mi("get", b(), [k()], getT), loc },
1224
+ bailIf(not(deq(valueT, ref("av.0", valueT), bValue))),
1225
+ ],
1226
+ loc,
1227
+ },
1228
+ ret(boolLit(true)),
1229
+ ];
1230
+ break;
1231
+ }
1232
+ case "set": {
1233
+ const si = (method, receiver, args, type) => ({ kind: "setIntrinsic", method, receiver, args, type, loc });
1234
+ locals.push({ id: "i.0", name: "i", type: F64, mutable: true });
1235
+ const i = () => ref("i.0", F64);
1236
+ body = [
1237
+ bailIf(neq(si("size", a(), [], F64), si("size", b(), [], F64))),
1238
+ {
1239
+ kind: "for",
1240
+ init: { kind: "varDecl", localId: "i.0", init: num(0), loc },
1241
+ cond: { kind: "bin", op: "<", left: i(), right: si("iterCount", a(), [], F64), type: BOOL, loc },
1242
+ update: { kind: "assign", localId: "i.0", value: { kind: "bin", op: "+", left: i(), right: num(1), type: F64, loc }, loc },
1243
+ body: [
1244
+ { kind: "if", cond: not(si("iterLive", a(), [i()], BOOL)), then: [{ kind: "continue", loc }], else_: null, loc },
1245
+ bailIf(not(si("has", b(), [si("iterKey", a(), [i()], t.elem)], BOOL))),
1246
+ ],
1247
+ loc,
1248
+ },
1249
+ ret(boolLit(true)),
1250
+ ];
1251
+ break;
1252
+ }
1253
+ default:
1254
+ throw new Error(`assert deep-equal helper over unexpected type ${typeKey(t)}`);
1255
+ }
1256
+ L.liftedFns.push({
1257
+ name,
1258
+ params: [
1259
+ { localId: "a.0", name: "a", type: t },
1260
+ { localId: "b.0", name: "b", type: t },
1261
+ ],
1262
+ returnType: BOOL,
1263
+ locals,
1264
+ body,
1265
+ loc,
1266
+ });
1267
+ return name;
1268
+ }
1269
+ //# sourceMappingURL=lower-assert.js.map