@scriptc/compiler 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +117 -37
- package/dist/backend/cc-cache.test.d.ts +1 -0
- package/dist/backend/cc-cache.test.js +2507 -0
- package/dist/backend/cc-cache.test.js.map +1 -0
- package/dist/backend/cc.d.ts +69 -11
- package/dist/backend/cc.js +1937 -222
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-async.js +3 -1
- package/dist/backend/emission/emit-async.js.map +1 -1
- package/dist/backend/emission/emit-exprs.d.ts +10 -0
- package/dist/backend/emission/emit-exprs.js +482 -18
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-stmts.js +13 -5
- package/dist/backend/emission/emit-stmts.js.map +1 -1
- package/dist/backend/emission/emit-types.js +4 -1
- package/dist/backend/emission/emit-types.js.map +1 -1
- package/dist/backend/emission/emit-walkers.js +108 -7
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +27 -1
- package/dist/backend/emission/emitter.js +84 -3
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +1 -0
- package/dist/backend/llvm/dyn.js +305 -8
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +831 -23
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/shapes.js +3 -2
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/compat/fetch-profile.d.ts +118 -0
- package/dist/compat/fetch-profile.js +531 -0
- package/dist/compat/fetch-profile.js.map +1 -0
- package/dist/coverage/surface-manifest.js +69 -0
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +10 -0
- package/dist/diagnostics/diagnostic.js +27 -1
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +24 -15
- package/dist/frontend/lowering/lower-builtins.js +217 -69
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +41 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +32 -11
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +3 -3
- package/dist/frontend/lowering/lower-exprs.js +87 -15
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.js +7 -1
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +123 -7
- package/dist/frontend/lowering/lower-island.js +1976 -26
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +17 -1
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +33 -9
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +40 -0
- package/dist/frontend/lowering/lowerer.js +290 -18
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +55 -12
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/program.d.ts +20 -4
- package/dist/frontend/program.js +391 -49
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.d.ts +10 -0
- package/dist/frontend/resolve.js +37 -0
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/ts7/enums.d.ts +9 -0
- package/dist/frontend/ts7/enums.js +8 -6
- package/dist/frontend/ts7/enums.js.map +1 -1
- package/dist/frontend/ts7/program.js +4 -1
- package/dist/frontend/ts7/program.js.map +1 -1
- package/dist/frontend/types.d.ts +16 -9
- package/dist/frontend/types.js +119 -38
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +70 -14
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +55 -26
- package/dist/ir/nodes.js +42 -29
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +106 -9
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/int-infer.js +7 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +80 -0
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/sidecar-validate.js +5 -0
- package/dist/library/sidecar-validate.js.map +1 -1
- package/dist/library/sidecar.d.ts +3 -0
- package/dist/library/sidecar.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1097 -130
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* surface (Math and number/string methods under --dynamic), and the npm
|
|
4
4
|
* package boundary fences for node_modules-declared symbols. */
|
|
5
5
|
import * as ts from "../ts7/adapter.js";
|
|
6
|
-
import { F64, JSVAL, MAX_ISLAND_CALLBACK_ARITY, STRING, VOID, canMarshalTypedFuncIntoIsland, islandPromisePayloadTag, isUnitType } from "../../ir/nodes.js";
|
|
6
|
+
import { BOOL, BYTES_U8, DYN, F64, JSVAL, MAX_ISLAND_CALLBACK_ARITY, STRING, VOID, canConvertToDyn, canMarshalTypedFuncIntoIsland, islandPromisePayloadTag, isUnitType } from "../../ir/nodes.js";
|
|
7
7
|
import { ISLAND_SURFACE, STATIC_MATH_FNS, boundaryIntoIslandMsg } from "./surfaces.js";
|
|
8
8
|
import { requiresDynamicApiDiag, requiresDynamicPackageDiag } from "../../diagnostics/diagnostic.js";
|
|
9
9
|
import { isCjsJsFile, isJsSourceFile, locOf, npmPackageNameOf } from "../program.js";
|
|
10
|
-
import { pureReemittable } from "./lower-exprs.js";
|
|
11
|
-
import { PoisonError, newFnCtx, own } from "./lowerer.js";
|
|
10
|
+
import { foldedStringKeyOf, lowerDynObjectLiteral, pureReemittable } from "./lower-exprs.js";
|
|
11
|
+
import { PoisonError, dynUndefinedExpr, newFnCtx, nodeThrowExpr, own } from "./lowerer.js";
|
|
12
|
+
import { NODE24_FETCH_COMPAT_PROFILE, STATIC_HEADERS_CALLS, STATIC_READABLE_STREAM_CALLS, STATIC_READABLE_STREAM_READS, STATIC_RESPONSE_CALLS, STATIC_RESPONSE_READS, } from "../../compat/fetch-profile.js";
|
|
12
13
|
/** True iff the checker's type for this node maps to jsval ('any') —
|
|
13
14
|
* the island test in front of every engine-op lowering (receivers,
|
|
14
15
|
* callees, assignment targets). ALSO true for an identifier bound to an
|
|
@@ -222,8 +223,468 @@ export function islandGlobalFnOf(L, ident) {
|
|
|
222
223
|
return null;
|
|
223
224
|
return entry;
|
|
224
225
|
}
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
function requestInitLiteralKey(L, prop) {
|
|
227
|
+
if (!ts.isPropertyAssignment(prop) &&
|
|
228
|
+
!ts.isShorthandPropertyAssignment(prop) &&
|
|
229
|
+
!ts.isMethodDeclaration(prop)) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
const name = prop.name;
|
|
233
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name))
|
|
234
|
+
return name.text;
|
|
235
|
+
if (ts.isNumericLiteral(name))
|
|
236
|
+
return String(Number(name.text));
|
|
237
|
+
if (ts.isComputedPropertyName(name))
|
|
238
|
+
return foldedStringKeyOf(L, name.expression);
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
/** Fence declared RequestInit members outside the selected compiler tier.
|
|
242
|
+
* Computed runtime keys and non-literal objects are validated again by the
|
|
243
|
+
* static runtime; source-visible members that neither tier preserves are
|
|
244
|
+
* rejected before the dynamic bridge can silently discard them. */
|
|
245
|
+
function requestInitValueExpr(expr) {
|
|
246
|
+
let current = expr;
|
|
247
|
+
while (ts.isParenthesizedExpression(current) ||
|
|
248
|
+
ts.isAsExpression(current) ||
|
|
249
|
+
ts.isSatisfiesExpression(current) ||
|
|
250
|
+
ts.isTypeAssertion(current) ||
|
|
251
|
+
ts.isNonNullExpression(current)) {
|
|
252
|
+
current = current.expression;
|
|
253
|
+
}
|
|
254
|
+
return current;
|
|
255
|
+
}
|
|
256
|
+
function requestInitStaticBoolean(L, value) {
|
|
257
|
+
const expr = requestInitValueExpr(value);
|
|
258
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword)
|
|
259
|
+
return true;
|
|
260
|
+
if (expr.kind === ts.SyntaxKind.FalseKeyword)
|
|
261
|
+
return false;
|
|
262
|
+
const text = L.checker.typeToString(L.typeOf(expr));
|
|
263
|
+
return text === "true" ? true : text === "false" ? false : null;
|
|
264
|
+
}
|
|
265
|
+
/** Symbols whose object value is mutated through a property write. A const
|
|
266
|
+
* binding keeps the binding stable, not the object: following its initializer
|
|
267
|
+
* after `init.cache = undefined` would diagnose the stale literal instead of
|
|
268
|
+
* the value fetch actually observes. Built lazily and diagnostic-free. */
|
|
269
|
+
function requestInitPropMutatedSymbols(L) {
|
|
270
|
+
const holder = L;
|
|
271
|
+
if (holder.requestInitPropMutatedSyms) {
|
|
272
|
+
return holder.requestInitPropMutatedSyms;
|
|
273
|
+
}
|
|
274
|
+
const symbols = new Set();
|
|
275
|
+
const noteBase = (target) => {
|
|
276
|
+
let base = requestInitValueExpr(target);
|
|
277
|
+
while (ts.isPropertyAccessExpression(base) ||
|
|
278
|
+
ts.isElementAccessExpression(base)) {
|
|
279
|
+
base = requestInitValueExpr(base.expression);
|
|
280
|
+
}
|
|
281
|
+
if (!ts.isIdentifier(base))
|
|
282
|
+
return;
|
|
283
|
+
try {
|
|
284
|
+
const symbol = L.resolveValueSymbol(base);
|
|
285
|
+
if (symbol)
|
|
286
|
+
symbols.add(symbol);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
/* not a traceable RequestInit candidate */
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
for (const source of L.program.getSourceFiles()) {
|
|
293
|
+
if (source.isDeclarationFile)
|
|
294
|
+
continue;
|
|
295
|
+
const walk = (node) => {
|
|
296
|
+
if (ts.isBinaryExpression(node) &&
|
|
297
|
+
node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
|
|
298
|
+
node.operatorToken.kind <= ts.SyntaxKind.LastAssignment &&
|
|
299
|
+
(ts.isPropertyAccessExpression(node.left) ||
|
|
300
|
+
ts.isElementAccessExpression(node.left))) {
|
|
301
|
+
noteBase(node.left);
|
|
302
|
+
}
|
|
303
|
+
else if ((ts.isPrefixUnaryExpression(node) ||
|
|
304
|
+
ts.isPostfixUnaryExpression(node)) &&
|
|
305
|
+
(node.operator === ts.SyntaxKind.PlusPlusToken ||
|
|
306
|
+
node.operator === ts.SyntaxKind.MinusMinusToken) &&
|
|
307
|
+
(ts.isPropertyAccessExpression(node.operand) ||
|
|
308
|
+
ts.isElementAccessExpression(node.operand))) {
|
|
309
|
+
noteBase(node.operand);
|
|
310
|
+
}
|
|
311
|
+
else if (ts.isDeleteExpression(node)) {
|
|
312
|
+
noteBase(node.expression);
|
|
313
|
+
}
|
|
314
|
+
ts.forEachChild(node, walk);
|
|
315
|
+
};
|
|
316
|
+
walk(source);
|
|
317
|
+
}
|
|
318
|
+
holder.requestInitPropMutatedSyms = symbols;
|
|
319
|
+
return symbols;
|
|
320
|
+
}
|
|
321
|
+
function requestInitConstBacked(L, value, seen) {
|
|
322
|
+
const expr = requestInitValueExpr(value);
|
|
323
|
+
if (ts.isObjectLiteralExpression(expr))
|
|
324
|
+
return true;
|
|
325
|
+
if (ts.isConditionalExpression(expr)) {
|
|
326
|
+
const selected = requestInitStaticBoolean(L, expr.condition);
|
|
327
|
+
if (selected !== null) {
|
|
328
|
+
return requestInitConstBacked(L, selected ? expr.whenTrue : expr.whenFalse, new Set(seen));
|
|
329
|
+
}
|
|
330
|
+
return requestInitConstBacked(L, expr.whenTrue, new Set(seen)) &&
|
|
331
|
+
requestInitConstBacked(L, expr.whenFalse, new Set(seen));
|
|
332
|
+
}
|
|
333
|
+
if (!ts.isIdentifier(expr))
|
|
334
|
+
return false;
|
|
335
|
+
const symbol = L.resolveValueSymbol(expr);
|
|
336
|
+
if (!symbol || seen.has(symbol))
|
|
337
|
+
return false;
|
|
338
|
+
if (requestInitPropMutatedSymbols(L).has(symbol))
|
|
339
|
+
return false;
|
|
340
|
+
seen.add(symbol);
|
|
341
|
+
const declarations = L.checker.declarationsOf(symbol).filter((declaration) => ts.isVariableDeclaration(declaration) &&
|
|
342
|
+
declaration.initializer !== undefined &&
|
|
343
|
+
ts.isVariableDeclarationList(declaration.parent) &&
|
|
344
|
+
(declaration.parent.flags & ts.NodeFlags.Const) !== 0);
|
|
345
|
+
return declarations.length > 0 && declarations.every((declaration) => requestInitConstBacked(L, declaration.initializer, new Set(seen)));
|
|
346
|
+
}
|
|
347
|
+
/** Visit the source values of one property on a const-backed object. The
|
|
348
|
+
* checker's property symbol can come from an annotation (a PropertySignature)
|
|
349
|
+
* rather than the object literal that supplies the runtime value, so source
|
|
350
|
+
* tracing has to follow the receiver initializer by key as well. */
|
|
351
|
+
function visitRequestInitConstPropertyValues(L, value, key, seen, visit) {
|
|
352
|
+
const expr = requestInitValueExpr(value);
|
|
353
|
+
if (ts.isObjectLiteralExpression(expr)) {
|
|
354
|
+
// Last contributor wins. A definite later property (including one from
|
|
355
|
+
// a statically traced spread) hides earlier values exactly as it does at
|
|
356
|
+
// runtime; a conditional/missing spread keeps the earlier contributor
|
|
357
|
+
// in the possible-value set.
|
|
358
|
+
for (let i = expr.properties.length - 1; i >= 0; i--) {
|
|
359
|
+
const property = expr.properties[i];
|
|
360
|
+
if (ts.isSpreadAssignment(property)) {
|
|
361
|
+
const defines = visitRequestInitConstPropertyValues(L, property.expression, key, new Set(seen), visit);
|
|
362
|
+
if (defines)
|
|
363
|
+
return true;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (requestInitLiteralKey(L, property) !== key)
|
|
367
|
+
continue;
|
|
368
|
+
if (ts.isPropertyAssignment(property)) {
|
|
369
|
+
visit(property.initializer);
|
|
370
|
+
}
|
|
371
|
+
else if (ts.isShorthandPropertyAssignment(property) &&
|
|
372
|
+
ts.isIdentifier(property.name)) {
|
|
373
|
+
visit(property.name);
|
|
374
|
+
}
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
if (ts.isConditionalExpression(expr)) {
|
|
380
|
+
const selected = requestInitStaticBoolean(L, expr.condition);
|
|
381
|
+
if (selected !== null) {
|
|
382
|
+
return visitRequestInitConstPropertyValues(L, selected ? expr.whenTrue : expr.whenFalse, key, seen, visit);
|
|
383
|
+
}
|
|
384
|
+
const whenTrue = visitRequestInitConstPropertyValues(L, expr.whenTrue, key, new Set(seen), visit);
|
|
385
|
+
const whenFalse = visitRequestInitConstPropertyValues(L, expr.whenFalse, key, new Set(seen), visit);
|
|
386
|
+
return whenTrue && whenFalse;
|
|
387
|
+
}
|
|
388
|
+
if (ts.isPropertyAccessExpression(expr) || ts.isElementAccessExpression(expr)) {
|
|
389
|
+
const member = ts.isPropertyAccessExpression(expr)
|
|
390
|
+
? expr.name.text
|
|
391
|
+
: foldedStringKeyOf(L, expr.argumentExpression);
|
|
392
|
+
if (member === null)
|
|
393
|
+
return false;
|
|
394
|
+
const nested = [];
|
|
395
|
+
const receiverDefines = visitRequestInitConstPropertyValues(L, expr.expression, member, new Set(seen), (value) => nested.push(value));
|
|
396
|
+
let nestedDefines = nested.length > 0;
|
|
397
|
+
for (const value of nested) {
|
|
398
|
+
if (!visitRequestInitConstPropertyValues(L, value, key, new Set(seen), visit)) {
|
|
399
|
+
nestedDefines = false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return receiverDefines && nestedDefines;
|
|
403
|
+
}
|
|
404
|
+
if (!ts.isIdentifier(expr))
|
|
405
|
+
return false;
|
|
406
|
+
const symbol = L.resolveValueSymbol(expr);
|
|
407
|
+
if (!symbol || seen.has(symbol))
|
|
408
|
+
return false;
|
|
409
|
+
if (requestInitPropMutatedSymbols(L).has(symbol))
|
|
410
|
+
return false;
|
|
411
|
+
seen.add(symbol);
|
|
412
|
+
let sawDeclaration = false;
|
|
413
|
+
let defines = true;
|
|
414
|
+
for (const declaration of L.checker.declarationsOf(symbol)) {
|
|
415
|
+
if (ts.isVariableDeclaration(declaration) &&
|
|
416
|
+
declaration.initializer !== undefined &&
|
|
417
|
+
ts.isVariableDeclarationList(declaration.parent) &&
|
|
418
|
+
(declaration.parent.flags & ts.NodeFlags.Const) !== 0) {
|
|
419
|
+
sawDeclaration = true;
|
|
420
|
+
if (!visitRequestInitConstPropertyValues(L, declaration.initializer, key, new Set(seen), visit)) {
|
|
421
|
+
defines = false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return sawDeclaration && defines;
|
|
426
|
+
}
|
|
427
|
+
function visitRequestInitConstIndexValues(L, value, index, seen, visit) {
|
|
428
|
+
const expr = requestInitValueExpr(value);
|
|
429
|
+
if (ts.isArrayLiteralExpression(expr)) {
|
|
430
|
+
const element = expr.elements[index];
|
|
431
|
+
if (element === undefined ||
|
|
432
|
+
ts.isOmittedExpression(element) ||
|
|
433
|
+
ts.isSpreadElement(element)) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
visit(element);
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
if (ts.isConditionalExpression(expr)) {
|
|
440
|
+
const selected = requestInitStaticBoolean(L, expr.condition);
|
|
441
|
+
if (selected !== null) {
|
|
442
|
+
return visitRequestInitConstIndexValues(L, selected ? expr.whenTrue : expr.whenFalse, index, seen, visit);
|
|
443
|
+
}
|
|
444
|
+
const whenTrue = visitRequestInitConstIndexValues(L, expr.whenTrue, index, new Set(seen), visit);
|
|
445
|
+
const whenFalse = visitRequestInitConstIndexValues(L, expr.whenFalse, index, new Set(seen), visit);
|
|
446
|
+
return whenTrue && whenFalse;
|
|
447
|
+
}
|
|
448
|
+
if (ts.isPropertyAccessExpression(expr) || ts.isElementAccessExpression(expr)) {
|
|
449
|
+
const member = ts.isPropertyAccessExpression(expr)
|
|
450
|
+
? expr.name.text
|
|
451
|
+
: foldedStringKeyOf(L, expr.argumentExpression);
|
|
452
|
+
if (member === null)
|
|
453
|
+
return false;
|
|
454
|
+
const nested = [];
|
|
455
|
+
const receiverDefines = visitRequestInitConstPropertyValues(L, expr.expression, member, new Set(seen), (value) => nested.push(value));
|
|
456
|
+
let nestedDefines = nested.length > 0;
|
|
457
|
+
for (const value of nested) {
|
|
458
|
+
if (!visitRequestInitConstIndexValues(L, value, index, new Set(seen), visit)) {
|
|
459
|
+
nestedDefines = false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return receiverDefines && nestedDefines;
|
|
463
|
+
}
|
|
464
|
+
if (!ts.isIdentifier(expr))
|
|
465
|
+
return false;
|
|
466
|
+
const symbol = L.resolveValueSymbol(expr);
|
|
467
|
+
if (!symbol || seen.has(symbol))
|
|
468
|
+
return false;
|
|
469
|
+
if (requestInitPropMutatedSymbols(L).has(symbol))
|
|
470
|
+
return false;
|
|
471
|
+
seen.add(symbol);
|
|
472
|
+
let sawDeclaration = false;
|
|
473
|
+
let defines = true;
|
|
474
|
+
for (const declaration of L.checker.declarationsOf(symbol)) {
|
|
475
|
+
if (ts.isVariableDeclaration(declaration) &&
|
|
476
|
+
declaration.initializer !== undefined &&
|
|
477
|
+
ts.isVariableDeclarationList(declaration.parent) &&
|
|
478
|
+
(declaration.parent.flags & ts.NodeFlags.Const) !== 0) {
|
|
479
|
+
sawDeclaration = true;
|
|
480
|
+
if (!visitRequestInitConstIndexValues(L, declaration.initializer, index, new Set(seen), visit)) {
|
|
481
|
+
defines = false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return sawDeclaration && defines;
|
|
486
|
+
}
|
|
487
|
+
function requestInitConstBindingSource(L, declaration) {
|
|
488
|
+
const selectors = [];
|
|
489
|
+
let current = declaration;
|
|
490
|
+
while (true) {
|
|
491
|
+
const pattern = current.parent;
|
|
492
|
+
if (ts.isObjectBindingPattern(pattern)) {
|
|
493
|
+
if (current.dotDotDotToken)
|
|
494
|
+
return null;
|
|
495
|
+
const key = fetchObjectBindingMemberName(L, current);
|
|
496
|
+
if (key === null)
|
|
497
|
+
return null;
|
|
498
|
+
selectors.unshift({ kind: "property", key });
|
|
499
|
+
}
|
|
500
|
+
else if (ts.isArrayBindingPattern(pattern)) {
|
|
501
|
+
if (current.dotDotDotToken)
|
|
502
|
+
return null;
|
|
503
|
+
const index = pattern.elements.indexOf(current);
|
|
504
|
+
if (index < 0)
|
|
505
|
+
return null;
|
|
506
|
+
selectors.unshift({ kind: "index", index });
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
const owner = pattern.parent;
|
|
512
|
+
if (ts.isBindingElement(owner)) {
|
|
513
|
+
current = owner;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (!ts.isVariableDeclaration(owner) ||
|
|
517
|
+
owner.initializer === undefined ||
|
|
518
|
+
!ts.isVariableDeclarationList(owner.parent) ||
|
|
519
|
+
(owner.parent.flags & ts.NodeFlags.Const) === 0) {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
return { initializer: owner.initializer, selectors };
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function visitRequestInitConstBindingValues(L, value, selectors, seen, visit) {
|
|
526
|
+
const selector = selectors[0];
|
|
527
|
+
if (selector === undefined) {
|
|
528
|
+
visit(value);
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
let nestedDefines = true;
|
|
532
|
+
const visitNested = (nested) => {
|
|
533
|
+
if (!visitRequestInitConstBindingValues(L, nested, selectors.slice(1), new Set(seen), visit)) {
|
|
534
|
+
nestedDefines = false;
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
const defines = selector.kind === "property"
|
|
538
|
+
? visitRequestInitConstPropertyValues(L, value, selector.key, seen, visitNested)
|
|
539
|
+
: visitRequestInitConstIndexValues(L, value, selector.index, seen, visitNested);
|
|
540
|
+
return defines && nestedDefines;
|
|
541
|
+
}
|
|
542
|
+
function fenceRequestInitProperty(L, access, seen) {
|
|
543
|
+
const key = ts.isPropertyAccessExpression(access)
|
|
544
|
+
? access.name.text
|
|
545
|
+
: foldedStringKeyOf(L, access.argumentExpression);
|
|
546
|
+
if (key === null)
|
|
547
|
+
return;
|
|
548
|
+
visitRequestInitConstPropertyValues(L, access.expression, key, new Set(), (value) => fenceRequestInitValueInner(L, value, seen));
|
|
549
|
+
if (!requestInitConstBacked(L, access.expression, new Set()))
|
|
550
|
+
return;
|
|
551
|
+
const symbol = ts.isPropertyAccessExpression(access)
|
|
552
|
+
? L.checker.getSymbolAtLocation(access.name)
|
|
553
|
+
: L.checker.getPropertyOfType(L.typeOf(access.expression), key);
|
|
554
|
+
if (!symbol || seen.has(symbol))
|
|
555
|
+
return;
|
|
556
|
+
seen.add(symbol);
|
|
557
|
+
for (const declaration of L.checker.declarationsOf(symbol)) {
|
|
558
|
+
if (ts.isPropertyAssignment(declaration)) {
|
|
559
|
+
fenceRequestInitValueInner(L, declaration.initializer, seen);
|
|
560
|
+
}
|
|
561
|
+
else if (ts.isShorthandPropertyAssignment(declaration) &&
|
|
562
|
+
ts.isIdentifier(declaration.name)) {
|
|
563
|
+
fenceRequestInitValueInner(L, declaration.name, seen);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function fenceRequestInitObject(L, init, seen) {
|
|
568
|
+
const contextual = L.checker.getContextualType(init);
|
|
569
|
+
const contextualArms = contextual?.isUnionType() ? contextual.getTypes() : contextual ? [contextual] : [];
|
|
570
|
+
const rows = NODE24_FETCH_COMPAT_PROFILE.inventory.entries.filter((entry) => entry.owner === "RequestInit" && entry.placement === "dictionary");
|
|
571
|
+
const shadowed = new Set();
|
|
572
|
+
const fence = (row, value, blame) => {
|
|
573
|
+
if (L.dynamic ? row.status !== "unsupported" : row.status === "static")
|
|
574
|
+
return;
|
|
575
|
+
if (value !== null) {
|
|
576
|
+
const type = L.typeOf(requestInitValueExpr(value));
|
|
577
|
+
if (ts.isVoidExpression(requestInitValueExpr(value)) ||
|
|
578
|
+
(type.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const sym = contextualArms
|
|
583
|
+
.map((arm) => L.checker.getPropertyOfType(arm, row.member))
|
|
584
|
+
.find((member) => member !== undefined);
|
|
585
|
+
L.noLowering(`RequestInit option '${row.member}'${L.dynamic ? "" : " in a static build"}`, blame, row.reason ??
|
|
586
|
+
"the native static RequestInit surface is method, headers, body, duplex, redirect, and signal", sym);
|
|
587
|
+
};
|
|
588
|
+
// Object spread is last-write-wins. Walk from the end and trace only the
|
|
589
|
+
// effective contributor for each dictionary member; a definite later
|
|
590
|
+
// `cache: undefined`, for example, suppresses an earlier unsupported
|
|
591
|
+
// cache value just as WebIDL observes at runtime.
|
|
592
|
+
for (let index = init.properties.length - 1; index >= 0; index--) {
|
|
593
|
+
const prop = init.properties[index];
|
|
594
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
595
|
+
for (const row of rows) {
|
|
596
|
+
if (shadowed.has(row.member))
|
|
597
|
+
continue;
|
|
598
|
+
const defines = visitRequestInitConstPropertyValues(L, prop.expression, row.member, new Set(seen), (value) => fence(row, value, value));
|
|
599
|
+
if (defines)
|
|
600
|
+
shadowed.add(row.member);
|
|
601
|
+
}
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
const key = requestInitLiteralKey(L, prop);
|
|
605
|
+
if (key === null || shadowed.has(key))
|
|
606
|
+
continue;
|
|
607
|
+
shadowed.add(key);
|
|
608
|
+
const row = rows.find((entry) => entry.member === key);
|
|
609
|
+
if (!row)
|
|
610
|
+
continue;
|
|
611
|
+
const value = ts.isPropertyAssignment(prop)
|
|
612
|
+
? prop.initializer
|
|
613
|
+
: ts.isShorthandPropertyAssignment(prop) && ts.isIdentifier(prop.name)
|
|
614
|
+
? prop.name
|
|
615
|
+
: null;
|
|
616
|
+
fence(row, value, prop);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
/** Fence statically knowable RequestInit gaps through the ordinary value
|
|
620
|
+
* plumbing, not only when the fetch argument's AST is itself an object
|
|
621
|
+
* literal. Const aliases and object spreads preserve the same dictionary
|
|
622
|
+
* members, so follow their initializers until the profile can make the
|
|
623
|
+
* decision. Runtime-computed values keep both fetch runtimes' defensive
|
|
624
|
+
* validation because there is no source member to diagnose. */
|
|
625
|
+
function fenceRequestInitValueInner(L, value, seen) {
|
|
626
|
+
const expr = requestInitValueExpr(value);
|
|
627
|
+
if (ts.isObjectLiteralExpression(expr)) {
|
|
628
|
+
fenceRequestInitObject(L, expr, seen);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (ts.isConditionalExpression(expr)) {
|
|
632
|
+
const selected = requestInitStaticBoolean(L, expr.condition);
|
|
633
|
+
if (selected !== null) {
|
|
634
|
+
fenceRequestInitValueInner(L, selected ? expr.whenTrue : expr.whenFalse, seen);
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
fenceRequestInitValueInner(L, expr.whenTrue, seen);
|
|
638
|
+
fenceRequestInitValueInner(L, expr.whenFalse, seen);
|
|
639
|
+
}
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (ts.isPropertyAccessExpression(expr) || ts.isElementAccessExpression(expr)) {
|
|
643
|
+
fenceRequestInitProperty(L, expr, seen);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (!ts.isIdentifier(expr))
|
|
647
|
+
return;
|
|
648
|
+
// Import bindings point at an ImportSpecifier declaration, not the
|
|
649
|
+
// exported const whose initializer carries the dictionary members.
|
|
650
|
+
// Chase the value alias so local and cross-module const plumbing share
|
|
651
|
+
// the same source-profile fence.
|
|
652
|
+
const symbol = L.resolveValueSymbol(expr);
|
|
653
|
+
if (!symbol || seen.has(symbol))
|
|
654
|
+
return;
|
|
655
|
+
if (requestInitPropMutatedSymbols(L).has(symbol))
|
|
656
|
+
return;
|
|
657
|
+
seen.add(symbol);
|
|
658
|
+
for (const declaration of L.checker.declarationsOf(symbol)) {
|
|
659
|
+
if (ts.isBindingElement(declaration)) {
|
|
660
|
+
const source = requestInitConstBindingSource(L, declaration);
|
|
661
|
+
if (source !== null) {
|
|
662
|
+
visitRequestInitConstBindingValues(L, source.initializer, source.selectors, new Set(seen), (bindingValue) => fenceRequestInitValueInner(L, bindingValue, new Set(seen)));
|
|
663
|
+
}
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (!ts.isVariableDeclaration(declaration) ||
|
|
667
|
+
declaration.initializer === undefined ||
|
|
668
|
+
!ts.isVariableDeclarationList(declaration.parent) ||
|
|
669
|
+
(declaration.parent.flags & ts.NodeFlags.Const) === 0) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
fenceRequestInitValueInner(L, declaration.initializer, seen);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function fenceRequestInitValue(L, value) {
|
|
676
|
+
fenceRequestInitValueInner(L, value, new Set());
|
|
677
|
+
}
|
|
678
|
+
/** USER-code `fetch(url)` / `fetch(url, init)` — the ambient global
|
|
679
|
+
* (provenance, not the name: a user's own `fetch` never matches).
|
|
680
|
+
*
|
|
681
|
+
* The static tier owns URL requests directly: scr_fetch_static runs
|
|
682
|
+
* over the native net/http/tls stack, consumes a checked-dynamic
|
|
683
|
+
* RequestInit object, and returns a promise of an opaque native
|
|
684
|
+
* Response handle. AbortSignal and readable Web Streams are native
|
|
685
|
+
* checked-dynamic handles too, so cancellation and streaming bodies
|
|
686
|
+
* stay engine-free.
|
|
687
|
+
*
|
|
227
688
|
* The engine's own fetch executes (the same one embedded npm code calls —
|
|
228
689
|
* scr_fetch.c over the native net/tls stack): the url marshals in (strings; template
|
|
229
690
|
* results), an init OBJECT LITERAL builds natively in the island through
|
|
@@ -234,8 +695,8 @@ export function islandGlobalFnOf(L, ident) {
|
|
|
234
695
|
* fiber like any await and resumes with the Response HANDLE. Member
|
|
235
696
|
* reads/calls on the handle are engine ops; typed extraction happens at
|
|
236
697
|
* the user's narrowing sites through the validated-exit machinery.
|
|
237
|
-
*
|
|
238
|
-
*
|
|
698
|
+
* Null for anything that isn't THE ambient fetch, so lowerCall keeps
|
|
699
|
+
* trying. */
|
|
239
700
|
export function lowerFetchCall(L, call) {
|
|
240
701
|
const callee = call.expression;
|
|
241
702
|
if (!ts.isIdentifier(callee) || callee.text !== "fetch")
|
|
@@ -245,10 +706,75 @@ export function lowerFetchCall(L, call) {
|
|
|
245
706
|
const symbol = L.resolveValueSymbol(callee);
|
|
246
707
|
if (!symbol || !L.isStdlibSymbol(symbol))
|
|
247
708
|
return null;
|
|
248
|
-
if (call.arguments.length < 1
|
|
709
|
+
if (call.arguments.length < 1)
|
|
249
710
|
return null;
|
|
250
|
-
L.requireDynamicApi("'fetch'", call);
|
|
251
711
|
const loc = locOf(call);
|
|
712
|
+
const initNode = call.arguments[1];
|
|
713
|
+
if (initNode !== undefined)
|
|
714
|
+
fenceRequestInitValue(L, initNode);
|
|
715
|
+
if (!L.dynamic) {
|
|
716
|
+
const inputNode = call.arguments[0];
|
|
717
|
+
const input = L.lowerExpr(inputNode);
|
|
718
|
+
let init;
|
|
719
|
+
if (initNode === undefined) {
|
|
720
|
+
init = dynUndefinedExpr(loc);
|
|
721
|
+
}
|
|
722
|
+
else if (ts.isObjectLiteralExpression(initNode)) {
|
|
723
|
+
init = lowerDynObjectLiteral(L, initNode);
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
init = L.lowerExprExpecting(initNode, DYN);
|
|
727
|
+
}
|
|
728
|
+
// Calling a WebIDL operation has two distinct phases: JavaScript
|
|
729
|
+
// first evaluates every argument expression, then the callee converts
|
|
730
|
+
// those values. In particular, evaluating RequestInit may mutate a
|
|
731
|
+
// URL object passed as input; its href must not be snapshotted until
|
|
732
|
+
// all argument expressions (including ignored surplus ones) ran.
|
|
733
|
+
const inputLocal = L.declareHiddenLocal("%fetchInput", input.type);
|
|
734
|
+
const initLocal = L.declareHiddenLocal("%fetchInit", init.type);
|
|
735
|
+
const inputRef = {
|
|
736
|
+
kind: "varRef",
|
|
737
|
+
localId: inputLocal.id,
|
|
738
|
+
type: input.type,
|
|
739
|
+
loc: locOf(inputNode),
|
|
740
|
+
};
|
|
741
|
+
const initRef = {
|
|
742
|
+
kind: "varRef",
|
|
743
|
+
localId: initLocal.id,
|
|
744
|
+
type: init.type,
|
|
745
|
+
loc,
|
|
746
|
+
};
|
|
747
|
+
const stmts = [
|
|
748
|
+
{ kind: "varDecl", localId: inputLocal.id, init: input, loc },
|
|
749
|
+
{ kind: "varDecl", localId: initLocal.id, init, loc },
|
|
750
|
+
];
|
|
751
|
+
for (const argument of call.arguments.slice(2)) {
|
|
752
|
+
if (ts.isSpreadElement(argument)) {
|
|
753
|
+
L.noLowering("spread surplus arguments on static fetch()", argument, "pass surplus arguments without spread syntax");
|
|
754
|
+
}
|
|
755
|
+
const discarded = ts.isVoidExpression(argument)
|
|
756
|
+
? L.lowerExpr(argument.expression)
|
|
757
|
+
: L.lowerExpr(argument);
|
|
758
|
+
stmts.push({ kind: "exprStmt", expr: discarded, loc: discarded.loc });
|
|
759
|
+
}
|
|
760
|
+
const url = input.type.kind === "url"
|
|
761
|
+
? {
|
|
762
|
+
kind: "libCall",
|
|
763
|
+
fn: "url.href",
|
|
764
|
+
args: [inputRef],
|
|
765
|
+
type: STRING,
|
|
766
|
+
loc: locOf(inputNode),
|
|
767
|
+
}
|
|
768
|
+
: L.coerceInto(inputNode, inputRef, STRING);
|
|
769
|
+
const answer = {
|
|
770
|
+
kind: "libCall",
|
|
771
|
+
fn: "fetch.start",
|
|
772
|
+
args: [url, initRef],
|
|
773
|
+
type: { kind: "promise", inner: DYN },
|
|
774
|
+
loc,
|
|
775
|
+
};
|
|
776
|
+
return { kind: "seqExpr", stmts, result: answer, type: answer.type, loc };
|
|
777
|
+
}
|
|
252
778
|
const fetchFn = { kind: "jsOp", op: "globalGet", name: "fetch", args: [], type: JSVAL, loc };
|
|
253
779
|
// Init OBJECT LITERALS build natively in the island (the general
|
|
254
780
|
// 'any'-contextual literal path can't claim them: the optional param's
|
|
@@ -261,6 +787,1373 @@ export function lowerFetchCall(L, call) {
|
|
|
261
787
|
const raw = { kind: "jsOp", op: "callFn", args: [fetchFn, ...args], type: JSVAL, loc };
|
|
262
788
|
return { kind: "jsBridgePromise", value: raw, type: { kind: "promise", inner: JSVAL }, loc };
|
|
263
789
|
}
|
|
790
|
+
/** Static Response.json(): consume the native Response's readable body
|
|
791
|
+
* stream and parse its UTF-8 payload into the ordinary checked-dynamic
|
|
792
|
+
* JSON tree. Syntax and stream failures reject at await like the web API. */
|
|
793
|
+
export function lowerStaticResponseCall(L, call) {
|
|
794
|
+
if (L.dynamic ||
|
|
795
|
+
call.questionDotToken ||
|
|
796
|
+
(!ts.isPropertyAccessExpression(call.expression) &&
|
|
797
|
+
!ts.isElementAccessExpression(call.expression))) {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
const access = call.expression;
|
|
801
|
+
const member = staticResponseMemberName(L, access);
|
|
802
|
+
if (access.questionDotToken ||
|
|
803
|
+
(member !== "json" &&
|
|
804
|
+
member !== "text" &&
|
|
805
|
+
member !== "bytes" &&
|
|
806
|
+
member !== "arrayBuffer") ||
|
|
807
|
+
call.arguments.length !== 0) {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
const recvType = L.checker.getBaseTypeOfLiteralType(L.typeOf(access.expression));
|
|
811
|
+
const sym = recvType.getAliasSymbol() ?? recvType.getSymbol();
|
|
812
|
+
if (!sym || sym.name !== "Response" || !L.isStdlibSymbol(sym))
|
|
813
|
+
return null;
|
|
814
|
+
if (member === "arrayBuffer") {
|
|
815
|
+
L.noLowering("Response.arrayBuffer() in a static build", call, "use Response.bytes() for the native Uint8Array body; free-standing ArrayBuffer values have no static representation", sym);
|
|
816
|
+
}
|
|
817
|
+
const recv = L.lowerExpr(access.expression);
|
|
818
|
+
if (recv.type.kind !== "dyn")
|
|
819
|
+
return null;
|
|
820
|
+
return lowerStaticFixedFetchMethodCall(L, call, access, member, recv, [], (receiver) => member === "text" || member === "bytes"
|
|
821
|
+
? {
|
|
822
|
+
kind: "libCall",
|
|
823
|
+
fn: member === "text" ? "fetch.responseText" : "fetch.responseBytes",
|
|
824
|
+
args: [receiver],
|
|
825
|
+
type: {
|
|
826
|
+
kind: "promise",
|
|
827
|
+
inner: member === "text" ? STRING : BYTES_U8,
|
|
828
|
+
},
|
|
829
|
+
loc: locOf(call),
|
|
830
|
+
}
|
|
831
|
+
: {
|
|
832
|
+
kind: "libCall",
|
|
833
|
+
fn: "fetch.responseJson",
|
|
834
|
+
args: [receiver],
|
|
835
|
+
type: { kind: "promise", inner: DYN },
|
|
836
|
+
loc: locOf(call),
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
function staticResponseMemberName(L, access) {
|
|
840
|
+
if (ts.isPropertyAccessExpression(access))
|
|
841
|
+
return access.name.text;
|
|
842
|
+
const members = fetchElementMemberNames(L, access.argumentExpression);
|
|
843
|
+
return members?.length === 1 ? members[0] : null;
|
|
844
|
+
}
|
|
845
|
+
/** A singleton computed member is still an evaluated JavaScript expression.
|
|
846
|
+
* The specialized static bridges know the only permitted method name, but
|
|
847
|
+
* must preserve receiver -> key -> arguments order and reject a lying cast
|
|
848
|
+
* whose runtime key is not that name. */
|
|
849
|
+
function lowerStaticFixedFetchMethodCall(L, call, access, member, receiver, argumentValues, invoke) {
|
|
850
|
+
if (ts.isPropertyAccessExpression(access)) {
|
|
851
|
+
return invoke(receiver, [...argumentValues]);
|
|
852
|
+
}
|
|
853
|
+
const loc = locOf(call);
|
|
854
|
+
const key = L.lowerExprExpecting(access.argumentExpression, STRING);
|
|
855
|
+
const receiverLocal = L.declareHiddenLocal("%fetchReceiver", receiver.type);
|
|
856
|
+
const keyLocal = L.declareHiddenLocal("%fetchMember", STRING);
|
|
857
|
+
const argumentLocals = argumentValues.map((argument) => L.declareHiddenLocal("%fetchMethodArg", argument.type));
|
|
858
|
+
const receiverRef = {
|
|
859
|
+
kind: "varRef",
|
|
860
|
+
localId: receiverLocal.id,
|
|
861
|
+
type: receiverLocal.type,
|
|
862
|
+
loc,
|
|
863
|
+
};
|
|
864
|
+
const keyRef = {
|
|
865
|
+
kind: "varRef",
|
|
866
|
+
localId: keyLocal.id,
|
|
867
|
+
type: STRING,
|
|
868
|
+
loc,
|
|
869
|
+
};
|
|
870
|
+
const argumentRefs = argumentLocals.map((argument) => ({
|
|
871
|
+
kind: "varRef",
|
|
872
|
+
localId: argument.id,
|
|
873
|
+
type: argument.type,
|
|
874
|
+
loc,
|
|
875
|
+
}));
|
|
876
|
+
const answer = invoke(receiverRef, argumentRefs);
|
|
877
|
+
const result = {
|
|
878
|
+
kind: "ternary",
|
|
879
|
+
cond: {
|
|
880
|
+
kind: "strEq",
|
|
881
|
+
negated: false,
|
|
882
|
+
left: keyRef,
|
|
883
|
+
right: { kind: "strLit", value: member, type: STRING, loc },
|
|
884
|
+
type: BOOL,
|
|
885
|
+
loc,
|
|
886
|
+
},
|
|
887
|
+
then: answer,
|
|
888
|
+
else_: nodeThrowExpr(1, "", `${access.getText()} is not a function`, answer.type, loc),
|
|
889
|
+
type: answer.type,
|
|
890
|
+
loc,
|
|
891
|
+
};
|
|
892
|
+
return {
|
|
893
|
+
kind: "seqExpr",
|
|
894
|
+
stmts: [
|
|
895
|
+
{ kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
|
|
896
|
+
{ kind: "varDecl", localId: keyLocal.id, init: key, loc },
|
|
897
|
+
// A computed call resolves the property before evaluating arguments.
|
|
898
|
+
// Native Web handles dispatch methods by name rather than storing
|
|
899
|
+
// callable properties, so perform the keyed read for its ordering and
|
|
900
|
+
// nullish-receiver check, then use the fixed native invocation below.
|
|
901
|
+
{
|
|
902
|
+
kind: "exprStmt",
|
|
903
|
+
expr: {
|
|
904
|
+
kind: "dynKeyGet",
|
|
905
|
+
value: receiverRef,
|
|
906
|
+
key: keyRef,
|
|
907
|
+
type: DYN,
|
|
908
|
+
loc,
|
|
909
|
+
},
|
|
910
|
+
loc,
|
|
911
|
+
},
|
|
912
|
+
...argumentValues.map((argument, index) => ({
|
|
913
|
+
kind: "varDecl",
|
|
914
|
+
localId: argumentLocals[index].id,
|
|
915
|
+
init: argument,
|
|
916
|
+
loc: argument.loc,
|
|
917
|
+
})),
|
|
918
|
+
],
|
|
919
|
+
result,
|
|
920
|
+
type: answer.type,
|
|
921
|
+
loc,
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function fetchElementMemberNames(L, keyExpr) {
|
|
925
|
+
if (ts.isPropertyAccessExpression(keyExpr) &&
|
|
926
|
+
L.isStdlibGlobal(keyExpr.expression, "Symbol") &&
|
|
927
|
+
(keyExpr.name.text === "iterator" ||
|
|
928
|
+
keyExpr.name.text === "asyncIterator" ||
|
|
929
|
+
keyExpr.name.text === "toStringTag")) {
|
|
930
|
+
return [`[Symbol.${keyExpr.name.text}]`];
|
|
931
|
+
}
|
|
932
|
+
const key = L.typeOf(keyExpr);
|
|
933
|
+
if (key.isStringLiteralType())
|
|
934
|
+
return [key.value];
|
|
935
|
+
if (!key.isUnionType())
|
|
936
|
+
return null;
|
|
937
|
+
const members = [];
|
|
938
|
+
for (const arm of key.getTypes()) {
|
|
939
|
+
if (!arm.isStringLiteralType())
|
|
940
|
+
return null;
|
|
941
|
+
if (!members.includes(arm.value))
|
|
942
|
+
members.push(arm.value);
|
|
943
|
+
}
|
|
944
|
+
return members;
|
|
945
|
+
}
|
|
946
|
+
function fetchAccessMemberNames(L, access) {
|
|
947
|
+
return ts.isPropertyAccessExpression(access)
|
|
948
|
+
? [access.name.text]
|
|
949
|
+
: fetchElementMemberNames(L, access.argumentExpression);
|
|
950
|
+
}
|
|
951
|
+
function fetchInventoryStatus(owner, member, placement) {
|
|
952
|
+
return NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find((entry) => entry.owner === owner &&
|
|
953
|
+
entry.member === member &&
|
|
954
|
+
entry.placement === placement)?.status ?? null;
|
|
955
|
+
}
|
|
956
|
+
function fetchInventoryReason(owner, member, placement) {
|
|
957
|
+
return NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find((entry) => entry.owner === owner &&
|
|
958
|
+
entry.member === member &&
|
|
959
|
+
entry.placement === placement)?.reason;
|
|
960
|
+
}
|
|
961
|
+
/** Constructor-object operations are distinct from Response instance
|
|
962
|
+
* methods. The inventory marks the former unsupported in both tiers; fence
|
|
963
|
+
* them explicitly so the generic call fallback cannot report SC1090 for
|
|
964
|
+
* Response.json while the other statics report SC2020. */
|
|
965
|
+
export function fenceUnsupportedFetchConstructorMember(L, access) {
|
|
966
|
+
if (!L.isStdlibGlobal(access.expression, "Response"))
|
|
967
|
+
return null;
|
|
968
|
+
const members = fetchAccessMemberNames(L, access);
|
|
969
|
+
if (members === null)
|
|
970
|
+
return null;
|
|
971
|
+
const member = members.find((candidate) => fetchInventoryStatus("Response", candidate, "static") === "unsupported");
|
|
972
|
+
if (member === undefined)
|
|
973
|
+
return null;
|
|
974
|
+
L.noLowering(`Response.${member}`, access, "Response static constructor-object operations have no compiler lowering in either tier");
|
|
975
|
+
}
|
|
976
|
+
function hasLiveWebMutableArm(L, type) {
|
|
977
|
+
if (type.kind === "record" ||
|
|
978
|
+
type.kind === "array" ||
|
|
979
|
+
type.kind === "bytes") {
|
|
980
|
+
return true;
|
|
981
|
+
}
|
|
982
|
+
if (type.kind !== "union")
|
|
983
|
+
return false;
|
|
984
|
+
return L.unions.get(type.unionId)?.arms.some((arm) => hasLiveWebMutableArm(L, arm)) ?? false;
|
|
985
|
+
}
|
|
986
|
+
/** Box mutable data for Web API slots that expose the exact JavaScript
|
|
987
|
+
* value again. Ordinary dynFrom remains the documented copy boundary;
|
|
988
|
+
* this capsule is deliberately limited to records/arrays/bytes (including
|
|
989
|
+
* those selected at runtime from a union) that the live materializer can
|
|
990
|
+
* snapshot and commit. */
|
|
991
|
+
function lowerLiveWebValue(L, node, lowered) {
|
|
992
|
+
const value = lowered ?? L.lowerExpr(node);
|
|
993
|
+
if (hasLiveWebMutableArm(L, value.type) &&
|
|
994
|
+
canConvertToDyn(value.type, (id) => L.shapes.get(id), (id) => L.unions.get(id))) {
|
|
995
|
+
return {
|
|
996
|
+
kind: "dynFrom",
|
|
997
|
+
value,
|
|
998
|
+
liveRef: true,
|
|
999
|
+
type: DYN,
|
|
1000
|
+
loc: value.loc,
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
return L.coerceInto(node, value, DYN);
|
|
1004
|
+
}
|
|
1005
|
+
function fetchMethodArgumentIsLive(owner, members, index) {
|
|
1006
|
+
return ((index === 0 &&
|
|
1007
|
+
((owner === "ReadableStreamDefaultController" &&
|
|
1008
|
+
members.some((member) => member === "enqueue" || member === "error")) ||
|
|
1009
|
+
((owner === "ReadableStream" ||
|
|
1010
|
+
owner === "ReadableStreamDefaultReader") &&
|
|
1011
|
+
members.includes("cancel")) ||
|
|
1012
|
+
(owner === "AbortController" && members.includes("abort")))) ||
|
|
1013
|
+
(index === 1 &&
|
|
1014
|
+
owner === "AbortSignal" &&
|
|
1015
|
+
members.some((member) => member === "addEventListener" || member === "removeEventListener")));
|
|
1016
|
+
}
|
|
1017
|
+
/** The adopted undici Response declaration is wider than the native static
|
|
1018
|
+
* handle. Keep the supported fallback slice on checked-dynamic dispatch, but
|
|
1019
|
+
* fence every other declared member before the generic dyn keyed-read/call
|
|
1020
|
+
* paths can turn a missing handle operation into a runtime TypeError. */
|
|
1021
|
+
export function fenceStaticResponseMember(L, access, use) {
|
|
1022
|
+
const recvType = L.checker.getBaseTypeOfLiteralType(L.typeOf(access.expression));
|
|
1023
|
+
const sym = recvType.getAliasSymbol() ?? recvType.getSymbol();
|
|
1024
|
+
if (!sym || sym.name !== "Response" || !L.isStdlibSymbol(sym))
|
|
1025
|
+
return null;
|
|
1026
|
+
const members = fetchAccessMemberNames(L, access);
|
|
1027
|
+
if (members === null)
|
|
1028
|
+
return null;
|
|
1029
|
+
const status = (member) => fetchInventoryStatus("Response", member, member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype");
|
|
1030
|
+
if (L.dynamic && members.every((member) => status(member) !== "unsupported")) {
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
const supported = (member) => use === "call"
|
|
1034
|
+
? STATIC_RESPONSE_CALLS.has(member)
|
|
1035
|
+
: STATIC_RESPONSE_READS.has(member);
|
|
1036
|
+
if (!L.dynamic && members.every(supported))
|
|
1037
|
+
return null;
|
|
1038
|
+
const member = members.find((candidate) => L.dynamic ? status(candidate) === "unsupported" : !supported(candidate));
|
|
1039
|
+
if (status(member) === "unsupported") {
|
|
1040
|
+
L.noLowering(`Response.${member}`, access, fetchInventoryReason("Response", member, "prototype") ??
|
|
1041
|
+
"this Response operation has no compiler lowering in either tier", sym);
|
|
1042
|
+
}
|
|
1043
|
+
L.noLowering(`Response.${member} in a static build`, access, "the native static Response surface is status/ok/statusText/url/redirected/headers/body/bodyUsed plus json(), text(), and bytes(); use --dynamic for the wider Web API", sym);
|
|
1044
|
+
}
|
|
1045
|
+
/** Response Headers are native checked-dynamic handles. Keep their exact
|
|
1046
|
+
* supported method slice aligned with the compatibility census so newly
|
|
1047
|
+
* observed Node members cannot fall through to a runtime missing-method
|
|
1048
|
+
* error in an engine-free build. */
|
|
1049
|
+
export function fenceStaticHeadersMember(L, access, use) {
|
|
1050
|
+
const recvType = L.checker.getBaseTypeOfLiteralType(L.typeOf(access.expression));
|
|
1051
|
+
const sym = recvType.getAliasSymbol() ?? recvType.getSymbol();
|
|
1052
|
+
if (!sym || sym.name !== "Headers" || !L.isStdlibSymbol(sym))
|
|
1053
|
+
return null;
|
|
1054
|
+
const members = fetchAccessMemberNames(L, access);
|
|
1055
|
+
if (members === null)
|
|
1056
|
+
return null;
|
|
1057
|
+
const status = (member) => fetchInventoryStatus("Headers", member, member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype");
|
|
1058
|
+
if (L.dynamic && members.every((member) => status(member) !== "unsupported")) {
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
if (!L.dynamic && use === "call" && members.every((member) => STATIC_HEADERS_CALLS.has(member))) {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
const member = members.find((candidate) => L.dynamic
|
|
1065
|
+
? status(candidate) === "unsupported"
|
|
1066
|
+
: use !== "call" || !STATIC_HEADERS_CALLS.has(candidate));
|
|
1067
|
+
if (status(member) === "unsupported") {
|
|
1068
|
+
L.noLowering(`Headers.${member}`, access, "symbol-keyed Headers iteration has no compiler lowering in either tier; call entries() with --dynamic instead", sym);
|
|
1069
|
+
}
|
|
1070
|
+
L.noLowering(`Headers.${member} in a static build`, access, "the native static Headers surface is append/delete/get/getSetCookie/has/set/forEach; use keys()/values()/entries() or symbol-keyed iteration with --dynamic, while construction remains unsupported", sym);
|
|
1071
|
+
}
|
|
1072
|
+
/** The native AbortController handle dispatches `abort()` directly and
|
|
1073
|
+
* exposes `.signal` as data, but it has no first-class function value for
|
|
1074
|
+
* the prototype method. Fence method extraction before the checked-dynamic
|
|
1075
|
+
* keyed-read fallback can silently answer `undefined`. Dynamic builds use
|
|
1076
|
+
* the engine's real AbortController object and retain ordinary method reads. */
|
|
1077
|
+
export function fenceStaticAbortControllerMemberRead(L, access) {
|
|
1078
|
+
if (L.dynamic)
|
|
1079
|
+
return null;
|
|
1080
|
+
const symbol = isStdlibFetchInterface(L, access.expression, "AbortController");
|
|
1081
|
+
if (!symbol)
|
|
1082
|
+
return null;
|
|
1083
|
+
const members = fetchAccessMemberNames(L, access);
|
|
1084
|
+
if (members === null || !members.includes("abort"))
|
|
1085
|
+
return null;
|
|
1086
|
+
L.noLowering("AbortController.abort through method extraction in a static build", access, "call controller.abort(...) directly; the native AbortController handle does not expose abort as a first-class function value", symbol);
|
|
1087
|
+
}
|
|
1088
|
+
function isStdlibFetchInterface(L, node, owner) {
|
|
1089
|
+
const type = L.checker.getBaseTypeOfLiteralType(L.typeOf(node));
|
|
1090
|
+
for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) {
|
|
1091
|
+
if (symbol?.name === owner && L.isStdlibSymbol(symbol))
|
|
1092
|
+
return symbol;
|
|
1093
|
+
}
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
function fetchObjectBindingMemberName(L, element) {
|
|
1097
|
+
const name = element.propertyName ??
|
|
1098
|
+
(element.name !== undefined && ts.isIdentifier(element.name) ? element.name : undefined);
|
|
1099
|
+
if (name === undefined)
|
|
1100
|
+
return null;
|
|
1101
|
+
if (ts.isIdentifier(name))
|
|
1102
|
+
return name.text;
|
|
1103
|
+
if (ts.isStringLiteral(name))
|
|
1104
|
+
return name.text;
|
|
1105
|
+
if (ts.isNumericLiteral(name))
|
|
1106
|
+
return String(Number(name.text));
|
|
1107
|
+
if (ts.isComputedPropertyName(name)) {
|
|
1108
|
+
const members = fetchElementMemberNames(L, name.expression);
|
|
1109
|
+
return members?.length === 1 ? members[0] : null;
|
|
1110
|
+
}
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
function fetchObjectAssignmentMemberName(L, property) {
|
|
1114
|
+
if (ts.isSpreadAssignment(property))
|
|
1115
|
+
return null;
|
|
1116
|
+
const name = property.name;
|
|
1117
|
+
if (name === undefined)
|
|
1118
|
+
return null;
|
|
1119
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name))
|
|
1120
|
+
return name.text;
|
|
1121
|
+
if (ts.isNumericLiteral(name))
|
|
1122
|
+
return String(Number(name.text));
|
|
1123
|
+
if (ts.isComputedPropertyName(name)) {
|
|
1124
|
+
const members = fetchElementMemberNames(L, name.expression);
|
|
1125
|
+
return members?.length === 1 ? members[0] : null;
|
|
1126
|
+
}
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
function fetchInterfaceInventoryOwner(L, node) {
|
|
1130
|
+
const type = L.checker.getBaseTypeOfLiteralType(L.typeOf(node));
|
|
1131
|
+
const symbol = [type.getSymbol(), type.getAliasSymbol()].find((candidate) => candidate !== undefined &&
|
|
1132
|
+
NODE24_FETCH_COMPAT_PROFILE.inventory.interfaces.includes(candidate.name) &&
|
|
1133
|
+
L.isStdlibSymbol(candidate));
|
|
1134
|
+
return symbol ? { owner: symbol.name, symbol } : null;
|
|
1135
|
+
}
|
|
1136
|
+
function fenceFetchObjectMembers(L, ownerNode, members) {
|
|
1137
|
+
const resolved = fetchInterfaceInventoryOwner(L, ownerNode);
|
|
1138
|
+
if (!resolved)
|
|
1139
|
+
return;
|
|
1140
|
+
const { owner, symbol } = resolved;
|
|
1141
|
+
for (const { member, node } of members) {
|
|
1142
|
+
const row = NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find((entry) => entry.owner === owner &&
|
|
1143
|
+
entry.member === member &&
|
|
1144
|
+
(entry.placement === "prototype" ||
|
|
1145
|
+
entry.placement === "prototype-inherited" ||
|
|
1146
|
+
entry.placement === "prototype-symbol"));
|
|
1147
|
+
if (!row || row.status === "out-of-scope")
|
|
1148
|
+
continue;
|
|
1149
|
+
if (L.dynamic && row.status !== "unsupported")
|
|
1150
|
+
continue;
|
|
1151
|
+
// Static data properties retain the ordinary checked-dynamic keyed-read
|
|
1152
|
+
// lowering. Only a method extraction loses the receiver that its direct
|
|
1153
|
+
// call bridge would preserve; non-static rows keep their inventory fence.
|
|
1154
|
+
const operation = NODE24_FETCH_COMPAT_PROFILE.operations.find((candidate) => candidate.id === row.id);
|
|
1155
|
+
if (!L.dynamic && row.status === "static" && operation?.kind === "property") {
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
L.noLowering(`${owner}.${member}${L.dynamic ? "" : " through object destructuring in a static build"}`, node, row.reason ??
|
|
1159
|
+
"the direct lowered read/call form has a compiler bridge, but extracting this member as a value does not", symbol);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
/** Object binding syntax reads members without constructing property-access
|
|
1163
|
+
* nodes, so the ordinary Response/Headers/ReadableStream chokepoints never
|
|
1164
|
+
* see it. Attribute those reads to the same inventory rows before generic
|
|
1165
|
+
* dyn/jsval destructuring either reports SC1031 or admits an unsupported
|
|
1166
|
+
* member in the dynamic tier. */
|
|
1167
|
+
export function fenceFetchObjectBinding(L, pattern) {
|
|
1168
|
+
fenceFetchObjectMembers(L, pattern, pattern.elements.flatMap((element) => {
|
|
1169
|
+
if (element.dotDotDotToken)
|
|
1170
|
+
return [];
|
|
1171
|
+
const member = fetchObjectBindingMemberName(L, element);
|
|
1172
|
+
return member === null ? [] : [{ member, node: element }];
|
|
1173
|
+
}));
|
|
1174
|
+
}
|
|
1175
|
+
/** Assignment-position object destructuring is the same member-read form as
|
|
1176
|
+
* a binding pattern, but its source type lives on the right-hand expression
|
|
1177
|
+
* instead of the target AST. */
|
|
1178
|
+
export function fenceFetchObjectAssignment(L, target, source) {
|
|
1179
|
+
fenceFetchObjectMembers(L, source, target.properties.flatMap((property) => {
|
|
1180
|
+
const member = fetchObjectAssignmentMemberName(L, property);
|
|
1181
|
+
return member === null ? [] : [{ member, node: property }];
|
|
1182
|
+
}));
|
|
1183
|
+
}
|
|
1184
|
+
/** Iteration syntax invokes Headers[Symbol.iterator] without constructing
|
|
1185
|
+
* an element-access node, so the member chokepoint above never sees it.
|
|
1186
|
+
* Keep engine-free builds on the same SC2020 profile row for for-of,
|
|
1187
|
+
* array spread, and array destructuring. The dynamic tier uses the
|
|
1188
|
+
* engine's iterator protocol and is intentionally allowed. */
|
|
1189
|
+
export function fenceStaticHeadersIteration(L, node) {
|
|
1190
|
+
if (L.dynamic)
|
|
1191
|
+
return;
|
|
1192
|
+
const value = ts.isExpression(node) ? requestInitValueExpr(node) : node;
|
|
1193
|
+
const sym = isStdlibFetchInterface(L, value, "Headers");
|
|
1194
|
+
if (!sym)
|
|
1195
|
+
return;
|
|
1196
|
+
L.noLowering("Headers.[Symbol.iterator] in a static build", node, "Headers iteration requires --dynamic; append/delete/get/getSetCookie/has/set/forEach remain engine-free", sym);
|
|
1197
|
+
}
|
|
1198
|
+
/** Dynamic element-spelled Headers iteration. Symbol.iterator uses the
|
|
1199
|
+
* engine's GetIterator operation. A finite union of keys()/values()/entries()
|
|
1200
|
+
* dispatches through fixed-name engine calls after evaluating the receiver
|
|
1201
|
+
* and key once, preserving the method receiver and JavaScript evaluation
|
|
1202
|
+
* order without trying to represent the union of method values. */
|
|
1203
|
+
export function lowerDynamicHeadersIteratorCall(L, call, access) {
|
|
1204
|
+
if (!L.dynamic ||
|
|
1205
|
+
call.questionDotToken ||
|
|
1206
|
+
access.questionDotToken ||
|
|
1207
|
+
call.arguments.length !== 0 ||
|
|
1208
|
+
!isStdlibFetchInterface(L, access.expression, "Headers")) {
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
const members = fetchElementMemberNames(L, access.argumentExpression);
|
|
1212
|
+
if (members === null)
|
|
1213
|
+
return null;
|
|
1214
|
+
const symbolIterator = members.length === 1 && members[0] === "[Symbol.iterator]";
|
|
1215
|
+
if (!symbolIterator &&
|
|
1216
|
+
!members.every((member) => member === "keys" || member === "values" || member === "entries")) {
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1220
|
+
if (receiver.type.kind !== "jsval")
|
|
1221
|
+
return null;
|
|
1222
|
+
const loc = locOf(call);
|
|
1223
|
+
if (!symbolIterator) {
|
|
1224
|
+
const key = L.lowerExpr(access.argumentExpression);
|
|
1225
|
+
if (key.type.kind !== "string")
|
|
1226
|
+
return null;
|
|
1227
|
+
const recvLocal = L.declareHiddenLocal("%headers", JSVAL);
|
|
1228
|
+
const keyLocal = L.declareHiddenLocal("%headersKey", STRING);
|
|
1229
|
+
const recvRef = {
|
|
1230
|
+
kind: "varRef",
|
|
1231
|
+
localId: recvLocal.id,
|
|
1232
|
+
type: JSVAL,
|
|
1233
|
+
loc,
|
|
1234
|
+
};
|
|
1235
|
+
const keyRef = {
|
|
1236
|
+
kind: "varRef",
|
|
1237
|
+
localId: keyLocal.id,
|
|
1238
|
+
type: STRING,
|
|
1239
|
+
loc,
|
|
1240
|
+
};
|
|
1241
|
+
const invoke = (member) => ({
|
|
1242
|
+
kind: "jsOp",
|
|
1243
|
+
op: "callMethod",
|
|
1244
|
+
name: member,
|
|
1245
|
+
args: [recvRef],
|
|
1246
|
+
type: JSVAL,
|
|
1247
|
+
loc,
|
|
1248
|
+
});
|
|
1249
|
+
// Same-kind literal unions erase to STRING in IR, so a lying assertion
|
|
1250
|
+
// can carry a runtime key outside `members`. Validate every arm instead
|
|
1251
|
+
// of treating the last member as an unconditional default: substituting
|
|
1252
|
+
// another method is silent corruption. The trust-but-verify fallback is
|
|
1253
|
+
// the catchable TypeError used by the compiler's other erased casts.
|
|
1254
|
+
let result = nodeThrowExpr(1, "", `${access.getText()} is not a function`, JSVAL, loc);
|
|
1255
|
+
for (let i = members.length - 1; i >= 0; i--) {
|
|
1256
|
+
const member = members[i];
|
|
1257
|
+
result = {
|
|
1258
|
+
kind: "ternary",
|
|
1259
|
+
cond: {
|
|
1260
|
+
kind: "strEq",
|
|
1261
|
+
negated: false,
|
|
1262
|
+
left: keyRef,
|
|
1263
|
+
right: { kind: "strLit", value: member, type: STRING, loc },
|
|
1264
|
+
type: BOOL,
|
|
1265
|
+
loc,
|
|
1266
|
+
},
|
|
1267
|
+
then: invoke(member),
|
|
1268
|
+
else_: result,
|
|
1269
|
+
type: JSVAL,
|
|
1270
|
+
loc,
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
return {
|
|
1274
|
+
kind: "seqExpr",
|
|
1275
|
+
stmts: [
|
|
1276
|
+
{ kind: "varDecl", localId: recvLocal.id, init: receiver, loc },
|
|
1277
|
+
{ kind: "varDecl", localId: keyLocal.id, init: key, loc },
|
|
1278
|
+
],
|
|
1279
|
+
result,
|
|
1280
|
+
type: JSVAL,
|
|
1281
|
+
loc,
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
return {
|
|
1285
|
+
kind: "jsOp",
|
|
1286
|
+
op: "iterNew",
|
|
1287
|
+
args: [receiver],
|
|
1288
|
+
type: JSVAL,
|
|
1289
|
+
loc,
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
/** Bracket-spelled calls on a profiled fetch/Web-stream handle. A call such
|
|
1293
|
+
* as `headers["get"](name)` is still a method reference in JavaScript: the
|
|
1294
|
+
* receiver must remain bound. Lower it before the generic callee-as-value
|
|
1295
|
+
* path (which correctly fences bare method extraction), evaluating receiver,
|
|
1296
|
+
* key, and arguments once in source order. Finite string-literal unions
|
|
1297
|
+
* dispatch through fixed-name calls after the key value is captured. */
|
|
1298
|
+
export function lowerFetchElementMethodCall(L, call) {
|
|
1299
|
+
if (call.questionDotToken ||
|
|
1300
|
+
!ts.isElementAccessExpression(call.expression) ||
|
|
1301
|
+
call.expression.questionDotToken ||
|
|
1302
|
+
call.arguments.some((argument) => ts.isSpreadElement(argument))) {
|
|
1303
|
+
return null;
|
|
1304
|
+
}
|
|
1305
|
+
const access = call.expression;
|
|
1306
|
+
const resolved = fetchInterfaceInventoryOwner(L, access.expression);
|
|
1307
|
+
if (!resolved)
|
|
1308
|
+
return null;
|
|
1309
|
+
const members = fetchElementMemberNames(L, access.argumentExpression);
|
|
1310
|
+
if (members === null ||
|
|
1311
|
+
members.length === 0 ||
|
|
1312
|
+
members.some((member) => member.startsWith("[Symbol."))) {
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
const rows = members.map((member) => NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find((entry) => entry.owner === resolved.owner &&
|
|
1316
|
+
entry.member === member &&
|
|
1317
|
+
(entry.placement === "prototype" ||
|
|
1318
|
+
entry.placement === "prototype-inherited")));
|
|
1319
|
+
if (rows.some((row) => row === undefined ||
|
|
1320
|
+
row.status === "out-of-scope" ||
|
|
1321
|
+
(L.dynamic ? row.status === "unsupported" : row.status !== "static"))) {
|
|
1322
|
+
return null;
|
|
1323
|
+
}
|
|
1324
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1325
|
+
if (receiver.type.kind !== (L.dynamic ? "jsval" : "dyn"))
|
|
1326
|
+
return null;
|
|
1327
|
+
const key = L.lowerExpr(access.argumentExpression);
|
|
1328
|
+
if (key.type.kind !== "string")
|
|
1329
|
+
return null;
|
|
1330
|
+
const loc = locOf(call);
|
|
1331
|
+
const receiverLocal = L.declareHiddenLocal("%fetchReceiver", L.dynamic ? JSVAL : DYN);
|
|
1332
|
+
const keyLocal = L.declareHiddenLocal("%fetchMember", STRING);
|
|
1333
|
+
const calleeLocal = L.dynamic
|
|
1334
|
+
? L.declareHiddenLocal("%fetchMethod", JSVAL)
|
|
1335
|
+
: null;
|
|
1336
|
+
const argumentValues = call.arguments.map((argument, index) => L.dynamic
|
|
1337
|
+
? L.jsvalIn(L.lowerExpr(argument), argument)
|
|
1338
|
+
: fetchMethodArgumentIsLive(resolved.owner, members, index)
|
|
1339
|
+
? lowerLiveWebValue(L, argument)
|
|
1340
|
+
: L.lowerExprExpecting(argument, DYN));
|
|
1341
|
+
const argumentLocals = argumentValues.map((argument) => L.declareHiddenLocal("%fetchMethodArg", argument.type));
|
|
1342
|
+
const receiverRef = {
|
|
1343
|
+
kind: "varRef",
|
|
1344
|
+
localId: receiverLocal.id,
|
|
1345
|
+
type: receiverLocal.type,
|
|
1346
|
+
loc,
|
|
1347
|
+
};
|
|
1348
|
+
const keyRef = {
|
|
1349
|
+
kind: "varRef",
|
|
1350
|
+
localId: keyLocal.id,
|
|
1351
|
+
type: STRING,
|
|
1352
|
+
loc,
|
|
1353
|
+
};
|
|
1354
|
+
const calleeRef = calleeLocal === null
|
|
1355
|
+
? null
|
|
1356
|
+
: {
|
|
1357
|
+
kind: "varRef",
|
|
1358
|
+
localId: calleeLocal.id,
|
|
1359
|
+
type: JSVAL,
|
|
1360
|
+
loc,
|
|
1361
|
+
};
|
|
1362
|
+
const memberReadStmt = calleeLocal === null
|
|
1363
|
+
? {
|
|
1364
|
+
kind: "exprStmt",
|
|
1365
|
+
expr: {
|
|
1366
|
+
kind: "dynKeyGet",
|
|
1367
|
+
value: receiverRef,
|
|
1368
|
+
key: keyRef,
|
|
1369
|
+
type: DYN,
|
|
1370
|
+
loc,
|
|
1371
|
+
},
|
|
1372
|
+
loc,
|
|
1373
|
+
}
|
|
1374
|
+
: {
|
|
1375
|
+
kind: "varDecl",
|
|
1376
|
+
localId: calleeLocal.id,
|
|
1377
|
+
init: {
|
|
1378
|
+
kind: "jsOp",
|
|
1379
|
+
op: "getIdx",
|
|
1380
|
+
args: [
|
|
1381
|
+
receiverRef,
|
|
1382
|
+
{ kind: "jsMarshal", value: keyRef, type: JSVAL, loc },
|
|
1383
|
+
],
|
|
1384
|
+
type: JSVAL,
|
|
1385
|
+
loc,
|
|
1386
|
+
},
|
|
1387
|
+
loc,
|
|
1388
|
+
};
|
|
1389
|
+
const argumentRefs = argumentLocals.map((argument) => ({
|
|
1390
|
+
kind: "varRef",
|
|
1391
|
+
localId: argument.id,
|
|
1392
|
+
type: argument.type,
|
|
1393
|
+
loc,
|
|
1394
|
+
}));
|
|
1395
|
+
const staticResponsePromise = !L.dynamic &&
|
|
1396
|
+
resolved.owner === "Response" &&
|
|
1397
|
+
call.arguments.length === 0 &&
|
|
1398
|
+
members.every((member) => member === "text" || member === "bytes")
|
|
1399
|
+
? (() => {
|
|
1400
|
+
// The checker spells this call as
|
|
1401
|
+
// `Promise<string> | Promise<Uint8Array>`, a promise union that
|
|
1402
|
+
// intentionally has no IR representation. At an await site its
|
|
1403
|
+
// result is the ordinary representable value union; use that
|
|
1404
|
+
// contextual type for the bridge's fulfillment adapter.
|
|
1405
|
+
const inner = ts.isAwaitExpression(call.parent)
|
|
1406
|
+
? L.mapTypeOf(L.typeOf(call.parent))
|
|
1407
|
+
: null;
|
|
1408
|
+
return inner ? { kind: "promise", inner } : null;
|
|
1409
|
+
})()
|
|
1410
|
+
: null;
|
|
1411
|
+
const invoke = (member) => L.dynamic
|
|
1412
|
+
? {
|
|
1413
|
+
kind: "jsOp",
|
|
1414
|
+
op: "callFnThis",
|
|
1415
|
+
args: [calleeRef, receiverRef, ...argumentRefs],
|
|
1416
|
+
type: JSVAL,
|
|
1417
|
+
loc,
|
|
1418
|
+
}
|
|
1419
|
+
: staticResponsePromise !== null
|
|
1420
|
+
? {
|
|
1421
|
+
kind: "libCall",
|
|
1422
|
+
fn: member === "text" ? "fetch.responseText" : "fetch.responseBytes",
|
|
1423
|
+
args: [receiverRef],
|
|
1424
|
+
type: staticResponsePromise,
|
|
1425
|
+
loc,
|
|
1426
|
+
}
|
|
1427
|
+
: {
|
|
1428
|
+
kind: "dynInvoke",
|
|
1429
|
+
recv: receiverRef,
|
|
1430
|
+
method: member,
|
|
1431
|
+
calleeName: access.getText(),
|
|
1432
|
+
args: argumentRefs,
|
|
1433
|
+
type: DYN,
|
|
1434
|
+
loc,
|
|
1435
|
+
};
|
|
1436
|
+
// Literal-string unions share the STRING carrier. A cast can therefore
|
|
1437
|
+
// smuggle a different runtime key into this call; validate all arms and
|
|
1438
|
+
// fail loudly rather than dispatching every mismatch to the last member.
|
|
1439
|
+
const invocationType = staticResponsePromise ?? (L.dynamic ? JSVAL : DYN);
|
|
1440
|
+
let result = nodeThrowExpr(1, "", `${access.getText()} is not a function`, invocationType, loc);
|
|
1441
|
+
for (let index = members.length - 1; index >= 0; index--) {
|
|
1442
|
+
const member = members[index];
|
|
1443
|
+
result = {
|
|
1444
|
+
kind: "ternary",
|
|
1445
|
+
cond: {
|
|
1446
|
+
kind: "strEq",
|
|
1447
|
+
negated: false,
|
|
1448
|
+
left: keyRef,
|
|
1449
|
+
right: { kind: "strLit", value: member, type: STRING, loc },
|
|
1450
|
+
type: BOOL,
|
|
1451
|
+
loc,
|
|
1452
|
+
},
|
|
1453
|
+
then: invoke(member),
|
|
1454
|
+
else_: result,
|
|
1455
|
+
type: result.type,
|
|
1456
|
+
loc,
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
const declared = L.dynamic ? L.mapTypeOf(L.typeOf(call)) : null;
|
|
1460
|
+
if (declared &&
|
|
1461
|
+
(declared.kind === "f64" ||
|
|
1462
|
+
declared.kind === "bool" ||
|
|
1463
|
+
declared.kind === "string")) {
|
|
1464
|
+
result = { kind: "jsExit", value: result, type: declared, loc };
|
|
1465
|
+
}
|
|
1466
|
+
return {
|
|
1467
|
+
kind: "seqExpr",
|
|
1468
|
+
stmts: [
|
|
1469
|
+
{ kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
|
|
1470
|
+
{ kind: "varDecl", localId: keyLocal.id, init: key, loc },
|
|
1471
|
+
memberReadStmt,
|
|
1472
|
+
...argumentValues.map((argument, index) => ({
|
|
1473
|
+
kind: "varDecl",
|
|
1474
|
+
localId: argumentLocals[index].id,
|
|
1475
|
+
init: argument,
|
|
1476
|
+
loc: argument.loc,
|
|
1477
|
+
})),
|
|
1478
|
+
],
|
|
1479
|
+
result,
|
|
1480
|
+
type: result.type,
|
|
1481
|
+
loc,
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
/** `[...headers]` in the dynamic tier: Array.from runs inside the engine
|
|
1485
|
+
* over the real Headers iterator, then the resulting pair array exits
|
|
1486
|
+
* through the checker's declared array type. For Headers this is the
|
|
1487
|
+
* spread algorithm's exact iterable snapshot. */
|
|
1488
|
+
export function lowerDynamicHeadersSpread(L, node, type) {
|
|
1489
|
+
if (!L.dynamic || !isStdlibFetchInterface(L, requestInitValueExpr(node), "Headers")) {
|
|
1490
|
+
return null;
|
|
1491
|
+
}
|
|
1492
|
+
const receiver = L.lowerExpr(node);
|
|
1493
|
+
if (receiver.type.kind !== "jsval")
|
|
1494
|
+
return null;
|
|
1495
|
+
const loc = locOf(node);
|
|
1496
|
+
const arrayCtor = {
|
|
1497
|
+
kind: "jsOp",
|
|
1498
|
+
op: "globalGet",
|
|
1499
|
+
name: "Array",
|
|
1500
|
+
args: [],
|
|
1501
|
+
type: JSVAL,
|
|
1502
|
+
loc,
|
|
1503
|
+
};
|
|
1504
|
+
const snapshot = {
|
|
1505
|
+
kind: "jsOp",
|
|
1506
|
+
op: "callMethod",
|
|
1507
|
+
name: "from",
|
|
1508
|
+
args: [arrayCtor, receiver],
|
|
1509
|
+
type: JSVAL,
|
|
1510
|
+
loc,
|
|
1511
|
+
};
|
|
1512
|
+
return { kind: "jsExit", value: snapshot, type, loc };
|
|
1513
|
+
}
|
|
1514
|
+
/** The fallback ambient declaration intentionally exposes only the native
|
|
1515
|
+
* stream slice, but @types/node adopts the complete WHATWG ReadableStream
|
|
1516
|
+
* interface. Fence that wider surface before its checked-dynamic handle can
|
|
1517
|
+
* fall through to a runtime missing-method error. */
|
|
1518
|
+
export function fenceStaticReadableStreamMember(L, access, use) {
|
|
1519
|
+
const sym = isStdlibFetchInterface(L, access.expression, "ReadableStream");
|
|
1520
|
+
if (!sym)
|
|
1521
|
+
return null;
|
|
1522
|
+
const members = fetchAccessMemberNames(L, access);
|
|
1523
|
+
if (members === null)
|
|
1524
|
+
return null;
|
|
1525
|
+
const status = (member) => fetchInventoryStatus("ReadableStream", member, member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype");
|
|
1526
|
+
if (L.dynamic && members.every((member) => status(member) !== "unsupported")) {
|
|
1527
|
+
return null;
|
|
1528
|
+
}
|
|
1529
|
+
const supported = (member) => use === "call"
|
|
1530
|
+
? STATIC_READABLE_STREAM_CALLS.has(member)
|
|
1531
|
+
: STATIC_READABLE_STREAM_READS.has(member);
|
|
1532
|
+
if (!L.dynamic && members.every(supported))
|
|
1533
|
+
return null;
|
|
1534
|
+
const member = members.find((candidate) => L.dynamic ? status(candidate) === "unsupported" : !supported(candidate));
|
|
1535
|
+
if (status(member) === "unsupported") {
|
|
1536
|
+
L.noLowering(`ReadableStream.${member}`, access, fetchInventoryReason("ReadableStream", member, member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype") ?? "this ReadableStream operation has no compiler lowering in either tier", sym);
|
|
1537
|
+
}
|
|
1538
|
+
L.noLowering(`ReadableStream.${member} in a static build`, access, "the native static ReadableStream surface is locked plus cancel() and getReader(); use a reader directly or --dynamic for the wider Web Streams API", sym);
|
|
1539
|
+
}
|
|
1540
|
+
/** Static AbortSignal constructors and ReadableStream.from(). These
|
|
1541
|
+
* ambient globals have no first-class constructor-object representation;
|
|
1542
|
+
* claim the direct calls by declaration provenance before the general
|
|
1543
|
+
* member-call path tries to lower the receiver as a value. */
|
|
1544
|
+
function staticFirstArgWithSurplusArgs(L, call, first) {
|
|
1545
|
+
const loc = locOf(call);
|
|
1546
|
+
if (call.arguments.length <= 1)
|
|
1547
|
+
return first;
|
|
1548
|
+
const firstLocal = L.declareHiddenLocal("%fetchArg", first.type);
|
|
1549
|
+
const firstRef = {
|
|
1550
|
+
kind: "varRef",
|
|
1551
|
+
localId: firstLocal.id,
|
|
1552
|
+
type: first.type,
|
|
1553
|
+
loc,
|
|
1554
|
+
};
|
|
1555
|
+
const stmts = [
|
|
1556
|
+
{ kind: "varDecl", localId: firstLocal.id, init: first, loc },
|
|
1557
|
+
];
|
|
1558
|
+
for (const argument of call.arguments.slice(1)) {
|
|
1559
|
+
if (ts.isSpreadElement(argument)) {
|
|
1560
|
+
L.noLowering("spread surplus arguments on the static fetch companion APIs", argument, "pass surplus arguments without spread syntax");
|
|
1561
|
+
}
|
|
1562
|
+
// The callee ignores the value, but JavaScript still evaluates every
|
|
1563
|
+
// surplus argument in source order. A direct void has the operand's
|
|
1564
|
+
// effects and no additional work of its own.
|
|
1565
|
+
const discarded = ts.isVoidExpression(argument)
|
|
1566
|
+
? L.lowerExpr(argument.expression)
|
|
1567
|
+
: L.lowerExpr(argument);
|
|
1568
|
+
stmts.push({ kind: "exprStmt", expr: discarded, loc: discarded.loc });
|
|
1569
|
+
}
|
|
1570
|
+
return {
|
|
1571
|
+
kind: "seqExpr",
|
|
1572
|
+
stmts,
|
|
1573
|
+
result: firstRef,
|
|
1574
|
+
type: first.type,
|
|
1575
|
+
loc,
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
function staticCallWithSurplusArgs(L, call, first, result) {
|
|
1579
|
+
return result(staticFirstArgWithSurplusArgs(L, call, first));
|
|
1580
|
+
}
|
|
1581
|
+
export function lowerStaticFetchCompanionCall(L, call) {
|
|
1582
|
+
if (L.dynamic ||
|
|
1583
|
+
call.questionDotToken ||
|
|
1584
|
+
!ts.isPropertyAccessExpression(call.expression) ||
|
|
1585
|
+
call.expression.questionDotToken ||
|
|
1586
|
+
!ts.isIdentifier(call.expression.expression)) {
|
|
1587
|
+
return null;
|
|
1588
|
+
}
|
|
1589
|
+
const root = call.expression.expression;
|
|
1590
|
+
const symbol = L.resolveValueSymbol(root);
|
|
1591
|
+
if (!symbol || !L.isStdlibSymbol(symbol))
|
|
1592
|
+
return null;
|
|
1593
|
+
const loc = locOf(call);
|
|
1594
|
+
if (root.text === "AbortSignal") {
|
|
1595
|
+
switch (call.expression.name.text) {
|
|
1596
|
+
case "timeout":
|
|
1597
|
+
if (call.arguments[0] && ts.isSpreadElement(call.arguments[0])) {
|
|
1598
|
+
return null;
|
|
1599
|
+
}
|
|
1600
|
+
return staticCallWithSurplusArgs(L, call, call.arguments[0]
|
|
1601
|
+
? L.lowerExprExpecting(call.arguments[0], DYN)
|
|
1602
|
+
: dynUndefinedExpr(loc), (first) => ({
|
|
1603
|
+
kind: "libCall",
|
|
1604
|
+
fn: "fetch.abortTimeout",
|
|
1605
|
+
args: [first],
|
|
1606
|
+
type: DYN,
|
|
1607
|
+
loc,
|
|
1608
|
+
}));
|
|
1609
|
+
case "abort":
|
|
1610
|
+
if (call.arguments.length === 0) {
|
|
1611
|
+
return {
|
|
1612
|
+
kind: "libCall",
|
|
1613
|
+
fn: "fetch.abortNow",
|
|
1614
|
+
args: [dynUndefinedExpr(loc)],
|
|
1615
|
+
type: DYN,
|
|
1616
|
+
loc,
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
if (ts.isSpreadElement(call.arguments[0]))
|
|
1620
|
+
return null;
|
|
1621
|
+
return staticCallWithSurplusArgs(L, call, lowerLiveWebValue(L, call.arguments[0]), (first) => ({
|
|
1622
|
+
kind: "libCall",
|
|
1623
|
+
fn: "fetch.abortNow",
|
|
1624
|
+
args: [first],
|
|
1625
|
+
type: DYN,
|
|
1626
|
+
loc,
|
|
1627
|
+
}));
|
|
1628
|
+
case "any":
|
|
1629
|
+
if (call.arguments[0] && ts.isSpreadElement(call.arguments[0])) {
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
return staticCallWithSurplusArgs(L, call, call.arguments[0]
|
|
1633
|
+
? L.lowerExprExpecting(call.arguments[0], DYN)
|
|
1634
|
+
: dynUndefinedExpr(loc), (first) => ({
|
|
1635
|
+
kind: "libCall",
|
|
1636
|
+
fn: "fetch.abortAny",
|
|
1637
|
+
args: [first],
|
|
1638
|
+
type: DYN,
|
|
1639
|
+
loc,
|
|
1640
|
+
}));
|
|
1641
|
+
default:
|
|
1642
|
+
return null;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
if (root.text === "ReadableStream" &&
|
|
1646
|
+
call.expression.name.text === "from" &&
|
|
1647
|
+
(!call.arguments[0] || !ts.isSpreadElement(call.arguments[0]))) {
|
|
1648
|
+
if (!call.arguments[0]) {
|
|
1649
|
+
return {
|
|
1650
|
+
kind: "libCall",
|
|
1651
|
+
fn: "fetch.streamFrom",
|
|
1652
|
+
args: [dynUndefinedExpr(loc)],
|
|
1653
|
+
type: DYN,
|
|
1654
|
+
loc,
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
let source = L.lowerExpr(call.arguments[0]);
|
|
1658
|
+
// A readonly tuple satisfies the fallback's readonly-array overload,
|
|
1659
|
+
// but maps to a monomorphic record in IR. Reuse the ordinary
|
|
1660
|
+
// tuple→array width conversion with the resolved generic parameter so
|
|
1661
|
+
// `ReadableStream.from([1, 2] as const)` follows every other tuple
|
|
1662
|
+
// flowing into a readonly T[] slot.
|
|
1663
|
+
if (source.type.kind === "record") {
|
|
1664
|
+
const signature = L.checker.getResolvedSignature(call);
|
|
1665
|
+
const iterableParam = signature?.getParameters()[0];
|
|
1666
|
+
const iterableType = iterableParam
|
|
1667
|
+
? L.mapTypeOf(L.checker.getTypeOfSymbol(iterableParam))
|
|
1668
|
+
: null;
|
|
1669
|
+
if (iterableType?.kind === "array") {
|
|
1670
|
+
source = L.widthCoerce(source, iterableType) ?? source;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
const preserve = source.type.kind === "string" ||
|
|
1674
|
+
(source.type.kind === "bytes" && source.type.elem === "u8") ||
|
|
1675
|
+
(source.type.kind === "array" &&
|
|
1676
|
+
canConvertToDyn(source.type.elem, (id) => L.shapes.get(id), (id) => L.unions.get(id))) ||
|
|
1677
|
+
source.type.kind === "dyn";
|
|
1678
|
+
if (!preserve) {
|
|
1679
|
+
L.noLowering(`ReadableStream.from() over ${L.checker.typeToString(L.typeOf(call.arguments[0]))} iterables`, call.arguments[0], "arrays, Uint8Array, and strings are supported — spread other synchronous iterables into an array first: [...iterable]");
|
|
1680
|
+
}
|
|
1681
|
+
return staticCallWithSurplusArgs(L, call, source, (first) => ({
|
|
1682
|
+
kind: "libCall",
|
|
1683
|
+
fn: "fetch.streamFrom",
|
|
1684
|
+
// Arrays and bytes must cross by reference: their iterators read each
|
|
1685
|
+
// entry at pull time, so mutations after ReadableStream.from() remain
|
|
1686
|
+
// observable just as they are in Node. Other supported iterable
|
|
1687
|
+
// shapes retain the checked-dynamic fallback.
|
|
1688
|
+
args: [first],
|
|
1689
|
+
type: DYN,
|
|
1690
|
+
loc,
|
|
1691
|
+
}));
|
|
1692
|
+
}
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
/** `AbortController.abort(reason?)` mutates the native signal shared with
|
|
1696
|
+
* `.signal` and fetch. Preserve a mutable reason's identity just like
|
|
1697
|
+
* `AbortSignal.abort(reason)`; surplus arguments still evaluate in order and
|
|
1698
|
+
* are ignored by the runtime operation. */
|
|
1699
|
+
export function lowerStaticAbortControllerCall(L, call) {
|
|
1700
|
+
const access = call.expression;
|
|
1701
|
+
if (L.dynamic ||
|
|
1702
|
+
call.questionDotToken ||
|
|
1703
|
+
call.arguments.some((argument) => ts.isSpreadElement(argument)) ||
|
|
1704
|
+
(!ts.isPropertyAccessExpression(access) &&
|
|
1705
|
+
!ts.isElementAccessExpression(access)) ||
|
|
1706
|
+
access.questionDotToken ||
|
|
1707
|
+
staticResponseMemberName(L, access) !== "abort") {
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1710
|
+
const receiverTs = L.checker.getBaseTypeOfLiteralType(L.typeOf(access.expression));
|
|
1711
|
+
const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol();
|
|
1712
|
+
if (symbol?.name !== "AbortController" ||
|
|
1713
|
+
!L.checker.declarationsOf(symbol).some((d) => ts.isInterfaceDeclaration(d) &&
|
|
1714
|
+
L.isStdlibFile(d.getSourceFile()))) {
|
|
1715
|
+
return null;
|
|
1716
|
+
}
|
|
1717
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1718
|
+
if (receiver.type.kind !== "dyn")
|
|
1719
|
+
return null;
|
|
1720
|
+
const loc = locOf(call);
|
|
1721
|
+
const first = call.arguments[0]
|
|
1722
|
+
? lowerLiveWebValue(L, call.arguments[0])
|
|
1723
|
+
: dynUndefinedExpr(loc);
|
|
1724
|
+
const args = [staticFirstArgWithSurplusArgs(L, call, first)];
|
|
1725
|
+
return lowerStaticFixedFetchMethodCall(L, call, access, "abort", receiver, args, (receiverRef, argumentRefs) => ({
|
|
1726
|
+
kind: "dynInvoke",
|
|
1727
|
+
recv: receiverRef,
|
|
1728
|
+
method: "abort",
|
|
1729
|
+
calleeName: access.getText(),
|
|
1730
|
+
args: argumentRefs,
|
|
1731
|
+
type: DYN,
|
|
1732
|
+
loc,
|
|
1733
|
+
}));
|
|
1734
|
+
}
|
|
1735
|
+
/** Controller chunks and error reasons are dyn-handle calls, but unlike a
|
|
1736
|
+
* general checked-dynamic boundary the Web Streams contract preserves their
|
|
1737
|
+
* object identity for the reader/source callbacks that observe them. */
|
|
1738
|
+
export function lowerStaticReadableStreamControllerCall(L, call) {
|
|
1739
|
+
const access = call.expression;
|
|
1740
|
+
if (L.dynamic ||
|
|
1741
|
+
call.questionDotToken ||
|
|
1742
|
+
call.arguments.length > 1 ||
|
|
1743
|
+
(!ts.isPropertyAccessExpression(access) &&
|
|
1744
|
+
!ts.isElementAccessExpression(access)) ||
|
|
1745
|
+
access.questionDotToken) {
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
const member = staticResponseMemberName(L, access);
|
|
1749
|
+
if (member !== "enqueue" && member !== "error")
|
|
1750
|
+
return null;
|
|
1751
|
+
const receiverTs = L.checker.getBaseTypeOfLiteralType(L.typeOf(access.expression));
|
|
1752
|
+
const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol();
|
|
1753
|
+
if (symbol?.name !== "ReadableStreamDefaultController" ||
|
|
1754
|
+
!L.checker.declarationsOf(symbol).some((d) => ts.isInterfaceDeclaration(d) &&
|
|
1755
|
+
L.isStdlibFile(d.getSourceFile()))) {
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
const recv = L.lowerExpr(access.expression);
|
|
1759
|
+
if (recv.type.kind !== "dyn")
|
|
1760
|
+
return null;
|
|
1761
|
+
const loc = locOf(call);
|
|
1762
|
+
const args = [
|
|
1763
|
+
call.arguments[0]
|
|
1764
|
+
? lowerLiveWebValue(L, call.arguments[0])
|
|
1765
|
+
: dynUndefinedExpr(loc),
|
|
1766
|
+
];
|
|
1767
|
+
return lowerStaticFixedFetchMethodCall(L, call, access, member, recv, args, (receiver, argumentRefs) => ({
|
|
1768
|
+
kind: "dynInvoke",
|
|
1769
|
+
recv: receiver,
|
|
1770
|
+
method: member,
|
|
1771
|
+
calleeName: access.getText(),
|
|
1772
|
+
args: argumentRefs,
|
|
1773
|
+
type: DYN,
|
|
1774
|
+
loc,
|
|
1775
|
+
}));
|
|
1776
|
+
}
|
|
1777
|
+
/** `ReadableStream.cancel(reason)` and default-reader `cancel(reason)` pass
|
|
1778
|
+
* the same reason object to the underlying source's cancel callback. Keep a
|
|
1779
|
+
* live typed-reference capsule instead of the general dyn boundary's copy. */
|
|
1780
|
+
export function lowerStaticReadableStreamCancelCall(L, call) {
|
|
1781
|
+
const access = call.expression;
|
|
1782
|
+
if (L.dynamic ||
|
|
1783
|
+
call.questionDotToken ||
|
|
1784
|
+
call.arguments.length > 1 ||
|
|
1785
|
+
call.arguments.some((argument) => ts.isSpreadElement(argument)) ||
|
|
1786
|
+
(!ts.isPropertyAccessExpression(access) &&
|
|
1787
|
+
!ts.isElementAccessExpression(access)) ||
|
|
1788
|
+
access.questionDotToken ||
|
|
1789
|
+
staticResponseMemberName(L, access) !== "cancel") {
|
|
1790
|
+
return null;
|
|
1791
|
+
}
|
|
1792
|
+
const resolved = fetchInterfaceInventoryOwner(L, access.expression);
|
|
1793
|
+
if (resolved?.owner !== "ReadableStream" &&
|
|
1794
|
+
resolved?.owner !== "ReadableStreamDefaultReader") {
|
|
1795
|
+
return null;
|
|
1796
|
+
}
|
|
1797
|
+
const receiver = L.lowerExpr(access.expression);
|
|
1798
|
+
if (receiver.type.kind !== "dyn")
|
|
1799
|
+
return null;
|
|
1800
|
+
const loc = locOf(call);
|
|
1801
|
+
const args = [
|
|
1802
|
+
call.arguments[0]
|
|
1803
|
+
? lowerLiveWebValue(L, call.arguments[0])
|
|
1804
|
+
: dynUndefinedExpr(loc),
|
|
1805
|
+
];
|
|
1806
|
+
return lowerStaticFixedFetchMethodCall(L, call, access, "cancel", receiver, args, (receiverRef, argumentRefs) => ({
|
|
1807
|
+
kind: "dynInvoke",
|
|
1808
|
+
recv: receiverRef,
|
|
1809
|
+
method: "cancel",
|
|
1810
|
+
calleeName: access.getText(),
|
|
1811
|
+
args: argumentRefs,
|
|
1812
|
+
type: DYN,
|
|
1813
|
+
loc,
|
|
1814
|
+
}));
|
|
1815
|
+
}
|
|
1816
|
+
/** AbortSignal retains listener object identity for duplicate detection and
|
|
1817
|
+
* removeEventListener(). Functions already box by closure identity; mutable
|
|
1818
|
+
* record/array/bytes listeners need the same live capsule used by stream
|
|
1819
|
+
* chunks so repeated crossings still denote one EventListener object. */
|
|
1820
|
+
export function lowerStaticAbortSignalListenerCall(L, call) {
|
|
1821
|
+
const access = call.expression;
|
|
1822
|
+
if (L.dynamic ||
|
|
1823
|
+
call.questionDotToken ||
|
|
1824
|
+
call.arguments.length < 2 ||
|
|
1825
|
+
call.arguments.some((arg) => ts.isSpreadElement(arg)) ||
|
|
1826
|
+
(!ts.isPropertyAccessExpression(access) &&
|
|
1827
|
+
!ts.isElementAccessExpression(access)) ||
|
|
1828
|
+
access.questionDotToken) {
|
|
1829
|
+
return null;
|
|
1830
|
+
}
|
|
1831
|
+
const member = staticResponseMemberName(L, access);
|
|
1832
|
+
if (member !== "addEventListener" && member !== "removeEventListener") {
|
|
1833
|
+
return null;
|
|
1834
|
+
}
|
|
1835
|
+
const receiverTs = L.checker.getBaseTypeOfLiteralType(L.typeOf(access.expression));
|
|
1836
|
+
const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol();
|
|
1837
|
+
if (symbol?.name !== "AbortSignal" ||
|
|
1838
|
+
!L.checker.declarationsOf(symbol).some((d) => ts.isInterfaceDeclaration(d) &&
|
|
1839
|
+
L.isStdlibFile(d.getSourceFile()))) {
|
|
1840
|
+
return null;
|
|
1841
|
+
}
|
|
1842
|
+
const recv = L.lowerExpr(access.expression);
|
|
1843
|
+
if (recv.type.kind !== "dyn")
|
|
1844
|
+
return null;
|
|
1845
|
+
const loc = locOf(call);
|
|
1846
|
+
const args = call.arguments.map((arg, index) => index === 1
|
|
1847
|
+
? lowerLiveWebValue(L, arg)
|
|
1848
|
+
: L.lowerExprExpecting(arg, DYN));
|
|
1849
|
+
return lowerStaticFixedFetchMethodCall(L, call, access, member, recv, args, (receiver, argumentRefs) => ({
|
|
1850
|
+
kind: "dynInvoke",
|
|
1851
|
+
recv: receiver,
|
|
1852
|
+
method: member,
|
|
1853
|
+
calleeName: access.getText(),
|
|
1854
|
+
args: argumentRefs,
|
|
1855
|
+
type: DYN,
|
|
1856
|
+
loc,
|
|
1857
|
+
}));
|
|
1858
|
+
}
|
|
1859
|
+
/** `new AbortController()`. Static builds create a native controller handle
|
|
1860
|
+
* over the same signal state consumed by fetch; dynamic builds construct the
|
|
1861
|
+
* island Web object. JavaScript accepts surplus constructor arguments, so
|
|
1862
|
+
* both tiers evaluate them even though AbortController ignores their values. */
|
|
1863
|
+
export function lowerAbortControllerNew(L, expr) {
|
|
1864
|
+
if (!ts.isIdentifier(expr.expression) ||
|
|
1865
|
+
expr.expression.text !== "AbortController") {
|
|
1866
|
+
return null;
|
|
1867
|
+
}
|
|
1868
|
+
const symbol = L.resolveValueSymbol(expr.expression);
|
|
1869
|
+
if (!symbol || !L.isStdlibSymbol(symbol))
|
|
1870
|
+
return null;
|
|
1871
|
+
const args = expr.arguments ?? [];
|
|
1872
|
+
if (args.some((argument) => ts.isSpreadElement(argument)))
|
|
1873
|
+
return null;
|
|
1874
|
+
const loc = locOf(expr);
|
|
1875
|
+
if (L.dynamic) {
|
|
1876
|
+
const ctor = {
|
|
1877
|
+
kind: "jsOp",
|
|
1878
|
+
op: "globalGet",
|
|
1879
|
+
name: "AbortController",
|
|
1880
|
+
args: [],
|
|
1881
|
+
type: JSVAL,
|
|
1882
|
+
loc,
|
|
1883
|
+
};
|
|
1884
|
+
return {
|
|
1885
|
+
kind: "jsOp",
|
|
1886
|
+
op: "construct",
|
|
1887
|
+
args: [ctor, ...args.map((argument) => L.jsvalIn(L.lowerExpr(argument), argument))],
|
|
1888
|
+
type: JSVAL,
|
|
1889
|
+
loc,
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
const result = {
|
|
1893
|
+
kind: "libCall",
|
|
1894
|
+
fn: "fetch.abortControllerNew",
|
|
1895
|
+
args: [],
|
|
1896
|
+
type: DYN,
|
|
1897
|
+
loc,
|
|
1898
|
+
};
|
|
1899
|
+
if (args.length === 0)
|
|
1900
|
+
return result;
|
|
1901
|
+
return {
|
|
1902
|
+
kind: "seqExpr",
|
|
1903
|
+
stmts: args.map((argument) => ({
|
|
1904
|
+
kind: "exprStmt",
|
|
1905
|
+
expr: L.lowerExpr(argument),
|
|
1906
|
+
loc: locOf(argument),
|
|
1907
|
+
})),
|
|
1908
|
+
result,
|
|
1909
|
+
type: DYN,
|
|
1910
|
+
loc,
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
/** `new ReadableStream(source?)`. Static builds box the source record into
|
|
1914
|
+
* the checked-dynamic tree; dynamic builds construct the island's Web
|
|
1915
|
+
* stream directly so the returned handle composes with dynamic fetch. */
|
|
1916
|
+
export function lowerStaticReadableStreamNew(L, expr) {
|
|
1917
|
+
if (!ts.isIdentifier(expr.expression) ||
|
|
1918
|
+
expr.expression.text !== "ReadableStream") {
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
const symbol = L.resolveValueSymbol(expr.expression);
|
|
1922
|
+
if (!symbol || !L.isStdlibSymbol(symbol))
|
|
1923
|
+
return null;
|
|
1924
|
+
const args = expr.arguments ?? [];
|
|
1925
|
+
if (args.length > 1)
|
|
1926
|
+
return null;
|
|
1927
|
+
const loc = locOf(expr);
|
|
1928
|
+
if (L.dynamic) {
|
|
1929
|
+
const ctor = {
|
|
1930
|
+
kind: "jsOp",
|
|
1931
|
+
op: "globalGet",
|
|
1932
|
+
name: "ReadableStream",
|
|
1933
|
+
args: [],
|
|
1934
|
+
type: JSVAL,
|
|
1935
|
+
loc,
|
|
1936
|
+
};
|
|
1937
|
+
const source = args[0] === undefined
|
|
1938
|
+
? null
|
|
1939
|
+
: ts.isObjectLiteralExpression(args[0])
|
|
1940
|
+
? lowerIslandObjectLiteral(L, args[0])
|
|
1941
|
+
: L.jsvalIn(L.lowerExpr(args[0]), args[0]);
|
|
1942
|
+
return {
|
|
1943
|
+
kind: "jsOp",
|
|
1944
|
+
op: "construct",
|
|
1945
|
+
args: source === null ? [ctor] : [ctor, source],
|
|
1946
|
+
type: JSVAL,
|
|
1947
|
+
loc,
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
const source = args.length === 0
|
|
1951
|
+
? dynUndefinedExpr(loc)
|
|
1952
|
+
: ts.isObjectLiteralExpression(args[0])
|
|
1953
|
+
? lowerDynObjectLiteral(L, args[0])
|
|
1954
|
+
: lowerLiveWebValue(L, args[0]);
|
|
1955
|
+
return {
|
|
1956
|
+
kind: "libCall",
|
|
1957
|
+
fn: "fetch.streamNew",
|
|
1958
|
+
args: [source],
|
|
1959
|
+
type: DYN,
|
|
1960
|
+
loc,
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
/** `new Response(body?, init?)`. Dynamic builds construct the island's
|
|
1964
|
+
* Web Response directly. Static builds hand a checked-dynamic BodyInit and
|
|
1965
|
+
* ResponseInit snapshot to the native fetch runtime, which returns the same
|
|
1966
|
+
* opaque Response handle as fetch(). */
|
|
1967
|
+
export function lowerResponseNew(L, expr) {
|
|
1968
|
+
if (!ts.isIdentifier(expr.expression) ||
|
|
1969
|
+
expr.expression.text !== "Response") {
|
|
1970
|
+
return null;
|
|
1971
|
+
}
|
|
1972
|
+
const symbol = L.resolveValueSymbol(expr.expression);
|
|
1973
|
+
if (!symbol || !L.isStdlibSymbol(symbol))
|
|
1974
|
+
return null;
|
|
1975
|
+
const args = expr.arguments ?? [];
|
|
1976
|
+
if (args.some(ts.isSpreadElement))
|
|
1977
|
+
return null;
|
|
1978
|
+
const loc = locOf(expr);
|
|
1979
|
+
if (L.dynamic) {
|
|
1980
|
+
const ctor = {
|
|
1981
|
+
kind: "jsOp",
|
|
1982
|
+
op: "globalGet",
|
|
1983
|
+
name: "Response",
|
|
1984
|
+
args: [],
|
|
1985
|
+
type: JSVAL,
|
|
1986
|
+
loc,
|
|
1987
|
+
};
|
|
1988
|
+
const lowered = args.map((arg) => ts.isObjectLiteralExpression(arg)
|
|
1989
|
+
? lowerIslandObjectLiteral(L, arg)
|
|
1990
|
+
: L.jsvalIn(L.lowerExpr(arg), arg));
|
|
1991
|
+
return {
|
|
1992
|
+
kind: "jsOp",
|
|
1993
|
+
op: "construct",
|
|
1994
|
+
args: [ctor, ...lowered],
|
|
1995
|
+
type: JSVAL,
|
|
1996
|
+
loc,
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
const toDyn = (node, value, what) => {
|
|
2000
|
+
if (node === undefined)
|
|
2001
|
+
return dynUndefinedExpr(loc);
|
|
2002
|
+
if (value.type.kind === "dyn")
|
|
2003
|
+
return value;
|
|
2004
|
+
/* A user class can satisfy ResponseInit structurally through plain
|
|
2005
|
+
* fields. Project just the declared dictionary members into a record,
|
|
2006
|
+
* then use the ordinary checked-dynamic copy. objRecordWidthHelper
|
|
2007
|
+
* deliberately declines accessors/methods, whose observable reads
|
|
2008
|
+
* cannot be moved into the runtime WebIDL conversion phase. */
|
|
2009
|
+
if (what === "an init value" && value.type.kind === "object") {
|
|
2010
|
+
const sourceType = L.typeOf(node);
|
|
2011
|
+
const fields = [];
|
|
2012
|
+
for (const name of ["headers", "status", "statusText"]) {
|
|
2013
|
+
const property = L.checker.getPropertyOfType(sourceType, name);
|
|
2014
|
+
if (!property)
|
|
2015
|
+
continue;
|
|
2016
|
+
const propertyType = L.checker.getTypeOfSymbolAtLocation(property, node);
|
|
2017
|
+
const mapped = L.mapTypeOf(propertyType);
|
|
2018
|
+
if (!mapped || mapped.kind === "void") {
|
|
2019
|
+
L.noLowering(`new Response with ${what} of type '${L.fmt(value.type)}'`, node, `the '${name}' field does not have a checked-dynamic representation`);
|
|
2020
|
+
}
|
|
2021
|
+
fields.push({ name, type: mapped });
|
|
2022
|
+
}
|
|
2023
|
+
const recordType = {
|
|
2024
|
+
kind: "record",
|
|
2025
|
+
shapeId: L.shapes.intern(fields, false, undefined, fields.map((field) => field.name)),
|
|
2026
|
+
};
|
|
2027
|
+
const projected = L.widthCoerce(value, recordType);
|
|
2028
|
+
if (projected && L.dynConvertible(projected.type)) {
|
|
2029
|
+
return {
|
|
2030
|
+
kind: "dynFrom",
|
|
2031
|
+
value: projected,
|
|
2032
|
+
liveRef: true,
|
|
2033
|
+
type: DYN,
|
|
2034
|
+
loc: value.loc,
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
if (value.kind === "unitLit" || L.dynConvertible(value.type)) {
|
|
2039
|
+
if (what === "an init value" &&
|
|
2040
|
+
hasLiveWebMutableArm(L, value.type)) {
|
|
2041
|
+
return {
|
|
2042
|
+
kind: "dynFrom",
|
|
2043
|
+
value,
|
|
2044
|
+
liveRef: true,
|
|
2045
|
+
type: DYN,
|
|
2046
|
+
loc: value.loc,
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
return {
|
|
2050
|
+
kind: "dynFrom",
|
|
2051
|
+
value,
|
|
2052
|
+
type: DYN,
|
|
2053
|
+
loc: value.loc,
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
L.noLowering(`new Response with ${what} of type '${L.fmt(value.type)}'`, node, "the static constructor accepts string, Uint8Array, ReadableStream, null, and the declared ResponseInit dictionary");
|
|
2057
|
+
};
|
|
2058
|
+
const bodyNode = args[0];
|
|
2059
|
+
const body = bodyNode === undefined
|
|
2060
|
+
? { kind: "unitLit", unit: "undefined", type: { kind: "undefinedT" }, loc }
|
|
2061
|
+
: L.lowerExpr(bodyNode);
|
|
2062
|
+
const initNode = args[1];
|
|
2063
|
+
const init = initNode === undefined
|
|
2064
|
+
? { kind: "unitLit", unit: "undefined", type: { kind: "undefinedT" }, loc }
|
|
2065
|
+
: ts.isObjectLiteralExpression(initNode)
|
|
2066
|
+
? lowerDynObjectLiteral(L, initNode, (node, value) => lowerLiveWebValue(L, node, value))
|
|
2067
|
+
: L.lowerExpr(initNode);
|
|
2068
|
+
/* JavaScript evaluates every argument expression before WebIDL starts
|
|
2069
|
+
* converting either BodyInit or ResponseInit. Capture the raw values in
|
|
2070
|
+
* source order; only the final libCall boxes the body and gives typed init
|
|
2071
|
+
* values a live capsule for the runtime's later dictionary reads. This is
|
|
2072
|
+
* observable when argument evaluation or BodyInit coercion mutates a
|
|
2073
|
+
* Uint8Array body or a typed ResponseInit record. Unit-valued expressions
|
|
2074
|
+
* still execute, then contribute the corresponding undefined/null literal. */
|
|
2075
|
+
const stmts = [];
|
|
2076
|
+
const capture = (value, name) => {
|
|
2077
|
+
if (isUnitType(value.type) || value.type.kind === "void") {
|
|
2078
|
+
if (value.kind !== "unitLit") {
|
|
2079
|
+
stmts.push({ kind: "exprStmt", expr: value, loc: value.loc });
|
|
2080
|
+
}
|
|
2081
|
+
return value.type.kind === "nullT"
|
|
2082
|
+
? { kind: "unitLit", unit: "null", type: value.type, loc: value.loc }
|
|
2083
|
+
: {
|
|
2084
|
+
kind: "unitLit",
|
|
2085
|
+
unit: "undefined",
|
|
2086
|
+
type: { kind: "undefinedT" },
|
|
2087
|
+
loc: value.loc,
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
const local = L.declareHiddenLocal(name, value.type);
|
|
2091
|
+
stmts.push({ kind: "varDecl", localId: local.id, init: value, loc: value.loc });
|
|
2092
|
+
return {
|
|
2093
|
+
kind: "varRef",
|
|
2094
|
+
localId: local.id,
|
|
2095
|
+
type: local.type,
|
|
2096
|
+
loc: value.loc,
|
|
2097
|
+
};
|
|
2098
|
+
};
|
|
2099
|
+
const bodyRef = capture(body, "%responseBody");
|
|
2100
|
+
const initRef = capture(init, "%responseInit");
|
|
2101
|
+
for (const argument of args.slice(2)) {
|
|
2102
|
+
const discarded = ts.isVoidExpression(argument)
|
|
2103
|
+
? L.lowerExpr(argument.expression)
|
|
2104
|
+
: L.lowerExpr(argument);
|
|
2105
|
+
stmts.push({ kind: "exprStmt", expr: discarded, loc: discarded.loc });
|
|
2106
|
+
}
|
|
2107
|
+
const result = {
|
|
2108
|
+
kind: "libCall",
|
|
2109
|
+
fn: "fetch.responseNew",
|
|
2110
|
+
args: [
|
|
2111
|
+
toDyn(bodyNode, bodyRef, "a body"),
|
|
2112
|
+
toDyn(initNode, initRef, "an init value"),
|
|
2113
|
+
],
|
|
2114
|
+
type: DYN,
|
|
2115
|
+
loc,
|
|
2116
|
+
};
|
|
2117
|
+
return { kind: "seqExpr", stmts, result, type: DYN, loc };
|
|
2118
|
+
}
|
|
2119
|
+
/** A static default reader's read() exits the native checked-dynamic
|
|
2120
|
+
* transport into the checker's concrete chunk type. An exact chunk type
|
|
2121
|
+
* preserves ReadableStream.from(array)'s element identity; structural
|
|
2122
|
+
* widening keeps the compiler's ordinary copy-based width semantics. */
|
|
2123
|
+
export function lowerStaticReadableStreamReaderCall(L, call) {
|
|
2124
|
+
const access = call.expression;
|
|
2125
|
+
if (L.dynamic ||
|
|
2126
|
+
call.questionDotToken ||
|
|
2127
|
+
call.arguments.length !== 0 ||
|
|
2128
|
+
(!ts.isPropertyAccessExpression(access) &&
|
|
2129
|
+
!ts.isElementAccessExpression(access)) ||
|
|
2130
|
+
access.questionDotToken) {
|
|
2131
|
+
return null;
|
|
2132
|
+
}
|
|
2133
|
+
const member = staticResponseMemberName(L, access);
|
|
2134
|
+
if (member !== "read")
|
|
2135
|
+
return null;
|
|
2136
|
+
const receiverTs = L.typeOf(access.expression);
|
|
2137
|
+
const symbol = receiverTs.getSymbol();
|
|
2138
|
+
if (symbol?.name !== "ReadableStreamDefaultReader" ||
|
|
2139
|
+
!L.checker.declarationsOf(symbol).some((d) => ts.isInterfaceDeclaration(d) &&
|
|
2140
|
+
L.isStdlibFile(d.getSourceFile()))) {
|
|
2141
|
+
return null;
|
|
2142
|
+
}
|
|
2143
|
+
const type = L.mapTypeOf(L.typeOf(call));
|
|
2144
|
+
if (type?.kind !== "promise" ||
|
|
2145
|
+
type.inner.kind !== "record") {
|
|
2146
|
+
return null;
|
|
2147
|
+
}
|
|
2148
|
+
const receiver = L.lowerExprExpecting(access.expression, DYN);
|
|
2149
|
+
return lowerStaticFixedFetchMethodCall(L, call, access, "read", receiver, [], (receiverRef) => ({
|
|
2150
|
+
kind: "libCall",
|
|
2151
|
+
fn: "fetch.readerRead",
|
|
2152
|
+
args: [receiverRef],
|
|
2153
|
+
type,
|
|
2154
|
+
loc: locOf(call),
|
|
2155
|
+
}));
|
|
2156
|
+
}
|
|
264
2157
|
/** Dynamic `import(spec)` — the island's module system at a USER site.
|
|
265
2158
|
* Under --dynamic the call lowers to island.importDyn(key): the engine
|
|
266
2159
|
* loads the module (embedded npm graph, a shipped local .js/.mjs the
|
|
@@ -572,35 +2465,70 @@ function islandTrapFnValue(L, message, loc) {
|
|
|
572
2465
|
* the JSON-safe copy-marshal). The same jsOp the 'any'-contextual
|
|
573
2466
|
* literal path emits, callable from lowerings that KNOW the literal is
|
|
574
2467
|
* island-bound (fetch's init) even when the contextual type is a union
|
|
575
|
-
* the generic path declines.
|
|
576
|
-
* property names have no identifier restriction
|
|
2468
|
+
* the generic path declines. Spelled and pure const-folded computed keys
|
|
2469
|
+
* are admitted; engine property names have no identifier restriction. Plain
|
|
2470
|
+
* spreads copy through the engine's CopyDataProperties operation in
|
|
2471
|
+
* source order, including nested RequestInit/header dictionaries. */
|
|
577
2472
|
export function lowerIslandObjectLiteral(L, expr) {
|
|
578
2473
|
const loc = locOf(expr);
|
|
579
|
-
|
|
2474
|
+
let args = [];
|
|
2475
|
+
let acc = null;
|
|
2476
|
+
const flushFields = () => {
|
|
2477
|
+
if (args.length === 0)
|
|
2478
|
+
return;
|
|
2479
|
+
const chunk = { kind: "jsOp", op: "objLit", args, type: JSVAL, loc };
|
|
2480
|
+
acc = acc === null
|
|
2481
|
+
? chunk
|
|
2482
|
+
: { kind: "jsOp", op: "objSpread", args: [acc, chunk], type: JSVAL, loc };
|
|
2483
|
+
args = [];
|
|
2484
|
+
};
|
|
580
2485
|
for (const prop of expr.properties) {
|
|
581
|
-
if (
|
|
582
|
-
|
|
583
|
-
|
|
2486
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
2487
|
+
flushFields();
|
|
2488
|
+
const spread = ts.isObjectLiteralExpression(prop.expression)
|
|
2489
|
+
? lowerIslandObjectLiteral(L, prop.expression)
|
|
2490
|
+
: L.jsvalIn(L.lowerExpr(prop.expression), prop.expression);
|
|
2491
|
+
acc ??= { kind: "jsOp", op: "objLit", args: [], type: JSVAL, loc };
|
|
2492
|
+
acc = { kind: "jsOp", op: "objSpread", args: [acc, spread], type: JSVAL, loc: locOf(prop) };
|
|
2493
|
+
continue;
|
|
2494
|
+
}
|
|
2495
|
+
if (!ts.isPropertyAssignment(prop) &&
|
|
2496
|
+
!ts.isShorthandPropertyAssignment(prop) &&
|
|
2497
|
+
!ts.isMethodDeclaration(prop)) {
|
|
2498
|
+
L.unsupported("SC1090", prop, "this property form in an island-built object literal (use a spelled or pure const-folded key with a value or method)");
|
|
2499
|
+
}
|
|
2500
|
+
const propertyName = requestInitLiteralKey(L, prop);
|
|
2501
|
+
if (propertyName === null) {
|
|
2502
|
+
L.unsupported("SC1090", prop, "this property key in an island-built object literal (use a spelled or pure const-folded key)");
|
|
584
2503
|
}
|
|
585
2504
|
const nameLoc = locOf(prop.name);
|
|
586
2505
|
args.push({
|
|
587
2506
|
kind: "jsMarshal",
|
|
588
|
-
value: { kind: "strLit", value:
|
|
2507
|
+
value: { kind: "strLit", value: propertyName, type: STRING, loc: nameLoc },
|
|
589
2508
|
type: JSVAL, loc: nameLoc,
|
|
590
2509
|
});
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
2510
|
+
const valueNode = ts.isPropertyAssignment(prop)
|
|
2511
|
+
? prop.initializer
|
|
2512
|
+
: ts.isShorthandPropertyAssignment(prop)
|
|
2513
|
+
? prop.name
|
|
2514
|
+
: prop;
|
|
2515
|
+
if (ts.isObjectLiteralExpression(valueNode)) {
|
|
2516
|
+
args.push(lowerIslandObjectLiteral(L, valueNode));
|
|
594
2517
|
}
|
|
595
|
-
else if (ts.isArrayLiteralExpression(
|
|
596
|
-
|
|
597
|
-
|
|
2518
|
+
else if (ts.isArrayLiteralExpression(valueNode) &&
|
|
2519
|
+
!valueNode.elements.some(ts.isSpreadElement)) {
|
|
2520
|
+
const elems = valueNode.elements.map((el) => L.jsvalIn(L.lowerExpr(el), el));
|
|
2521
|
+
args.push({ kind: "jsOp", op: "arrLit", args: elems, type: JSVAL, loc: locOf(valueNode) });
|
|
598
2522
|
}
|
|
599
2523
|
else {
|
|
600
|
-
|
|
2524
|
+
const value = ts.isMethodDeclaration(valueNode)
|
|
2525
|
+
? (L.rejectThisInObjectMethod(valueNode.body ?? valueNode), L.lowerLambda(valueNode))
|
|
2526
|
+
: L.lowerExpr(valueNode);
|
|
2527
|
+
args.push(L.jsvalIn(value, valueNode));
|
|
601
2528
|
}
|
|
602
2529
|
}
|
|
603
|
-
|
|
2530
|
+
flushFields();
|
|
2531
|
+
return acc ?? { kind: "jsOp", op: "objLit", args: [], type: JSVAL, loc };
|
|
604
2532
|
}
|
|
605
2533
|
/** Method calls on the island-backed ambient surface: `Math.<fn>(...)`
|
|
606
2534
|
* (the engine's own Math object executes) and the number/string methods
|
|
@@ -633,8 +2561,7 @@ export function lowerIslandMethodCall(L, call, access) {
|
|
|
633
2561
|
{
|
|
634
2562
|
const sigMember = L.stdlibGlobalMember(access, "AbortSignal");
|
|
635
2563
|
if ((sigMember === "timeout" || sigMember === "abort" || sigMember === "any") &&
|
|
636
|
-
call.arguments.
|
|
637
|
-
(sigMember === "abort" || call.arguments.length === 1)) {
|
|
2564
|
+
call.arguments.every((argument) => !ts.isSpreadElement(argument))) {
|
|
638
2565
|
L.requireDynamicApi(`'AbortSignal.${sigMember}'`, call);
|
|
639
2566
|
const args = call.arguments.map((a) => L.jsvalIn(L.lowerExpr(a), a));
|
|
640
2567
|
const signalGlobal = {
|
|
@@ -646,6 +2573,29 @@ export function lowerIslandMethodCall(L, call, access) {
|
|
|
646
2573
|
};
|
|
647
2574
|
}
|
|
648
2575
|
}
|
|
2576
|
+
if (L.stdlibGlobalMember(access, "ReadableStream") === "from" &&
|
|
2577
|
+
call.arguments.every((argument) => !ts.isSpreadElement(argument))) {
|
|
2578
|
+
L.requireDynamicApi("'ReadableStream.from'", call);
|
|
2579
|
+
const streamGlobal = {
|
|
2580
|
+
kind: "jsOp",
|
|
2581
|
+
op: "globalGet",
|
|
2582
|
+
name: "ReadableStream",
|
|
2583
|
+
args: [],
|
|
2584
|
+
type: JSVAL,
|
|
2585
|
+
loc,
|
|
2586
|
+
};
|
|
2587
|
+
return {
|
|
2588
|
+
kind: "jsOp",
|
|
2589
|
+
op: "callMethod",
|
|
2590
|
+
name: "from",
|
|
2591
|
+
args: [
|
|
2592
|
+
streamGlobal,
|
|
2593
|
+
...call.arguments.map((argument) => L.jsvalIn(L.lowerExpr(argument), argument)),
|
|
2594
|
+
],
|
|
2595
|
+
type: JSVAL,
|
|
2596
|
+
loc,
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
649
2599
|
const isMath = L.stdlibGlobalMember(access, "Math") !== null;
|
|
650
2600
|
// The STATIC Math members (floor/min/max/random): one C call IS the
|
|
651
2601
|
// JS operation — no island, no --dynamic. Only the tabled arity with
|