@scriptc/compiler 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +78 -34
  2. package/dist/backend/cc-cache.test.d.ts +1 -0
  3. package/dist/backend/cc-cache.test.js +2507 -0
  4. package/dist/backend/cc-cache.test.js.map +1 -0
  5. package/dist/backend/cc.d.ts +69 -11
  6. package/dist/backend/cc.js +1937 -222
  7. package/dist/backend/cc.js.map +1 -1
  8. package/dist/backend/emission/emit-exprs.js +355 -8
  9. package/dist/backend/emission/emit-exprs.js.map +1 -1
  10. package/dist/backend/emission/emit-walkers.js +108 -7
  11. package/dist/backend/emission/emit-walkers.js.map +1 -1
  12. package/dist/backend/emission/emitter.d.ts +16 -0
  13. package/dist/backend/emission/emitter.js +23 -2
  14. package/dist/backend/emission/emitter.js.map +1 -1
  15. package/dist/backend/llvm/dyn.d.ts +1 -0
  16. package/dist/backend/llvm/dyn.js +305 -8
  17. package/dist/backend/llvm/dyn.js.map +1 -1
  18. package/dist/backend/llvm/emitter.js +527 -2
  19. package/dist/backend/llvm/emitter.js.map +1 -1
  20. package/dist/compat/fetch-profile.d.ts +108 -0
  21. package/dist/compat/fetch-profile.js +482 -0
  22. package/dist/compat/fetch-profile.js.map +1 -0
  23. package/dist/coverage/surface-manifest.js +54 -0
  24. package/dist/coverage/surface-manifest.js.map +1 -1
  25. package/dist/frontend/lowering/lower-builtins.d.ts +21 -8
  26. package/dist/frontend/lowering/lower-builtins.js +137 -14
  27. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  28. package/dist/frontend/lowering/lower-calls.js +40 -0
  29. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  30. package/dist/frontend/lowering/lower-classes.js +4 -3
  31. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  32. package/dist/frontend/lowering/lower-exprs.d.ts +2 -2
  33. package/dist/frontend/lowering/lower-exprs.js +63 -12
  34. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  35. package/dist/frontend/lowering/lower-island.d.ts +102 -7
  36. package/dist/frontend/lowering/lower-island.js +1702 -26
  37. package/dist/frontend/lowering/lower-island.js.map +1 -1
  38. package/dist/frontend/lowering/lower-modules.js +17 -1
  39. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  40. package/dist/frontend/lowering/lower-stmts.js +25 -5
  41. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  42. package/dist/frontend/lowering/lowerer.d.ts +39 -0
  43. package/dist/frontend/lowering/lowerer.js +286 -18
  44. package/dist/frontend/lowering/lowerer.js.map +1 -1
  45. package/dist/frontend/lowering/surfaces.js +12 -0
  46. package/dist/frontend/lowering/surfaces.js.map +1 -1
  47. package/dist/frontend/program.d.ts +16 -0
  48. package/dist/frontend/program.js +130 -6
  49. package/dist/frontend/program.js.map +1 -1
  50. package/dist/frontend/types.d.ts +14 -7
  51. package/dist/frontend/types.js +95 -31
  52. package/dist/frontend/types.js.map +1 -1
  53. package/dist/index.d.ts +7 -1
  54. package/dist/index.js +86 -20
  55. package/dist/index.js.map +1 -1
  56. package/dist/ir/nodes.d.ts +32 -16
  57. package/dist/ir/nodes.js +36 -28
  58. package/dist/ir/nodes.js.map +1 -1
  59. package/dist/ir/validate.js +61 -1
  60. package/dist/ir/validate.js.map +1 -1
  61. package/dist/library/int-infer.d.ts +7 -12
  62. package/dist/library/int-infer.js +72 -30
  63. package/dist/library/int-infer.js.map +1 -1
  64. package/dist/library/int-infer.test.js +200 -1
  65. package/dist/library/int-infer.test.js.map +1 -1
  66. package/dist/library/profile.d.ts +7 -0
  67. package/dist/library/profile.js +2 -0
  68. package/dist/library/profile.js.map +1 -1
  69. package/dist/library/sidecar-validate.js +5 -0
  70. package/dist/library/sidecar-validate.js.map +1 -1
  71. package/dist/library/sidecar.d.ts +3 -0
  72. package/dist/library/sidecar.js +168 -30
  73. package/dist/library/sidecar.js.map +1 -1
  74. package/package.json +2 -2
  75. package/surface-manifest.json +859 -1
@@ -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
- /** USER-code `fetch(url)` / `fetch(url, init)` — the island-backed ambient
226
- * global (provenance, not the name: a user's own `fetch` never matches).
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
- * Without --dynamic every call site is its own SC2012. Null for
238
- * anything that isn't THE ambient fetch, so lowerCall keeps trying. */
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 || call.arguments.length > 2)
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,1099 @@ 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 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) {
992
+ const value = 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
+ function isStdlibFetchInterface(L, node, owner) {
1073
+ const type = L.checker.getBaseTypeOfLiteralType(L.typeOf(node));
1074
+ for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) {
1075
+ if (symbol?.name === owner && L.isStdlibSymbol(symbol))
1076
+ return symbol;
1077
+ }
1078
+ return null;
1079
+ }
1080
+ function fetchObjectBindingMemberName(L, element) {
1081
+ const name = element.propertyName ??
1082
+ (element.name !== undefined && ts.isIdentifier(element.name) ? element.name : undefined);
1083
+ if (name === undefined)
1084
+ return null;
1085
+ if (ts.isIdentifier(name))
1086
+ return name.text;
1087
+ if (ts.isStringLiteral(name))
1088
+ return name.text;
1089
+ if (ts.isNumericLiteral(name))
1090
+ return String(Number(name.text));
1091
+ if (ts.isComputedPropertyName(name)) {
1092
+ const members = fetchElementMemberNames(L, name.expression);
1093
+ return members?.length === 1 ? members[0] : null;
1094
+ }
1095
+ return null;
1096
+ }
1097
+ function fetchObjectAssignmentMemberName(L, property) {
1098
+ if (ts.isSpreadAssignment(property))
1099
+ return null;
1100
+ const name = property.name;
1101
+ if (name === undefined)
1102
+ return null;
1103
+ if (ts.isIdentifier(name) || 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 fetchInterfaceInventoryOwner(L, node) {
1114
+ const type = L.checker.getBaseTypeOfLiteralType(L.typeOf(node));
1115
+ const symbol = [type.getSymbol(), type.getAliasSymbol()].find((candidate) => candidate !== undefined &&
1116
+ NODE24_FETCH_COMPAT_PROFILE.inventory.interfaces.includes(candidate.name) &&
1117
+ L.isStdlibSymbol(candidate));
1118
+ return symbol ? { owner: symbol.name, symbol } : null;
1119
+ }
1120
+ function fenceFetchObjectMembers(L, ownerNode, members) {
1121
+ const resolved = fetchInterfaceInventoryOwner(L, ownerNode);
1122
+ if (!resolved)
1123
+ return;
1124
+ const { owner, symbol } = resolved;
1125
+ for (const { member, node } of members) {
1126
+ const row = NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find((entry) => entry.owner === owner &&
1127
+ entry.member === member &&
1128
+ (entry.placement === "prototype" ||
1129
+ entry.placement === "prototype-inherited" ||
1130
+ entry.placement === "prototype-symbol"));
1131
+ if (!row || row.status === "out-of-scope")
1132
+ continue;
1133
+ if (L.dynamic && row.status !== "unsupported")
1134
+ continue;
1135
+ // Static data properties retain the ordinary checked-dynamic keyed-read
1136
+ // lowering. Only a method extraction loses the receiver that its direct
1137
+ // call bridge would preserve; non-static rows keep their inventory fence.
1138
+ const operation = NODE24_FETCH_COMPAT_PROFILE.operations.find((candidate) => candidate.id === row.id);
1139
+ if (!L.dynamic && row.status === "static" && operation?.kind === "property") {
1140
+ continue;
1141
+ }
1142
+ L.noLowering(`${owner}.${member}${L.dynamic ? "" : " through object destructuring in a static build"}`, node, row.reason ??
1143
+ "the direct lowered read/call form has a compiler bridge, but extracting this member as a value does not", symbol);
1144
+ }
1145
+ }
1146
+ /** Object binding syntax reads members without constructing property-access
1147
+ * nodes, so the ordinary Response/Headers/ReadableStream chokepoints never
1148
+ * see it. Attribute those reads to the same inventory rows before generic
1149
+ * dyn/jsval destructuring either reports SC1031 or admits an unsupported
1150
+ * member in the dynamic tier. */
1151
+ export function fenceFetchObjectBinding(L, pattern) {
1152
+ fenceFetchObjectMembers(L, pattern, pattern.elements.flatMap((element) => {
1153
+ if (element.dotDotDotToken)
1154
+ return [];
1155
+ const member = fetchObjectBindingMemberName(L, element);
1156
+ return member === null ? [] : [{ member, node: element }];
1157
+ }));
1158
+ }
1159
+ /** Assignment-position object destructuring is the same member-read form as
1160
+ * a binding pattern, but its source type lives on the right-hand expression
1161
+ * instead of the target AST. */
1162
+ export function fenceFetchObjectAssignment(L, target, source) {
1163
+ fenceFetchObjectMembers(L, source, target.properties.flatMap((property) => {
1164
+ const member = fetchObjectAssignmentMemberName(L, property);
1165
+ return member === null ? [] : [{ member, node: property }];
1166
+ }));
1167
+ }
1168
+ /** Iteration syntax invokes Headers[Symbol.iterator] without constructing
1169
+ * an element-access node, so the member chokepoint above never sees it.
1170
+ * Keep engine-free builds on the same SC2020 profile row for for-of,
1171
+ * array spread, and array destructuring. The dynamic tier uses the
1172
+ * engine's iterator protocol and is intentionally allowed. */
1173
+ export function fenceStaticHeadersIteration(L, node) {
1174
+ if (L.dynamic)
1175
+ return;
1176
+ const value = ts.isExpression(node) ? requestInitValueExpr(node) : node;
1177
+ const sym = isStdlibFetchInterface(L, value, "Headers");
1178
+ if (!sym)
1179
+ return;
1180
+ 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);
1181
+ }
1182
+ /** Dynamic element-spelled Headers iteration. Symbol.iterator uses the
1183
+ * engine's GetIterator operation. A finite union of keys()/values()/entries()
1184
+ * dispatches through fixed-name engine calls after evaluating the receiver
1185
+ * and key once, preserving the method receiver and JavaScript evaluation
1186
+ * order without trying to represent the union of method values. */
1187
+ export function lowerDynamicHeadersIteratorCall(L, call, access) {
1188
+ if (!L.dynamic ||
1189
+ call.questionDotToken ||
1190
+ access.questionDotToken ||
1191
+ call.arguments.length !== 0 ||
1192
+ !isStdlibFetchInterface(L, access.expression, "Headers")) {
1193
+ return null;
1194
+ }
1195
+ const members = fetchElementMemberNames(L, access.argumentExpression);
1196
+ if (members === null)
1197
+ return null;
1198
+ const symbolIterator = members.length === 1 && members[0] === "[Symbol.iterator]";
1199
+ if (!symbolIterator &&
1200
+ !members.every((member) => member === "keys" || member === "values" || member === "entries")) {
1201
+ return null;
1202
+ }
1203
+ const receiver = L.lowerExpr(access.expression);
1204
+ if (receiver.type.kind !== "jsval")
1205
+ return null;
1206
+ const loc = locOf(call);
1207
+ if (!symbolIterator) {
1208
+ const key = L.lowerExpr(access.argumentExpression);
1209
+ if (key.type.kind !== "string")
1210
+ return null;
1211
+ const recvLocal = L.declareHiddenLocal("%headers", JSVAL);
1212
+ const keyLocal = L.declareHiddenLocal("%headersKey", STRING);
1213
+ const recvRef = {
1214
+ kind: "varRef",
1215
+ localId: recvLocal.id,
1216
+ type: JSVAL,
1217
+ loc,
1218
+ };
1219
+ const keyRef = {
1220
+ kind: "varRef",
1221
+ localId: keyLocal.id,
1222
+ type: STRING,
1223
+ loc,
1224
+ };
1225
+ const invoke = (member) => ({
1226
+ kind: "jsOp",
1227
+ op: "callMethod",
1228
+ name: member,
1229
+ args: [recvRef],
1230
+ type: JSVAL,
1231
+ loc,
1232
+ });
1233
+ // Same-kind literal unions erase to STRING in IR, so a lying assertion
1234
+ // can carry a runtime key outside `members`. Validate every arm instead
1235
+ // of treating the last member as an unconditional default: substituting
1236
+ // another method is silent corruption. The trust-but-verify fallback is
1237
+ // the catchable TypeError used by the compiler's other erased casts.
1238
+ let result = nodeThrowExpr(1, "", `${access.getText()} is not a function`, JSVAL, loc);
1239
+ for (let i = members.length - 1; i >= 0; i--) {
1240
+ const member = members[i];
1241
+ result = {
1242
+ kind: "ternary",
1243
+ cond: {
1244
+ kind: "strEq",
1245
+ negated: false,
1246
+ left: keyRef,
1247
+ right: { kind: "strLit", value: member, type: STRING, loc },
1248
+ type: BOOL,
1249
+ loc,
1250
+ },
1251
+ then: invoke(member),
1252
+ else_: result,
1253
+ type: JSVAL,
1254
+ loc,
1255
+ };
1256
+ }
1257
+ return {
1258
+ kind: "seqExpr",
1259
+ stmts: [
1260
+ { kind: "varDecl", localId: recvLocal.id, init: receiver, loc },
1261
+ { kind: "varDecl", localId: keyLocal.id, init: key, loc },
1262
+ ],
1263
+ result,
1264
+ type: JSVAL,
1265
+ loc,
1266
+ };
1267
+ }
1268
+ return {
1269
+ kind: "jsOp",
1270
+ op: "iterNew",
1271
+ args: [receiver],
1272
+ type: JSVAL,
1273
+ loc,
1274
+ };
1275
+ }
1276
+ /** Bracket-spelled calls on a profiled fetch/Web-stream handle. A call such
1277
+ * as `headers["get"](name)` is still a method reference in JavaScript: the
1278
+ * receiver must remain bound. Lower it before the generic callee-as-value
1279
+ * path (which correctly fences bare method extraction), evaluating receiver,
1280
+ * key, and arguments once in source order. Finite string-literal unions
1281
+ * dispatch through fixed-name calls after the key value is captured. */
1282
+ export function lowerFetchElementMethodCall(L, call) {
1283
+ if (call.questionDotToken ||
1284
+ !ts.isElementAccessExpression(call.expression) ||
1285
+ call.expression.questionDotToken ||
1286
+ call.arguments.some((argument) => ts.isSpreadElement(argument))) {
1287
+ return null;
1288
+ }
1289
+ const access = call.expression;
1290
+ const resolved = fetchInterfaceInventoryOwner(L, access.expression);
1291
+ if (!resolved)
1292
+ return null;
1293
+ const members = fetchElementMemberNames(L, access.argumentExpression);
1294
+ if (members === null ||
1295
+ members.length === 0 ||
1296
+ members.some((member) => member.startsWith("[Symbol."))) {
1297
+ return null;
1298
+ }
1299
+ const rows = members.map((member) => NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find((entry) => entry.owner === resolved.owner &&
1300
+ entry.member === member &&
1301
+ (entry.placement === "prototype" ||
1302
+ entry.placement === "prototype-inherited")));
1303
+ if (rows.some((row) => row === undefined ||
1304
+ row.status === "out-of-scope" ||
1305
+ (L.dynamic ? row.status === "unsupported" : row.status !== "static"))) {
1306
+ return null;
1307
+ }
1308
+ const receiver = L.lowerExpr(access.expression);
1309
+ if (receiver.type.kind !== (L.dynamic ? "jsval" : "dyn"))
1310
+ return null;
1311
+ const key = L.lowerExpr(access.argumentExpression);
1312
+ if (key.type.kind !== "string")
1313
+ return null;
1314
+ const loc = locOf(call);
1315
+ const receiverLocal = L.declareHiddenLocal("%fetchReceiver", L.dynamic ? JSVAL : DYN);
1316
+ const keyLocal = L.declareHiddenLocal("%fetchMember", STRING);
1317
+ const calleeLocal = L.dynamic
1318
+ ? L.declareHiddenLocal("%fetchMethod", JSVAL)
1319
+ : null;
1320
+ const argumentValues = call.arguments.map((argument, index) => L.dynamic
1321
+ ? L.jsvalIn(L.lowerExpr(argument), argument)
1322
+ : fetchMethodArgumentIsLive(resolved.owner, members, index)
1323
+ ? lowerLiveWebValue(L, argument)
1324
+ : L.lowerExprExpecting(argument, DYN));
1325
+ const argumentLocals = argumentValues.map((argument) => L.declareHiddenLocal("%fetchMethodArg", argument.type));
1326
+ const receiverRef = {
1327
+ kind: "varRef",
1328
+ localId: receiverLocal.id,
1329
+ type: receiverLocal.type,
1330
+ loc,
1331
+ };
1332
+ const keyRef = {
1333
+ kind: "varRef",
1334
+ localId: keyLocal.id,
1335
+ type: STRING,
1336
+ loc,
1337
+ };
1338
+ const calleeRef = calleeLocal === null
1339
+ ? null
1340
+ : {
1341
+ kind: "varRef",
1342
+ localId: calleeLocal.id,
1343
+ type: JSVAL,
1344
+ loc,
1345
+ };
1346
+ const memberReadStmt = calleeLocal === null
1347
+ ? {
1348
+ kind: "exprStmt",
1349
+ expr: {
1350
+ kind: "dynKeyGet",
1351
+ value: receiverRef,
1352
+ key: keyRef,
1353
+ type: DYN,
1354
+ loc,
1355
+ },
1356
+ loc,
1357
+ }
1358
+ : {
1359
+ kind: "varDecl",
1360
+ localId: calleeLocal.id,
1361
+ init: {
1362
+ kind: "jsOp",
1363
+ op: "getIdx",
1364
+ args: [
1365
+ receiverRef,
1366
+ { kind: "jsMarshal", value: keyRef, type: JSVAL, loc },
1367
+ ],
1368
+ type: JSVAL,
1369
+ loc,
1370
+ },
1371
+ loc,
1372
+ };
1373
+ const argumentRefs = argumentLocals.map((argument) => ({
1374
+ kind: "varRef",
1375
+ localId: argument.id,
1376
+ type: argument.type,
1377
+ loc,
1378
+ }));
1379
+ const staticResponsePromise = !L.dynamic &&
1380
+ resolved.owner === "Response" &&
1381
+ call.arguments.length === 0 &&
1382
+ members.every((member) => member === "text" || member === "bytes")
1383
+ ? (() => {
1384
+ // The checker spells this call as
1385
+ // `Promise<string> | Promise<Uint8Array>`, a promise union that
1386
+ // intentionally has no IR representation. At an await site its
1387
+ // result is the ordinary representable value union; use that
1388
+ // contextual type for the bridge's fulfillment adapter.
1389
+ const inner = ts.isAwaitExpression(call.parent)
1390
+ ? L.mapTypeOf(L.typeOf(call.parent))
1391
+ : null;
1392
+ return inner ? { kind: "promise", inner } : null;
1393
+ })()
1394
+ : null;
1395
+ const invoke = (member) => L.dynamic
1396
+ ? {
1397
+ kind: "jsOp",
1398
+ op: "callFnThis",
1399
+ args: [calleeRef, receiverRef, ...argumentRefs],
1400
+ type: JSVAL,
1401
+ loc,
1402
+ }
1403
+ : staticResponsePromise !== null
1404
+ ? {
1405
+ kind: "libCall",
1406
+ fn: member === "text" ? "fetch.responseText" : "fetch.responseBytes",
1407
+ args: [receiverRef],
1408
+ type: staticResponsePromise,
1409
+ loc,
1410
+ }
1411
+ : {
1412
+ kind: "dynInvoke",
1413
+ recv: receiverRef,
1414
+ method: member,
1415
+ calleeName: access.getText(),
1416
+ args: argumentRefs,
1417
+ type: DYN,
1418
+ loc,
1419
+ };
1420
+ // Literal-string unions share the STRING carrier. A cast can therefore
1421
+ // smuggle a different runtime key into this call; validate all arms and
1422
+ // fail loudly rather than dispatching every mismatch to the last member.
1423
+ const invocationType = staticResponsePromise ?? (L.dynamic ? JSVAL : DYN);
1424
+ let result = nodeThrowExpr(1, "", `${access.getText()} is not a function`, invocationType, loc);
1425
+ for (let index = members.length - 1; index >= 0; index--) {
1426
+ const member = members[index];
1427
+ result = {
1428
+ kind: "ternary",
1429
+ cond: {
1430
+ kind: "strEq",
1431
+ negated: false,
1432
+ left: keyRef,
1433
+ right: { kind: "strLit", value: member, type: STRING, loc },
1434
+ type: BOOL,
1435
+ loc,
1436
+ },
1437
+ then: invoke(member),
1438
+ else_: result,
1439
+ type: result.type,
1440
+ loc,
1441
+ };
1442
+ }
1443
+ const declared = L.dynamic ? L.mapTypeOf(L.typeOf(call)) : null;
1444
+ if (declared &&
1445
+ (declared.kind === "f64" ||
1446
+ declared.kind === "bool" ||
1447
+ declared.kind === "string")) {
1448
+ result = { kind: "jsExit", value: result, type: declared, loc };
1449
+ }
1450
+ return {
1451
+ kind: "seqExpr",
1452
+ stmts: [
1453
+ { kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
1454
+ { kind: "varDecl", localId: keyLocal.id, init: key, loc },
1455
+ memberReadStmt,
1456
+ ...argumentValues.map((argument, index) => ({
1457
+ kind: "varDecl",
1458
+ localId: argumentLocals[index].id,
1459
+ init: argument,
1460
+ loc: argument.loc,
1461
+ })),
1462
+ ],
1463
+ result,
1464
+ type: result.type,
1465
+ loc,
1466
+ };
1467
+ }
1468
+ /** `[...headers]` in the dynamic tier: Array.from runs inside the engine
1469
+ * over the real Headers iterator, then the resulting pair array exits
1470
+ * through the checker's declared array type. For Headers this is the
1471
+ * spread algorithm's exact iterable snapshot. */
1472
+ export function lowerDynamicHeadersSpread(L, node, type) {
1473
+ if (!L.dynamic || !isStdlibFetchInterface(L, requestInitValueExpr(node), "Headers")) {
1474
+ return null;
1475
+ }
1476
+ const receiver = L.lowerExpr(node);
1477
+ if (receiver.type.kind !== "jsval")
1478
+ return null;
1479
+ const loc = locOf(node);
1480
+ const arrayCtor = {
1481
+ kind: "jsOp",
1482
+ op: "globalGet",
1483
+ name: "Array",
1484
+ args: [],
1485
+ type: JSVAL,
1486
+ loc,
1487
+ };
1488
+ const snapshot = {
1489
+ kind: "jsOp",
1490
+ op: "callMethod",
1491
+ name: "from",
1492
+ args: [arrayCtor, receiver],
1493
+ type: JSVAL,
1494
+ loc,
1495
+ };
1496
+ return { kind: "jsExit", value: snapshot, type, loc };
1497
+ }
1498
+ /** The fallback ambient declaration intentionally exposes only the native
1499
+ * stream slice, but @types/node adopts the complete WHATWG ReadableStream
1500
+ * interface. Fence that wider surface before its checked-dynamic handle can
1501
+ * fall through to a runtime missing-method error. */
1502
+ export function fenceStaticReadableStreamMember(L, access, use) {
1503
+ const sym = isStdlibFetchInterface(L, access.expression, "ReadableStream");
1504
+ if (!sym)
1505
+ return null;
1506
+ const members = fetchAccessMemberNames(L, access);
1507
+ if (members === null)
1508
+ return null;
1509
+ const status = (member) => fetchInventoryStatus("ReadableStream", member, member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype");
1510
+ if (L.dynamic && members.every((member) => status(member) !== "unsupported")) {
1511
+ return null;
1512
+ }
1513
+ const supported = (member) => use === "call"
1514
+ ? STATIC_READABLE_STREAM_CALLS.has(member)
1515
+ : STATIC_READABLE_STREAM_READS.has(member);
1516
+ if (!L.dynamic && members.every(supported))
1517
+ return null;
1518
+ const member = members.find((candidate) => L.dynamic ? status(candidate) === "unsupported" : !supported(candidate));
1519
+ if (status(member) === "unsupported") {
1520
+ 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);
1521
+ }
1522
+ 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);
1523
+ }
1524
+ /** Static AbortSignal constructors and ReadableStream.from(). These
1525
+ * ambient globals have no first-class constructor-object representation;
1526
+ * claim the direct calls by declaration provenance before the general
1527
+ * member-call path tries to lower the receiver as a value. */
1528
+ function staticCallWithSurplusArgs(L, call, first, result) {
1529
+ const loc = locOf(call);
1530
+ if (call.arguments.length === 1)
1531
+ return result(first);
1532
+ const firstLocal = L.declareHiddenLocal("%fetchArg", first.type);
1533
+ const firstRef = () => ({
1534
+ kind: "varRef",
1535
+ localId: firstLocal.id,
1536
+ type: first.type,
1537
+ loc,
1538
+ });
1539
+ const stmts = [
1540
+ { kind: "varDecl", localId: firstLocal.id, init: first, loc },
1541
+ ];
1542
+ for (const argument of call.arguments.slice(1)) {
1543
+ if (ts.isSpreadElement(argument)) {
1544
+ L.noLowering("spread surplus arguments on the static fetch companion APIs", argument, "pass surplus arguments without spread syntax");
1545
+ }
1546
+ // The callee ignores the value, but JavaScript still evaluates every
1547
+ // surplus argument in source order. A direct void has the operand's
1548
+ // effects and no additional work of its own.
1549
+ const discarded = ts.isVoidExpression(argument)
1550
+ ? L.lowerExpr(argument.expression)
1551
+ : L.lowerExpr(argument);
1552
+ stmts.push({ kind: "exprStmt", expr: discarded, loc: discarded.loc });
1553
+ }
1554
+ const answer = result(firstRef());
1555
+ return { kind: "seqExpr", stmts, result: answer, type: answer.type, loc };
1556
+ }
1557
+ export function lowerStaticFetchCompanionCall(L, call) {
1558
+ if (L.dynamic ||
1559
+ call.questionDotToken ||
1560
+ !ts.isPropertyAccessExpression(call.expression) ||
1561
+ call.expression.questionDotToken ||
1562
+ !ts.isIdentifier(call.expression.expression)) {
1563
+ return null;
1564
+ }
1565
+ const root = call.expression.expression;
1566
+ const symbol = L.resolveValueSymbol(root);
1567
+ if (!symbol || !L.isStdlibSymbol(symbol))
1568
+ return null;
1569
+ const loc = locOf(call);
1570
+ if (root.text === "AbortSignal") {
1571
+ switch (call.expression.name.text) {
1572
+ case "timeout":
1573
+ if (call.arguments[0] && ts.isSpreadElement(call.arguments[0])) {
1574
+ return null;
1575
+ }
1576
+ return staticCallWithSurplusArgs(L, call, call.arguments[0]
1577
+ ? L.lowerExprExpecting(call.arguments[0], DYN)
1578
+ : dynUndefinedExpr(loc), (first) => ({
1579
+ kind: "libCall",
1580
+ fn: "fetch.abortTimeout",
1581
+ args: [first],
1582
+ type: DYN,
1583
+ loc,
1584
+ }));
1585
+ case "abort":
1586
+ if (call.arguments.length === 0) {
1587
+ return {
1588
+ kind: "libCall",
1589
+ fn: "fetch.abortNow",
1590
+ args: [dynUndefinedExpr(loc)],
1591
+ type: DYN,
1592
+ loc,
1593
+ };
1594
+ }
1595
+ if (ts.isSpreadElement(call.arguments[0]))
1596
+ return null;
1597
+ return staticCallWithSurplusArgs(L, call, lowerLiveWebValue(L, call.arguments[0]), (first) => ({
1598
+ kind: "libCall",
1599
+ fn: "fetch.abortNow",
1600
+ args: [first],
1601
+ type: DYN,
1602
+ loc,
1603
+ }));
1604
+ case "any":
1605
+ if (call.arguments[0] && ts.isSpreadElement(call.arguments[0])) {
1606
+ return null;
1607
+ }
1608
+ return staticCallWithSurplusArgs(L, call, call.arguments[0]
1609
+ ? L.lowerExprExpecting(call.arguments[0], DYN)
1610
+ : dynUndefinedExpr(loc), (first) => ({
1611
+ kind: "libCall",
1612
+ fn: "fetch.abortAny",
1613
+ args: [first],
1614
+ type: DYN,
1615
+ loc,
1616
+ }));
1617
+ default:
1618
+ return null;
1619
+ }
1620
+ }
1621
+ if (root.text === "ReadableStream" &&
1622
+ call.expression.name.text === "from" &&
1623
+ (!call.arguments[0] || !ts.isSpreadElement(call.arguments[0]))) {
1624
+ if (!call.arguments[0]) {
1625
+ return {
1626
+ kind: "libCall",
1627
+ fn: "fetch.streamFrom",
1628
+ args: [dynUndefinedExpr(loc)],
1629
+ type: DYN,
1630
+ loc,
1631
+ };
1632
+ }
1633
+ let source = L.lowerExpr(call.arguments[0]);
1634
+ // A readonly tuple satisfies the fallback's readonly-array overload,
1635
+ // but maps to a monomorphic record in IR. Reuse the ordinary
1636
+ // tuple→array width conversion with the resolved generic parameter so
1637
+ // `ReadableStream.from([1, 2] as const)` follows every other tuple
1638
+ // flowing into a readonly T[] slot.
1639
+ if (source.type.kind === "record") {
1640
+ const signature = L.checker.getResolvedSignature(call);
1641
+ const iterableParam = signature?.getParameters()[0];
1642
+ const iterableType = iterableParam
1643
+ ? L.mapTypeOf(L.checker.getTypeOfSymbol(iterableParam))
1644
+ : null;
1645
+ if (iterableType?.kind === "array") {
1646
+ source = L.widthCoerce(source, iterableType) ?? source;
1647
+ }
1648
+ }
1649
+ const preserve = source.type.kind === "string" ||
1650
+ (source.type.kind === "bytes" && source.type.elem === "u8") ||
1651
+ (source.type.kind === "array" &&
1652
+ canConvertToDyn(source.type.elem, (id) => L.shapes.get(id), (id) => L.unions.get(id))) ||
1653
+ source.type.kind === "dyn";
1654
+ if (!preserve) {
1655
+ 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]");
1656
+ }
1657
+ return staticCallWithSurplusArgs(L, call, source, (first) => ({
1658
+ kind: "libCall",
1659
+ fn: "fetch.streamFrom",
1660
+ // Arrays and bytes must cross by reference: their iterators read each
1661
+ // entry at pull time, so mutations after ReadableStream.from() remain
1662
+ // observable just as they are in Node. Other supported iterable
1663
+ // shapes retain the checked-dynamic fallback.
1664
+ args: [first],
1665
+ type: DYN,
1666
+ loc,
1667
+ }));
1668
+ }
1669
+ return null;
1670
+ }
1671
+ /** Controller chunks and error reasons are dyn-handle calls, but unlike a
1672
+ * general checked-dynamic boundary the Web Streams contract preserves their
1673
+ * object identity for the reader/source callbacks that observe them. */
1674
+ export function lowerStaticReadableStreamControllerCall(L, call) {
1675
+ const access = call.expression;
1676
+ if (L.dynamic ||
1677
+ call.questionDotToken ||
1678
+ call.arguments.length > 1 ||
1679
+ (!ts.isPropertyAccessExpression(access) &&
1680
+ !ts.isElementAccessExpression(access)) ||
1681
+ access.questionDotToken) {
1682
+ return null;
1683
+ }
1684
+ const member = staticResponseMemberName(L, access);
1685
+ if (member !== "enqueue" && member !== "error")
1686
+ return null;
1687
+ const receiverTs = L.checker.getBaseTypeOfLiteralType(L.typeOf(access.expression));
1688
+ const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol();
1689
+ if (symbol?.name !== "ReadableStreamDefaultController" ||
1690
+ !L.checker.declarationsOf(symbol).some((d) => ts.isInterfaceDeclaration(d) &&
1691
+ L.isStdlibFile(d.getSourceFile()))) {
1692
+ return null;
1693
+ }
1694
+ const recv = L.lowerExpr(access.expression);
1695
+ if (recv.type.kind !== "dyn")
1696
+ return null;
1697
+ const loc = locOf(call);
1698
+ const args = [
1699
+ call.arguments[0]
1700
+ ? lowerLiveWebValue(L, call.arguments[0])
1701
+ : dynUndefinedExpr(loc),
1702
+ ];
1703
+ return lowerStaticFixedFetchMethodCall(L, call, access, member, recv, args, (receiver, argumentRefs) => ({
1704
+ kind: "dynInvoke",
1705
+ recv: receiver,
1706
+ method: member,
1707
+ calleeName: access.getText(),
1708
+ args: argumentRefs,
1709
+ type: DYN,
1710
+ loc,
1711
+ }));
1712
+ }
1713
+ /** `ReadableStream.cancel(reason)` and default-reader `cancel(reason)` pass
1714
+ * the same reason object to the underlying source's cancel callback. Keep a
1715
+ * live typed-reference capsule instead of the general dyn boundary's copy. */
1716
+ export function lowerStaticReadableStreamCancelCall(L, call) {
1717
+ const access = call.expression;
1718
+ if (L.dynamic ||
1719
+ call.questionDotToken ||
1720
+ call.arguments.length > 1 ||
1721
+ call.arguments.some((argument) => ts.isSpreadElement(argument)) ||
1722
+ (!ts.isPropertyAccessExpression(access) &&
1723
+ !ts.isElementAccessExpression(access)) ||
1724
+ access.questionDotToken ||
1725
+ staticResponseMemberName(L, access) !== "cancel") {
1726
+ return null;
1727
+ }
1728
+ const resolved = fetchInterfaceInventoryOwner(L, access.expression);
1729
+ if (resolved?.owner !== "ReadableStream" &&
1730
+ resolved?.owner !== "ReadableStreamDefaultReader") {
1731
+ return null;
1732
+ }
1733
+ const receiver = L.lowerExpr(access.expression);
1734
+ if (receiver.type.kind !== "dyn")
1735
+ return null;
1736
+ const loc = locOf(call);
1737
+ const args = [
1738
+ call.arguments[0]
1739
+ ? lowerLiveWebValue(L, call.arguments[0])
1740
+ : dynUndefinedExpr(loc),
1741
+ ];
1742
+ return lowerStaticFixedFetchMethodCall(L, call, access, "cancel", receiver, args, (receiverRef, argumentRefs) => ({
1743
+ kind: "dynInvoke",
1744
+ recv: receiverRef,
1745
+ method: "cancel",
1746
+ calleeName: access.getText(),
1747
+ args: argumentRefs,
1748
+ type: DYN,
1749
+ loc,
1750
+ }));
1751
+ }
1752
+ /** AbortSignal retains listener object identity for duplicate detection and
1753
+ * removeEventListener(). Functions already box by closure identity; mutable
1754
+ * record/array/bytes listeners need the same live capsule used by stream
1755
+ * chunks so repeated crossings still denote one EventListener object. */
1756
+ export function lowerStaticAbortSignalListenerCall(L, call) {
1757
+ const access = call.expression;
1758
+ if (L.dynamic ||
1759
+ call.questionDotToken ||
1760
+ call.arguments.length < 2 ||
1761
+ call.arguments.some((arg) => ts.isSpreadElement(arg)) ||
1762
+ (!ts.isPropertyAccessExpression(access) &&
1763
+ !ts.isElementAccessExpression(access)) ||
1764
+ access.questionDotToken) {
1765
+ return null;
1766
+ }
1767
+ const member = staticResponseMemberName(L, access);
1768
+ if (member !== "addEventListener" && member !== "removeEventListener") {
1769
+ return null;
1770
+ }
1771
+ const receiverTs = L.checker.getBaseTypeOfLiteralType(L.typeOf(access.expression));
1772
+ const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol();
1773
+ if (symbol?.name !== "AbortSignal" ||
1774
+ !L.checker.declarationsOf(symbol).some((d) => ts.isInterfaceDeclaration(d) &&
1775
+ L.isStdlibFile(d.getSourceFile()))) {
1776
+ return null;
1777
+ }
1778
+ const recv = L.lowerExpr(access.expression);
1779
+ if (recv.type.kind !== "dyn")
1780
+ return null;
1781
+ const loc = locOf(call);
1782
+ const args = call.arguments.map((arg, index) => index === 1
1783
+ ? lowerLiveWebValue(L, arg)
1784
+ : L.lowerExprExpecting(arg, DYN));
1785
+ return lowerStaticFixedFetchMethodCall(L, call, access, member, recv, args, (receiver, argumentRefs) => ({
1786
+ kind: "dynInvoke",
1787
+ recv: receiver,
1788
+ method: member,
1789
+ calleeName: access.getText(),
1790
+ args: argumentRefs,
1791
+ type: DYN,
1792
+ loc,
1793
+ }));
1794
+ }
1795
+ /** `new ReadableStream(source?)`. Static builds box the source record into
1796
+ * the checked-dynamic tree; dynamic builds construct the island's Web
1797
+ * stream directly so the returned handle composes with dynamic fetch. */
1798
+ export function lowerStaticReadableStreamNew(L, expr) {
1799
+ if (!ts.isIdentifier(expr.expression) ||
1800
+ expr.expression.text !== "ReadableStream") {
1801
+ return null;
1802
+ }
1803
+ const symbol = L.resolveValueSymbol(expr.expression);
1804
+ if (!symbol || !L.isStdlibSymbol(symbol))
1805
+ return null;
1806
+ const args = expr.arguments ?? [];
1807
+ if (args.length > 1)
1808
+ return null;
1809
+ const loc = locOf(expr);
1810
+ if (L.dynamic) {
1811
+ const ctor = {
1812
+ kind: "jsOp",
1813
+ op: "globalGet",
1814
+ name: "ReadableStream",
1815
+ args: [],
1816
+ type: JSVAL,
1817
+ loc,
1818
+ };
1819
+ const source = args[0] === undefined
1820
+ ? null
1821
+ : ts.isObjectLiteralExpression(args[0])
1822
+ ? lowerIslandObjectLiteral(L, args[0])
1823
+ : L.jsvalIn(L.lowerExpr(args[0]), args[0]);
1824
+ return {
1825
+ kind: "jsOp",
1826
+ op: "construct",
1827
+ args: source === null ? [ctor] : [ctor, source],
1828
+ type: JSVAL,
1829
+ loc,
1830
+ };
1831
+ }
1832
+ const source = args.length === 0
1833
+ ? dynUndefinedExpr(loc)
1834
+ : ts.isObjectLiteralExpression(args[0])
1835
+ ? lowerDynObjectLiteral(L, args[0])
1836
+ : lowerLiveWebValue(L, args[0]);
1837
+ return {
1838
+ kind: "libCall",
1839
+ fn: "fetch.streamNew",
1840
+ args: [source],
1841
+ type: DYN,
1842
+ loc,
1843
+ };
1844
+ }
1845
+ /** A static default reader's read() exits the native checked-dynamic
1846
+ * transport into the checker's concrete chunk type. An exact chunk type
1847
+ * preserves ReadableStream.from(array)'s element identity; structural
1848
+ * widening keeps the compiler's ordinary copy-based width semantics. */
1849
+ export function lowerStaticReadableStreamReaderCall(L, call) {
1850
+ const access = call.expression;
1851
+ if (L.dynamic ||
1852
+ call.questionDotToken ||
1853
+ call.arguments.length !== 0 ||
1854
+ (!ts.isPropertyAccessExpression(access) &&
1855
+ !ts.isElementAccessExpression(access)) ||
1856
+ access.questionDotToken) {
1857
+ return null;
1858
+ }
1859
+ const member = staticResponseMemberName(L, access);
1860
+ if (member !== "read")
1861
+ return null;
1862
+ const receiverTs = L.typeOf(access.expression);
1863
+ const symbol = receiverTs.getSymbol();
1864
+ if (symbol?.name !== "ReadableStreamDefaultReader" ||
1865
+ !L.checker.declarationsOf(symbol).some((d) => ts.isInterfaceDeclaration(d) &&
1866
+ L.isStdlibFile(d.getSourceFile()))) {
1867
+ return null;
1868
+ }
1869
+ const type = L.mapTypeOf(L.typeOf(call));
1870
+ if (type?.kind !== "promise" ||
1871
+ type.inner.kind !== "record") {
1872
+ return null;
1873
+ }
1874
+ const receiver = L.lowerExprExpecting(access.expression, DYN);
1875
+ return lowerStaticFixedFetchMethodCall(L, call, access, "read", receiver, [], (receiverRef) => ({
1876
+ kind: "libCall",
1877
+ fn: "fetch.readerRead",
1878
+ args: [receiverRef],
1879
+ type,
1880
+ loc: locOf(call),
1881
+ }));
1882
+ }
264
1883
  /** Dynamic `import(spec)` — the island's module system at a USER site.
265
1884
  * Under --dynamic the call lowers to island.importDyn(key): the engine
266
1885
  * loads the module (embedded npm graph, a shipped local .js/.mjs the
@@ -572,35 +2191,70 @@ function islandTrapFnValue(L, message, loc) {
572
2191
  * the JSON-safe copy-marshal). The same jsOp the 'any'-contextual
573
2192
  * literal path emits, callable from lowerings that KNOW the literal is
574
2193
  * island-bound (fetch's init) even when the contextual type is a union
575
- * the generic path declines. Identifier AND string-literal keys — engine
576
- * property names have no identifier restriction ("content-type"). */
2194
+ * the generic path declines. Spelled and pure const-folded computed keys
2195
+ * are admitted; engine property names have no identifier restriction. Plain
2196
+ * spreads copy through the engine's CopyDataProperties operation in
2197
+ * source order, including nested RequestInit/header dictionaries. */
577
2198
  export function lowerIslandObjectLiteral(L, expr) {
578
2199
  const loc = locOf(expr);
579
- const args = [];
2200
+ let args = [];
2201
+ let acc = null;
2202
+ const flushFields = () => {
2203
+ if (args.length === 0)
2204
+ return;
2205
+ const chunk = { kind: "jsOp", op: "objLit", args, type: JSVAL, loc };
2206
+ acc = acc === null
2207
+ ? chunk
2208
+ : { kind: "jsOp", op: "objSpread", args: [acc, chunk], type: JSVAL, loc };
2209
+ args = [];
2210
+ };
580
2211
  for (const prop of expr.properties) {
581
- if (!ts.isPropertyAssignment(prop) ||
582
- !(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) {
583
- L.unsupported("SC1090", prop, "this property form in an island-built object literal (only `name: value` is supported)");
2212
+ if (ts.isSpreadAssignment(prop)) {
2213
+ flushFields();
2214
+ const spread = ts.isObjectLiteralExpression(prop.expression)
2215
+ ? lowerIslandObjectLiteral(L, prop.expression)
2216
+ : L.jsvalIn(L.lowerExpr(prop.expression), prop.expression);
2217
+ acc ??= { kind: "jsOp", op: "objLit", args: [], type: JSVAL, loc };
2218
+ acc = { kind: "jsOp", op: "objSpread", args: [acc, spread], type: JSVAL, loc: locOf(prop) };
2219
+ continue;
2220
+ }
2221
+ if (!ts.isPropertyAssignment(prop) &&
2222
+ !ts.isShorthandPropertyAssignment(prop) &&
2223
+ !ts.isMethodDeclaration(prop)) {
2224
+ 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)");
2225
+ }
2226
+ const propertyName = requestInitLiteralKey(L, prop);
2227
+ if (propertyName === null) {
2228
+ L.unsupported("SC1090", prop, "this property key in an island-built object literal (use a spelled or pure const-folded key)");
584
2229
  }
585
2230
  const nameLoc = locOf(prop.name);
586
2231
  args.push({
587
2232
  kind: "jsMarshal",
588
- value: { kind: "strLit", value: prop.name.text, type: STRING, loc: nameLoc },
2233
+ value: { kind: "strLit", value: propertyName, type: STRING, loc: nameLoc },
589
2234
  type: JSVAL, loc: nameLoc,
590
2235
  });
591
- const init = prop.initializer;
592
- if (ts.isObjectLiteralExpression(init)) {
593
- args.push(lowerIslandObjectLiteral(L, init));
2236
+ const valueNode = ts.isPropertyAssignment(prop)
2237
+ ? prop.initializer
2238
+ : ts.isShorthandPropertyAssignment(prop)
2239
+ ? prop.name
2240
+ : prop;
2241
+ if (ts.isObjectLiteralExpression(valueNode)) {
2242
+ args.push(lowerIslandObjectLiteral(L, valueNode));
594
2243
  }
595
- else if (ts.isArrayLiteralExpression(init) && !init.elements.some(ts.isSpreadElement)) {
596
- const elems = init.elements.map((el) => L.jsvalIn(L.lowerExpr(el), el));
597
- args.push({ kind: "jsOp", op: "arrLit", args: elems, type: JSVAL, loc: locOf(init) });
2244
+ else if (ts.isArrayLiteralExpression(valueNode) &&
2245
+ !valueNode.elements.some(ts.isSpreadElement)) {
2246
+ const elems = valueNode.elements.map((el) => L.jsvalIn(L.lowerExpr(el), el));
2247
+ args.push({ kind: "jsOp", op: "arrLit", args: elems, type: JSVAL, loc: locOf(valueNode) });
598
2248
  }
599
2249
  else {
600
- args.push(L.jsvalIn(L.lowerExpr(init), init));
2250
+ const value = ts.isMethodDeclaration(valueNode)
2251
+ ? (L.rejectThisInObjectMethod(valueNode.body ?? valueNode), L.lowerLambda(valueNode))
2252
+ : L.lowerExpr(valueNode);
2253
+ args.push(L.jsvalIn(value, valueNode));
601
2254
  }
602
2255
  }
603
- return { kind: "jsOp", op: "objLit", args, type: JSVAL, loc };
2256
+ flushFields();
2257
+ return acc ?? { kind: "jsOp", op: "objLit", args: [], type: JSVAL, loc };
604
2258
  }
605
2259
  /** Method calls on the island-backed ambient surface: `Math.<fn>(...)`
606
2260
  * (the engine's own Math object executes) and the number/string methods
@@ -633,8 +2287,7 @@ export function lowerIslandMethodCall(L, call, access) {
633
2287
  {
634
2288
  const sigMember = L.stdlibGlobalMember(access, "AbortSignal");
635
2289
  if ((sigMember === "timeout" || sigMember === "abort" || sigMember === "any") &&
636
- call.arguments.length <= 1 &&
637
- (sigMember === "abort" || call.arguments.length === 1)) {
2290
+ call.arguments.every((argument) => !ts.isSpreadElement(argument))) {
638
2291
  L.requireDynamicApi(`'AbortSignal.${sigMember}'`, call);
639
2292
  const args = call.arguments.map((a) => L.jsvalIn(L.lowerExpr(a), a));
640
2293
  const signalGlobal = {
@@ -646,6 +2299,29 @@ export function lowerIslandMethodCall(L, call, access) {
646
2299
  };
647
2300
  }
648
2301
  }
2302
+ if (L.stdlibGlobalMember(access, "ReadableStream") === "from" &&
2303
+ call.arguments.every((argument) => !ts.isSpreadElement(argument))) {
2304
+ L.requireDynamicApi("'ReadableStream.from'", call);
2305
+ const streamGlobal = {
2306
+ kind: "jsOp",
2307
+ op: "globalGet",
2308
+ name: "ReadableStream",
2309
+ args: [],
2310
+ type: JSVAL,
2311
+ loc,
2312
+ };
2313
+ return {
2314
+ kind: "jsOp",
2315
+ op: "callMethod",
2316
+ name: "from",
2317
+ args: [
2318
+ streamGlobal,
2319
+ ...call.arguments.map((argument) => L.jsvalIn(L.lowerExpr(argument), argument)),
2320
+ ],
2321
+ type: JSVAL,
2322
+ loc,
2323
+ };
2324
+ }
649
2325
  const isMath = L.stdlibGlobalMember(access, "Math") !== null;
650
2326
  // The STATIC Math members (floor/min/max/random): one C call IS the
651
2327
  // JS operation — no island, no --dynamic. Only the tabled arity with