@scriptc/compiler 0.0.0 → 0.0.1
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/LICENSE +202 -0
- package/ambient/package.json +3 -0
- package/ambient/scriptc-node-fallback.d.ts +3001 -0
- package/ambient/scriptc-overrides.d.ts +95 -0
- package/ambient/scriptc.d.ts +74 -0
- package/dist/backend/cc.d.ts +169 -0
- package/dist/backend/cc.js +897 -0
- package/dist/backend/cc.js.map +1 -0
- package/dist/backend/emission/emit-async.d.ts +145 -0
- package/dist/backend/emission/emit-async.js +996 -0
- package/dist/backend/emission/emit-async.js.map +1 -0
- package/dist/backend/emission/emit-exprs.d.ts +3 -0
- package/dist/backend/emission/emit-exprs.js +5947 -0
- package/dist/backend/emission/emit-exprs.js.map +1 -0
- package/dist/backend/emission/emit-island.d.ts +45 -0
- package/dist/backend/emission/emit-island.js +271 -0
- package/dist/backend/emission/emit-island.js.map +1 -0
- package/dist/backend/emission/emit-shapes.d.ts +142 -0
- package/dist/backend/emission/emit-shapes.js +575 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -0
- package/dist/backend/emission/emit-stmts.d.ts +78 -0
- package/dist/backend/emission/emit-stmts.js +960 -0
- package/dist/backend/emission/emit-stmts.js.map +1 -0
- package/dist/backend/emission/emit-types.d.ts +65 -0
- package/dist/backend/emission/emit-types.js +652 -0
- package/dist/backend/emission/emit-types.js.map +1 -0
- package/dist/backend/emission/emit-walkers.d.ts +154 -0
- package/dist/backend/emission/emit-walkers.js +1587 -0
- package/dist/backend/emission/emit-walkers.js.map +1 -0
- package/dist/backend/emission/emitter.d.ts +452 -0
- package/dist/backend/emission/emitter.js +1249 -0
- package/dist/backend/emission/emitter.js.map +1 -0
- package/dist/backend/emission/may-throw.d.ts +15 -0
- package/dist/backend/emission/may-throw.js +250 -0
- package/dist/backend/emission/may-throw.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +21 -0
- package/dist/backend/llvm/blocks.js +68 -0
- package/dist/backend/llvm/blocks.js.map +1 -0
- package/dist/backend/llvm/classes.d.ts +84 -0
- package/dist/backend/llvm/classes.js +438 -0
- package/dist/backend/llvm/classes.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +123 -0
- package/dist/backend/llvm/dyn.js +2624 -0
- package/dist/backend/llvm/dyn.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +10087 -0
- package/dist/backend/llvm/emitter.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +78 -0
- package/dist/backend/llvm/shapes.js +754 -0
- package/dist/backend/llvm/shapes.js.map +1 -0
- package/dist/backend/llvm/unsupported.d.ts +6 -0
- package/dist/backend/llvm/unsupported.js +12 -0
- package/dist/backend/llvm/unsupported.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +58 -0
- package/dist/backend/llvm/walkers.js +793 -0
- package/dist/backend/llvm/walkers.js.map +1 -0
- package/dist/backend/mangle.d.ts +124 -0
- package/dist/backend/mangle.js +232 -0
- package/dist/backend/mangle.js.map +1 -0
- package/dist/coverage/report.d.ts +64 -0
- package/dist/coverage/report.js +226 -0
- package/dist/coverage/report.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +140 -0
- package/dist/diagnostics/diagnostic.js +458 -0
- package/dist/diagnostics/diagnostic.js.map +1 -0
- package/dist/diagnostics/render.d.ts +11 -0
- package/dist/diagnostics/render.js +58 -0
- package/dist/diagnostics/render.js.map +1 -0
- package/dist/frontend/cjs-lexer.d.ts +20 -0
- package/dist/frontend/cjs-lexer.js +813 -0
- package/dist/frontend/cjs-lexer.js.map +1 -0
- package/dist/frontend/lowering/http2-constants.d.ts +1 -0
- package/dist/frontend/lowering/http2-constants.js +251 -0
- package/dist/frontend/lowering/http2-constants.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
- package/dist/frontend/lowering/lib-boundary.js +143 -0
- package/dist/frontend/lowering/lib-boundary.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.d.ts +18 -0
- package/dist/frontend/lowering/lower-assert.js +1269 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
- package/dist/frontend/lowering/lower-builtins.js +5249 -0
- package/dist/frontend/lowering/lower-builtins.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.d.ts +581 -0
- package/dist/frontend/lowering/lower-calls.js +6281 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -0
- package/dist/frontend/lowering/lower-classes.d.ts +727 -0
- package/dist/frontend/lowering/lower-classes.js +4701 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -0
- package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
- package/dist/frontend/lowering/lower-comptime.js +244 -0
- package/dist/frontend/lowering/lower-comptime.js.map +1 -0
- package/dist/frontend/lowering/lower-containers.d.ts +483 -0
- package/dist/frontend/lowering/lower-containers.js +6281 -0
- package/dist/frontend/lowering/lower-containers.js.map +1 -0
- package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
- package/dist/frontend/lowering/lower-dgram.js +319 -0
- package/dist/frontend/lowering/lower-dgram.js.map +1 -0
- package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
- package/dist/frontend/lowering/lower-emitter.js +688 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -0
- package/dist/frontend/lowering/lower-enums.d.ts +22 -0
- package/dist/frontend/lowering/lower-enums.js +235 -0
- package/dist/frontend/lowering/lower-enums.js.map +1 -0
- package/dist/frontend/lowering/lower-expando.d.ts +35 -0
- package/dist/frontend/lowering/lower-expando.js +276 -0
- package/dist/frontend/lowering/lower-expando.js.map +1 -0
- package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
- package/dist/frontend/lowering/lower-exprs.js +8099 -0
- package/dist/frontend/lowering/lower-exprs.js.map +1 -0
- package/dist/frontend/lowering/lower-generators.d.ts +50 -0
- package/dist/frontend/lowering/lower-generators.js +421 -0
- package/dist/frontend/lowering/lower-generators.js.map +1 -0
- package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
- package/dist/frontend/lowering/lower-inspect.js +1069 -0
- package/dist/frontend/lowering/lower-inspect.js.map +1 -0
- package/dist/frontend/lowering/lower-island.d.ts +108 -0
- package/dist/frontend/lowering/lower-island.js +650 -0
- package/dist/frontend/lowering/lower-island.js.map +1 -0
- package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
- package/dist/frontend/lowering/lower-mixins.js +534 -0
- package/dist/frontend/lowering/lower-mixins.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +135 -0
- package/dist/frontend/lowering/lower-modules.js +1613 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -0
- package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
- package/dist/frontend/lowering/lower-namespaces.js +706 -0
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
- package/dist/frontend/lowering/lower-server.d.ts +90 -0
- package/dist/frontend/lowering/lower-server.js +3471 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
- package/dist/frontend/lowering/lower-stmts.js +5341 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.d.ts +88 -0
- package/dist/frontend/lowering/lower-stream.js +1533 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -0
- package/dist/frontend/lowering/lower-test.d.ts +27 -0
- package/dist/frontend/lowering/lower-test.js +353 -0
- package/dist/frontend/lowering/lower-test.js.map +1 -0
- package/dist/frontend/lowering/lowerer.d.ts +1748 -0
- package/dist/frontend/lowering/lowerer.js +6256 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +272 -0
- package/dist/frontend/lowering/surfaces.js +1096 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +55 -0
- package/dist/frontend/npm-static.js +300 -0
- package/dist/frontend/npm-static.js.map +1 -0
- package/dist/frontend/npm.d.ts +330 -0
- package/dist/frontend/npm.js +1259 -0
- package/dist/frontend/npm.js.map +1 -0
- package/dist/frontend/program.d.ts +186 -0
- package/dist/frontend/program.js +2172 -0
- package/dist/frontend/program.js.map +1 -0
- package/dist/frontend/provenance-registry.d.ts +48 -0
- package/dist/frontend/provenance-registry.js +87 -0
- package/dist/frontend/provenance-registry.js.map +1 -0
- package/dist/frontend/provenance.d.ts +6 -0
- package/dist/frontend/provenance.js +459 -0
- package/dist/frontend/provenance.js.map +1 -0
- package/dist/frontend/resolve.d.ts +56 -0
- package/dist/frontend/resolve.js +682 -0
- package/dist/frontend/resolve.js.map +1 -0
- package/dist/frontend/shared.d.ts +60 -0
- package/dist/frontend/shared.js +232 -0
- package/dist/frontend/shared.js.map +1 -0
- package/dist/frontend/ts7/adapter.d.ts +7 -0
- package/dist/frontend/ts7/adapter.js +54 -0
- package/dist/frontend/ts7/adapter.js.map +1 -0
- package/dist/frontend/ts7/ast.d.ts +50 -0
- package/dist/frontend/ts7/ast.js +211 -0
- package/dist/frontend/ts7/ast.js.map +1 -0
- package/dist/frontend/ts7/census-check.d.ts +1 -0
- package/dist/frontend/ts7/census-check.js +12 -0
- package/dist/frontend/ts7/census-check.js.map +1 -0
- package/dist/frontend/ts7/checker.d.ts +140 -0
- package/dist/frontend/ts7/checker.js +544 -0
- package/dist/frontend/ts7/checker.js.map +1 -0
- package/dist/frontend/ts7/enums.d.ts +18 -0
- package/dist/frontend/ts7/enums.js +43 -0
- package/dist/frontend/ts7/enums.js.map +1 -0
- package/dist/frontend/ts7/program.d.ts +99 -0
- package/dist/frontend/ts7/program.js +278 -0
- package/dist/frontend/ts7/program.js.map +1 -0
- package/dist/frontend/ts7/world-check.d.ts +3 -0
- package/dist/frontend/ts7/world-check.js +34 -0
- package/dist/frontend/ts7/world-check.js.map +1 -0
- package/dist/frontend/types.d.ts +205 -0
- package/dist/frontend/types.js +2485 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +424 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/nodes.d.ts +4334 -0
- package/dist/ir/nodes.js +1906 -0
- package/dist/ir/nodes.js.map +1 -0
- package/dist/ir/serialize.d.ts +4 -0
- package/dist/ir/serialize.js +34 -0
- package/dist/ir/serialize.js.map +1 -0
- package/dist/ir/validate.d.ts +36 -0
- package/dist/ir/validate.js +4884 -0
- package/dist/ir/validate.js.map +1 -0
- package/package.json +30 -6
- package/README.md +0 -3
- package/index.js +0 -2
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
/* The node:events EventEmitter lowering (the spoke-module pattern, like
|
|
2
|
+
* lower-server.ts): every EMITTER_API_MEMBERS method call on a receiver
|
|
3
|
+
* whose class roots at %EventEmitter lands here, from
|
|
4
|
+
* lowerObjectMethodCall.
|
|
5
|
+
*
|
|
6
|
+
* THE TYPING STANCE. Node types EventEmitter untyped — `emit(name,
|
|
7
|
+
* ...args: any[])`, listeners `(...args: any[]) => void` — which has no
|
|
8
|
+
* static lowering. The honest model here is per-event monomorphization:
|
|
9
|
+
* event names must be compile-time string literals (the net/http/process
|
|
10
|
+
* precedent), and each event name gets ONE argument tuple, unified across
|
|
11
|
+
* the whole program (a pre-pass scans every emit site's argument types
|
|
12
|
+
* and every registered listener's annotated parameter types). Emit sites
|
|
13
|
+
* must supply exactly the tuple; listeners may declare any PREFIX of it
|
|
14
|
+
* (Node calls listeners with all arguments; extra parameters would read
|
|
15
|
+
* undefined and have no honest type). The table is program-global, not
|
|
16
|
+
* per-class: every emitter class shares the %EventEmitter root and
|
|
17
|
+
* upcasts alias freely, so per-class tables would be unsound. The JS
|
|
18
|
+
* lane's unannotated listeners (checker `any` — dyn) do not fence: they
|
|
19
|
+
* register through the checked-dynamic boundary (lowerDynListenerCall —
|
|
20
|
+
* emit arguments box to dyn, JS-exact arity, the original kept as the
|
|
21
|
+
* registry entry's identity). What the static model cannot carry is
|
|
22
|
+
* fenced with its own words: non-literal names, symbol names,
|
|
23
|
+
* conflicting tuples, the meta-events' listener-function argument (meta
|
|
24
|
+
* listeners take at most the event name; dyn meta listeners fence — Node
|
|
25
|
+
* passes the listener function second, which has no dyn conversion), and
|
|
26
|
+
* listeners()/rawListeners() of an event with any dyn-flavored
|
|
27
|
+
* registration (the bucket has no one honest element type).
|
|
28
|
+
*
|
|
29
|
+
* Two special names: 'error' is forced to the one-%Error tuple (emit
|
|
30
|
+
* routes through emitter.emitError — no listener means the payload
|
|
31
|
+
* THROWS, Node's contract — and subclass payloads upcast to the root);
|
|
32
|
+
* 'newListener'/'removeListener' are forced to the one-string tuple (the
|
|
33
|
+
* runtime emits them internally with the affected event's name). */
|
|
34
|
+
import * as ts from "../ts7/adapter.js";
|
|
35
|
+
import { dynFallbackType } from "./lowerer.js";
|
|
36
|
+
import { locOf } from "../program.js";
|
|
37
|
+
import { arrayOf, BOOL, canBoxFuncIntoDyn, canConvertToDyn, DYN, F64, isUnitType, STRING, typeEquals, typeKey, VOID } from "../../ir/nodes.js";
|
|
38
|
+
import { streamForcedTuple, streamSidesOf } from "./lower-stream.js";
|
|
39
|
+
/** True when the class descends from (or is) the runtime emitter. */
|
|
40
|
+
export function emitterRooted(L, info) {
|
|
41
|
+
for (let c = info ?? null; c; c = c.base) {
|
|
42
|
+
if (c.builtinEmitter)
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const REGISTER_MEMBERS = new Set([
|
|
48
|
+
"on", "addListener", "once", "prependListener", "prependOnceListener",
|
|
49
|
+
]);
|
|
50
|
+
const META_EVENTS = new Set(["newListener", "removeListener"]);
|
|
51
|
+
/** The program-wide event-signature table, built lazily on the first
|
|
52
|
+
* emitter lowering (class shapes are collected before any body lowers, so
|
|
53
|
+
* receiver classes resolve). The scan is DIAGNOSTIC-FREE: unmappable
|
|
54
|
+
* types and non-literal names are simply not candidates — the touching
|
|
55
|
+
* sites speak for themselves when they lower. */
|
|
56
|
+
function emitterEvents(L) {
|
|
57
|
+
const holder = L;
|
|
58
|
+
if (holder.emitterEventTable)
|
|
59
|
+
return holder.emitterEventTable;
|
|
60
|
+
const table = new Map();
|
|
61
|
+
const sigOf = (name) => {
|
|
62
|
+
let sig = table.get(name);
|
|
63
|
+
if (!sig)
|
|
64
|
+
table.set(name, (sig = { tuple: [], fromEmit: false, conflict: null, dynListener: false }));
|
|
65
|
+
return sig;
|
|
66
|
+
};
|
|
67
|
+
// The two forced tuples (see the header comment).
|
|
68
|
+
table.set("error", { tuple: [{ kind: "object", className: "%Error" }], fromEmit: true, conflict: null, dynListener: false });
|
|
69
|
+
table.set("newListener", { tuple: [STRING], fromEmit: true, conflict: null, dynListener: false });
|
|
70
|
+
table.set("removeListener", { tuple: [STRING], fromEmit: true, conflict: null, dynListener: false });
|
|
71
|
+
const fmt = (t) => L.fmt(t);
|
|
72
|
+
const mergeEmit = (name, args) => {
|
|
73
|
+
if (args.some((a) => a === null))
|
|
74
|
+
return; // its own site will diagnose
|
|
75
|
+
const tuple = args;
|
|
76
|
+
const sig = sigOf(name);
|
|
77
|
+
if (sig.conflict)
|
|
78
|
+
return;
|
|
79
|
+
if (!sig.fromEmit) {
|
|
80
|
+
// Listener prefixes seen so far must fit under this tuple.
|
|
81
|
+
if (sig.tuple.length > tuple.length) {
|
|
82
|
+
sig.conflict = `a listener declares ${sig.tuple.length} parameters but an emit supplies ${tuple.length} arguments`;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (let i = 0; i < sig.tuple.length; i++) {
|
|
86
|
+
if (!typeEquals(sig.tuple[i], tuple[i])) {
|
|
87
|
+
sig.conflict = `position ${i} is '${fmt(sig.tuple[i])}' at one site and '${fmt(tuple[i])}' at another`;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
sig.tuple = tuple;
|
|
92
|
+
sig.fromEmit = true;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (sig.tuple.length !== tuple.length) {
|
|
96
|
+
sig.conflict = `emit sites supply ${sig.tuple.length} and ${tuple.length} arguments`;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
100
|
+
if (!typeEquals(sig.tuple[i], tuple[i])) {
|
|
101
|
+
sig.conflict = `position ${i} is '${fmt(sig.tuple[i])}' at one site and '${fmt(tuple[i])}' at another`;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const mergeListener = (name, params) => {
|
|
107
|
+
if (params.some((p) => p === null))
|
|
108
|
+
return;
|
|
109
|
+
const prefix = params;
|
|
110
|
+
const sig = sigOf(name);
|
|
111
|
+
if (sig.conflict)
|
|
112
|
+
return;
|
|
113
|
+
if (sig.fromEmit) {
|
|
114
|
+
if (prefix.length > sig.tuple.length) {
|
|
115
|
+
sig.conflict = `a listener declares ${prefix.length} parameters but emits supply ${sig.tuple.length} arguments`;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (prefix.length > sig.tuple.length) {
|
|
120
|
+
// The longest listener extends the provisional tuple.
|
|
121
|
+
for (let i = 0; i < sig.tuple.length; i++) {
|
|
122
|
+
if (!typeEquals(sig.tuple[i], prefix[i])) {
|
|
123
|
+
sig.conflict = `position ${i} is '${fmt(sig.tuple[i])}' at one site and '${fmt(prefix[i])}' at another`;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
sig.tuple = prefix;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
131
|
+
if (!typeEquals(sig.tuple[i], prefix[i])) {
|
|
132
|
+
sig.conflict = `position ${i} is '${fmt(sig.tuple[i])}' at one site and '${fmt(prefix[i])}' at another`;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
for (const sf of L.program.getSourceFiles()) {
|
|
138
|
+
if (sf.isDeclarationFile)
|
|
139
|
+
continue;
|
|
140
|
+
const walk = (node) => {
|
|
141
|
+
ts.forEachChild(node, walk);
|
|
142
|
+
if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression))
|
|
143
|
+
return;
|
|
144
|
+
const member = node.expression.name.text;
|
|
145
|
+
const isEmit = member === "emit";
|
|
146
|
+
if (!isEmit && !REGISTER_MEMBERS.has(member))
|
|
147
|
+
return;
|
|
148
|
+
const arg0 = node.arguments[0];
|
|
149
|
+
if (!arg0)
|
|
150
|
+
return;
|
|
151
|
+
let recvT = null;
|
|
152
|
+
let nameT = null;
|
|
153
|
+
try {
|
|
154
|
+
recvT = L.mapTypeOf(L.typeOf(node.expression.expression));
|
|
155
|
+
nameT = L.typeOf(arg0);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return; // checker trouble is the lowering's business, not the scan's
|
|
159
|
+
}
|
|
160
|
+
if (recvT?.kind !== "object" || !emitterRooted(L, L.classes.get(recvT.className)))
|
|
161
|
+
return;
|
|
162
|
+
if (!nameT.isStringLiteralType())
|
|
163
|
+
return;
|
|
164
|
+
const name = nameT.value;
|
|
165
|
+
if (META_EVENTS.has(name) || name === "error")
|
|
166
|
+
return; // forced tuples
|
|
167
|
+
// Stream receivers' runtime-emitted events carry PER-BASE forced
|
|
168
|
+
// tuples (lower-stream.ts) — their sites never join the program-
|
|
169
|
+
// global table, so a stream's 'data' cannot collide with a user
|
|
170
|
+
// event named 'data' on a plain emitter.
|
|
171
|
+
if (streamForcedTuple(L, L.classes.get(recvT.className), name) !== null)
|
|
172
|
+
return;
|
|
173
|
+
try {
|
|
174
|
+
if (isEmit) {
|
|
175
|
+
mergeEmit(name, node.arguments.slice(1).map((a) => L.mapTypeOf(L.typeOf(a))));
|
|
176
|
+
}
|
|
177
|
+
else if (node.arguments[1]) {
|
|
178
|
+
const cbCt = L.typeOf(node.arguments[1]);
|
|
179
|
+
const cbT = L.mapTypeOf(cbCt) ?? dynFallbackType(L, node.arguments[1], cbCt);
|
|
180
|
+
// Dyn-flavored listeners (a checked-dynamic value, or a func
|
|
181
|
+
// with dyn parameters — the JS lane) register through the
|
|
182
|
+
// adapter and constrain nothing: a dyn parameter accepts any
|
|
183
|
+
// tuple position, and positions past the tuple read the boxed
|
|
184
|
+
// undefined (exactly JS's extra-parameter semantics).
|
|
185
|
+
if (cbT?.kind === "dyn" || (cbT?.kind === "func" && cbT.params.some((p) => p.kind === "dyn"))) {
|
|
186
|
+
sigOf(name).dynListener = true;
|
|
187
|
+
}
|
|
188
|
+
else if (cbT?.kind === "func") {
|
|
189
|
+
mergeListener(name, cbT.params);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
/* not a candidate */
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
walk(sf);
|
|
198
|
+
}
|
|
199
|
+
holder.emitterEventTable = table;
|
|
200
|
+
return table;
|
|
201
|
+
}
|
|
202
|
+
/** The compile-time event name of the first argument, or a pointed fence
|
|
203
|
+
* (string-literal TYPE, so const bindings and literal unions of one
|
|
204
|
+
* member count — the staticHeaderKeyOf stance). */
|
|
205
|
+
function eventNameOf(L, member, arg) {
|
|
206
|
+
const t = L.typeOf(arg);
|
|
207
|
+
if (t.isStringLiteralType())
|
|
208
|
+
return t.value;
|
|
209
|
+
L.noLowering(`${member} with a non-literal event name`, arg, "event names must be compile-time string literals (each event's argument tuple is unified statically; symbol names have no lowering)");
|
|
210
|
+
}
|
|
211
|
+
const strLit = (value, loc) => ({ kind: "strLit", value, type: STRING, loc });
|
|
212
|
+
const boolLit = (value, loc) => ({ kind: "boolLit", value, type: BOOL, loc });
|
|
213
|
+
/** The event's unified tuple, with conflicts reported at this site. */
|
|
214
|
+
function tupleOf(L, table, name, blame) {
|
|
215
|
+
const sig = table.get(name);
|
|
216
|
+
if (!sig)
|
|
217
|
+
return [];
|
|
218
|
+
if (sig.conflict) {
|
|
219
|
+
L.noLowering(`the event '${name}' with conflicting argument types`, blame, `every emit site and listener of one event name must agree on one argument tuple — ${sig.conflict}`);
|
|
220
|
+
}
|
|
221
|
+
return sig.tuple;
|
|
222
|
+
}
|
|
223
|
+
/** How a listener argument is dyn-flavored: "dyn" for a checked-dynamic
|
|
224
|
+
* VALUE (a mustCall wrapper, a listener that rode an untyped binding),
|
|
225
|
+
* "func" for a function whose parameters include dyn (the JS lane's
|
|
226
|
+
* unannotated listeners — their checker type is any). Null for everything
|
|
227
|
+
* else (the static path, with its own diagnostics). */
|
|
228
|
+
function dynListenerFlavor(L, node) {
|
|
229
|
+
let t;
|
|
230
|
+
try {
|
|
231
|
+
const ct = L.typeOf(node);
|
|
232
|
+
// The JS declaration fallback is what the EXPRESSION lowering applies
|
|
233
|
+
// to inference residue (an inline `(a) => {}` maps null as a checker
|
|
234
|
+
// type but lowers as a dyn-parameter function) — classify by the same
|
|
235
|
+
// rule, so the flavor always matches what lowerExpr will produce.
|
|
236
|
+
t = L.mapTypeOf(ct) ?? dynFallbackType(L, node, ct);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
if (t?.kind === "dyn")
|
|
242
|
+
return "dyn";
|
|
243
|
+
if (t?.kind === "func")
|
|
244
|
+
return t.params.some((p) => p.kind === "dyn") ? "func" : null;
|
|
245
|
+
// A statically-known NON-function listener (a literal number, a record,
|
|
246
|
+
// null): route it through the dyn path too — the registration helper's
|
|
247
|
+
// checkListener throws Node's exact ERR_INVALID_ARG_TYPE TypeError
|
|
248
|
+
// (the TS lane never reaches this: tsc rejects the argument first).
|
|
249
|
+
if (t && t.kind !== "jsval" && (isUnitType(t) || canConvertToDyn(t, (id) => L.shapes.get(id), (id) => L.unions.get(id)))) {
|
|
250
|
+
return "dyn";
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
/** A dyn-flavored listener registration/removal (the JS lane's unannotated
|
|
255
|
+
* listeners, and mustCall-style wrapped ones): the listener registers as a
|
|
256
|
+
* checked-dynamic value through an interned helper —
|
|
257
|
+
*
|
|
258
|
+
* %emitter.onDyn.<n>(recv, name, cb, once, prepend) {
|
|
259
|
+
* emitter.checkListener(cb); // Node's ERR_INVALID_ARG_TYPE
|
|
260
|
+
* const a: (tuple) => void = cb; // dynCheck: the boxing adapter
|
|
261
|
+
* return emitter.onDyn(recv, name, cb, a, once, prepend);
|
|
262
|
+
* }
|
|
263
|
+
*
|
|
264
|
+
* The dynCheck adapter boxes each emitted tuple argument to dyn and calls
|
|
265
|
+
* the original through the checked-dynamic call machinery — JS-exact
|
|
266
|
+
* arity (parameters past the tuple read undefined, extra arguments are
|
|
267
|
+
* ignored). The runtime entry registers the ADAPTER for dispatch but
|
|
268
|
+
* keeps the ORIGINAL (the dyn box's underlying closure) as the entry's
|
|
269
|
+
* identity, so off/removeListener and listenerCount(name, fn) match it.
|
|
270
|
+
* The helper shape exists because cb must evaluate ONCE and feed both
|
|
271
|
+
* roles. Fenced honestly: tuple positions that cannot box to dyn (class
|
|
272
|
+
* payloads — 'error' events), and the meta events (Node passes the
|
|
273
|
+
* listener FUNCTION second, which has no dyn conversion — a dyn listener
|
|
274
|
+
* reading it would see undefined). */
|
|
275
|
+
function lowerDynListenerCall(L, member, name, tuple, receiver, cbNode, flavor, registering, once, prepend, loc,
|
|
276
|
+
/** Stream 'data': the adapter registers through emitter.onDataDyn so
|
|
277
|
+
* the backend emits the two-slot DATA thunk (box by runtime tag); the
|
|
278
|
+
* tuple is [DYN] — the adapter passes the boxed chunk through. */
|
|
279
|
+
streamData) {
|
|
280
|
+
if (META_EVENTS.has(name)) {
|
|
281
|
+
L.noLowering(`'${member}' of '${name}' with a checked-dynamic listener`, cbNode, "Node passes the listener FUNCTION as the meta event's second argument, which has no dynamic conversion — annotate the listener's parameter as a string");
|
|
282
|
+
}
|
|
283
|
+
const getRecord = (id) => L.shapes.get(id);
|
|
284
|
+
const getUnion = (id) => L.unions.get(id);
|
|
285
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
286
|
+
const p = tuple[i];
|
|
287
|
+
if (p.kind !== "dyn" && !canConvertToDyn(p, getRecord, getUnion)) {
|
|
288
|
+
L.noLowering(`'${member}' of '${name}' with a checked-dynamic listener`, cbNode, `the event's argument ${i} is '${L.fmt(p)}', which cannot box into a dynamic value — annotate the listener's parameters with the emitted types`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
let cbDyn;
|
|
292
|
+
if (flavor === "dyn") {
|
|
293
|
+
const v = L.lowerExpr(cbNode);
|
|
294
|
+
if (v.type.kind === "dyn") {
|
|
295
|
+
cbDyn = v;
|
|
296
|
+
}
|
|
297
|
+
else if (v.kind === "unitLit" || canConvertToDyn(v.type, getRecord, getUnion)) {
|
|
298
|
+
// A non-dyn residue the classification saw as dynamic (a literal
|
|
299
|
+
// null/undefined listener, a convertible scalar): box it — the
|
|
300
|
+
// checkListener call answers with Node's exact TypeError.
|
|
301
|
+
cbDyn = { kind: "dynFrom", value: v, type: DYN, loc };
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
L.noLowering(`'${member}' with a non-function listener`, cbNode);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
const cb = L.lowerExpr(cbNode);
|
|
309
|
+
if (cb.type.kind !== "func" || !canBoxFuncIntoDyn(cb.type, getRecord, getUnion)) {
|
|
310
|
+
L.noLowering(`'${member}' with a listener of this signature`, cbNode, "a checked-dynamic listener's own parameters and return must box across the dynamic boundary");
|
|
311
|
+
}
|
|
312
|
+
cbDyn = { kind: "dynFrom", value: cb, type: DYN, loc };
|
|
313
|
+
}
|
|
314
|
+
const recvT = receiver.type;
|
|
315
|
+
const adapterT = { kind: "func", params: tuple, ret: VOID };
|
|
316
|
+
const onFn = streamData ? "emitter.onDataDyn" : "emitter.onDyn";
|
|
317
|
+
const key = registering
|
|
318
|
+
? `${onFn}:${typeKey(recvT)}:${typeKey(adapterT)}`
|
|
319
|
+
: `emitter.offDyn:${typeKey(recvT)}`;
|
|
320
|
+
const existing = L.arrHofHelpers.get(key);
|
|
321
|
+
let helper = existing;
|
|
322
|
+
if (!helper) {
|
|
323
|
+
helper = `%emitter.${registering ? "onDyn" : "offDyn"}.${L.arrHofHelpers.size}`;
|
|
324
|
+
L.arrHofHelpers.set(key, helper);
|
|
325
|
+
const ref = (localId, type) => ({ kind: "varRef", localId, type, loc });
|
|
326
|
+
const check = {
|
|
327
|
+
kind: "exprStmt",
|
|
328
|
+
expr: { kind: "libCall", fn: "emitter.checkListener", args: [ref("cb.0", DYN)], type: VOID, loc },
|
|
329
|
+
loc,
|
|
330
|
+
};
|
|
331
|
+
const params = [
|
|
332
|
+
{ localId: "r.0", name: "r", type: recvT },
|
|
333
|
+
{ localId: "n.0", name: "n", type: STRING },
|
|
334
|
+
{ localId: "cb.0", name: "cb", type: DYN },
|
|
335
|
+
...(registering
|
|
336
|
+
? [
|
|
337
|
+
{ localId: "o.0", name: "o", type: BOOL },
|
|
338
|
+
{ localId: "p.0", name: "p", type: BOOL },
|
|
339
|
+
]
|
|
340
|
+
: []),
|
|
341
|
+
];
|
|
342
|
+
const locals = params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false }));
|
|
343
|
+
let body;
|
|
344
|
+
if (registering) {
|
|
345
|
+
locals.push({ id: "a.0", name: "a", type: adapterT, mutable: false });
|
|
346
|
+
body = [
|
|
347
|
+
check,
|
|
348
|
+
{ kind: "varDecl", localId: "a.0", init: { kind: "dynCheck", value: ref("cb.0", DYN), type: adapterT, loc }, loc },
|
|
349
|
+
{
|
|
350
|
+
kind: "return",
|
|
351
|
+
value: {
|
|
352
|
+
kind: "libCall",
|
|
353
|
+
fn: onFn,
|
|
354
|
+
args: [ref("r.0", recvT), ref("n.0", STRING), ref("cb.0", DYN), ref("a.0", adapterT), ref("o.0", BOOL), ref("p.0", BOOL)],
|
|
355
|
+
type: recvT,
|
|
356
|
+
loc,
|
|
357
|
+
},
|
|
358
|
+
loc,
|
|
359
|
+
},
|
|
360
|
+
];
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
body = [
|
|
364
|
+
check,
|
|
365
|
+
{
|
|
366
|
+
kind: "return",
|
|
367
|
+
value: {
|
|
368
|
+
kind: "libCall",
|
|
369
|
+
fn: "emitter.offDyn",
|
|
370
|
+
args: [ref("r.0", recvT), ref("n.0", STRING), ref("cb.0", DYN)],
|
|
371
|
+
type: recvT,
|
|
372
|
+
loc,
|
|
373
|
+
},
|
|
374
|
+
loc,
|
|
375
|
+
},
|
|
376
|
+
];
|
|
377
|
+
}
|
|
378
|
+
L.liftedFns.push({ name: helper, params, returnType: recvT, locals, body, loc });
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
kind: "call",
|
|
382
|
+
callee: helper,
|
|
383
|
+
args: registering
|
|
384
|
+
? [receiver, strLit(name, loc), cbDyn, boolLit(once, loc), boolLit(prepend, loc)]
|
|
385
|
+
: [receiver, strLit(name, loc), cbDyn],
|
|
386
|
+
type: recvT,
|
|
387
|
+
loc,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
/** A listener argument: lowered, checked void-returning, and its
|
|
391
|
+
* parameters checked as a typeEquals PREFIX of the event tuple. */
|
|
392
|
+
function lowerListenerArg(L, member, name, node, tuple) {
|
|
393
|
+
// Unannotated non-empty parameter lists have no static types (checker
|
|
394
|
+
// `any`; dyn in JS sources) — say so before the generic SC2001 speaks.
|
|
395
|
+
if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && node.parameters.length > 0) {
|
|
396
|
+
for (const p of node.parameters) {
|
|
397
|
+
if (p.type === undefined && ts.isIdentifier(p.name)) {
|
|
398
|
+
const mapped = (() => {
|
|
399
|
+
try {
|
|
400
|
+
return L.mapTypeOf(L.typeOf(p.name));
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
})();
|
|
406
|
+
if (mapped === null || mapped.kind === "dyn" || mapped.kind === "jsval") {
|
|
407
|
+
L.noLowering(`'${member}' listeners with unannotated parameters`, p, "EventEmitter's declared listener type is (...args: any[]) — annotate each parameter with the emitted argument's type");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const cb = L.lowerExpr(node);
|
|
413
|
+
if (cb.type.kind !== "func") {
|
|
414
|
+
L.noLowering(`'${member}' with a non-function listener`, node);
|
|
415
|
+
}
|
|
416
|
+
const cbT = cb.type;
|
|
417
|
+
// Node ignores listener return values, so value-returning listeners
|
|
418
|
+
// (expression-body arrows, mustCall wrappers) register fine — the
|
|
419
|
+
// emitted invoke adapter calls through the listener's true signature
|
|
420
|
+
// and discards (releasing refcounted results). An async listener's
|
|
421
|
+
// promise is abandoned unobserved (SEMANTICS.md).
|
|
422
|
+
if (META_EVENTS.has(name) && cbT.params.length > 1) {
|
|
423
|
+
L.noLowering(`'${name}' listeners taking the listener-function argument`, node, "meta-event listeners take at most the event name (the listener argument has no unified static type)");
|
|
424
|
+
}
|
|
425
|
+
if (cbT.params.length > tuple.length) {
|
|
426
|
+
L.noLowering(`a '${name}' listener declaring ${cbT.params.length} parameters where the event's tuple has ${tuple.length}`, node, "listeners may declare a prefix of the event's emitted arguments");
|
|
427
|
+
}
|
|
428
|
+
for (let i = 0; i < cbT.params.length; i++) {
|
|
429
|
+
if (!typeEquals(cbT.params[i], tuple[i])) {
|
|
430
|
+
L.noLowering(`a '${name}' listener whose parameter ${i} is '${L.fmt(cbT.params[i])}' where the event's tuple has '${L.fmt(tuple[i])}'`, node, "every emit site and listener of one event name must agree on one argument tuple");
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return cb;
|
|
434
|
+
}
|
|
435
|
+
/** `recv.<member>(...)` over an emitter-rooted receiver — the whole
|
|
436
|
+
* EventEmitter method surface. Returns null only for members this spoke
|
|
437
|
+
* does not own (the caller falls through to its own fences). */
|
|
438
|
+
export function lowerEmitterMethodCall(L, call, access, info) {
|
|
439
|
+
const member = access.name.text;
|
|
440
|
+
const loc = locOf(call);
|
|
441
|
+
const args = call.arguments;
|
|
442
|
+
// The class VALUE, not an instance (`EventEmitter.setMaxListeners(n)` —
|
|
443
|
+
// the receiver's checker type carries construct signatures; mapType
|
|
444
|
+
// answers the instance object for every EventEmitter-symbol type, so the
|
|
445
|
+
// instance dispatch conflated the two and handed the instance libCalls a
|
|
446
|
+
// receiver that lowers to the class value, the setMax ICE). The statics
|
|
447
|
+
// are a DIFFERENT surface: setMaxListeners(n) writes the process-wide
|
|
448
|
+
// defaultMaxListeners (validated at runtime — Node's ERR_OUT_OF_RANGE);
|
|
449
|
+
// everything else fences by its static name.
|
|
450
|
+
if (L.checker.getConstructSignatures(L.typeOf(access.expression)).length > 0) {
|
|
451
|
+
if (member === "setMaxListeners" && args.length === 1) {
|
|
452
|
+
const n = L.lowerExprExpecting(args[0], F64);
|
|
453
|
+
if (n.type.kind !== "f64") {
|
|
454
|
+
L.noLowering(`EventEmitter.setMaxListeners with a '${L.fmt(n.type)}' argument`, args[0], "the lowered form takes a number (Node throws ERR_INVALID_ARG_TYPE at runtime for other values)");
|
|
455
|
+
}
|
|
456
|
+
return { kind: "libCall", fn: "emitter.setDefaultMax", args: [n], type: VOID, loc };
|
|
457
|
+
}
|
|
458
|
+
if (member === "setMaxListeners") {
|
|
459
|
+
L.noLowering(`EventEmitter.setMaxListeners with ${args.length} arguments`, call, "the lowered static form is EventEmitter.setMaxListeners(n) — per-target application (…, ...eventTargets) has no lowering; call target.setMaxListeners(n) instead");
|
|
460
|
+
}
|
|
461
|
+
L.noLowering(`the static EventEmitter.${member} form`, call, "instance emitters lower this member; the class-value statics have no lowering yet");
|
|
462
|
+
}
|
|
463
|
+
const table = emitterEvents(L);
|
|
464
|
+
if (REGISTER_MEMBERS.has(member) || member === "off" || member === "removeListener") {
|
|
465
|
+
if (args.length !== 2) {
|
|
466
|
+
L.noLowering(`${member} with ${args.length} arguments`, call, "the supported form is (eventName, listener)");
|
|
467
|
+
}
|
|
468
|
+
const name = eventNameOf(L, member, args[0]);
|
|
469
|
+
const receiver = L.lowerExpr(access.expression);
|
|
470
|
+
const registering = REGISTER_MEMBERS.has(member);
|
|
471
|
+
const once = member === "once" || member === "prependOnceListener";
|
|
472
|
+
const prepend = member === "prependListener" || member === "prependOnceListener";
|
|
473
|
+
// Stream 'data' rides its own two-slot payload ABI (bytes + string —
|
|
474
|
+
// encoded streams deliver strings; scr_stream_emit_data): listeners
|
|
475
|
+
// on readable-sided receivers register through DATA thunks that
|
|
476
|
+
// unwrap the declared side (typed) or box by tag (dyn).
|
|
477
|
+
const sides = streamSidesOf(L, info);
|
|
478
|
+
if (name === "data" && (sides === "r" || sides === "rw")) {
|
|
479
|
+
const dynFlavor = dynListenerFlavor(L, args[1]);
|
|
480
|
+
if (dynFlavor !== null) {
|
|
481
|
+
return lowerDynListenerCall(L, member, name, [DYN], receiver, args[1], dynFlavor, registering, once, prepend, loc, true);
|
|
482
|
+
}
|
|
483
|
+
const cb = L.lowerExpr(args[1]);
|
|
484
|
+
if (cb.type.kind !== "func") {
|
|
485
|
+
L.noLowering(`'${member}' with a non-function listener`, args[1]);
|
|
486
|
+
}
|
|
487
|
+
const cbT = cb.type;
|
|
488
|
+
if (cbT.params.length > 1) {
|
|
489
|
+
L.noLowering(`a 'data' listener declaring ${cbT.params.length} parameters where the event carries one chunk`, args[1]);
|
|
490
|
+
}
|
|
491
|
+
const p = cbT.params[0];
|
|
492
|
+
if (p !== undefined && !(p.kind === "bytes" && p.elem === "u8") && p.kind !== "string") {
|
|
493
|
+
L.noLowering(`a 'data' listener whose chunk parameter is '${L.fmt(p)}'`, args[1], "chunks are Buffers — or strings once setEncoding/the encoding option applies");
|
|
494
|
+
}
|
|
495
|
+
if (!registering) {
|
|
496
|
+
return {
|
|
497
|
+
kind: "libCall",
|
|
498
|
+
fn: "emitter.off",
|
|
499
|
+
args: [receiver, strLit(name, loc), cb],
|
|
500
|
+
type: receiver.type,
|
|
501
|
+
loc,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
kind: "libCall",
|
|
506
|
+
fn: "emitter.onData",
|
|
507
|
+
args: [receiver, strLit(name, loc), cb, boolLit(once, loc), boolLit(prepend, loc)],
|
|
508
|
+
type: receiver.type,
|
|
509
|
+
loc,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
// Stream-rooted receivers consult the per-base FORCED tuples first
|
|
513
|
+
// (runtime-emitted 'end'/'pipe'/... payloads); the dyn-listener check
|
|
514
|
+
// below then sees the forced tuple exactly like a table one.
|
|
515
|
+
const tuple = streamForcedTuple(L, info, name) ?? tupleOf(L, table, name, call);
|
|
516
|
+
const dynFlavor = dynListenerFlavor(L, args[1]);
|
|
517
|
+
if (dynFlavor !== null) {
|
|
518
|
+
return lowerDynListenerCall(L, member, name, tuple, receiver, args[1], dynFlavor, registering, once, prepend, loc, false);
|
|
519
|
+
}
|
|
520
|
+
const cb = lowerListenerArg(L, member, name, args[1], tuple);
|
|
521
|
+
return {
|
|
522
|
+
kind: "libCall",
|
|
523
|
+
fn: registering ? "emitter.on" : "emitter.off",
|
|
524
|
+
args: registering
|
|
525
|
+
? [receiver, strLit(name, loc), cb, boolLit(once, loc), boolLit(prepend, loc)]
|
|
526
|
+
: [receiver, strLit(name, loc), cb],
|
|
527
|
+
type: receiver.type,
|
|
528
|
+
loc,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (member === "emit") {
|
|
532
|
+
if (args.length === 0) {
|
|
533
|
+
L.noLowering("emit without an event name", call);
|
|
534
|
+
}
|
|
535
|
+
const name = eventNameOf(L, member, args[0]);
|
|
536
|
+
const receiver = L.lowerExpr(access.expression);
|
|
537
|
+
if (name === "error") {
|
|
538
|
+
// The special event: exactly one %Error-rooted payload; no listener
|
|
539
|
+
// means the runtime THROWS it (emitter.emitError, may-throw).
|
|
540
|
+
if (args.length !== 2) {
|
|
541
|
+
L.noLowering(`emit('error') with ${args.length - 1} payload arguments`, call, "the supported form is emit('error', err) with an Error-hierarchy payload (non-Error payloads would need Node's ERR_UNHANDLED_ERROR wrapping — no lowering yet)");
|
|
542
|
+
}
|
|
543
|
+
const err = L.lowerExpr(args[1]);
|
|
544
|
+
const rootsAtError = (t) => {
|
|
545
|
+
if (t.kind !== "object")
|
|
546
|
+
return false;
|
|
547
|
+
for (let c = L.classes.get(t.className) ?? null; c; c = c.base) {
|
|
548
|
+
if (c.def.name === "%Error")
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
return false;
|
|
552
|
+
};
|
|
553
|
+
if (!rootsAtError(err.type)) {
|
|
554
|
+
L.noLowering(`emit('error') with a '${L.fmt(err.type)}' payload`, args[1], "the supported payload is an Error-hierarchy instance");
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
kind: "libCall",
|
|
558
|
+
fn: "emitter.emitError",
|
|
559
|
+
args: [receiver, strLit(name, loc), L.upcastTo(err, "%Error")],
|
|
560
|
+
type: BOOL,
|
|
561
|
+
loc,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
// Stream 'data' rides the two-slot payload ABI: a user emit fills
|
|
565
|
+
// the chunk's slot (bytes or string), NULLs the other.
|
|
566
|
+
const emitSides = streamSidesOf(L, info);
|
|
567
|
+
if (name === "data" && (emitSides === "r" || emitSides === "rw")) {
|
|
568
|
+
if (args.length !== 2) {
|
|
569
|
+
L.noLowering(`emit('data') with ${args.length - 1} payload arguments`, call, "the event carries one chunk (a Buffer or string)");
|
|
570
|
+
}
|
|
571
|
+
const chunk = L.lowerExpr(args[1]);
|
|
572
|
+
if (!(chunk.type.kind === "bytes" && chunk.type.elem === "u8") && chunk.type.kind !== "string") {
|
|
573
|
+
L.noLowering(`emit('data') with a '${L.fmt(chunk.type)}' chunk`, args[1], "chunks are Buffers or strings");
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
kind: "libCall",
|
|
577
|
+
fn: "emitter.emitData",
|
|
578
|
+
args: [receiver, strLit(name, loc), chunk],
|
|
579
|
+
type: BOOL,
|
|
580
|
+
loc,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
const tuple = streamForcedTuple(L, info, name) ?? tupleOf(L, table, name, call);
|
|
584
|
+
if (args.length - 1 !== tuple.length) {
|
|
585
|
+
L.noLowering(`emit('${name}') with ${args.length - 1} arguments where the event's tuple has ${tuple.length}`, call, "every emit site of one event name must supply the same argument tuple (listeners may declare a prefix)");
|
|
586
|
+
}
|
|
587
|
+
const payload = args.slice(1).map((a, i) => L.lowerExprExpecting(a, tuple[i]));
|
|
588
|
+
return {
|
|
589
|
+
kind: "libCall",
|
|
590
|
+
fn: "emitter.emit",
|
|
591
|
+
args: [receiver, strLit(name, loc), ...payload],
|
|
592
|
+
type: BOOL,
|
|
593
|
+
loc,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
// The pure-introspection members take ANY string-typed name — no tuple
|
|
597
|
+
// is involved, so the literal rule has nothing to protect (a meta
|
|
598
|
+
// listener naturally passes its name parameter along).
|
|
599
|
+
if (member === "removeAllListeners") {
|
|
600
|
+
if (args.length > 1) {
|
|
601
|
+
L.noLowering(`removeAllListeners with ${args.length} arguments`, call);
|
|
602
|
+
}
|
|
603
|
+
const all = args.length === 0;
|
|
604
|
+
const receiver = L.lowerExpr(access.expression);
|
|
605
|
+
const name = all ? strLit("", loc) : L.lowerExprExpecting(args[0], STRING);
|
|
606
|
+
if (name.type.kind !== "string") {
|
|
607
|
+
L.noLowering(`removeAllListeners with a '${L.fmt(name.type)}' event name`, args[0] ?? call);
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
kind: "libCall",
|
|
611
|
+
fn: "emitter.removeAll",
|
|
612
|
+
args: [receiver, name, boolLit(all, loc)],
|
|
613
|
+
type: receiver.type,
|
|
614
|
+
loc,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
if (member === "listenerCount") {
|
|
618
|
+
if (args.length !== 1 && args.length !== 2) {
|
|
619
|
+
L.noLowering(`listenerCount with ${args.length} arguments`, call);
|
|
620
|
+
}
|
|
621
|
+
const receiver = L.lowerExpr(access.expression);
|
|
622
|
+
const name = L.lowerExprExpecting(args[0], STRING);
|
|
623
|
+
if (name.type.kind !== "string") {
|
|
624
|
+
L.noLowering(`listenerCount with a '${L.fmt(name.type)}' event name`, args[0]);
|
|
625
|
+
}
|
|
626
|
+
if (args.length === 2) {
|
|
627
|
+
// The fn filter matches by identity — the listener's own type is
|
|
628
|
+
// its word; no tuple check is needed to count.
|
|
629
|
+
const cb = L.lowerExpr(args[1]);
|
|
630
|
+
if (cb.type.kind !== "func") {
|
|
631
|
+
L.noLowering(`listenerCount with a non-function filter`, args[1]);
|
|
632
|
+
}
|
|
633
|
+
return { kind: "libCall", fn: "emitter.countFn", args: [receiver, name, cb], type: F64, loc };
|
|
634
|
+
}
|
|
635
|
+
return { kind: "libCall", fn: "emitter.count", args: [receiver, name], type: F64, loc };
|
|
636
|
+
}
|
|
637
|
+
if (member === "listeners" || member === "rawListeners") {
|
|
638
|
+
// A fresh +1 closure array of the event's listeners in list order,
|
|
639
|
+
// element-typed by the event's unified tuple: every registered
|
|
640
|
+
// listener declared a typeEquals PREFIX of it, and calling one through
|
|
641
|
+
// the full-tuple signature delivers exactly what emit would (extra
|
|
642
|
+
// trailing arguments are ignored — JS's semantics, and the C calling
|
|
643
|
+
// convention's). The event NAME must be a compile-time literal — the
|
|
644
|
+
// element type depends on it. Both members answer the ORIGINALS: the
|
|
645
|
+
// once wrapper is runtime-internal, so rawListeners' wrapper identity
|
|
646
|
+
// is a documented divergence (SEMANTICS.md).
|
|
647
|
+
if (args.length !== 1) {
|
|
648
|
+
L.noLowering(`${member} with ${args.length} arguments`, call, "the supported form is (eventName)");
|
|
649
|
+
}
|
|
650
|
+
const name = eventNameOf(L, member, args[0]);
|
|
651
|
+
const sig = table.get(name);
|
|
652
|
+
if (sig?.dynListener) {
|
|
653
|
+
// A dyn-adapted registration means the runtime bucket can hold
|
|
654
|
+
// originals of MIXED signatures — no one honest element type.
|
|
655
|
+
L.noLowering(`${member} of the event '${name}'`, call, "a listener of this event is checked-dynamic (unannotated parameters), so the listener array has no one static element type — listenerCount(name) counts");
|
|
656
|
+
}
|
|
657
|
+
const tuple = tupleOf(L, table, name, call);
|
|
658
|
+
const receiver = L.lowerExpr(access.expression);
|
|
659
|
+
return {
|
|
660
|
+
kind: "libCall",
|
|
661
|
+
fn: "emitter.listeners",
|
|
662
|
+
args: [receiver, strLit(name, loc)],
|
|
663
|
+
type: arrayOf({ kind: "func", params: tuple, ret: { kind: "void" } }),
|
|
664
|
+
loc,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
if (member === "eventNames") {
|
|
668
|
+
if (args.length !== 0)
|
|
669
|
+
L.noLowering(`eventNames with ${args.length} arguments`, call);
|
|
670
|
+
const receiver = L.lowerExpr(access.expression);
|
|
671
|
+
return { kind: "libCall", fn: "emitter.names", args: [receiver], type: arrayOf(STRING), loc };
|
|
672
|
+
}
|
|
673
|
+
if (member === "setMaxListeners") {
|
|
674
|
+
if (args.length !== 1)
|
|
675
|
+
L.noLowering(`setMaxListeners with ${args.length} arguments`, call);
|
|
676
|
+
const receiver = L.lowerExpr(access.expression);
|
|
677
|
+
const n = L.lowerExprExpecting(args[0], F64);
|
|
678
|
+
return { kind: "libCall", fn: "emitter.setMax", args: [receiver, n], type: receiver.type, loc };
|
|
679
|
+
}
|
|
680
|
+
if (member === "getMaxListeners") {
|
|
681
|
+
if (args.length !== 0)
|
|
682
|
+
L.noLowering(`getMaxListeners with ${args.length} arguments`, call);
|
|
683
|
+
const receiver = L.lowerExpr(access.expression);
|
|
684
|
+
return { kind: "libCall", fn: "emitter.getMax", args: [receiver], type: F64, loc };
|
|
685
|
+
}
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
//# sourceMappingURL=lower-emitter.js.map
|