@velarscript/compiler 0.12.0 → 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.
- package/dist/advisory-suppression.d.ts +62 -0
- package/dist/advisory-suppression.d.ts.map +1 -0
- package/dist/advisory-suppression.js +179 -0
- package/dist/advisory-suppression.js.map +1 -0
- package/dist/analyzer.d.ts +405 -17
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +1755 -211
- package/dist/analyzer.js.map +1 -1
- package/dist/ast.d.ts +6 -3
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js.map +1 -1
- package/dist/binding-stability.d.ts +22 -0
- package/dist/binding-stability.d.ts.map +1 -0
- package/dist/binding-stability.js +63 -0
- package/dist/binding-stability.js.map +1 -0
- package/dist/collection-lowering-runtime.d.ts +1 -1
- package/dist/collection-lowering-runtime.d.ts.map +1 -1
- package/dist/collection-lowering-runtime.js +183 -56
- package/dist/collection-lowering-runtime.js.map +1 -1
- package/dist/diagnostic.d.ts +34 -0
- package/dist/diagnostic.d.ts.map +1 -1
- package/dist/diagnostic.js +11 -1
- package/dist/diagnostic.js.map +1 -1
- package/dist/embedded-javascript.d.ts +12 -1
- package/dist/embedded-javascript.d.ts.map +1 -1
- package/dist/embedded-javascript.js +111 -2
- package/dist/embedded-javascript.js.map +1 -1
- package/dist/embedded-module.d.ts.map +1 -1
- package/dist/embedded-module.js +22 -0
- package/dist/embedded-module.js.map +1 -1
- package/dist/emitter.d.ts +83 -1
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +518 -41
- package/dist/emitter.js.map +1 -1
- package/dist/error-runtime.d.ts +9 -0
- package/dist/error-runtime.d.ts.map +1 -1
- package/dist/error-runtime.js +21 -0
- package/dist/error-runtime.js.map +1 -1
- package/dist/extension.d.ts +5 -2
- package/dist/extension.d.ts.map +1 -1
- package/dist/extension.js +2 -1
- package/dist/extension.js.map +1 -1
- package/dist/formatter.d.ts.map +1 -1
- package/dist/formatter.js +222 -91
- package/dist/formatter.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -5
- package/dist/index.js.map +1 -1
- package/dist/interpolated-string.d.ts.map +1 -1
- package/dist/interpolated-string.js +29 -2
- package/dist/interpolated-string.js.map +1 -1
- package/dist/json-runtime.d.ts.map +1 -1
- package/dist/json-runtime.js +27 -3
- package/dist/json-runtime.js.map +1 -1
- package/dist/lexer.d.ts +217 -1
- package/dist/lexer.d.ts.map +1 -1
- package/dist/lexer.js +799 -37
- package/dist/lexer.js.map +1 -1
- package/dist/limits.d.ts +9 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +9 -0
- package/dist/limits.js.map +1 -1
- package/dist/mechanical-fix.d.ts.map +1 -1
- package/dist/mechanical-fix.js +38 -0
- package/dist/mechanical-fix.js.map +1 -1
- package/dist/parser.d.ts +30 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +137 -21
- package/dist/parser.js.map +1 -1
- package/dist/promise-runtime.d.ts +20 -1
- package/dist/promise-runtime.d.ts.map +1 -1
- package/dist/promise-runtime.js +38 -3
- package/dist/promise-runtime.js.map +1 -1
- package/dist/reactive-bridge-runtime.d.ts.map +1 -1
- package/dist/reactive-bridge-runtime.js +7 -1
- package/dist/reactive-bridge-runtime.js.map +1 -1
- package/dist/runtime-abi.d.ts +21 -1
- package/dist/runtime-abi.d.ts.map +1 -1
- package/dist/runtime-abi.js +23 -3
- package/dist/runtime-abi.js.map +1 -1
- package/dist/source-names.d.ts +26 -0
- package/dist/source-names.d.ts.map +1 -1
- package/dist/source-names.js +49 -5
- package/dist/source-names.js.map +1 -1
- package/dist/source.d.ts.map +1 -1
- package/dist/source.js +13 -0
- package/dist/source.js.map +1 -1
- package/dist/stable-order.d.ts +16 -0
- package/dist/stable-order.d.ts.map +1 -0
- package/dist/stable-order.js +18 -0
- package/dist/stable-order.js.map +1 -0
- package/dist/text-runtime.d.ts.map +1 -1
- package/dist/text-runtime.js +182 -41
- package/dist/text-runtime.js.map +1 -1
- package/dist/token.d.ts +11 -0
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js.map +1 -1
- package/dist/type-registry-runtime.d.ts.map +1 -1
- package/dist/type-registry-runtime.js +8 -1
- package/dist/type-registry-runtime.js.map +1 -1
- package/dist/type-validation-runtime.d.ts.map +1 -1
- package/dist/type-validation-runtime.js +129 -1
- package/dist/type-validation-runtime.js.map +1 -1
- package/dist/types.d.ts +17 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +161 -17
- package/dist/types.js.map +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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}
|
|
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}
|
|
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
|
-
|
|
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 =
|
|
1536
|
-
const generated =
|
|
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 (!
|
|
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:
|
|
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
|
-
|
|
1572
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
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
|
-
|
|
2371
|
-
this.
|
|
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,
|