@velarscript/compiler 0.12.1 → 0.13.0

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 (109) hide show
  1. package/dist/advisory-suppression.d.ts +62 -0
  2. package/dist/advisory-suppression.d.ts.map +1 -0
  3. package/dist/advisory-suppression.js +179 -0
  4. package/dist/advisory-suppression.js.map +1 -0
  5. package/dist/analyzer.d.ts +405 -17
  6. package/dist/analyzer.d.ts.map +1 -1
  7. package/dist/analyzer.js +1755 -211
  8. package/dist/analyzer.js.map +1 -1
  9. package/dist/ast.d.ts +6 -3
  10. package/dist/ast.d.ts.map +1 -1
  11. package/dist/ast.js.map +1 -1
  12. package/dist/binding-stability.d.ts +22 -0
  13. package/dist/binding-stability.d.ts.map +1 -0
  14. package/dist/binding-stability.js +63 -0
  15. package/dist/binding-stability.js.map +1 -0
  16. package/dist/collection-lowering-runtime.d.ts +1 -1
  17. package/dist/collection-lowering-runtime.d.ts.map +1 -1
  18. package/dist/collection-lowering-runtime.js +183 -56
  19. package/dist/collection-lowering-runtime.js.map +1 -1
  20. package/dist/diagnostic.d.ts +34 -0
  21. package/dist/diagnostic.d.ts.map +1 -1
  22. package/dist/diagnostic.js +11 -1
  23. package/dist/diagnostic.js.map +1 -1
  24. package/dist/embedded-javascript.d.ts +12 -1
  25. package/dist/embedded-javascript.d.ts.map +1 -1
  26. package/dist/embedded-javascript.js +111 -2
  27. package/dist/embedded-javascript.js.map +1 -1
  28. package/dist/embedded-module.d.ts.map +1 -1
  29. package/dist/embedded-module.js +22 -0
  30. package/dist/embedded-module.js.map +1 -1
  31. package/dist/emitter.d.ts +83 -1
  32. package/dist/emitter.d.ts.map +1 -1
  33. package/dist/emitter.js +518 -41
  34. package/dist/emitter.js.map +1 -1
  35. package/dist/error-runtime.d.ts +9 -0
  36. package/dist/error-runtime.d.ts.map +1 -1
  37. package/dist/error-runtime.js +21 -0
  38. package/dist/error-runtime.js.map +1 -1
  39. package/dist/extension.d.ts +5 -2
  40. package/dist/extension.d.ts.map +1 -1
  41. package/dist/extension.js +2 -1
  42. package/dist/extension.js.map +1 -1
  43. package/dist/formatter.d.ts.map +1 -1
  44. package/dist/formatter.js +222 -91
  45. package/dist/formatter.js.map +1 -1
  46. package/dist/index.d.ts +10 -2
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +43 -5
  49. package/dist/index.js.map +1 -1
  50. package/dist/interpolated-string.d.ts.map +1 -1
  51. package/dist/interpolated-string.js +29 -2
  52. package/dist/interpolated-string.js.map +1 -1
  53. package/dist/json-runtime.d.ts.map +1 -1
  54. package/dist/json-runtime.js +27 -3
  55. package/dist/json-runtime.js.map +1 -1
  56. package/dist/lexer.d.ts +217 -1
  57. package/dist/lexer.d.ts.map +1 -1
  58. package/dist/lexer.js +799 -37
  59. package/dist/lexer.js.map +1 -1
  60. package/dist/limits.d.ts +9 -0
  61. package/dist/limits.d.ts.map +1 -1
  62. package/dist/limits.js +9 -0
  63. package/dist/limits.js.map +1 -1
  64. package/dist/mechanical-fix.d.ts.map +1 -1
  65. package/dist/mechanical-fix.js +38 -0
  66. package/dist/mechanical-fix.js.map +1 -1
  67. package/dist/parser.d.ts +30 -1
  68. package/dist/parser.d.ts.map +1 -1
  69. package/dist/parser.js +137 -21
  70. package/dist/parser.js.map +1 -1
  71. package/dist/promise-runtime.d.ts +20 -1
  72. package/dist/promise-runtime.d.ts.map +1 -1
  73. package/dist/promise-runtime.js +38 -3
  74. package/dist/promise-runtime.js.map +1 -1
  75. package/dist/reactive-bridge-runtime.d.ts.map +1 -1
  76. package/dist/reactive-bridge-runtime.js +7 -1
  77. package/dist/reactive-bridge-runtime.js.map +1 -1
  78. package/dist/runtime-abi.d.ts +21 -1
  79. package/dist/runtime-abi.d.ts.map +1 -1
  80. package/dist/runtime-abi.js +23 -3
  81. package/dist/runtime-abi.js.map +1 -1
  82. package/dist/source-names.d.ts +26 -0
  83. package/dist/source-names.d.ts.map +1 -1
  84. package/dist/source-names.js +49 -5
  85. package/dist/source-names.js.map +1 -1
  86. package/dist/source.d.ts.map +1 -1
  87. package/dist/source.js +13 -0
  88. package/dist/source.js.map +1 -1
  89. package/dist/stable-order.d.ts +16 -0
  90. package/dist/stable-order.d.ts.map +1 -0
  91. package/dist/stable-order.js +18 -0
  92. package/dist/stable-order.js.map +1 -0
  93. package/dist/text-runtime.d.ts.map +1 -1
  94. package/dist/text-runtime.js +182 -41
  95. package/dist/text-runtime.js.map +1 -1
  96. package/dist/token.d.ts +11 -0
  97. package/dist/token.d.ts.map +1 -1
  98. package/dist/token.js.map +1 -1
  99. package/dist/type-registry-runtime.d.ts.map +1 -1
  100. package/dist/type-registry-runtime.js +8 -1
  101. package/dist/type-registry-runtime.js.map +1 -1
  102. package/dist/type-validation-runtime.d.ts.map +1 -1
  103. package/dist/type-validation-runtime.js +129 -1
  104. package/dist/type-validation-runtime.js.map +1 -1
  105. package/dist/types.d.ts +17 -1
  106. package/dist/types.d.ts.map +1 -1
  107. package/dist/types.js +161 -17
  108. package/dist/types.js.map +1 -1
  109. package/package.json +1 -1
package/dist/emitter.js CHANGED
@@ -3,8 +3,8 @@ import { VELAR_CLASS_FIELD_MODULE, VELAR_CLASS_FIELD_RUNTIME } from "./class-run
3
3
  import { VELAR_COLLECTION_HOST_EXPORTS, VELAR_COLLECTION_HOST_MODULE, VELAR_COLLECTION_IDENTITY_RUNTIME, VELAR_COLLECTION_LIST_RUNTIME, VELAR_COLLECTION_RECORD_RUNTIME, VELAR_COLLECTION_SET_MAP_RUNTIME, VELAR_COLLECTION_TYPE_RUNTIME } from "./collection-runtime.js";
4
4
  import { VELAR_COLLECTION_LOWERING_EXPORTS, VELAR_COLLECTION_LOWERING_MODULE, VELAR_COLLECTION_LOWERING_RUNTIME } from "./collection-lowering-runtime.js";
5
5
  import { describeType, formatTypeReference, formatTypeSyntax, mapNestedTypes, resolveTypeReference, semanticTypeIdentity, typeContainsParameter } from "./types.js";
6
- import { disposeMemberKey, iterateMemberKey } from "./analyzer.js";
7
- import { VELAR_ERROR_NORMALIZATION_MODULE, VELAR_ERROR_NORMALIZATION_RUNTIME, VELAR_HOST_ERROR_NAMES, VELAR_HOST_ERROR_RUNTIME } from "./error-runtime.js";
6
+ import { disposeMemberKey, iterateAsyncMemberKey, iterateMemberKey } from "./analyzer.js";
7
+ import { VELAR_ASSERTION_ERROR_RUNTIME, VELAR_ERROR_NORMALIZATION_MODULE, VELAR_ERROR_NORMALIZATION_RUNTIME, VELAR_HOST_ERROR_NAMES, VELAR_HOST_ERROR_RUNTIME } from "./error-runtime.js";
8
8
  import { embeddedJavaScriptSpecifier } from "./embedded-module.js";
9
9
  import { VELAR_NARROWING_MODULE, VELAR_NARROWING_RUNTIME } from "./narrowing-runtime.js";
10
10
  import { VELAR_NUMBER_METHOD_RUNTIME } from "./number-runtime.js";
@@ -16,11 +16,28 @@ import { VELAR_TEXT_METHOD_RUNTIME } from "./text-runtime.js";
16
16
  import { VELAR_TYPE_REGISTRY_RUNTIME } from "./type-registry-runtime.js";
17
17
  import { VELAR_RUNTIME_TYPE_COLLECTION_RUNTIME, VELAR_TYPE_VALIDATION_MODULE, VELAR_TYPE_VALIDATION_RUNTIME, VELAR_VALIDATION_ERROR_RUNTIME, } from "./type-validation-runtime.js";
18
18
  const javaScriptNodeMarker = /\u0000VELAR_MAP_(\d+)\u0000/gu;
19
+ /**
20
+ * How deep a structural record's inline field proof nests before it degrades
21
+ * to the presence test. A generated validator recurses through a function
22
+ * call; an expression can only recurse by growing, so the depth is what keeps
23
+ * a deeply nested (or self-referential) structural type from expanding without
24
+ * bound.
25
+ */
26
+ const maximumStructuralFieldDepth = 4;
27
+ /**
28
+ * D90 rule R5: the placeholder a container's copy plan carries where its own
29
+ * identity goes, until interning has decided the name that identity is spelled
30
+ * with. A container's plan is both the callback it hands the runtime helper
31
+ * and the key that helper's memo files the copy under, so the body has to name
32
+ * itself before it has a name.
33
+ */
34
+ const copyPlanSelfReference = "__velarCopyPlanSelf";
19
35
  // ENM-U4 + COL-U5: the compiler-raised error types are nameable in source;
20
36
  // their runtime classes carry compiler-owned names. The source names are
21
37
  // reserved Core bindings, so a bare reference is always the builtin.
22
38
  const builtinErrorRuntimeNames = new Map([
23
39
  ["ValidationError", "__VelarValidationError"],
40
+ ["AssertionError", "__VelarAssertionError"],
24
41
  ["NarrowingError", "__VelarNarrowingError"],
25
42
  ["IndexError", "__VelarIndexError"],
26
43
  ]);
@@ -39,6 +56,30 @@ export class JavaScriptEmitter {
39
56
  genericTypeParameters = null;
40
57
  /** Hoisted `function __velarTypeOf_N()` bodies for instantiations written outside a generic. */
41
58
  hoistedGenericInstances = new Map();
59
+ /**
60
+ * D90 rule R5: one hoisted `function __velarCopyPlanN` per distinct copy
61
+ * plan, found by the plan's own emitted text. The memo a copy keeps is keyed
62
+ * by source object *and* plan, so a plan needs an identity that is the same
63
+ * object at every visit; a module-level function declaration is that identity
64
+ * and the element callback in one, and it hoists past any temporal dead zone.
65
+ */
66
+ copyPlans = new Map();
67
+ copyPlanDeclarations = [];
68
+ /**
69
+ * The copy plans of the generic record currently being emitted. A plan that
70
+ * reads `__velarArguments` cannot hoist to module level and must not be
71
+ * shared between instantiations, so it is interned onto the instantiation's
72
+ * own arguments object instead — one array per instantiation, built once.
73
+ */
74
+ genericCopyPlans = null;
75
+ genericCopyPlanNames = null;
76
+ /**
77
+ * The plan array the generic record whose copy was emitted last needs on its
78
+ * arguments object, empty when every one of its plans hoisted to module level.
79
+ */
80
+ pendingGenericCopyPlans = [];
81
+ /** Whether a copy plan is being asked about rather than emitted, so nothing is interned. */
82
+ copyPlanProbe = false;
42
83
  externModuleExports = new Map();
43
84
  needsExternExportHelper = false;
44
85
  hints;
@@ -64,9 +105,11 @@ export class JavaScriptEmitter {
64
105
  needsIntegrityFailureHelper = false;
65
106
  needsRequiredValueHelper = false;
66
107
  needsNarrowingErrorClass = false;
67
- suppressPromiseNormalization = 0;
108
+ needsAssertionErrorClass = false;
109
+ suppressedPromiseValues = new Set();
68
110
  nextJavaScriptNodeId = 0;
69
111
  javaScriptNodeSpans = new Map();
112
+ structuralFieldChecks = new Set();
70
113
  generatedMappings = [];
71
114
  generatedCode = "";
72
115
  sourcePath;
@@ -280,7 +323,10 @@ export class JavaScriptEmitter {
280
323
  helpers.push(VELAR_PROMISE_NORMALIZATION_RUNTIME);
281
324
  }
282
325
  }
283
- if (this.hints.asyncForStatements.size > 0) {
326
+ // D90 R18: the structural-pull helpers serve only the capability-handle
327
+ // shapes; an `async for` over a declared asynchronous `@iterate:` calls
328
+ // the emitted member directly and needs none of them.
329
+ if ([...this.hints.asyncForStatements].some((start) => !this.hints.asyncIterationStatements.has(start))) {
284
330
  helpers.push([
285
331
  "const __velarAsyncPullGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;",
286
332
  "const __velarAsyncPullGetPrototypeOf = Object.getPrototypeOf;",
@@ -316,6 +362,18 @@ export class JavaScriptEmitter {
316
362
  if (this.needsDisposalHelper) {
317
363
  helpers.push(...this.disposalHelpers());
318
364
  }
365
+ // D86 rule 212: `assert` and `value!` raise one compiler-owned class, so
366
+ // the class is emitted wherever either lowering is, independently of the
367
+ // error-normalization runtime a module may or may not also need.
368
+ if (this.needsAssertionErrorClass) {
369
+ if (this.sharedRuntimeModules) {
370
+ this.requireRuntimeModule(VELAR_ERROR_NORMALIZATION_MODULE);
371
+ helpers.push(`import { AssertionError as __VelarAssertionError } from ${JSON.stringify(VELAR_ERROR_NORMALIZATION_MODULE)};`);
372
+ }
373
+ else {
374
+ helpers.push(VELAR_ASSERTION_ERROR_RUNTIME);
375
+ }
376
+ }
319
377
  if (this.needsIntegrityFailureHelper) {
320
378
  helpers.push(...this.integrityFailureHelpers());
321
379
  }
@@ -537,6 +595,10 @@ export class JavaScriptEmitter {
537
595
  const chunks = [
538
596
  ...helpers.map((code) => ({ code, mappings: [] })),
539
597
  ...instances.map((code) => ({ code, mappings: [] })),
598
+ // D90 rule R5: the interned copy plans. They are `function` declarations
599
+ // for the same reason the instantiation accessors are — a plan names the
600
+ // Type objects declared below it and is only ever called after they exist.
601
+ ...this.copyPlanDeclarations.map((code) => ({ code, mappings: [] })),
540
602
  ...statements.map((node) => this.renderJavaScriptNode(node)),
541
603
  ];
542
604
  let output = "";
@@ -585,10 +647,14 @@ export class JavaScriptEmitter {
585
647
  const markerIndex = marker.index;
586
648
  code += node.code.slice(cursor, markerIndex);
587
649
  const sourceSpan = this.javaScriptNodeSpans.get(Number(marker[1]));
650
+ cursor = markerIndex + marker[0].length;
651
+ // A marker id this emit never issued cannot come from a marked node, so
652
+ // it is text that only looks like emitter metadata. Markers are
653
+ // invisible by construction: the render drops the sequence and keeps one
654
+ // mapping fewer rather than failing the whole compile with a host throw.
588
655
  if (!sourceSpan)
589
- throw new Error("A generated JavaScript mapping marker has no source node");
656
+ continue;
590
657
  mappings.push({ offset: code.length, sourceSpan });
591
- cursor = markerIndex + marker[0].length;
592
658
  }
593
659
  code += node.code.slice(cursor);
594
660
  return { code, mappings };
@@ -704,12 +770,25 @@ export class JavaScriptEmitter {
704
770
  integrityFailureHelpers() {
705
771
  return [[
706
772
  "const __velarIntegrityDescriptor = Object.getOwnPropertyDescriptor;",
773
+ "const __velarIntegrityPrototypeOf = Object.getPrototypeOf;",
707
774
  "function __velarIsIntegrityFailure(value) {",
708
775
  " if (value === null || (typeof value !== \"object\" && typeof value !== \"function\")) return false;",
709
776
  " const descriptor = __velarIntegrityDescriptor(value, \"name\");",
710
777
  " if (!descriptor || !(\"value\" in descriptor)) return false;",
711
778
  " const name = descriptor.value;",
712
- " return name === \"AssertionError\" || name === \"NarrowingError\" || name === \"IndexError\";",
779
+ " if (name !== \"AssertionError\" && name !== \"NarrowingError\" && name !== \"IndexError\") return false;",
780
+ // D51 rule 107: the class a value was constructed from is what decides
781
+ // here, exactly as it decides `code`. A relabelled host error carries
782
+ // the name but no class declaring it, and must not pass through `try`
783
+ // as though the language had raised it. The comparison is on the
784
+ // declared name rather than on the class object because a module that
785
+ // inlines its runtime holds its own copy of each class, and a failure
786
+ // raised inside another module is still the same language failure.
787
+ " const prototype = __velarIntegrityPrototypeOf(value);",
788
+ " const constructor = prototype === null ? null : __velarIntegrityDescriptor(prototype, \"constructor\");",
789
+ " if (!constructor || !(\"value\" in constructor) || typeof constructor.value !== \"function\") return false;",
790
+ " const declared = __velarIntegrityDescriptor(constructor.value, \"name\");",
791
+ " return !!declared && \"value\" in declared && declared.value === name;",
713
792
  "}",
714
793
  ].join("\n")];
715
794
  }
@@ -722,9 +801,7 @@ export class JavaScriptEmitter {
722
801
  return [[
723
802
  "function __velarRequired(value, description, offset) {",
724
803
  " if (value === null || value === undefined) {",
725
- " const __velarRequiredError = new Error(\"Required value \" + description + \" is absent at source offset \" + offset);",
726
- " __velarRequiredError.name = \"AssertionError\";",
727
- " throw __velarRequiredError;",
804
+ " throw new __VelarAssertionError(\"Required value \" + description + \" is absent at source offset \" + offset);",
728
805
  " }",
729
806
  " return value;",
730
807
  "}",
@@ -997,6 +1074,7 @@ export class JavaScriptEmitter {
997
1074
  }
998
1075
  markRuntimeType(type) {
999
1076
  this.needsRuntimeTypeHelpers = true;
1077
+ const structural = new Set();
1000
1078
  const visit = (value) => {
1001
1079
  // D55 rule 121: `Box<string>` needs `Box`'s factory emitted and every
1002
1080
  // argument's own runtime types marked; the application's display name is
@@ -1052,17 +1130,40 @@ export class JavaScriptEmitter {
1052
1130
  else if (value.kind === "union") {
1053
1131
  value.members.forEach(visit);
1054
1132
  }
1133
+ else if (value.kind === "object") {
1134
+ // A structural field is proved inline, so whatever its own check needs
1135
+ // — a collection's `TypeIs` helper, a declared record's validator — is
1136
+ // this module's dependency exactly as a named field's would be.
1137
+ if (structural.has(value))
1138
+ return;
1139
+ structural.add(value);
1140
+ value.fields.forEach(visit);
1141
+ structural.delete(value);
1142
+ }
1055
1143
  };
1056
1144
  visit(type);
1057
1145
  }
1058
- markRuntimeNarrowingType(type) {
1146
+ markRuntimeNarrowingType(type, structural = new Set()) {
1059
1147
  if (type.kind === "optional") {
1060
- this.markRuntimeNarrowingType(type.inner);
1148
+ this.markRuntimeNarrowingType(type.inner, structural);
1061
1149
  return;
1062
1150
  }
1063
1151
  if (type.kind === "union") {
1064
1152
  for (const member of type.members)
1065
- this.markRuntimeNarrowingType(member);
1153
+ this.markRuntimeNarrowingType(member, structural);
1154
+ return;
1155
+ }
1156
+ // A structural object's recheck spells its field table inline, so every
1157
+ // field's own evidence is emitted into this module and its helpers must be
1158
+ // required here. The expansion the emitter bounds is the *expression*; the
1159
+ // dependency walk only has to terminate, so one visit per object suffices.
1160
+ if (type.kind === "object") {
1161
+ if (structural.has(type))
1162
+ return;
1163
+ structural.add(type);
1164
+ for (const field of type.fields.values())
1165
+ this.markRuntimeNarrowingType(field, structural);
1166
+ structural.delete(type);
1066
1167
  return;
1067
1168
  }
1068
1169
  if (type.kind === "list" || type.kind === "set" || type.kind === "map" || type.kind === "record"
@@ -1186,12 +1287,11 @@ export class JavaScriptEmitter {
1186
1287
  case "ThrowStatement":
1187
1288
  return `${indentation}throw ${this.emitMappedExpression(statement.value)};`;
1188
1289
  case "AssertStatement": {
1290
+ this.needsAssertionErrorClass = true;
1189
1291
  const message = statement.message ? this.emitMappedExpression(statement.message) : JSON.stringify("Assertion failed");
1190
1292
  return [
1191
1293
  `${indentation}if (!(${this.emitCondition(statement.condition)})) {`,
1192
- `${indentation} const __velarAssertionError = new Error(${message});`,
1193
- `${indentation} __velarAssertionError.name = "AssertionError";`,
1194
- `${indentation} throw __velarAssertionError;`,
1294
+ `${indentation} throw new __VelarAssertionError(${message});`,
1195
1295
  `${indentation}}`,
1196
1296
  ].join("\n");
1197
1297
  }
@@ -1255,13 +1355,18 @@ export class JavaScriptEmitter {
1255
1355
  const valueName = `__velarAsyncForValue${suffix}`;
1256
1356
  const indexName = `__velarAsyncForIndex${suffix}`;
1257
1357
  const bodyDepth = depth + 2;
1358
+ // D90 R18: a source whose class declares the asynchronous
1359
+ // `@iterate:` form is pulled through the declared member — no
1360
+ // structural `next` capture, because the contract is a declaration,
1361
+ // not a resemblance.
1362
+ const declared = this.hints.asyncIterationStatements.has(statement.span.start);
1258
1363
  const lines = [
1259
1364
  `${indentation}{`,
1260
1365
  `${" ".repeat(depth + 1)}const ${sourceName} = ${this.emitMappedExpression(statement.iterable)};`,
1261
- `${" ".repeat(depth + 1)}const ${nextName} = __velarAsyncPullNext(${sourceName});`,
1366
+ ...(declared ? [] : [`${" ".repeat(depth + 1)}const ${nextName} = __velarAsyncPullNext(${sourceName});`]),
1262
1367
  `${" ".repeat(depth + 1)}let ${indexName} = 0;`,
1263
1368
  `${" ".repeat(depth + 1)}while (true) {`,
1264
- `${" ".repeat(bodyDepth)}const ${valueName} = await __velarNormalizePromiseValue(__velarAsyncPullCall(${sourceName}, ${nextName}));`,
1369
+ `${" ".repeat(bodyDepth)}const ${valueName} = await __velarNormalizePromiseValue(${declared ? `${sourceName}[${JSON.stringify(iterateAsyncMemberKey)}]()` : `__velarAsyncPullCall(${sourceName}, ${nextName})`});`,
1265
1370
  `${" ".repeat(bodyDepth)}if (${valueName} === null) break;`,
1266
1371
  ...this.emitBindingPatternStatements(statement.pattern, valueName, "const", false, bodyDepth, "Async for"),
1267
1372
  ...(statement.secondPattern
@@ -1513,10 +1618,27 @@ export class JavaScriptEmitter {
1513
1618
  }
1514
1619
  if (checked.length > 0) {
1515
1620
  this.needsExternExportHelper = true;
1621
+ // Charter section 12, line 2737: "an `export let` remains a live
1622
+ // ES-module value: the exporting module can reassign it between reads".
1623
+ // So the name binds through a real `import` and the presence probe runs
1624
+ // beside it as its own statement. Reading the namespace *into* a `const`
1625
+ // would have frozen the foreign binding at its initial value, which is
1626
+ // neither what `import js * as`, nor `unsafe js`, nor JavaScript itself
1627
+ // does with the same declaration.
1628
+ //
1629
+ // W-22's probe survives as the interop backstop rather than the primary
1630
+ // check: a host that link-checks named imports refuses a missing export
1631
+ // before any statement runs, and where the name links to `undefined`
1632
+ // instead — bundled CommonJS interop — the probe beside it is what
1633
+ // reports, in the velar voice.
1634
+ const names = checked
1635
+ .map((specifier) => specifier.imported === specifier.local ? specifier.imported : `${specifier.imported} as ${specifier.local}`)
1636
+ .join(", ");
1516
1637
  const namespaceName = `__velarExternModule${statement.span.start}`;
1638
+ lines.push(`${indentation}import { ${names} } from ${JSON.stringify(emittedSource)};`);
1517
1639
  lines.push(`${indentation}import * as ${namespaceName} from ${JSON.stringify(emittedSource)};`);
1518
1640
  for (const specifier of checked) {
1519
- lines.push(`${indentation}const ${specifier.local} = __velarExternExport(${namespaceName}, ${JSON.stringify(specifier.imported)}, ${JSON.stringify(source)});`);
1641
+ lines.push(`${indentation}__velarExternExport(${namespaceName}, ${JSON.stringify(specifier.imported)}, ${JSON.stringify(source)});`);
1520
1642
  }
1521
1643
  }
1522
1644
  return lines.join("\n");
@@ -1529,11 +1651,15 @@ export class JavaScriptEmitter {
1529
1651
  continue;
1530
1652
  const specifier = embeddedJavaScriptSpecifier(this.sourcePath, ordinal);
1531
1653
  const occupiedJavaScriptNames = new Set(statement.bindings.map((binding) => binding.name));
1532
- let factoryName = statement.contract ? `__velarEmbeddedFactory_${ordinal}` : null;
1654
+ // A factory exists to hand the block its captures. With no captures
1655
+ // there is nothing to hand over, so the block stays a real ES module and
1656
+ // its exports stay live bindings — see `emitEmbeddedJavaScript`.
1657
+ const needsFactory = statement.contract !== null && statement.captures.length > 0;
1658
+ let factoryName = needsFactory ? `__velarEmbeddedFactory_${ordinal}` : null;
1533
1659
  while (factoryName && occupiedJavaScriptNames.has(factoryName))
1534
1660
  factoryName += "_";
1535
- const localFactoryName = statement.contract ? `__velarEmbeddedFactoryBinding_${ordinal}` : null;
1536
- const generated = statement.contract
1661
+ const localFactoryName = needsFactory ? `__velarEmbeddedFactoryBinding_${ordinal}` : null;
1662
+ const generated = factoryName
1537
1663
  ? emitCheckedEmbeddedJavaScript(statement, factoryName)
1538
1664
  : mappedSource(statement.source, statement.sourceSpan.start);
1539
1665
  this.embeddedJavaScript.set(statement, {
@@ -1551,14 +1677,24 @@ export class JavaScriptEmitter {
1551
1677
  const prepared = this.embeddedJavaScript.get(statement);
1552
1678
  if (!prepared)
1553
1679
  throw new Error("An embedded JavaScript declaration has no prepared sibling module");
1554
- if (!statement.contract) {
1680
+ if (!prepared.factoryName) {
1681
+ // Charter section 12, line 2737: an `export let` the block reassigns is
1682
+ // a live ES-module value. A block with no captures needs no factory to
1683
+ // receive them, so its sibling module keeps its own `export`
1684
+ // declarations and the names arrive here as real imported bindings —
1685
+ // the same value `unsafe js` and `import js * as` observe. A contract changes what the
1686
+ // compiler proves about a block, never what the program observes.
1687
+ const declared = statement.contract ? contractExportNames(statement.contract) : null;
1688
+ const exported = declared
1689
+ ? statement.exports.filter((item) => declared.has(item.name))
1690
+ : statement.exports;
1555
1691
  const imported = {
1556
1692
  kind: "ImportDeclaration",
1557
1693
  source: prepared.specifier,
1558
1694
  sourceSpan: statement.sourceSpan,
1559
1695
  javascript: true,
1560
1696
  unsafe: true,
1561
- specifiers: statement.exports.map((item) => ({
1697
+ specifiers: exported.map((item) => ({
1562
1698
  imported: item.name,
1563
1699
  local: item.name,
1564
1700
  namespace: false,
@@ -1568,11 +1704,8 @@ export class JavaScriptEmitter {
1568
1704
  };
1569
1705
  return this.emitImport(imported, indentation);
1570
1706
  }
1571
- const names = [
1572
- ...statement.contract.functions.map((item) => item.name),
1573
- ...statement.contract.constants.map((item) => item.name),
1574
- ...statement.contract.classes.map((item) => item.name),
1575
- ];
1707
+ // A factory only exists for a checked block, and only when it has captures.
1708
+ const names = statement.contract ? [...contractExportNames(statement.contract)] : [];
1576
1709
  const captureValues = statement.captures.map((capture) => this.emitMappedExpression({
1577
1710
  kind: "IdentifierExpression",
1578
1711
  name: capture.name,
@@ -1614,6 +1747,13 @@ export class JavaScriptEmitter {
1614
1747
  type: this.resolveDeclarationType(field.type),
1615
1748
  syntax: field.type.syntax,
1616
1749
  }));
1750
+ // D90 rule R5: the predicate stays the charter's "present own enumerable
1751
+ // data properties" and deliberately does not demand `writable` and
1752
+ // `configurable` the way `__velarRecordFields` does. Since parse now
1753
+ // returns a copy whose every field is an ordinary mutable data property, a
1754
+ // frozen source can no longer make a later write to the validated record
1755
+ // fail — so refusing frozen host configuration would cost expressiveness
1756
+ // and buy nothing.
1617
1757
  const checks = fields.map(({ descriptor, type }) => {
1618
1758
  const present = `${descriptor}?.enumerable && "value" in ${descriptor} && ${this.emitTypeCheck(type, `${descriptor}.value`, guarded ? "__state" : "undefined")}`;
1619
1759
  return type.kind === "optional" ? `(${descriptor} === undefined || (${present}))` : present;
@@ -1637,6 +1777,13 @@ export class JavaScriptEmitter {
1637
1777
  // type the author wrote and the memo still answers with one Type object per
1638
1778
  // instantiation.
1639
1779
  const argumentsParameter = generic ? ", __velarArguments" : "";
1780
+ const copyName = this.runtimeTypeCopyName(statement.name);
1781
+ // The copy plan this declaration files its own copies under. It has to be
1782
+ // the same value at every visit within one parse and a different value for
1783
+ // every other declared shape: the copy function itself is that for a plain
1784
+ // record, and the arguments object is that for an instantiation, exactly as
1785
+ // the traversal guard already reads them.
1786
+ const ownCopyPlan = generic ? "__velarArguments" : copyName;
1640
1787
  const displayName = generic ? "__velarArguments.name" : JSON.stringify(statement.name);
1641
1788
  const pathText = (suffix) => generic
1642
1789
  ? (suffix === "" ? displayName : `${displayName} + ${JSON.stringify(suffix)}`)
@@ -1696,15 +1843,35 @@ export class JavaScriptEmitter {
1696
1843
  `${indentation} const __velarDetail = ${explainName}(value${generic ? ", __velarArguments" : ""});`,
1697
1844
  `${indentation} throw new __VelarValidationError(${generic ? `"Value does not match " + ${displayName}` : JSON.stringify(`Value does not match ${statement.name}`)} + (__velarDetail.reason ? " — " + __velarDetail.reason : "") + __velarValidationRejectionHint(value), __velarDetail);`,
1698
1845
  `${indentation} }`,
1699
- `${indentation} return value;`,
1846
+ // D90 rule R5: parse hands back a fresh value built from the validated
1847
+ // shape, so a later write through the argument cannot falsify a field
1848
+ // the caller was handed, and a value reached through a readonly view
1849
+ // does not widen by passing through parse. The copy memo is keyed by
1850
+ // source object and plan, and this type's own plan is the identity that
1851
+ // is one per declaration — its arguments for an instantiation, since two
1852
+ // instantiations of one generic are two different declared shapes.
1853
+ `${indentation} return ${copyName}(value, __velarValidationState(), ${ownCopyPlan}${generic ? ", __velarArguments" : ""});`,
1854
+ `${indentation} },`,
1855
+ // A derived type calls this with the plan it is itself copying under, so
1856
+ // the inherited prefix lands on the derived copy instead of on a base
1857
+ // copy another position in the same parse may already be holding.
1858
+ `${indentation} copy(value, __state = __velarValidationState(), __velarCopyPlan = ${ownCopyPlan}) {`,
1859
+ `${indentation} return ${copyName}(value, __state, __velarCopyPlan${generic ? ", __velarArguments" : ""});`,
1700
1860
  `${indentation} },`,
1701
1861
  ];
1702
1862
  if (generic) {
1703
1863
  const instances = `__velarGenericInstances_${statement.name}`;
1864
+ const copyLines = this.recordCopyFunctionLines(fields, copyName, baseExpression, indentation, argumentsParameter);
1865
+ // A plan that reads the instantiation's arguments cannot hoist to module
1866
+ // level and must not be shared between instantiations, so it is built
1867
+ // once here, beside the arguments object it belongs to and reads.
1868
+ const plans = this.pendingGenericCopyPlans;
1704
1869
  return [
1705
1870
  ...explainLines,
1706
1871
  ...this.recordCheckFunctionLines(fields, predicate, checkName, indentation, argumentsParameter, guarded),
1707
1872
  "",
1873
+ ...copyLines,
1874
+ "",
1708
1875
  `${indentation}const ${instances} = [];`,
1709
1876
  // The instantiation memo: one frozen Type object per set of arguments,
1710
1877
  // found by a key the emitter builds from the arguments' own identities.
@@ -1721,6 +1888,11 @@ export class JavaScriptEmitter {
1721
1888
  `${indentation} for (let __velarIndex = 0; __velarIndex < __velarTexts.length; __velarIndex += 1) __velarName += (__velarIndex === 0 ? "" : ", ") + __velarTexts[__velarIndex];`,
1722
1889
  `${indentation} __velarName += ">";`,
1723
1890
  `${indentation} const __velarArguments = { keys: __velarKeys, texts: __velarTexts, checks: __velarChecks, name: __velarName };`,
1891
+ ...(plans.length > 0 ? [
1892
+ `${indentation} __velarArguments.plans = [`,
1893
+ ...plans.map((plan) => `${indentation} ${plan},`),
1894
+ `${indentation} ];`,
1895
+ ] : []),
1724
1896
  `${indentation} const __velarType = __velarRegisterRuntimeType(__velarValidationFreeze({`,
1725
1897
  ...typeObject.map((line) => `${indentation} ${line}`),
1726
1898
  `${indentation} }));`,
@@ -1734,6 +1906,8 @@ export class JavaScriptEmitter {
1734
1906
  ...explainLines,
1735
1907
  ...this.recordCheckFunctionLines(fields, predicate, checkName, indentation, "", guarded),
1736
1908
  "",
1909
+ ...this.recordCopyFunctionLines(fields, copyName, baseExpression, indentation, ""),
1910
+ "",
1737
1911
  `${indentation}${exportPrefix}const ${statement.name} = __velarRegisterRuntimeType(__velarValidationFreeze({`,
1738
1912
  ...typeObject,
1739
1913
  `${indentation}}));`,
@@ -1779,6 +1953,195 @@ export class JavaScriptEmitter {
1779
1953
  `${indentation}}`,
1780
1954
  ];
1781
1955
  }
1956
+ runtimeTypeCopyName(name) {
1957
+ return `__velarTypeCopy_${name}`;
1958
+ }
1959
+ /**
1960
+ * D90 rule R5: the record's copy — one fresh object per source object *and*
1961
+ * declared type, with every declared field rebuilt. The plan the caller is
1962
+ * copying under is threaded in and passed on to the base, so a value reached
1963
+ * once as `Base` and once as `Derived` in the same parse is two copies, each
1964
+ * complete for its own type, rather than the base's copy with the derived
1965
+ * fields written over it. Within one plan a base still builds the object and
1966
+ * records it, and the derived fields land on that same copy, so one source
1967
+ * object still maps to exactly one copy however deep the chain is.
1968
+ */
1969
+ recordCopyFunctionLines(fields, copyName, baseExpression, indentation, argumentsParameter) {
1970
+ const fresh = baseExpression
1971
+ ? `${baseExpression}.copy(value, __state, __velarCopyPlan)`
1972
+ : `__state.copy.object(__state, value, __velarCopyPlan)`;
1973
+ const previousPlans = this.genericCopyPlans;
1974
+ const previousNames = this.genericCopyPlanNames;
1975
+ this.genericCopyPlans = argumentsParameter ? [] : null;
1976
+ this.genericCopyPlanNames = argumentsParameter ? new Map() : null;
1977
+ const fieldLines = fields.flatMap(({ name, type }) => {
1978
+ const descriptor = "__velarCopyField";
1979
+ const copied = this.typeCopyExpression(type, `${descriptor}.value`, "__state");
1980
+ return [
1981
+ `${indentation} {`,
1982
+ `${indentation} const ${descriptor} = __velarValidationOwnDescriptor(value, ${JSON.stringify(name)});`,
1983
+ `${indentation} if (${descriptor} !== undefined) __state.copy.field(__velarCopy, ${JSON.stringify(name)}, ${copied ?? `${descriptor}.value`});`,
1984
+ `${indentation} }`,
1985
+ ];
1986
+ });
1987
+ this.pendingGenericCopyPlans = this.genericCopyPlans ?? [];
1988
+ this.genericCopyPlans = previousPlans;
1989
+ this.genericCopyPlanNames = previousNames;
1990
+ return [
1991
+ `${indentation}function ${copyName}(value, __state, __velarCopyPlan${argumentsParameter}) {`,
1992
+ `${indentation} const __velarCopySeen = __state.copy.seen(__state, value, __velarCopyPlan);`,
1993
+ `${indentation} if (__velarCopySeen !== undefined) return __velarCopySeen;`,
1994
+ `${indentation} const __velarCopy = ${fresh};`,
1995
+ ...fieldLines,
1996
+ `${indentation} return __velarCopy;`,
1997
+ `${indentation}}`,
1998
+ ];
1999
+ }
2000
+ /**
2001
+ * D90 rule R5: the module-level function that carries one copy plan, or null
2002
+ * when the position rebuilds nothing. Interning is by the plan's own emitted
2003
+ * text — which is what the plan means, module-locally — so two positions that
2004
+ * copy the same shape share one plan and one memo entry, and two that copy
2005
+ * different shapes can never be handed each other's copy.
2006
+ */
2007
+ copyPlanName(type) {
2008
+ const body = this.copyPlanBody(type);
2009
+ if (body === null)
2010
+ return null;
2011
+ if (this.copyPlanProbe)
2012
+ return copyPlanSelfReference;
2013
+ const generic = this.genericCopyPlans;
2014
+ const genericNames = this.genericCopyPlanNames;
2015
+ if (generic !== null && genericNames !== null && body.includes("__velarArguments")) {
2016
+ const interned = genericNames.get(body);
2017
+ if (interned !== undefined)
2018
+ return interned;
2019
+ const name = `__velarArguments.plans[${generic.length}]`;
2020
+ genericNames.set(body, name);
2021
+ generic.push(`(__velarCopyItem, __velarCopyState) => ${body.replaceAll(copyPlanSelfReference, name)}`);
2022
+ return name;
2023
+ }
2024
+ const known = this.copyPlans.get(body);
2025
+ if (known !== undefined)
2026
+ return known;
2027
+ const name = `__velarCopyPlan${this.copyPlans.size}`;
2028
+ this.copyPlans.set(body, name);
2029
+ this.copyPlanDeclarations.push([
2030
+ `function ${name}(__velarCopyItem, __velarCopyState) {`,
2031
+ ` return ${body.replaceAll(copyPlanSelfReference, name)};`,
2032
+ "}",
2033
+ ].join("\n"));
2034
+ return name;
2035
+ }
2036
+ /**
2037
+ * One copy plan's body. A container names itself where its memo key goes,
2038
+ * because the copy it files is the one a later visit under the same plan must
2039
+ * find — including the visit that reaches it through its own elements.
2040
+ */
2041
+ copyPlanBody(type) {
2042
+ switch (type.kind) {
2043
+ case "list":
2044
+ return `__velarCopyState.copy.listOf(__velarCopyItem, __velarCopyState, ${this.typeCopyCallback(type.element)}, ${copyPlanSelfReference})`;
2045
+ case "set":
2046
+ return `__velarCopyState.copy.setOf(__velarCopyItem, __velarCopyState, ${this.typeCopyCallback(type.element)}, ${copyPlanSelfReference})`;
2047
+ case "map":
2048
+ return `__velarCopyState.copy.mapOf(__velarCopyItem, __velarCopyState, ${this.typeCopyCallback(type.key)}, ${this.typeCopyCallback(type.value)}, ${copyPlanSelfReference})`;
2049
+ case "record":
2050
+ return `__velarCopyState.copy.recordOf(__velarCopyItem, __velarCopyState, ${this.typeCopyCallback(type.value)}, ${copyPlanSelfReference})`;
2051
+ default:
2052
+ return this.typeCopyExpression(type, "__velarCopyItem", "__velarCopyState");
2053
+ }
2054
+ }
2055
+ /** Whether a position rebuilds anything, asked without interning the plan it would need. */
2056
+ typeCopiesAnything(type) {
2057
+ const previous = this.copyPlanProbe;
2058
+ this.copyPlanProbe = true;
2059
+ try {
2060
+ return this.typeCopyExpression(type, "__velarCopyItem", "__velarCopyState") !== null;
2061
+ }
2062
+ finally {
2063
+ this.copyPlanProbe = previous;
2064
+ }
2065
+ }
2066
+ /**
2067
+ * D90 rule R5: the expression that rebuilds one validated position, or null
2068
+ * when the position has nothing to copy — a primitive, an enum member, a
2069
+ * class instance, or an opaque `unknown`. The copy follows the declared
2070
+ * shape rather than the value, so an `unknown` field keeps handing back the
2071
+ * reference the author was given: copying an opaque value structurally would
2072
+ * change what parse returns.
2073
+ */
2074
+ typeCopyExpression(type, value, state) {
2075
+ switch (type.kind) {
2076
+ case "unknown":
2077
+ case "any":
2078
+ case "null":
2079
+ case "string":
2080
+ case "number":
2081
+ case "bool":
2082
+ case "promise":
2083
+ case "class":
2084
+ case "enum":
2085
+ case "enumMember":
2086
+ case "function":
2087
+ case "action":
2088
+ case "intrinsic":
2089
+ case "typeObject":
2090
+ case "runtimeType":
2091
+ case "enumObject":
2092
+ case "classConstructor":
2093
+ case "extension":
2094
+ return null;
2095
+ case "optional": {
2096
+ const inner = this.typeCopyExpression(type.inner, value, state);
2097
+ return inner === null ? null : `(${value} == null ? ${value} : ${inner})`;
2098
+ }
2099
+ // A container copies through its own interned plan, because the plan is
2100
+ // the identity its memo files the copy under and a fresh closure at every
2101
+ // visit would be a different identity every time.
2102
+ case "list":
2103
+ case "set":
2104
+ case "map":
2105
+ case "record": {
2106
+ const plan = this.copyPlanName(type);
2107
+ return plan === null ? null : `${plan}(${value}, ${state})`;
2108
+ }
2109
+ case "named":
2110
+ // An instantiation's copy is the declaration's, reached through the
2111
+ // same memoized Type object its predicate is reached through.
2112
+ if (type.application && this.genericTypeBinding(type.application.name)) {
2113
+ this.needsRuntimeTypeHelpers = true;
2114
+ return `${this.genericInstanceExpression(type.application)}.copy(${value}, ${state})`;
2115
+ }
2116
+ // Duration is text, an enum member is text, and a class instance is
2117
+ // not plain data — none of them can or should be rebuilt.
2118
+ if (type.name === "Duration")
2119
+ return null;
2120
+ if (this.hints.enumNames.has(type.name))
2121
+ return null;
2122
+ if (this.hints.classNames.has(type.name))
2123
+ return null;
2124
+ if (this.enumAliasTarget(type.name) !== null)
2125
+ return null;
2126
+ if (this.typeDeclarations.has(type.name))
2127
+ return `${type.name}.copy(${value}, ${state})`;
2128
+ return this.runtimeTypeBinding(type.name) ? `${state}.copy.through(${type.name}, ${value}, ${state})` : null;
2129
+ // A union, a structural object, and an erased type parameter are all
2130
+ // positions the predicate did not fully decide, so the copy is the
2131
+ // structural one: plain data recurses and anything else passes through.
2132
+ case "union":
2133
+ return type.members.every((member) => !this.typeCopiesAnything(member))
2134
+ ? null
2135
+ : `${state}.copy.plain(${value}, ${state})`;
2136
+ case "object":
2137
+ case "parameter":
2138
+ return `${state}.copy.plain(${value}, ${state})`;
2139
+ }
2140
+ }
2141
+ /** The per-element copy a container hands its runtime helper, or `null` when the element position has nothing to copy. */
2142
+ typeCopyCallback(type) {
2143
+ return this.copyPlanName(type) ?? "null";
2144
+ }
1782
2145
  emitTypeAliasDeclaration(statement, depth) {
1783
2146
  const indentation = " ".repeat(depth);
1784
2147
  // ENM-I4: identities follow aliases, so an alias whose target resolves to
@@ -1801,6 +2164,17 @@ export class JavaScriptEmitter {
1801
2164
  const checkName = this.runtimeTypeCheckName(statement.name);
1802
2165
  const guarded = this.runtimeTypeNeedsTraversalGuard(statement.name);
1803
2166
  const predicate = this.emitTypeCheck(resolveTypeReference(statement.target), "value", guarded ? "__state" : "undefined");
2167
+ // D90 rule R5: an alias copies whatever its target copies. An alias of a
2168
+ // primitive has nothing to rebuild, so its parse still returns the same
2169
+ // value and allocates nothing. An alias of a declared record is that
2170
+ // record's copy, so it passes on the plan it was called under too — an
2171
+ // alias is a legal base, and the derived fields must not land on a copy
2172
+ // the aliased record filed under its own plan.
2173
+ const aliasTarget = resolveTypeReference(statement.target);
2174
+ const copied = this.typeCopyExpression(aliasTarget, "value", "__state");
2175
+ const forwarded = copied !== null && aliasTarget.kind === "named" && this.typeDeclarations.has(aliasTarget.name)
2176
+ ? `${aliasTarget.name}.copy(value, __state, __velarCopyPlan)`
2177
+ : copied;
1804
2178
  const exportPrefix = statement.exported ? "export " : "";
1805
2179
  return [
1806
2180
  guarded
@@ -1819,8 +2193,11 @@ export class JavaScriptEmitter {
1819
2193
  `${indentation} if (!${checkName}(value)) {`,
1820
2194
  `${indentation} throw new __VelarValidationError(${JSON.stringify(`Value does not match ${statement.name}`)}, { path: ${JSON.stringify(statement.name)} });`,
1821
2195
  `${indentation} }`,
1822
- `${indentation} return value;`,
2196
+ `${indentation} return ${copied === null ? "value" : `${statement.name}.copy(value)`};`,
1823
2197
  `${indentation} },`,
2198
+ ...(copied === null
2199
+ ? [`${indentation} copy(value) {`, `${indentation} return value;`, `${indentation} },`]
2200
+ : [`${indentation} copy(value, __state = __velarValidationState(), __velarCopyPlan) {`, `${indentation} return ${forwarded};`, `${indentation} },`]),
1824
2201
  `${indentation}}));`,
1825
2202
  ].join("\n");
1826
2203
  }
@@ -1843,6 +2220,12 @@ export class JavaScriptEmitter {
1843
2220
  `${indentation} }`,
1844
2221
  `${indentation} return value;`,
1845
2222
  `${indentation} },`,
2223
+ // D90 rule R5: every runtime Type object answers `copy`, so a record
2224
+ // field typed by an imported enum reaches the same ABI a record does. An
2225
+ // enum member is text, so the copy is the value itself.
2226
+ `${indentation} copy(value) {`,
2227
+ `${indentation} return value;`,
2228
+ `${indentation} },`,
1846
2229
  // ENM-U1: the members in declaration order, a fresh mutable List per call.
1847
2230
  `${indentation} values() {`,
1848
2231
  `${indentation} return [${values.join(", ")}];`,
@@ -1909,7 +2292,7 @@ export class JavaScriptEmitter {
1909
2292
  case "union":
1910
2293
  return `(${type.members.map((member) => this.emitTypeCheck(member, value, state)).join(" || ")})`;
1911
2294
  case "object":
1912
- return `${value} !== null && typeof ${value} === "object"`;
2295
+ return this.emitObjectTypeCheck(type, value, (field, read) => this.emitTypeCheck(field, read, state));
1913
2296
  case "function":
1914
2297
  case "action":
1915
2298
  case "intrinsic":
@@ -1966,6 +2349,8 @@ export class JavaScriptEmitter {
1966
2349
  if (!this.runtimeTypeBinding(type.name))
1967
2350
  return `${value} != null`;
1968
2351
  return this.emitTypeCheck(type, value, state);
2352
+ case "object":
2353
+ return this.emitObjectTypeCheck(type, value, (field, read) => this.emitNarrowingCheck(field, read, state));
1969
2354
  case "parameter":
1970
2355
  case "typeObject":
1971
2356
  case "runtimeType":
@@ -1977,6 +2362,44 @@ export class JavaScriptEmitter {
1977
2362
  return this.emitTypeCheck(type, value, state);
1978
2363
  }
1979
2364
  }
2365
+ /**
2366
+ * Charter section 5: a record proves its fields, not merely its presence. A
2367
+ * declared record answers through the deep validator its declaration emits;
2368
+ * a structural one has no declaration to hang a function on, so the same
2369
+ * evidence is spelled inline as one expression over the field table the type
2370
+ * already carries. `check` is the caller's own recursion, so a narrowing
2371
+ * recheck keeps degrading a field it cannot prove rather than refusing it.
2372
+ *
2373
+ * The expansion is bounded, because an expression cannot recurse the way a
2374
+ * generated function can: a structural type already being expanded, or one
2375
+ * nested deeper than `maximumStructuralFieldDepth`, falls back to the
2376
+ * presence test — the same evidence charter line 1006 allows an erased
2377
+ * position. A field whose own check is a constant is dropped from the
2378
+ * conjunction for the same reason: `false` there would refuse a value the
2379
+ * language cannot inspect, and `true` proves nothing worth emitting.
2380
+ */
2381
+ emitObjectTypeCheck(type, value, check) {
2382
+ const presence = `${value} !== null && typeof ${value} === "object"`;
2383
+ if (type.fields.size === 0 || this.structuralFieldChecks.has(type)
2384
+ || this.structuralFieldChecks.size >= maximumStructuralFieldDepth) {
2385
+ return presence;
2386
+ }
2387
+ this.structuralFieldChecks.add(type);
2388
+ try {
2389
+ const fields = [];
2390
+ for (const [name, field] of type.fields) {
2391
+ const read = `${value}${javaScriptMemberAccess(name)}`;
2392
+ const proof = check(field, read);
2393
+ if (proof === "true" || proof === "false")
2394
+ continue;
2395
+ fields.push(type.optionalFields?.has(name) ? `(${read} === undefined || ${proof})` : proof);
2396
+ }
2397
+ return fields.length === 0 ? presence : `(${presence} && ${fields.join(" && ")})`;
2398
+ }
2399
+ finally {
2400
+ this.structuralFieldChecks.delete(type);
2401
+ }
2402
+ }
1980
2403
  runtimeTypeCheckName(name) {
1981
2404
  return `__velarTypeCheck_${name}`;
1982
2405
  }
@@ -2194,6 +2617,8 @@ export class JavaScriptEmitter {
2194
2617
  return null;
2195
2618
  if (name === "ValidationError")
2196
2619
  this.needsRuntimeTypeHelpers = true;
2620
+ else if (name === "AssertionError")
2621
+ this.needsAssertionErrorClass = true;
2197
2622
  else if (name === "NarrowingError")
2198
2623
  this.needsNarrowingErrorClass = true;
2199
2624
  else
@@ -2308,8 +2733,12 @@ export class JavaScriptEmitter {
2308
2733
  // D68 rule 177: `@iterate:` lands the same way, under its own unspellable
2309
2734
  // key. It is a plain prototype member, so a derived block simply shadows
2310
2735
  // the base's — overriding replaces, which is what "one answer" means.
2736
+ // D90 R18: the asynchronous pull form is an async method under its own
2737
+ // key — `async for` calls it once per element, and the trailing
2738
+ // `return null` makes falling off the end mean exhaustion.
2311
2739
  const iterate = [];
2312
2740
  if (statement.iterate) {
2741
+ const asynchronous = this.hints.asyncIterateBlocks.has(spanIdentity(statement.iterate.keywordSpan));
2313
2742
  const iterateDepth = depth + 2;
2314
2743
  const indent = " ".repeat(iterateDepth);
2315
2744
  const body = [
@@ -2317,7 +2746,7 @@ export class JavaScriptEmitter {
2317
2746
  ...this.emitStatementLines(statement.iterate.body, iterateDepth),
2318
2747
  `${indent}return null;`,
2319
2748
  ];
2320
- iterate.push(`${indentation} [${JSON.stringify(iterateMemberKey)}]() {\n${body.join("\n")}\n${indentation} }`);
2749
+ iterate.push(`${indentation} ${asynchronous ? "async " : ""}[${JSON.stringify(asynchronous ? iterateAsyncMemberKey : iterateMemberKey)}]() {\n${body.join("\n")}\n${indentation} }`);
2321
2750
  }
2322
2751
  const extension = statement.base ? ` extends ${statement.base.name}` : "";
2323
2752
  return `${indentation}${statement.exported ? "export " : ""}class ${statement.name}${extension} {\n${[...privateFields, ...staticFields, constructor, ...getters, ...methods, ...dispose, ...iterate].join("\n\n")}\n${indentation}}`;
@@ -2357,18 +2786,24 @@ export class JavaScriptEmitter {
2357
2786
  emitMappedExpression(expression, normalizeNull = true) {
2358
2787
  return this.emitMappedJavaScript(expression.span, () => {
2359
2788
  const key = spanIdentity(expression.span);
2360
- const normalizePromise = normalizeNull
2361
- && this.suppressPromiseNormalization === 0
2362
- && this.hints.normalizedPromiseValues.has(key);
2363
- if (normalizePromise)
2364
- this.suppressPromiseNormalization += 1;
2789
+ // The wrapper covers exactly the value it wraps, so suppression follows
2790
+ // the value rather than the whole subtree: an `await` in an argument
2791
+ // position produces a *different* Promise and keeps its own boundary,
2792
+ // which is what makes `await use(await supply())` check both of them.
2793
+ const suppressed = this.suppressedPromiseValues.delete(key);
2794
+ const normalizePromise = normalizeNull && !suppressed && this.hints.normalizedPromiseValues.has(key);
2795
+ const passThrough = normalizePromise || suppressed
2796
+ ? promiseValuePassThrough(expression).map((item) => spanIdentity(item.span))
2797
+ : [];
2798
+ for (const item of passThrough)
2799
+ this.suppressedPromiseValues.add(item);
2365
2800
  let emitted;
2366
2801
  try {
2367
2802
  emitted = this.emitExpression(expression);
2368
2803
  }
2369
2804
  finally {
2370
- if (normalizePromise)
2371
- this.suppressPromiseNormalization -= 1;
2805
+ for (const item of passThrough)
2806
+ this.suppressedPromiseValues.delete(item);
2372
2807
  }
2373
2808
  const narrowing = this.hints.runtimeNarrowings.get(key);
2374
2809
  if (narrowing) {
@@ -2469,6 +2904,15 @@ export class JavaScriptEmitter {
2469
2904
  });
2470
2905
  return `${asynchronous ? "await __velarCreateListAsync" : "__velarCreateList"}([${parts.join(", ")}])`;
2471
2906
  }
2907
+ if (expression.elements.length === 0) {
2908
+ // COL-P1: an empty List literal is the one array the runtime cannot
2909
+ // tell from an array JavaScript handed over empty, and the difference
2910
+ // decides whether every later element read re-proves its slot. The
2911
+ // compiler knows which one this is, so it is the compiler that says
2912
+ // so; a `[]` that arrives from the host is never adopted.
2913
+ this.needsCollectionHelpers = true;
2914
+ return "__velarAdoptList([])";
2915
+ }
2472
2916
  return `[${expression.elements.map((element) => this.emitMappedExpression(element)).join(", ")}]`;
2473
2917
  case "ObjectExpression": {
2474
2918
  const needsControlledConstruction = expression.properties.some((property) => property.kind === "ObjectSpread"
@@ -2498,6 +2942,7 @@ export class JavaScriptEmitter {
2498
2942
  // absence is, not ten lines later where the `undefined` would surface.
2499
2943
  case "RequiredExpression": {
2500
2944
  this.needsRequiredValueHelper = true;
2945
+ this.needsAssertionErrorClass = true;
2501
2946
  const description = JSON.stringify(requiredValueDescription(expression.value));
2502
2947
  return `__velarRequired(${this.emitMappedExpression(expression.value)}, ${description}, ${expression.span.start})`;
2503
2948
  }
@@ -3257,7 +3702,14 @@ export class JavaScriptEmitter {
3257
3702
  return lines;
3258
3703
  }
3259
3704
  escapeTemplateText(value) {
3260
- return value.replaceAll("\\", "\\\\").replaceAll("\r", "\\r").replaceAll("`", "\\`").replaceAll("${", "\\${");
3705
+ return value.replaceAll("\\", "\\\\").replaceAll("\r", "\\r").replaceAll("`", "\\`").replaceAll("${", "\\${")
3706
+ // `\u{0}` is a sanctioned source spelling, so a C0 control reaches here
3707
+ // as a raw byte. U+0000 delimits this emitter's own source-map markers
3708
+ // (see `javaScriptNodeMarker`), so author text could otherwise spell a
3709
+ // marker the renderer would delete out of the program. Every C0 control
3710
+ // leaves as an escape sequence instead of a byte, which no scan of the
3711
+ // generated text can mistake for emitter metadata.
3712
+ .replaceAll(/[\u0000-\u001F]/gu, (control) => `\\u${control.codePointAt(0).toString(16).padStart(4, "0")}`);
3261
3713
  }
3262
3714
  blockAlwaysReturns(statements) {
3263
3715
  for (const statement of statements) {
@@ -3279,6 +3731,23 @@ export class JavaScriptEmitter {
3279
3731
  return false;
3280
3732
  }
3281
3733
  }
3734
+ /**
3735
+ * The sub-expressions whose value *is* this expression's value. A Promise
3736
+ * wrapper on the outer node already normalizes whatever these produce, so they
3737
+ * skip a second one; every other position — an argument, a receiver, a
3738
+ * function body — carries a Promise of its own and keeps its own boundary.
3739
+ */
3740
+ function promiseValuePassThrough(expression) {
3741
+ if (expression.kind === "ConditionalExpression")
3742
+ return [expression.thenValue, expression.elseValue];
3743
+ if (expression.kind === "BinaryExpression" && expression.operator === "??")
3744
+ return [expression.left, expression.right];
3745
+ return [];
3746
+ }
3747
+ /** The member-read suffix for a field name: a dot when the name is spellable, a subscript otherwise. */
3748
+ function javaScriptMemberAccess(name) {
3749
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(name) ? `.${name}` : `[${JSON.stringify(name)}]`;
3750
+ }
3282
3751
  function javaScriptIdentifiers(sources) {
3283
3752
  const identifiers = new Set();
3284
3753
  const identifierStart = (character) => /[A-Za-z_$]/u.test(character);
@@ -3468,6 +3937,14 @@ function mappedSource(source, sourceStart) {
3468
3937
  }
3469
3938
  return { code: source, mappings };
3470
3939
  }
3940
+ /** The names a checked block's contract publishes into VelarScript scope. */
3941
+ function contractExportNames(contract) {
3942
+ return new Set([
3943
+ ...contract.functions.map((item) => item.name),
3944
+ ...contract.constants.map((item) => item.name),
3945
+ ...contract.classes.map((item) => item.name),
3946
+ ]);
3947
+ }
3471
3948
  function emitCheckedEmbeddedJavaScript(statement, factoryName) {
3472
3949
  const relative = (value) => ({
3473
3950
  start: value.start - statement.sourceSpan.start,