@scriptc/compiler 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +84 -19
- package/dist/backend/cycle-analysis.js +1 -0
- package/dist/backend/cycle-analysis.js.map +1 -1
- package/dist/backend/llvm/classes.js +21 -5
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +8 -0
- package/dist/backend/llvm/dyn.js +247 -108
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +6 -3
- package/dist/backend/llvm/emitter.js +99 -25
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +9 -1
- package/dist/backend/llvm/emitter.test.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.d.ts +1 -1
- package/dist/backend/llvm/expr-bytes.js +28 -17
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +5 -0
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-control.js +1 -1
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +47 -13
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.js +11 -1
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-records.js +11 -3
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +187 -18
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/expr-stream-callbacks.d.ts +2 -2
- package/dist/backend/llvm/expr-stream-callbacks.js +7 -57
- package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +41 -3
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +12 -9
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +23 -3
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +80 -3
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/shapes.js +8 -2
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/may-throw.js +4 -0
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +1 -1
- package/dist/backend/native-codegen.js +4 -3
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +11 -0
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-toolchain.d.ts +3 -6
- package/dist/backend/native-toolchain.js +53 -23
- package/dist/backend/native-toolchain.js.map +1 -1
- package/dist/backend/native-toolchain.test.js +72 -0
- package/dist/backend/native-toolchain.test.js.map +1 -1
- package/dist/backend/runtime-abi.d.ts +2 -2
- package/dist/backend/runtime-abi.js +2 -2
- package/dist/backend/runtime-pack-native.js +4 -1
- package/dist/backend/runtime-pack-native.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack.test.js +1 -1
- package/dist/backend/target-diagnostics.d.ts +5 -0
- package/dist/backend/target-diagnostics.js +72 -1
- package/dist/backend/target-diagnostics.js.map +1 -1
- package/dist/backend/target-diagnostics.test.d.ts +1 -0
- package/dist/backend/target-diagnostics.test.js +68 -0
- package/dist/backend/target-diagnostics.test.js.map +1 -0
- package/dist/backend/targets.js +10 -6
- package/dist/backend/targets.js.map +1 -1
- package/dist/backend/targets.test.js +7 -0
- package/dist/backend/targets.test.js.map +1 -1
- package/dist/compat/fetch-profile.js +34 -4
- package/dist/compat/fetch-profile.js.map +1 -1
- package/dist/coverage/surface-manifest.js +5 -3
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.js +2 -7
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +7 -3
- package/dist/executable/executable-cache.js +47 -11
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +109 -2
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/executable/prepare.js +2 -1
- package/dist/executable/prepare.js.map +1 -1
- package/dist/frontend/builtin-modules.d.ts +1 -1
- package/dist/frontend/builtin-modules.js +1 -1
- package/dist/frontend/builtin-modules.js.map +1 -1
- package/dist/frontend/function-completion.d.ts +6 -0
- package/dist/frontend/function-completion.js +50 -0
- package/dist/frontend/function-completion.js.map +1 -0
- package/dist/frontend/lowering/abstract-equality.js +5 -1
- package/dist/frontend/lowering/abstract-equality.js.map +1 -1
- package/dist/frontend/lowering/checked-iterable-spread.d.ts +4 -0
- package/dist/frontend/lowering/checked-iterable-spread.js +30 -0
- package/dist/frontend/lowering/checked-iterable-spread.js.map +1 -0
- package/dist/frontend/lowering/checked-promise-all.d.ts +5 -0
- package/dist/frontend/lowering/checked-promise-all.js +31 -0
- package/dist/frontend/lowering/checked-promise-all.js.map +1 -0
- package/dist/frontend/lowering/class-callbacks.d.ts +4 -4
- package/dist/frontend/lowering/class-callbacks.js +29 -17
- package/dist/frontend/lowering/class-callbacks.js.map +1 -1
- package/dist/frontend/lowering/class-construction.d.ts +14 -0
- package/dist/frontend/lowering/class-construction.js +185 -0
- package/dist/frontend/lowering/class-construction.js.map +1 -0
- package/dist/frontend/lowering/class-descriptors.d.ts +2 -1
- package/dist/frontend/lowering/class-descriptors.js +58 -10
- package/dist/frontend/lowering/class-descriptors.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +36 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.js +965 -91
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.test.js +66 -3
- package/dist/frontend/lowering/class-dynamic-dispatch.test.js.map +1 -1
- package/dist/frontend/lowering/class-instance-constructor.d.ts +1 -1
- package/dist/frontend/lowering/class-instance-constructor.js +15 -42
- package/dist/frontend/lowering/class-instance-constructor.js.map +1 -1
- package/dist/frontend/lowering/class-method-values.d.ts +20 -1
- package/dist/frontend/lowering/class-method-values.js +120 -24
- package/dist/frontend/lowering/class-method-values.js.map +1 -1
- package/dist/frontend/lowering/class-prototypes.d.ts +10 -3
- package/dist/frontend/lowering/class-prototypes.js +188 -21
- package/dist/frontend/lowering/class-prototypes.js.map +1 -1
- package/dist/frontend/lowering/class-runtime-statics.d.ts +8 -0
- package/dist/frontend/lowering/class-runtime-statics.js +84 -0
- package/dist/frontend/lowering/class-runtime-statics.js.map +1 -0
- package/dist/frontend/lowering/class-static-data.d.ts +1 -0
- package/dist/frontend/lowering/class-static-data.js +6 -4
- package/dist/frontend/lowering/class-static-data.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.js +66 -17
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/containers/string-and-regexp.js +24 -47
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
- package/dist/frontend/lowering/deferred-module-initializers.d.ts +13 -0
- package/dist/frontend/lowering/deferred-module-initializers.js +410 -0
- package/dist/frontend/lowering/deferred-module-initializers.js.map +1 -0
- package/dist/frontend/lowering/deferred-module-initializers.test.d.ts +1 -0
- package/dist/frontend/lowering/deferred-module-initializers.test.js +92 -0
- package/dist/frontend/lowering/deferred-module-initializers.test.js.map +1 -0
- package/dist/frontend/lowering/error-methods.d.ts +4 -1
- package/dist/frontend/lowering/error-methods.js +100 -1
- package/dist/frontend/lowering/error-methods.js.map +1 -1
- package/dist/frontend/lowering/expressions/native-proxy.js +3 -1
- package/dist/frontend/lowering/expressions/native-proxy.js.map +1 -1
- package/dist/frontend/lowering/expressions/nullish-assignment.js +19 -11
- package/dist/frontend/lowering/expressions/nullish-assignment.js.map +1 -1
- package/dist/frontend/lowering/expressions/object-literals.js +68 -7
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
- package/dist/frontend/lowering/expressions/optional-chains.d.ts +3 -4
- package/dist/frontend/lowering/expressions/optional-chains.js +72 -20
- package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
- package/dist/frontend/lowering/function-context.d.ts +11 -3
- package/dist/frontend/lowering/function-context.js +11 -5
- package/dist/frontend/lowering/function-context.js.map +1 -1
- package/dist/frontend/lowering/generator-dynamic-dispatch.d.ts +1 -0
- package/dist/frontend/lowering/generator-dynamic-dispatch.js +39 -1
- package/dist/frontend/lowering/generator-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/lib-boundary.d.ts +2 -0
- package/dist/frontend/lowering/lib-boundary.js +54 -1
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-builtin-values.d.ts +7 -1
- package/dist/frontend/lowering/lower-builtin-values.js +113 -3
- package/dist/frontend/lowering/lower-builtin-values.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +10 -14
- package/dist/frontend/lowering/lower-builtins.js +443 -88
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +8 -5
- package/dist/frontend/lowering/lower-calls.js +717 -142
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +18 -13
- package/dist/frontend/lowering/lower-classes.js +1076 -188
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +9 -0
- package/dist/frontend/lowering/lower-containers.js +172 -51
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.js +3 -2
- package/dist/frontend/lowering/lower-dgram.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +12 -8
- package/dist/frontend/lowering/lower-exprs.js +802 -232
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.js +17 -2
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +15 -1
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +2 -0
- package/dist/frontend/lowering/lower-island.js +36 -10
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-island.test.d.ts +1 -0
- package/dist/frontend/lowering/lower-island.test.js +51 -0
- package/dist/frontend/lowering/lower-island.test.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +1 -3
- package/dist/frontend/lowering/lower-modules.js +380 -73
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.test.d.ts +1 -0
- package/dist/frontend/lowering/lower-modules.test.js +107 -0
- package/dist/frontend/lowering/lower-modules.test.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +7 -3
- package/dist/frontend/lowering/lower-stmts.js +221 -47
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.test.d.ts +1 -0
- package/dist/frontend/lowering/lower-stmts.test.js +77 -0
- package/dist/frontend/lowering/lower-stmts.test.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.js +3 -1
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lower-url.d.ts +2 -1
- package/dist/frontend/lowering/lower-url.js +32 -9
- package/dist/frontend/lowering/lower-url.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +53 -10
- package/dist/frontend/lowering/lowerer.js +390 -77
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/lowerer.test.js +187 -3
- package/dist/frontend/lowering/lowerer.test.js.map +1 -1
- package/dist/frontend/lowering/module-namespace-elements.d.ts +7 -0
- package/dist/frontend/lowering/module-namespace-elements.js +54 -0
- package/dist/frontend/lowering/module-namespace-elements.js.map +1 -0
- package/dist/frontend/lowering/object-factory-new.d.ts +4 -3
- package/dist/frontend/lowering/object-factory-new.js +27 -14
- package/dist/frontend/lowering/object-factory-new.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +3 -0
- package/dist/frontend/lowering/surfaces.js +48 -19
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/lowering/text-decoder-encoding.d.ts +2 -0
- package/dist/frontend/lowering/text-decoder-encoding.js +8 -0
- package/dist/frontend/lowering/text-decoder-encoding.js.map +1 -1
- package/dist/frontend/lowering/text-decoder-encoding.test.js +2 -1
- package/dist/frontend/lowering/text-decoder-encoding.test.js.map +1 -1
- package/dist/frontend/lowering/unregistered-class-types.test.js +3 -3
- package/dist/frontend/lowering/unregistered-class-types.test.js.map +1 -1
- package/dist/frontend/npm-static-prune.js +116 -34
- package/dist/frontend/npm-static-prune.js.map +1 -1
- package/dist/frontend/npm-static-prune.test.js +8 -0
- package/dist/frontend/npm-static-prune.test.js.map +1 -1
- package/dist/frontend/program.d.ts +1 -1
- package/dist/frontend/program.js +57 -26
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/program.test.js +18 -0
- package/dist/frontend/program.test.js.map +1 -1
- package/dist/frontend/ts7/ast.js +4 -3
- package/dist/frontend/ts7/ast.js.map +1 -1
- package/dist/frontend/ts7/semantic-json.js +5 -10
- package/dist/frontend/ts7/semantic-json.js.map +1 -1
- package/dist/frontend/type-mapper.d.ts +9 -1
- package/dist/frontend/type-mapper.js +169 -23
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/frontend/type-mapper.test.js +23 -0
- package/dist/frontend/type-mapper.test.js.map +1 -1
- package/dist/frontend/union-discriminants.d.ts +1 -1
- package/dist/frontend/union-discriminants.js +24 -9
- package/dist/frontend/union-discriminants.js.map +1 -1
- package/dist/index.js +16 -10
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.js +2 -0
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/ir.d.ts +74 -41
- package/dist/ir/ir.js +167 -43
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/ir.test.js +35 -0
- package/dist/ir/ir.test.js.map +1 -1
- package/dist/ir/serialize.js +35 -3
- package/dist/ir/serialize.js.map +1 -1
- package/dist/ir/serialize.test.js +26 -1
- package/dist/ir/serialize.test.js.map +1 -1
- package/dist/ir/traverse.d.ts +0 -3
- package/dist/ir/traverse.js +33 -20
- package/dist/ir/traverse.js.map +1 -1
- package/dist/ir/validate.js +136 -16
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +77 -1
- package/dist/ir/validate.test.js.map +1 -1
- package/dist/library/compiler-self-identity.js +1 -1
- package/dist/library/compiler-self-identity.test.js +2 -2
- package/dist/library/compiler-self-identity.test.js.map +1 -1
- package/dist/library/int-infer.d.ts +2 -2
- package/dist/library/int-infer.js +10 -7
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +44 -0
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/prepare.js +2 -1
- package/dist/library/prepare.js.map +1 -1
- package/dist/native/sanitizer.js +3 -1
- package/dist/native/sanitizer.js.map +1 -1
- package/dist/native/toolchain.d.ts +1 -1
- package/dist/native/toolchain.js +26 -6
- package/dist/native/toolchain.js.map +1 -1
- package/dist/native/toolchain.test.js +53 -1
- package/dist/native/toolchain.test.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +170 -93
|
@@ -1,26 +1,76 @@
|
|
|
1
|
-
import { BOOL, DYN, DYN_CLASS_PROPERTIES as PROPERTY_BAG, STRING, VOID, canConvertToDyn, canDynCheckTo, isClassOwnEnumerableFieldName, isDynTypedRefType, isUnitType, typeEquals, typeKey } from "../../ir/ir.js";
|
|
1
|
+
import { BOOL, DYN, F64, DYN_CLASS_PROPERTIES as PROPERTY_BAG, STRING, VOID, canConvertToDyn, canDynCheckTo, isClassOwnEnumerableFieldName, isDynTypedRefType, isUnitType, typeEquals, typeKey } from "../../ir/ir.js";
|
|
2
2
|
import { streamTypedRefEligible } from "../../ir/analysis.js";
|
|
3
3
|
import { varRef } from "../../ir/build.js";
|
|
4
|
-
import { everyStmtList, transformStmtList } from "../../ir/traverse.js";
|
|
4
|
+
import { everyStmtList, transformExpr, transformStmtList } from "../../ir/traverse.js";
|
|
5
5
|
import { dynUndefinedExpr, PoisonError } from "./lowerer.js";
|
|
6
6
|
import { implicitDefaultInstance } from "./lower-calls.js";
|
|
7
|
-
import { accessorCall, findGenericMethodOn, findMethodOn, genericOverrideBelow, upcastTo } from "./lower-classes.js";
|
|
8
|
-
import { classPrototypeData, hasClassPrototypeData } from "./class-prototypes.js";
|
|
9
|
-
import { errorToStringCall, errorToStringMethod, refreshErrorMethodDispatch } from "./error-methods.js";
|
|
7
|
+
import { accessorCall, classValueRef, findGenericMethodOn, findGenericStaticOn, findMethodOn, findStaticOn, genericOverrideBelow, upcastTo } from "./lower-classes.js";
|
|
8
|
+
import { classPrototypeData, hasClassPrototypeData, reflectClassPrototype } from "./class-prototypes.js";
|
|
9
|
+
import { errorPropertyRead, errorPropertyWrite, errorToStringCall, errorToStringMethod, refreshErrorPropertyDispatch, refreshErrorMethodDispatch } from "./error-methods.js";
|
|
10
10
|
import { GeneratorDynamicDispatch } from "./generator-dynamic-dispatch.js";
|
|
11
|
-
import { classMethodValue } from "./class-method-values.js";
|
|
11
|
+
import { classMethodValue, reflectedClassStaticMethodValue, refreshClassMethodValueSelections } from "./class-method-values.js";
|
|
12
|
+
import { classStaticData } from "./class-static-data.js";
|
|
13
|
+
import { isJsSourceFile } from "../program.js";
|
|
12
14
|
import * as ts from "../ts7/adapter.js";
|
|
13
15
|
import { SYMBOL_T } from "../../ir/ir.js";
|
|
14
16
|
import { isClassCallback } from "./class-callbacks.js";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
import { refreshDescriptorGuards } from "./class-descriptors.js";
|
|
18
|
+
import { ClassConstructionDispatch } from "./class-construction.js";
|
|
19
|
+
import { emitterRooted } from "./lower-event-emitter.js";
|
|
20
|
+
const STREAM_METHODS = new Set(["on", "addListener", "once", "prependListener", "prependOnceListener", "off", "removeListener", "read", "pause", "resume", "isPaused", "destroy"]);
|
|
21
|
+
const EMITTER_METHODS = new Set(["emit", "on", "addListener", "once", "prependListener", "prependOnceListener", "off", "removeListener"]);
|
|
22
|
+
const STREAM_BOOL_PROPERTIES = new Set(["readable", "readableEnded", "writable", "writableEnded", "writableFinished", "writableNeedDrain", "destroyed", "closed", "readableObjectMode", "writableObjectMode", "allowHalfOpen"]);
|
|
23
|
+
const STREAM_NUM_PROPERTIES = new Set(["readableLength", "readableHighWaterMark", "writableLength", "writableHighWaterMark", "writableCorked"]);
|
|
24
|
+
const streamProperty = (name) => STREAM_BOOL_PROPERTIES.has(name) || STREAM_NUM_PROPERTIES.has(name);
|
|
25
|
+
function symbolMemberKey(lowerer, info, name, loc) {
|
|
26
|
+
if (name.startsWith("sym:"))
|
|
27
|
+
return lowerer.coerceToExpected({ kind: "libCall", fn: "sym.wellKnown",
|
|
28
|
+
args: [{ kind: "strLit", value: name.slice(4), type: STRING, loc }], type: SYMBOL_T, loc }, DYN);
|
|
29
|
+
if (info.symbolFields !== undefined) {
|
|
30
|
+
for (const [symbol, member] of info.symbolFields) {
|
|
31
|
+
if (member !== name)
|
|
32
|
+
continue;
|
|
33
|
+
const global = lowerer.globalsBySymbol.get(symbol);
|
|
34
|
+
if (global)
|
|
35
|
+
return lowerer.coerceToExpected(varRef(global.id, global.type, loc), DYN);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (info.symbolMethods !== undefined) {
|
|
39
|
+
for (const [symbol, member] of info.symbolMethods) {
|
|
40
|
+
if (member !== name)
|
|
41
|
+
continue;
|
|
42
|
+
const global = lowerer.globalsBySymbol.get(symbol);
|
|
43
|
+
if (global)
|
|
44
|
+
return lowerer.coerceToExpected(varRef(global.id, global.type, loc), DYN);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
export function classInstanceOf(lowerer, value, info, loc, classValue) {
|
|
50
|
+
const name = `%dyn.class.${classValue ? "valueInstanceof" : "instanceof"}:${info.def.name}`;
|
|
51
|
+
const params = [{ localId: "value", name: "value", type: DYN },
|
|
52
|
+
...(classValue ? [{ localId: "class", name: "class", type: classValue.type }] : [])];
|
|
53
|
+
if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
|
|
54
|
+
const candidate = varRef("candidate", DYN, loc);
|
|
55
|
+
const prototype = classPrototypeData(lowerer, info, loc, classValue ? varRef("class", classValue.type, loc) : undefined);
|
|
56
|
+
const body = prototype ? [
|
|
57
|
+
{ kind: "varDecl", localId: "candidate", init: varRef("value", DYN, loc), loc },
|
|
58
|
+
{ kind: "while", cond: { kind: "ternary", cond: { kind: "dynTest", test: "nullish", value: candidate, type: BOOL, loc },
|
|
59
|
+
then: { kind: "boolLit", value: false, type: BOOL, loc }, else_: { kind: "dynTest", test: "object", value: candidate, type: BOOL, loc }, type: BOOL, loc }, body: [
|
|
60
|
+
{ kind: "assign", localId: "candidate", value: { kind: "libCall", fn: "dyn.getPrototype", args: [candidate], type: DYN, loc, prototypeIdentityOnly: true }, loc },
|
|
61
|
+
{ kind: "if", cond: { kind: "dynScalarEq", left: candidate, right: prototype, type: BOOL, loc }, then: [
|
|
62
|
+
{ kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc },
|
|
63
|
+
], else_: null, loc },
|
|
64
|
+
], loc },
|
|
65
|
+
] : [];
|
|
66
|
+
body.push({ kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc });
|
|
18
67
|
lowerer.liftedFns.push({
|
|
19
|
-
name, params
|
|
20
|
-
locals: [{ id:
|
|
21
|
-
|
|
68
|
+
name, params, returnType: BOOL,
|
|
69
|
+
locals: [...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
|
|
70
|
+
...(prototype ? [{ id: "candidate", name: "candidate", type: DYN, mutable: true }] : [])], body, loc,
|
|
22
71
|
});
|
|
23
|
-
|
|
72
|
+
}
|
|
73
|
+
return { kind: "call", callee: name, args: [value, ...(classValue ? [classValue] : [])], type: BOOL, loc };
|
|
24
74
|
}
|
|
25
75
|
export function classPropertiesHelper(lowerer, loc) {
|
|
26
76
|
const name = "%dyn.class.properties";
|
|
@@ -35,6 +85,82 @@ export function classPropertiesHelper(lowerer, loc) {
|
|
|
35
85
|
lowerer.liftedFns.push(helper);
|
|
36
86
|
return helper;
|
|
37
87
|
}
|
|
88
|
+
/** A for-in presence recheck needs only the live property table, avoiding
|
|
89
|
+
* value conversion or a dispatch branch for every possible member name. */
|
|
90
|
+
export function classForInHasKey(lowerer, value, key, loc) {
|
|
91
|
+
const name = "%dyn.class.forInHasKey";
|
|
92
|
+
if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
|
|
93
|
+
lowerer.liftedFns.push({ name, params: [{ localId: "value", name: "value", type: DYN }, { localId: "key", name: "key", type: STRING }], returnType: BOOL,
|
|
94
|
+
locals: [{ id: "value", name: "value", type: DYN, mutable: false }, { id: "key", name: "key", type: STRING, mutable: false }],
|
|
95
|
+
body: [{ kind: "return", value: { kind: "libCall", fn: "dyn.hasKey", args: [
|
|
96
|
+
{ kind: "call", callee: classPropertiesHelper(lowerer, loc).name, args: [varRef("value", DYN, loc)], type: DYN, loc },
|
|
97
|
+
varRef("key", STRING, loc),
|
|
98
|
+
], type: BOOL, loc }, loc }], loc,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return { kind: "call", callee: name, args: [value, key], type: BOOL, loc };
|
|
102
|
+
}
|
|
103
|
+
/** Record field creation after the dispatch worklist closes, including writes
|
|
104
|
+
* in generated member tables. Native values stay in their slots; the bag
|
|
105
|
+
* owns only their property presence, attributes, and insertion position. */
|
|
106
|
+
export function trackClassFieldCreation(lowerer, functions) {
|
|
107
|
+
if (![...lowerer.classes.values()].some((info) => info.def.tracksOwnFields))
|
|
108
|
+
return;
|
|
109
|
+
const loc = functions[0].loc;
|
|
110
|
+
const name = "%dyn.class.fieldCreated";
|
|
111
|
+
const value = varRef("value", DYN, loc), key = varRef("key", DYN, loc);
|
|
112
|
+
const bag = varRef("bag", DYN, loc);
|
|
113
|
+
const created = (receiver, className, field, loc) => ({ kind: "exprStmt", expr: {
|
|
114
|
+
kind: "call", callee: name, args: [lowerer.coerceToExpected(receiver, DYN), symbolMemberKey(lowerer, lowerer.classes.get(className), field, loc) ??
|
|
115
|
+
lowerer.coerceToExpected({ kind: "strLit", value: field, type: STRING, loc }, DYN)], type: VOID, loc,
|
|
116
|
+
}, loc });
|
|
117
|
+
const tracked = (className, field) => !!lowerer.classes.get(className)?.def.tracksOwnFields && (isClassOwnEnumerableFieldName(field) ||
|
|
118
|
+
symbolMemberKey(lowerer, lowerer.classes.get(className), field, loc) !== null);
|
|
119
|
+
for (const fn of functions) {
|
|
120
|
+
const instrumented = new WeakSet();
|
|
121
|
+
const local = (type, loc) => {
|
|
122
|
+
const id = `%fieldCreated.${fn.locals.length}`;
|
|
123
|
+
fn.locals.push({ id, name: id, type, mutable: false });
|
|
124
|
+
return { kind: "varRef", localId: id, type, loc };
|
|
125
|
+
};
|
|
126
|
+
fn.body = transformStmtList(fn.body, {
|
|
127
|
+
stmt: (statement) => {
|
|
128
|
+
if (statement.kind !== "fieldSet" || instrumented.has(statement) || !tracked(statement.className, statement.field))
|
|
129
|
+
return statement;
|
|
130
|
+
const receiver = local(statement.obj.type, statement.loc);
|
|
131
|
+
const store = { ...statement, obj: receiver };
|
|
132
|
+
instrumented.add(store);
|
|
133
|
+
return { kind: "block", body: [
|
|
134
|
+
{ kind: "varDecl", localId: receiver.localId, init: statement.obj, loc: statement.loc },
|
|
135
|
+
store, created(receiver, statement.className, statement.field, statement.loc),
|
|
136
|
+
], loc: statement.loc };
|
|
137
|
+
},
|
|
138
|
+
expr: (expression) => {
|
|
139
|
+
if (expression.kind !== "fieldIncDec" || instrumented.has(expression) || !tracked(expression.className, expression.field))
|
|
140
|
+
return expression;
|
|
141
|
+
const receiver = local(expression.obj.type, expression.loc), result = local(expression.type, expression.loc);
|
|
142
|
+
const update = { ...expression, obj: receiver };
|
|
143
|
+
instrumented.add(update);
|
|
144
|
+
return { kind: "seqExpr", stmts: [
|
|
145
|
+
{ kind: "varDecl", localId: receiver.localId, init: expression.obj, loc: expression.loc },
|
|
146
|
+
{ kind: "varDecl", localId: result.localId, init: update, loc: expression.loc },
|
|
147
|
+
created(receiver, expression.className, expression.field, expression.loc),
|
|
148
|
+
], result, type: expression.type, loc: expression.loc };
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
functions.push({ name, params: [{ localId: "value", name: "value", type: DYN }, { localId: "key", name: "key", type: DYN }], returnType: VOID,
|
|
153
|
+
locals: [{ id: "value", name: "value", type: DYN, mutable: false }, { id: "key", name: "key", type: DYN, mutable: false }, { id: "bag", name: "bag", type: DYN, mutable: false }],
|
|
154
|
+
body: [
|
|
155
|
+
{ kind: "varDecl", localId: "bag", init: { kind: "call", callee: classPropertiesHelper(lowerer, loc).name, args: [value], type: DYN, loc }, loc },
|
|
156
|
+
{ kind: "if", cond: { kind: "unary", op: "!", operand: { kind: "libCall", fn: "dyn.hasOwnComputed", args: [bag, key], type: BOOL, loc }, type: BOOL, loc }, then: [
|
|
157
|
+
{ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.defineProperty", args: [bag, lowerer.coerceToExpected(key, DYN), { kind: "dynObjLit", fields: [
|
|
158
|
+
{ key: { kind: "strLit", value: "value", type: STRING, loc }, value: dynUndefinedExpr(loc) },
|
|
159
|
+
...["writable", "enumerable", "configurable"].map((key) => ({ key: { kind: "strLit", value: key, type: STRING, loc }, value: lowerer.coerceToExpected({ kind: "boolLit", value: true, type: BOOL, loc }, DYN) })),
|
|
160
|
+
], type: DYN, loc }], type: DYN, loc }, loc },
|
|
161
|
+
], else_: null, loc },
|
|
162
|
+
], loc, });
|
|
163
|
+
}
|
|
38
164
|
/** Calls on native class capsules keep the instance's compiled methods. The
|
|
39
165
|
* reachable-body fixed point discovers both the boxed classes and method
|
|
40
166
|
* names before generating checked native dispatch; ordinary dyn receivers
|
|
@@ -45,20 +171,51 @@ export class ClassDynamicDispatch {
|
|
|
45
171
|
this.asyncFree = asyncFree;
|
|
46
172
|
}
|
|
47
173
|
generatorDispatch = new GeneratorDynamicDispatch();
|
|
174
|
+
constructionDispatch = new ClassConstructionDispatch();
|
|
48
175
|
boxed = new Set();
|
|
176
|
+
boxedConstructors = new Set();
|
|
49
177
|
dispatches = new Map();
|
|
50
178
|
properties = new Map();
|
|
51
179
|
computed = new Map();
|
|
180
|
+
computedClassHelpers = new Map();
|
|
181
|
+
normalization = null;
|
|
182
|
+
normalizationKey = "";
|
|
183
|
+
staticPropertyBag = null;
|
|
184
|
+
staticBagClasses = new Set();
|
|
52
185
|
propertyBag = null;
|
|
53
186
|
errorStrings = new Map();
|
|
54
187
|
bagClasses = new Set();
|
|
55
188
|
bagInitializers = new Map();
|
|
56
189
|
typedPropertyBags = new Map();
|
|
57
190
|
generated = new Set();
|
|
191
|
+
constructDispatch = null;
|
|
192
|
+
constructors = new Map();
|
|
193
|
+
constructed = new Set();
|
|
58
194
|
instanceTests = new Map();
|
|
195
|
+
jsonPrototypes = new Set();
|
|
196
|
+
serializesClasses = false;
|
|
197
|
+
descriptorHelper = null;
|
|
198
|
+
prototypeHelper = null;
|
|
199
|
+
prototypeClasses = new Set();
|
|
200
|
+
prototypeIdentityHelper = null;
|
|
201
|
+
prototypeIdentityClasses = new Set();
|
|
202
|
+
instancePrototypes = new Map();
|
|
203
|
+
checkedCasts = new Map();
|
|
204
|
+
enumeration = null;
|
|
205
|
+
enumerationClasses = new Set();
|
|
59
206
|
process(lowerer, functions) {
|
|
207
|
+
// Dispatch tables grow between worklist waves. Keep their normalization
|
|
208
|
+
// entry ahead of every newly inserted branch after rebuilding the tables.
|
|
209
|
+
for (const fn of this.normalizedDispatches()) {
|
|
210
|
+
const first = fn.body[0];
|
|
211
|
+
if (first?.kind === "assign" && first.value.kind === "call" && first.value.callee === this.normalization?.name)
|
|
212
|
+
fn.body.shift();
|
|
213
|
+
}
|
|
60
214
|
let changed = refreshErrorMethodDispatch(lowerer, functions);
|
|
215
|
+
changed = refreshClassMethodValueSelections(lowerer) || changed;
|
|
216
|
+
changed = refreshErrorPropertyDispatch(lowerer, functions) || changed;
|
|
61
217
|
changed = this.generatorDispatch.process(lowerer, functions, this.generated) || changed;
|
|
218
|
+
changed = this.constructionDispatch.process(lowerer, functions) || changed;
|
|
62
219
|
const seenTypes = new Set();
|
|
63
220
|
const rewrite = new Set();
|
|
64
221
|
const discover = (type) => {
|
|
@@ -66,8 +223,12 @@ export class ClassDynamicDispatch {
|
|
|
66
223
|
if (seenTypes.has(key))
|
|
67
224
|
return;
|
|
68
225
|
seenTypes.add(key);
|
|
69
|
-
if (
|
|
70
|
-
|
|
226
|
+
if (type.kind === "object") {
|
|
227
|
+
if (isDynTypedRefType(type))
|
|
228
|
+
this.boxed.add(type.className);
|
|
229
|
+
}
|
|
230
|
+
else if (type.kind === "classval")
|
|
231
|
+
this.boxedConstructors.add(type.className);
|
|
71
232
|
else if (type.kind === "array")
|
|
72
233
|
discover(type.elem);
|
|
73
234
|
else if (type.kind === "record") {
|
|
@@ -85,12 +246,27 @@ export class ClassDynamicDispatch {
|
|
|
85
246
|
};
|
|
86
247
|
for (const fn of functions)
|
|
87
248
|
everyStmtList(fn.body, {
|
|
88
|
-
stmt: () =>
|
|
249
|
+
stmt: (statement) => {
|
|
250
|
+
// Field-creation instrumentation can box a base-constructor receiver
|
|
251
|
+
// even when only its derived instances otherwise cross the boundary.
|
|
252
|
+
if (statement.kind === "fieldSet" && lowerer.classes.get(statement.className)?.def.tracksOwnFields &&
|
|
253
|
+
isClassOwnEnumerableFieldName(statement.field))
|
|
254
|
+
discover(statement.obj.type);
|
|
255
|
+
return true;
|
|
256
|
+
},
|
|
89
257
|
expr: (expr) => {
|
|
258
|
+
if (expr.kind === "jsonStringify" || expr.kind === "libCall" &&
|
|
259
|
+
(expr.fn === "json.stringifyValue" || expr.fn === "json.stringifyReplacer")) {
|
|
260
|
+
this.serializesClasses = true;
|
|
261
|
+
}
|
|
90
262
|
switch (expr.kind) {
|
|
91
263
|
case "dynFrom":
|
|
92
264
|
discover(expr.value.type);
|
|
93
265
|
break;
|
|
266
|
+
case "fieldIncDec":
|
|
267
|
+
if (lowerer.classes.get(expr.className)?.def.tracksOwnFields && isClassOwnEnumerableFieldName(expr.field))
|
|
268
|
+
discover(expr.obj.type);
|
|
269
|
+
break;
|
|
94
270
|
case "call":
|
|
95
271
|
if (expr.callee === "%dyn.class.properties" && expr.args[0]?.kind === "dynFrom" && isDynTypedRefType(expr.args[0].value.type))
|
|
96
272
|
rewrite.add(fn);
|
|
@@ -99,9 +275,13 @@ export class ClassDynamicDispatch {
|
|
|
99
275
|
case "dynInvoke":
|
|
100
276
|
rewrite.add(fn);
|
|
101
277
|
break;
|
|
278
|
+
case "dynCheck":
|
|
279
|
+
if (isDynTypedRefType(expr.type))
|
|
280
|
+
rewrite.add(fn);
|
|
281
|
+
break;
|
|
102
282
|
case "libCall":
|
|
103
|
-
if (expr.fn === "dyn.keySet" || expr.fn === "dyn.keySetComputed" || (expr.fn === "dyn.iterator" || expr.fn === "dyn.arrayFromIterator") || expr.fn === "dyn.toString" || expr.fn === "dyn.stringConstructor" ||
|
|
104
|
-
expr.fn === "dyn.hasKeyComputed" || expr.fn === "dyn.hasOwnComputed" || expr.fn === "dyn.propertyIsEnumerableComputed")
|
|
283
|
+
if (expr.fn === "bytes.construct" || expr.fn === "dyn.keySet" || expr.fn === "dyn.keySetComputed" || (expr.fn === "dyn.iterator" || expr.fn === "dyn.arrayFromIterator") || expr.fn === "dyn.toString" || expr.fn === "dyn.stringConstructor" ||
|
|
284
|
+
expr.fn === "dyn.forInKeys" || expr.fn === "dyn.hasKeyComputed" || expr.fn === "dyn.hasOwnComputed" || expr.fn === "dyn.propertyIsEnumerableComputed" || expr.fn === "dyn.defineProperty" || expr.fn === "dyn.getPrototype" || expr.fn === "dyn.reflectGet" || expr.fn === "dyn.reflectSet")
|
|
105
285
|
rewrite.add(fn);
|
|
106
286
|
break;
|
|
107
287
|
}
|
|
@@ -111,9 +291,9 @@ export class ClassDynamicDispatch {
|
|
|
111
291
|
if (this.boxed.size === 0 && rewrite.size === 0)
|
|
112
292
|
return changed;
|
|
113
293
|
for (const fn of functions) {
|
|
114
|
-
if (!fn.name.startsWith("%dyn.class.instanceof:"))
|
|
294
|
+
if (!fn.name.startsWith("%dyn.class.instanceof:") && !fn.name.startsWith("%dyn.class.valueInstanceof:"))
|
|
115
295
|
continue;
|
|
116
|
-
const target = fn.name.slice(
|
|
296
|
+
const target = fn.name.slice(fn.name.indexOf(":") + 1);
|
|
117
297
|
let checked = this.instanceTests.get(fn.name);
|
|
118
298
|
if (!checked) {
|
|
119
299
|
checked = new Set();
|
|
@@ -129,8 +309,10 @@ export class ClassDynamicDispatch {
|
|
|
129
309
|
const loc = fn.loc;
|
|
130
310
|
const value = varRef("value", DYN, loc);
|
|
131
311
|
const type = { kind: "object", className };
|
|
132
|
-
const result = subtype
|
|
133
|
-
|
|
312
|
+
const result = subtype && fn.params[1] && lowerer.classes.get(target)?.localClass
|
|
313
|
+
? { kind: "instanceOfValue", value: { kind: "dynCheck", value, type, loc }, classValue: varRef("class", fn.params[1].type, loc), type: BOOL, loc }
|
|
314
|
+
: subtype ? { kind: "boolLit", value: true, type: BOOL, loc }
|
|
315
|
+
: { kind: "instanceOf", value: { kind: "dynCheck", value, type, loc }, className: target, type: BOOL, loc };
|
|
134
316
|
fn.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
|
|
135
317
|
{ kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc }, then: [{ kind: "return", value: result, loc }], else_: null, loc });
|
|
136
318
|
changed = true;
|
|
@@ -145,33 +327,146 @@ export class ClassDynamicDispatch {
|
|
|
145
327
|
const info = lowerer.classes.get(className);
|
|
146
328
|
if (!info || info.builtinEmitter || info.builtinStream || info.builtinError)
|
|
147
329
|
continue;
|
|
330
|
+
if (info.symbolFields && info.def.symbolFields === undefined) {
|
|
331
|
+
info.def.symbolFields = [...info.symbolFields].flatMap(([symbol, field]) => {
|
|
332
|
+
const global = lowerer.globalsBySymbol.get(symbol);
|
|
333
|
+
return global?.type.kind === "symbol" ? [{ field, globalId: global.id }] : [];
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (this.prototypeHelper && !this.prototypeClasses.has(className)) {
|
|
337
|
+
this.prototypeClasses.add(className);
|
|
338
|
+
const loc = this.prototypeHelper.loc;
|
|
339
|
+
const type = { kind: "object", className };
|
|
340
|
+
const value = varRef("value", DYN, loc);
|
|
341
|
+
const prototype = reflectClassPrototype(lowerer, info, loc, { kind: "dynCheck", value, type, loc });
|
|
342
|
+
if (prototype)
|
|
343
|
+
this.prototypeHelper.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
|
|
344
|
+
{ kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc },
|
|
345
|
+
then: [{ kind: "return", value: prototype, loc }], else_: null, loc });
|
|
346
|
+
changed = true;
|
|
347
|
+
}
|
|
348
|
+
if (this.prototypeIdentityHelper && !this.prototypeIdentityClasses.has(className)) {
|
|
349
|
+
this.prototypeIdentityClasses.add(className);
|
|
350
|
+
const loc = this.prototypeIdentityHelper.loc;
|
|
351
|
+
const type = { kind: "object", className };
|
|
352
|
+
const value = varRef("value", DYN, loc);
|
|
353
|
+
const prototype = classPrototypeData(lowerer, info, loc, { kind: "dynCheck", value, type, loc });
|
|
354
|
+
if (prototype)
|
|
355
|
+
this.prototypeIdentityHelper.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
|
|
356
|
+
{ kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc },
|
|
357
|
+
then: [{ kind: "return", value: prototype, loc }], else_: null, loc });
|
|
358
|
+
changed = true;
|
|
359
|
+
}
|
|
360
|
+
if (this.serializesClasses && !this.jsonPrototypes.has(className) && info.decl && findMethodOn(lowerer, info, "toJSON")) {
|
|
361
|
+
this.jsonPrototypes.add(className);
|
|
362
|
+
const loc = this.propertyBag.loc;
|
|
363
|
+
const method = classMethodValue(lowerer, info.decl, info, "toJSON", loc);
|
|
364
|
+
const prototype = method ? classPrototypeData(lowerer, info, loc) : null;
|
|
365
|
+
const helper = lowerer.liftedFns.find((fn) => fn.name === info.def.prototypeDataHelper);
|
|
366
|
+
const init = helper?.body[0];
|
|
367
|
+
if (!info.prototypeReflectionReady && method && prototype && init?.kind === "if" && canConvertToDyn(method.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
|
|
368
|
+
const descriptor = { kind: "exprStmt", expr: {
|
|
369
|
+
kind: "libCall", fn: "dyn.defineProperty", args: [prototype,
|
|
370
|
+
{ kind: "dynFrom", value: { kind: "strLit", value: "toJSON", type: STRING, loc }, type: DYN, loc },
|
|
371
|
+
{ kind: "dynObjLit", fields: [
|
|
372
|
+
{ key: { kind: "strLit", value: "value", type: STRING, loc }, value: { kind: "dynFrom", value: method, type: DYN, loc } },
|
|
373
|
+
...["writable", "configurable"].map((key) => ({ key: { kind: "strLit", value: key, type: STRING, loc }, value: { kind: "dynFrom", value: { kind: "boolLit", value: true, type: BOOL, loc }, type: DYN, loc } })),
|
|
374
|
+
], type: DYN, loc },
|
|
375
|
+
], type: DYN, loc
|
|
376
|
+
}, loc };
|
|
377
|
+
init.then.push(descriptor);
|
|
378
|
+
changed = true;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
for (const plan of this.propertyReceivers(lowerer, false, false)) {
|
|
383
|
+
const info = plan.info;
|
|
384
|
+
const className = info.def.name;
|
|
385
|
+
let instancePrototype = this.instancePrototypes.get(plan.capsule.def.name);
|
|
386
|
+
if (!instancePrototype && hasClassPrototypeData(info)) {
|
|
387
|
+
const name = `%class.instancePrototype:${plan.capsule.def.name}`;
|
|
388
|
+
const type = { kind: "object", className: plan.capsule.def.name };
|
|
389
|
+
const loc = this.propertyBag.loc;
|
|
390
|
+
const fn = { name, params: [{ localId: "this", name: "this", type }],
|
|
391
|
+
locals: [{ id: "this", name: "this", type, mutable: false }], returnType: DYN,
|
|
392
|
+
body: [{ kind: "return", value: { kind: "libCall", fn: "dyn.getPrototype",
|
|
393
|
+
args: [{ kind: "dynObjLit", fields: [], type: DYN, loc }], type: DYN, loc }, loc }], loc };
|
|
394
|
+
plan.capsule.def.instancePrototypeHelper = name;
|
|
395
|
+
instancePrototype = { fn, classes: new Set() };
|
|
396
|
+
this.instancePrototypes.set(plan.capsule.def.name, instancePrototype);
|
|
397
|
+
this.generated.add(fn);
|
|
398
|
+
lowerer.liftedFns.push(fn);
|
|
399
|
+
}
|
|
400
|
+
if (instancePrototype && hasClassPrototypeData(info) && !instancePrototype.classes.has(className)) {
|
|
401
|
+
instancePrototype.classes.add(className);
|
|
402
|
+
const loc = instancePrototype.fn.loc;
|
|
403
|
+
const receiver = varRef("this", instancePrototype.fn.params[0].type, loc);
|
|
404
|
+
const prototype = classPrototypeData(lowerer, info, loc, info === plan.capsule ? receiver
|
|
405
|
+
: { kind: "downcast", value: receiver, type: { kind: "object", className }, loc });
|
|
406
|
+
const returned = { kind: "return", value: prototype ?? { kind: "libCall", fn: "dyn.getPrototype",
|
|
407
|
+
args: [{ kind: "dynObjLit", fields: [], type: DYN, loc }], type: DYN, loc }, loc };
|
|
408
|
+
if (info === plan.capsule)
|
|
409
|
+
instancePrototype.fn.body[instancePrototype.fn.body.length - 1] = returned;
|
|
410
|
+
else
|
|
411
|
+
instancePrototype.fn.body.unshift({ kind: "if",
|
|
412
|
+
cond: { kind: "instanceOf", value: receiver, className, type: BOOL, loc }, then: [returned], else_: null, loc });
|
|
413
|
+
changed = true;
|
|
414
|
+
}
|
|
415
|
+
const needsPrototype = (current) => hasClassPrototypeData(current) || current.subclasses.some((child) => lowerer.classCanBeConstructed(child) && needsPrototype(child));
|
|
416
|
+
const prototypeFor = (current, receiver, loc) => {
|
|
417
|
+
const currentReceiver = receiver.type.kind === "object" && receiver.type.className !== current.def.name
|
|
418
|
+
? { kind: "downcast", value: receiver, type: { kind: "object", className: current.def.name }, loc }
|
|
419
|
+
: receiver;
|
|
420
|
+
let result = classPrototypeData(lowerer, current, loc, currentReceiver);
|
|
421
|
+
for (const child of current.subclasses) {
|
|
422
|
+
if (!lowerer.classCanBeConstructed(child))
|
|
423
|
+
continue;
|
|
424
|
+
const selected = prototypeFor(child, receiver, loc);
|
|
425
|
+
if (selected && result)
|
|
426
|
+
result = { kind: "ternary", cond: { kind: "instanceOf", value: receiver, className: child.def.name, type: BOOL, loc },
|
|
427
|
+
then: selected, else_: result, type: DYN, loc };
|
|
428
|
+
}
|
|
429
|
+
return result;
|
|
430
|
+
};
|
|
148
431
|
for (const existingInit of this.bagInitializers.get(className) ?? []) {
|
|
149
|
-
if (existingInit.value.kind === "dynObjLit" &&
|
|
150
|
-
const prototype =
|
|
432
|
+
if (existingInit.value.kind === "dynObjLit" && needsPrototype(info)) {
|
|
433
|
+
const prototype = prototypeFor(info, existingInit.obj, existingInit.loc);
|
|
151
434
|
if (prototype) {
|
|
152
435
|
existingInit.value = { kind: "libCall", fn: "dyn.objCreate", args: [prototype], type: DYN, loc: existingInit.loc };
|
|
153
436
|
changed = true;
|
|
154
437
|
}
|
|
155
438
|
}
|
|
156
439
|
}
|
|
157
|
-
if (this.bagClasses.has(
|
|
440
|
+
if (this.bagClasses.has(plan.key))
|
|
158
441
|
continue;
|
|
159
|
-
this.bagClasses.add(
|
|
442
|
+
this.bagClasses.add(plan.key);
|
|
160
443
|
this.ensurePropertyBag(info);
|
|
444
|
+
if (info.decl && isJsSourceFile(info.decl.getSourceFile())) {
|
|
445
|
+
let root = info;
|
|
446
|
+
while (root.base && !root.base.def.runtime)
|
|
447
|
+
root = root.base;
|
|
448
|
+
const track = (current) => {
|
|
449
|
+
current.def.tracksOwnFields = true;
|
|
450
|
+
current.subclasses.forEach(track);
|
|
451
|
+
};
|
|
452
|
+
// Runtime-owned roots keep their dedicated property contracts.
|
|
453
|
+
if (!root.base?.def.runtime)
|
|
454
|
+
track(root);
|
|
455
|
+
}
|
|
161
456
|
const loc = this.propertyBag.loc;
|
|
162
|
-
const
|
|
163
|
-
const value = varRef("p.0", DYN, loc);
|
|
164
|
-
const receiver = { kind: "dynCheck", value, type, loc };
|
|
457
|
+
const { receiver, condition } = this.propertyReceiver(plan, loc);
|
|
165
458
|
const bag = { kind: "fieldGet", obj: receiver, className, field: PROPERTY_BAG, type: DYN, loc };
|
|
166
|
-
const prototype =
|
|
459
|
+
const prototype = needsPrototype(info) ? prototypeFor(info, receiver, loc) : null;
|
|
167
460
|
const initialize = {
|
|
168
461
|
kind: "fieldSet", obj: receiver, className, field: PROPERTY_BAG, value: prototype
|
|
169
462
|
? { kind: "libCall", fn: "dyn.objCreate", args: [prototype], type: DYN, loc }
|
|
170
463
|
: { kind: "dynObjLit", fields: [], type: DYN, loc }, loc,
|
|
171
464
|
};
|
|
172
|
-
this.bagInitializers.
|
|
465
|
+
const initializers = this.bagInitializers.get(className) ?? [];
|
|
466
|
+
initializers.push(initialize);
|
|
467
|
+
this.bagInitializers.set(className, initializers);
|
|
173
468
|
this.propertyBag.body.unshift({
|
|
174
|
-
kind: "if", cond:
|
|
469
|
+
kind: "if", cond: condition,
|
|
175
470
|
then: [
|
|
176
471
|
{ kind: "if", cond: { kind: "dynTest", test: "undefined", value: bag, type: BOOL, loc }, then: [
|
|
177
472
|
initialize,
|
|
@@ -181,15 +476,19 @@ export class ClassDynamicDispatch {
|
|
|
181
476
|
});
|
|
182
477
|
changed = true;
|
|
183
478
|
}
|
|
479
|
+
refreshDescriptorGuards(lowerer);
|
|
184
480
|
const byMethod = new Map();
|
|
185
481
|
const candidates = (method) => {
|
|
186
482
|
const found = byMethod.get(method);
|
|
187
483
|
if (found)
|
|
188
484
|
return found;
|
|
189
|
-
const matching =
|
|
190
|
-
|
|
191
|
-
if (!info || info.fields.has(method) || info.builtinEmitter || info.builtinStream || info.builtinError)
|
|
485
|
+
const matching = this.propertyReceivers(lowerer, true).flatMap(({ info }) => {
|
|
486
|
+
if (!info || info.fields.has(method) || info.builtinError)
|
|
192
487
|
return [];
|
|
488
|
+
if (emitterRooted(lowerer, info) && EMITTER_METHODS.has(method))
|
|
489
|
+
return [info];
|
|
490
|
+
if (info.builtinStream)
|
|
491
|
+
return STREAM_METHODS.has(method) ? [info] : [];
|
|
193
492
|
return findMethodOn(lowerer, info, method) || findGenericMethodOn(lowerer, info, method) ? [info] : [];
|
|
194
493
|
});
|
|
195
494
|
byMethod.set(method, matching);
|
|
@@ -201,9 +500,82 @@ export class ClassDynamicDispatch {
|
|
|
201
500
|
// every reachability pass. Recompute this set as new bodies appear.
|
|
202
501
|
if (this.generated.has(fn) || !rewrite.has(fn))
|
|
203
502
|
continue;
|
|
503
|
+
const keyInitializers = new Map();
|
|
504
|
+
const written = new Set();
|
|
505
|
+
everyStmtList(fn.body, { expr: (expression) => {
|
|
506
|
+
if (expression.kind === "incDec" || expression.kind === "assignExpr")
|
|
507
|
+
written.add(expression.localId);
|
|
508
|
+
return true;
|
|
509
|
+
}, stmt: (statement) => {
|
|
510
|
+
if (statement.kind === "varDecl" && statement.init)
|
|
511
|
+
keyInitializers.set(statement.localId, statement.init);
|
|
512
|
+
if (statement.kind === "assign")
|
|
513
|
+
written.add(statement.localId);
|
|
514
|
+
return true;
|
|
515
|
+
} });
|
|
516
|
+
const keyValue = (value) => {
|
|
517
|
+
const visited = new Set();
|
|
518
|
+
while (true) {
|
|
519
|
+
if (value.kind === "dynFrom") {
|
|
520
|
+
value = value.value;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (value.kind === "libCall" && value.fn === "dyn.propertyKey") {
|
|
524
|
+
value = value.args[0];
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (value.kind === "varRef" && !written.has(value.localId) && !visited.has(value.localId) && keyInitializers.has(value.localId)) {
|
|
528
|
+
visited.add(value.localId);
|
|
529
|
+
value = keyInitializers.get(value.localId);
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
return value;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
204
535
|
fn.body = transformStmtList(fn.body, {
|
|
205
536
|
stmt: (stmt) => stmt,
|
|
206
537
|
expr: (expr) => {
|
|
538
|
+
if (expr.kind === "dynCheck" && isDynTypedRefType(expr.type)) {
|
|
539
|
+
const type = expr.type;
|
|
540
|
+
let cast = this.checkedCasts.get(type.className);
|
|
541
|
+
if (!cast) {
|
|
542
|
+
const loc = expr.loc;
|
|
543
|
+
const fn = { name: `%dyn.class.cast:${type.className}`,
|
|
544
|
+
params: [{ localId: "value", name: "value", type: DYN }],
|
|
545
|
+
locals: [{ id: "value", name: "value", type: DYN, mutable: false }], returnType: type,
|
|
546
|
+
body: [{ kind: "return", value: { ...expr, value: varRef("value", DYN, loc) }, loc }], loc };
|
|
547
|
+
cast = { fn, classes: new Set() };
|
|
548
|
+
this.checkedCasts.set(type.className, cast);
|
|
549
|
+
this.generated.add(fn);
|
|
550
|
+
lowerer.liftedFns.push(fn);
|
|
551
|
+
changed = true;
|
|
552
|
+
}
|
|
553
|
+
return { kind: "call", callee: cast.fn.name, args: [expr.value], type, loc: expr.loc };
|
|
554
|
+
}
|
|
555
|
+
if (expr.kind === "libCall" && expr.fn === "dyn.getPrototype" && expr.args[0]?.kind !== "dynObjLit") {
|
|
556
|
+
if (expr.prototypeIdentityOnly) {
|
|
557
|
+
if (!this.prototypeIdentityHelper) {
|
|
558
|
+
const loc = expr.loc;
|
|
559
|
+
this.prototypeIdentityHelper = { name: "%dyn.class.prototypeIdentity", params: [{ localId: "value", name: "value", type: DYN }],
|
|
560
|
+
locals: [{ id: "value", name: "value", type: DYN, mutable: false }], returnType: DYN, loc,
|
|
561
|
+
body: [{ kind: "return", value: { ...expr, args: [varRef("value", DYN, loc)] }, loc }] };
|
|
562
|
+
lowerer.liftedFns.push(this.prototypeIdentityHelper);
|
|
563
|
+
this.generated.add(this.prototypeIdentityHelper);
|
|
564
|
+
changed = true;
|
|
565
|
+
}
|
|
566
|
+
return { kind: "call", callee: this.prototypeIdentityHelper.name, args: expr.args, type: DYN, loc: expr.loc };
|
|
567
|
+
}
|
|
568
|
+
if (!this.prototypeHelper) {
|
|
569
|
+
const loc = expr.loc;
|
|
570
|
+
this.prototypeHelper = { name: "%dyn.class.getPrototype", params: [{ localId: "value", name: "value", type: DYN }],
|
|
571
|
+
locals: [{ id: "value", name: "value", type: DYN, mutable: false }], returnType: DYN, loc,
|
|
572
|
+
body: [{ kind: "return", value: { ...expr, args: [varRef("value", DYN, loc)] }, loc }] };
|
|
573
|
+
lowerer.liftedFns.push(this.prototypeHelper);
|
|
574
|
+
this.generated.add(this.prototypeHelper);
|
|
575
|
+
changed = true;
|
|
576
|
+
}
|
|
577
|
+
return { kind: "call", callee: this.prototypeHelper.name, args: expr.args, type: DYN, loc: expr.loc };
|
|
578
|
+
}
|
|
207
579
|
if (expr.kind === "libCall" && (expr.fn === "dyn.toString" || expr.fn === "dyn.stringConstructor") && lowerer.classes.has("%Error")) {
|
|
208
580
|
const key = JSON.stringify([expr.fn, ...expr.args.map((arg) => typeKey(arg.type))]);
|
|
209
581
|
let errorString = this.errorStrings.get(key);
|
|
@@ -239,13 +611,74 @@ export class ClassDynamicDispatch {
|
|
|
239
611
|
}
|
|
240
612
|
}
|
|
241
613
|
}
|
|
242
|
-
|
|
243
|
-
|
|
614
|
+
if (expr.kind === "libCall" && expr.fn === "bytes.construct") {
|
|
615
|
+
if (!this.constructDispatch) {
|
|
616
|
+
const loc = expr.loc;
|
|
617
|
+
const params = [DYN, DYN, STRING].map((type, i) => ({ localId: `p.${i}`, name: `p${i}`, type }));
|
|
618
|
+
this.constructDispatch = { name: "%dyn.class.construct", params, returnType: DYN,
|
|
619
|
+
locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
|
|
620
|
+
body: [{ kind: "return", value: { ...expr, args: params.map((p) => varRef(p.localId, p.type, loc)) }, loc }], loc };
|
|
621
|
+
this.generated.add(this.constructDispatch);
|
|
622
|
+
lowerer.liftedFns.push(this.constructDispatch);
|
|
623
|
+
changed = true;
|
|
624
|
+
}
|
|
625
|
+
return { kind: "call", callee: this.constructDispatch.name, args: expr.args, type: DYN, loc: expr.loc };
|
|
626
|
+
}
|
|
627
|
+
if (expr.kind === "libCall" && expr.fn === "dyn.forInKeys") {
|
|
628
|
+
if (!this.enumeration) {
|
|
629
|
+
const loc = expr.loc;
|
|
630
|
+
const value = varRef("p.0", DYN, loc);
|
|
631
|
+
this.enumeration = { name: "%dyn.class.forInKeys", params: [{ localId: "p.0", name: "value", type: DYN }], returnType: DYN,
|
|
632
|
+
locals: [{ id: "p.0", name: "value", type: DYN, mutable: false }],
|
|
633
|
+
body: [{ kind: "return", value: { ...expr, args: [value] }, loc }], loc };
|
|
634
|
+
this.generated.add(this.enumeration);
|
|
635
|
+
lowerer.liftedFns.push(this.enumeration);
|
|
636
|
+
changed = true;
|
|
637
|
+
}
|
|
638
|
+
return { kind: "call", callee: this.enumeration.name, args: expr.args, type: DYN, loc: expr.loc };
|
|
639
|
+
}
|
|
640
|
+
if (expr.kind === "libCall" && expr.fn === "dyn.defineProperty") {
|
|
641
|
+
const loc = expr.loc;
|
|
642
|
+
if (!this.descriptorHelper) {
|
|
643
|
+
const params = ["target", "key", "descriptor"].map((name) => ({ localId: name, name, type: DYN }));
|
|
644
|
+
const target = varRef("target", DYN, loc);
|
|
645
|
+
const key = varRef("key", DYN, loc);
|
|
646
|
+
const descriptor = varRef("descriptor", DYN, loc);
|
|
647
|
+
this.descriptorHelper = {
|
|
648
|
+
name: "%dyn.class.defineProperty", params, returnType: DYN,
|
|
649
|
+
locals: params.map((param) => ({ id: param.localId, name: param.name, type: DYN, mutable: false })),
|
|
650
|
+
body: [
|
|
651
|
+
{ kind: "if", cond: { kind: "dynTest", test: "symbol", value: key, type: BOOL, loc }, then: [
|
|
652
|
+
{ kind: "exprStmt", expr: { ...expr, args: [{ kind: "call", callee: this.propertyBag.name, args: [target], type: DYN, loc }, key, descriptor] }, loc },
|
|
653
|
+
{ kind: "return", value: target, loc },
|
|
654
|
+
], else_: null, loc },
|
|
655
|
+
{ kind: "return", value: { ...expr, args: [target, key, descriptor] }, loc },
|
|
656
|
+
], loc,
|
|
657
|
+
};
|
|
658
|
+
this.generated.add(this.descriptorHelper);
|
|
659
|
+
lowerer.liftedFns.push(this.descriptorHelper);
|
|
660
|
+
changed = true;
|
|
661
|
+
}
|
|
662
|
+
return { kind: "call", callee: this.descriptorHelper.name, args: expr.args, type: DYN, loc };
|
|
663
|
+
}
|
|
664
|
+
if (expr.kind === "dynKeyGet" && expr.value.kind === "dynObjLit")
|
|
665
|
+
return expr;
|
|
666
|
+
const reflectRead = expr.kind === "libCall" && expr.fn === "dyn.reflectGet" ? expr : null;
|
|
667
|
+
const reflectWrite = expr.kind === "libCall" && expr.fn === "dyn.reflectSet" ? expr : null;
|
|
668
|
+
const reflect = !!reflectRead || !!reflectWrite;
|
|
669
|
+
const computedRead = reflectRead ? { kind: "dynKeyGet", value: reflectRead.args[0], key: reflectRead.args[1], type: DYN, loc: expr.loc }
|
|
670
|
+
: expr.kind === "dynKeyGet" && expr.key.kind !== "strLit" ? expr : null;
|
|
671
|
+
const computedWrite = reflectWrite ? { ...reflectWrite, fn: "dyn.keySetComputed", args: reflectWrite.args.slice(0, 3), type: VOID }
|
|
672
|
+
: expr.kind === "libCall" && (expr.fn === "dyn.keySetComputed" || expr.fn === "dyn.keySet" && expr.args[1]?.kind !== "strLit") ? expr : null;
|
|
244
673
|
const computedProbe = expr.kind === "libCall" && ["dyn.hasKeyComputed", "dyn.hasOwnComputed", "dyn.propertyIsEnumerableComputed"].includes(expr.fn) ? expr : null;
|
|
245
674
|
if (computedRead || computedWrite || computedProbe) {
|
|
246
675
|
const probe = computedProbe ? computedProbe.fn === "dyn.hasKeyComputed" ? "in" : computedProbe.fn === "dyn.hasOwnComputed" ? "own" : "enumerable" : undefined;
|
|
247
|
-
const dynamicKey = !!computedProbe || computedWrite?.fn === "dyn.keySetComputed" || computedRead?.key.type.kind === "dyn";
|
|
248
|
-
const
|
|
676
|
+
const dynamicKey = reflect || !!computedProbe || computedWrite?.fn === "dyn.keySetComputed" || computedRead?.key.type.kind === "dyn";
|
|
677
|
+
const sourceKey = keyValue(computedRead?.key ?? (computedWrite ?? computedProbe).args[1]);
|
|
678
|
+
const numericKey = sourceKey.type.kind === "f64" ||
|
|
679
|
+
sourceKey.kind === "dynFrom" && sourceKey.value.type.kind === "f64" ||
|
|
680
|
+
sourceKey.kind === "toString" && sourceKey.operand.type.kind === "f64";
|
|
681
|
+
const key = JSON.stringify([!!computedWrite, computedRead?.optional ?? false, dynamicKey, probe, numericKey, reflect]);
|
|
249
682
|
let dispatch = this.computed.get(key);
|
|
250
683
|
if (!dispatch) {
|
|
251
684
|
const loc = expr.loc;
|
|
@@ -253,23 +686,28 @@ export class ClassDynamicDispatch {
|
|
|
253
686
|
{ localId: "p.0", name: "value", type: DYN },
|
|
254
687
|
{ localId: "p.key", name: "key", type: dynamicKey ? DYN : STRING },
|
|
255
688
|
...(computedWrite ? [{ localId: "p.1", name: "stored", type: DYN }] : []),
|
|
689
|
+
...(reflect ? [{ localId: "p.reflectReceiver", name: "receiver", type: DYN }] : []),
|
|
256
690
|
];
|
|
257
691
|
const keyLocal = dynamicKey ? "key.string" : "p.key";
|
|
258
692
|
const bag = { kind: "call", callee: this.propertyBag.name, args: [varRef("p.0", DYN, loc)], type: DYN, loc };
|
|
259
|
-
const
|
|
693
|
+
const reflectionFallback = (target, property) => ({ kind: "libCall", fn: computedWrite ? "dyn.reflectSet" : "dyn.reflectGet",
|
|
694
|
+
args: [target, lowerer.coerceToExpected(property, DYN), ...(computedWrite ? [varRef("p.1", DYN, loc)] : []), varRef("p.reflectReceiver", DYN, loc)], type: computedWrite ? BOOL : DYN, loc });
|
|
695
|
+
const fallback = reflect ? reflectionFallback(bag, varRef(keyLocal, STRING, loc)) : computedProbe
|
|
260
696
|
? { ...computedProbe, fn: probe === "in" ? "dyn.hasKey" : probe === "own" ? "dyn.hasOwn" : "dyn.propertyIsEnumerable", args: [bag, varRef(keyLocal, STRING, loc)] }
|
|
261
697
|
: computedRead
|
|
262
698
|
? { ...computedRead, value: bag, key: varRef(keyLocal, STRING, loc) }
|
|
263
699
|
: { ...computedWrite, fn: "dyn.keySet", args: [bag, varRef(keyLocal, STRING, loc), varRef("p.1", DYN, loc)] };
|
|
264
700
|
const helper = {
|
|
265
|
-
name: `%dyn.class.computed.${this.computed.size}`, params, returnType: computedProbe ? BOOL : computedWrite ? VOID : DYN,
|
|
701
|
+
name: `%dyn.class.computed.${this.computed.size}`, params, returnType: reflect && computedWrite || computedProbe ? BOOL : computedWrite ? VOID : DYN,
|
|
266
702
|
locals: [...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), ...(dynamicKey ? [{ id: keyLocal, name: "key", type: STRING, mutable: false }] : [])],
|
|
267
|
-
body: computedWrite
|
|
703
|
+
body: reflect ? [{ kind: "return", value: fallback, loc }] : computedWrite
|
|
268
704
|
? [{ kind: "exprStmt", expr: fallback, loc }, { kind: "return", value: null, loc }]
|
|
269
705
|
: [{ kind: "return", value: fallback, loc }], loc,
|
|
270
706
|
};
|
|
271
707
|
if (dynamicKey)
|
|
272
|
-
helper.body.unshift({ kind: "if", cond: { kind: "dynTest", test: "symbol", value: varRef("p.key", DYN, loc), type: BOOL, loc }, then:
|
|
708
|
+
helper.body.unshift({ kind: "if", cond: { kind: "dynTest", test: "symbol", value: varRef("p.key", DYN, loc), type: BOOL, loc }, then: reflect ? [
|
|
709
|
+
{ kind: "return", value: reflectionFallback(bag, varRef("p.key", DYN, loc)), loc },
|
|
710
|
+
] : computedProbe ? [
|
|
273
711
|
{ kind: "return", value: { ...computedProbe, args: [bag, varRef("p.key", DYN, loc)] }, loc },
|
|
274
712
|
] : computedRead ? [
|
|
275
713
|
{ kind: "return", value: { ...computedRead, value: bag, key: varRef("p.key", DYN, loc) }, loc },
|
|
@@ -277,18 +715,18 @@ export class ClassDynamicDispatch {
|
|
|
277
715
|
{ kind: "exprStmt", expr: { ...computedWrite, args: [bag, varRef("p.key", DYN, loc), varRef("p.1", DYN, loc)] }, loc },
|
|
278
716
|
{ kind: "return", value: null, loc },
|
|
279
717
|
], else_: null, loc }, { kind: "if", cond: { kind: "dynTest", test: "nullish", value: varRef("p.0", DYN, loc), type: BOOL, loc }, then: [
|
|
280
|
-
...(computedProbe ? [{ kind: "return", value: { ...computedProbe, args: [varRef("p.0", DYN, loc), varRef("p.key", DYN, loc)] }, loc }] : computedRead ? [{ kind: "return", value: { ...computedRead, value: varRef("p.0", DYN, loc), key: varRef("p.key", DYN, loc) }, loc }] : [
|
|
718
|
+
...(reflect ? [{ kind: "return", value: reflectionFallback(varRef("p.0", DYN, loc), varRef("p.key", DYN, loc)), loc }] : computedProbe ? [{ kind: "return", value: { ...computedProbe, args: [varRef("p.0", DYN, loc), varRef("p.key", DYN, loc)] }, loc }] : computedRead ? [{ kind: "return", value: { ...computedRead, value: varRef("p.0", DYN, loc), key: varRef("p.key", DYN, loc) }, loc }] : [
|
|
281
719
|
{ kind: "exprStmt", expr: { ...computedWrite, args: [varRef("p.0", DYN, loc), varRef("p.key", DYN, loc), varRef("p.1", DYN, loc)] }, loc },
|
|
282
720
|
{ kind: "return", value: null, loc },
|
|
283
721
|
]),
|
|
284
722
|
], else_: null, loc }, { kind: "varDecl", localId: keyLocal, init: { kind: "libCall", fn: "dyn.toStringCoerce", args: [varRef("p.key", DYN, loc)], type: STRING, loc }, loc });
|
|
285
|
-
dispatch = { write: !!computedWrite, fn: helper, classes: new Set(), keyLocal, branchIndex: dynamicKey ? 3 : 0, dynamicKey, probe };
|
|
723
|
+
dispatch = { write: !!computedWrite, reflect, fn: helper, classes: new Set(), keyLocal, branchIndex: dynamicKey ? 3 : 0, dynamicKey, numericKey, probe };
|
|
286
724
|
this.computed.set(key, dispatch);
|
|
287
725
|
this.generated.add(helper);
|
|
288
726
|
lowerer.liftedFns.push(helper);
|
|
289
727
|
changed = true;
|
|
290
728
|
}
|
|
291
|
-
return { kind: "call", callee: dispatch.fn.name, args: computedRead ? [computedRead.value, computedRead.key] : (computedWrite ?? computedProbe).args, type: dispatch.fn.returnType, loc: expr.loc };
|
|
729
|
+
return { kind: "call", callee: dispatch.fn.name, args: reflect ? (reflectRead ?? reflectWrite).args : computedRead ? [computedRead.value, computedRead.key] : (computedWrite ?? computedProbe).args, type: dispatch.fn.returnType, loc: expr.loc };
|
|
292
730
|
}
|
|
293
731
|
const read = expr.kind === "dynKeyGet" && expr.key.kind === "strLit" ? expr : null;
|
|
294
732
|
const write = expr.kind === "libCall" && expr.fn === "dyn.keySet" && expr.args[1]?.kind === "strLit" ? expr : null;
|
|
@@ -299,9 +737,14 @@ export class ClassDynamicDispatch {
|
|
|
299
737
|
if (!dispatch) {
|
|
300
738
|
const loc = expr.loc;
|
|
301
739
|
const params = (write ? [0, 1] : [0]).map((i) => ({ localId: `p.${i}`, name: `p${i}`, type: DYN }));
|
|
302
|
-
const
|
|
740
|
+
const instanceBag = { kind: "call", callee: this.propertyBag.name, args: [varRef("p.0", DYN, loc)], type: DYN, loc };
|
|
741
|
+
const bag = read && !["name", "length", "apply"].includes(name)
|
|
742
|
+
? { kind: "call", callee: this.constructorPropertyBag(lowerer, loc).name, args: [instanceBag], type: DYN, loc }
|
|
743
|
+
: instanceBag;
|
|
303
744
|
const fallback = read
|
|
304
|
-
? {
|
|
745
|
+
? { kind: "ternary", cond: { kind: "dynScalarEq", left: bag, right: varRef("p.0", DYN, loc), type: BOOL, loc },
|
|
746
|
+
then: { ...read, value: bag }, else_: { kind: "libCall", fn: "dyn.bagGet",
|
|
747
|
+
args: [bag, read.key, varRef("p.0", DYN, loc)], type: DYN, loc }, type: DYN, loc }
|
|
305
748
|
: { ...write, args: [bag, write.args[1], varRef("p.1", DYN, loc)] };
|
|
306
749
|
const helper = {
|
|
307
750
|
name: `%dyn.class.property.${this.properties.size}`, params, returnType: write ? VOID : DYN,
|
|
@@ -328,13 +771,19 @@ export class ClassDynamicDispatch {
|
|
|
328
771
|
const invoke = iterator
|
|
329
772
|
? { kind: "dynInvoke", recv: iterator.args[0], method: "sym:iterator", calleeName: "value[Symbol.iterator]", args: [], type: DYN, loc: iterator.loc }
|
|
330
773
|
: expr.kind === "dynInvoke" ? expr : null;
|
|
331
|
-
|
|
774
|
+
const staticCandidates = invoke && [...this.boxedConstructors].some((name) => {
|
|
775
|
+
const info = lowerer.classes.get(name);
|
|
776
|
+
return info && findStaticOn(lowerer, info, invoke.method)?.method;
|
|
777
|
+
});
|
|
778
|
+
if (!invoke || candidates(invoke.method).length === 0 && !staticCandidates && !(invoke.method === "toString" && lowerer.classes.has("%Error")))
|
|
332
779
|
return expr;
|
|
333
|
-
const key = JSON.stringify([invoke.method, invoke.
|
|
780
|
+
const key = JSON.stringify([invoke.method, invoke.args.length, iterator?.fn, iterator?.args[1]]);
|
|
334
781
|
let dispatch = this.dispatches.get(key);
|
|
335
782
|
if (!dispatch) {
|
|
336
783
|
const params = [invoke.recv, ...invoke.args].map((_, i) => ({ localId: `p.${i}`, name: `p${i}`, type: DYN }));
|
|
337
784
|
params.splice(1, 0, { localId: "p.callback", name: "callback", type: DYN });
|
|
785
|
+
iterator?.args.slice(1).forEach((argument, i) => params.push({ localId: `p.iterator.${i}`, name: `iterator${i}`, type: argument.type }));
|
|
786
|
+
params.push({ localId: "p.calleeName", name: "calleeName", type: STRING });
|
|
338
787
|
const callback = {
|
|
339
788
|
name: `%dyn.class.callback.${this.dispatches.size}`, params: [params[0]], returnType: DYN,
|
|
340
789
|
locals: [{ id: "p.0", name: "receiver", type: DYN, mutable: false }],
|
|
@@ -342,10 +791,10 @@ export class ClassDynamicDispatch {
|
|
|
342
791
|
};
|
|
343
792
|
const helper = {
|
|
344
793
|
name: `%dyn.class.call.${this.dispatches.size}`, params, returnType: DYN,
|
|
345
|
-
locals: params.map((p) => ({ id: p.localId, name: p.name, type:
|
|
794
|
+
locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
|
|
346
795
|
body: [{ kind: "return", value: iterator
|
|
347
|
-
? { ...iterator, args: [varRef("p.0", DYN, expr.loc), ...iterator.args.slice(1)] }
|
|
348
|
-
: { ...invoke, recv: varRef("p.0", DYN, expr.loc), args: invoke.args.map((_, i) => varRef(`p.${i + 1}`, DYN, expr.loc)) }, loc: expr.loc }],
|
|
796
|
+
? { ...iterator, args: [varRef("p.0", DYN, expr.loc), ...iterator.args.slice(1).map((argument, i) => varRef(`p.iterator.${i}`, argument.type, expr.loc))] }
|
|
797
|
+
: { ...invoke, calleeNameValue: varRef("p.calleeName", STRING, expr.loc), recv: varRef("p.0", DYN, expr.loc), args: invoke.args.map((_, i) => varRef(`p.${i + 1}`, DYN, expr.loc)) }, loc: expr.loc }],
|
|
349
798
|
loc: expr.loc,
|
|
350
799
|
};
|
|
351
800
|
if (invoke.method === "toString" && lowerer.classes.has("%Error")) {
|
|
@@ -354,7 +803,7 @@ export class ClassDynamicDispatch {
|
|
|
354
803
|
helper.body.unshift({ kind: "if", cond: { kind: "dynTest", test: "error", value, type: BOOL, loc }, then: [
|
|
355
804
|
{ kind: "return", value: {
|
|
356
805
|
kind: "dynCall", callee: errorToStringMethod(lowerer, { kind: "dynCheck", value, type: { kind: "object", className: "%Error" }, loc }),
|
|
357
|
-
receiver: value, calleeName: invoke.calleeName, args: invoke.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc)), type: DYN, loc,
|
|
806
|
+
receiver: value, calleeName: invoke.calleeName, calleeNameValue: varRef("p.calleeName", STRING, loc), args: invoke.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc)), type: DYN, loc,
|
|
358
807
|
}, loc },
|
|
359
808
|
], else_: null, loc });
|
|
360
809
|
}
|
|
@@ -371,30 +820,75 @@ export class ClassDynamicDispatch {
|
|
|
371
820
|
const receiver = varRef(local.id, DYN, expr.loc);
|
|
372
821
|
return { kind: "seqExpr", stmts: [{ kind: "varDecl", localId: local.id, init: invoke.recv, loc: expr.loc }], result: {
|
|
373
822
|
kind: "call", callee: dispatch.fn.name, args: [receiver,
|
|
374
|
-
{ kind: "call", callee: dispatch.callback.name, args: [receiver], type: DYN, loc: expr.loc }, ...invoke.args], type: DYN, loc: expr.loc,
|
|
823
|
+
{ kind: "call", callee: dispatch.callback.name, args: [receiver], type: DYN, loc: expr.loc }, ...invoke.args, ...(iterator?.args.slice(1) ?? []), invoke.calleeNameValue ?? { kind: "strLit", value: invoke.calleeName, type: STRING, loc: expr.loc }], type: DYN, loc: expr.loc,
|
|
375
824
|
}, type: DYN, loc: expr.loc };
|
|
376
825
|
},
|
|
377
826
|
});
|
|
378
827
|
}
|
|
828
|
+
for (const [target, cast] of this.checkedCasts) {
|
|
829
|
+
for (const source of this.boxed) {
|
|
830
|
+
if (source === target || !lowerer.isSubclassOf(target, source) || cast.classes.has(source))
|
|
831
|
+
continue;
|
|
832
|
+
cast.classes.add(source);
|
|
833
|
+
const loc = cast.fn.loc;
|
|
834
|
+
const value = varRef("value", DYN, loc);
|
|
835
|
+
const sourceType = { kind: "object", className: source };
|
|
836
|
+
const object = { kind: "dynCheck", value, type: sourceType, loc };
|
|
837
|
+
cast.fn.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
|
|
838
|
+
{ kind: "strLit", value: typeKey(sourceType), type: STRING, loc }], type: BOOL, loc }, then: [
|
|
839
|
+
{ kind: "if", cond: { kind: "instanceOf", value: object, className: target, type: BOOL, loc }, then: [
|
|
840
|
+
{ kind: "return", value: { kind: "downcast", value: object, type: cast.fn.returnType, loc }, loc },
|
|
841
|
+
], else_: null, loc },
|
|
842
|
+
], else_: null, loc });
|
|
843
|
+
changed = true;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
379
846
|
for (const dispatch of this.dispatches.values()) {
|
|
380
|
-
for (const
|
|
381
|
-
|
|
847
|
+
for (const name of this.boxedConstructors) {
|
|
848
|
+
const info = lowerer.classes.get(name);
|
|
849
|
+
const found = info && findStaticOn(lowerer, info, dispatch.source.method);
|
|
850
|
+
const key = `constructor:${name}`;
|
|
851
|
+
if (!found?.method || dispatch.classes.has(key))
|
|
852
|
+
continue;
|
|
853
|
+
const method = reflectedClassStaticMethodValue(lowerer, found.declarer, dispatch.source.method);
|
|
854
|
+
if (!method)
|
|
382
855
|
continue;
|
|
383
|
-
dispatch.classes.add(
|
|
856
|
+
dispatch.classes.add(key);
|
|
384
857
|
const loc = dispatch.source.loc;
|
|
385
|
-
const type = { kind: "object", className: info.def.name };
|
|
386
858
|
const receiver = varRef("p.0", DYN, loc);
|
|
859
|
+
const condition = { kind: "libCall", fn: "dyn.classIs", args: [receiver,
|
|
860
|
+
{ kind: "strLit", value: typeKey({ kind: "classval", className: name }), type: STRING, loc }], type: BOOL, loc };
|
|
861
|
+
dispatch.callback.body.unshift({ kind: "if", cond: condition, then: [{ kind: "return", value: {
|
|
862
|
+
kind: "dynObjLit", fields: [{ key: { kind: "strLit", value: "value", type: STRING, loc }, value: lowerer.coerceToExpected(method, DYN) }], type: DYN, loc,
|
|
863
|
+
}, loc }], else_: null, loc });
|
|
864
|
+
dispatch.fn.body.unshift({ kind: "if", cond: condition, then: [{ kind: "return", value: {
|
|
865
|
+
kind: "dynCall", callee: { kind: "dynKeyGet", value: varRef("p.callback", DYN, loc), key: { kind: "strLit", value: "value", type: STRING, loc }, type: DYN, loc },
|
|
866
|
+
receiver, calleeName: dispatch.source.calleeName, calleeNameValue: varRef("p.calleeName", STRING, loc), args: dispatch.source.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc)), type: DYN, loc,
|
|
867
|
+
}, loc }], else_: null, loc });
|
|
868
|
+
changed = true;
|
|
869
|
+
}
|
|
870
|
+
for (const plan of this.propertyReceivers(lowerer, true)) {
|
|
871
|
+
const { info } = plan;
|
|
872
|
+
if (!candidates(dispatch.source.method).includes(info) || dispatch.classes.has(plan.key))
|
|
873
|
+
continue;
|
|
874
|
+
dispatch.classes.add(plan.key);
|
|
875
|
+
const loc = dispatch.source.loc;
|
|
876
|
+
const value = varRef("p.0", DYN, loc);
|
|
877
|
+
const { receiver, condition } = this.propertyReceiver(plan, loc);
|
|
387
878
|
if (isClassCallback(lowerer, info, dispatch.source.method)) {
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
879
|
+
const prototypeMethod = lowerer.prototypeMethodAccesses.has(dispatch.source.method);
|
|
880
|
+
if (prototypeMethod)
|
|
881
|
+
classPrototypeData(lowerer, info, loc);
|
|
882
|
+
const bag = { kind: "call", callee: this.propertyBag.name, args: [value], type: DYN, loc };
|
|
883
|
+
dispatch.callback.body.unshift({ kind: "if", cond: condition, then: [{ kind: "return", value: { kind: "ternary", cond: prototypeMethod ? { kind: "boolLit", value: true, type: BOOL, loc } : { kind: "libCall", fn: "dyn.hasKey", args: [bag,
|
|
884
|
+
{ kind: "strLit", value: dispatch.source.method, type: STRING, loc }], type: BOOL, loc }, then: { kind: "dynObjLit", fields: [
|
|
885
|
+
{ key: { kind: "strLit", value: "value", type: STRING, loc }, value: { kind: "dynKeyGet", value: bag, key: { kind: "strLit", value: dispatch.source.method, type: STRING, loc }, type: DYN, loc } },
|
|
886
|
+
], type: DYN, loc }, else_: dynUndefinedExpr(loc), type: DYN, loc }, loc }], else_: null, loc });
|
|
393
887
|
}
|
|
394
888
|
const before = lowerer.diags.length;
|
|
395
889
|
let branch;
|
|
396
890
|
try {
|
|
397
|
-
branch = this.methodBody(lowerer, dispatch, info,
|
|
891
|
+
branch = this.methodBody(lowerer, dispatch, info, receiver);
|
|
398
892
|
}
|
|
399
893
|
catch (error) {
|
|
400
894
|
if (!(error instanceof PoisonError) || !info.decl)
|
|
@@ -405,19 +899,42 @@ export class ClassDynamicDispatch {
|
|
|
405
899
|
branch = [fence];
|
|
406
900
|
}
|
|
407
901
|
dispatch.fn.body.unshift({
|
|
408
|
-
kind: "if", cond:
|
|
409
|
-
kind: "libCall", fn: "dyn.typedRefIs", args: [receiver, { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc,
|
|
410
|
-
}, then: branch, else_: null, loc,
|
|
902
|
+
kind: "if", cond: condition, then: branch, else_: null, loc,
|
|
411
903
|
});
|
|
412
904
|
changed = true;
|
|
413
905
|
}
|
|
414
906
|
}
|
|
415
907
|
for (const dispatch of this.properties.values()) {
|
|
416
|
-
|
|
908
|
+
if (!dispatch.write)
|
|
909
|
+
for (const name of this.boxedConstructors) {
|
|
910
|
+
const info = lowerer.classes.get(name);
|
|
911
|
+
const found = info && findStaticOn(lowerer, info, dispatch.name);
|
|
912
|
+
const field = found?.field;
|
|
913
|
+
const key = `constructor:${name}`;
|
|
914
|
+
if (!info || dispatch.name.startsWith("#") || dispatch.classes.has(key) || !found && dispatch.name !== "prototype")
|
|
915
|
+
continue;
|
|
916
|
+
const method = found?.method && reflectedClassStaticMethodValue(lowerer, found.declarer, dispatch.name);
|
|
917
|
+
if (found && !field && !method || findGenericStaticOn(lowerer, info, dispatch.name) && !method)
|
|
918
|
+
continue;
|
|
919
|
+
const loc = dispatch.fn.loc;
|
|
920
|
+
const result = field ? varRef(field.globalId, field.type, loc) : method ??
|
|
921
|
+
(dispatch.name === "prototype" ? classPrototypeData(lowerer, info, loc) : null) ??
|
|
922
|
+
dynUndefinedExpr(loc);
|
|
923
|
+
if (dispatch.name === "name" || dispatch.name === "length" || dispatch.name === "apply")
|
|
924
|
+
continue;
|
|
925
|
+
dispatch.classes.add(key);
|
|
926
|
+
const value = varRef("p.0", DYN, loc);
|
|
927
|
+
const type = { kind: "classval", className: name };
|
|
928
|
+
dispatch.fn.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.classIs", args: [value,
|
|
929
|
+
{ kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc }, then: [
|
|
930
|
+
{ kind: "return", value: lowerer.coerceToExpected(result, DYN), loc },
|
|
931
|
+
], else_: null, loc });
|
|
932
|
+
changed = true;
|
|
933
|
+
}
|
|
934
|
+
for (const plan of this.propertyReceivers(lowerer, true)) {
|
|
417
935
|
const { info } = plan;
|
|
418
936
|
if (!isClassOwnEnumerableFieldName(dispatch.name) ||
|
|
419
|
-
!info.fields.has(dispatch.name) && !findMethodOn(lowerer, info, `get:${dispatch.name}`) && !findMethodOn(lowerer, info, `set:${dispatch.name}`)
|
|
420
|
-
!findMethodOn(lowerer, info, dispatch.name) && !findGenericMethodOn(lowerer, info, dispatch.name))
|
|
937
|
+
!(info.builtinStream !== undefined && streamProperty(dispatch.name)) && !findMethodOn(lowerer, info, dispatch.name) && !findGenericMethodOn(lowerer, info, dispatch.name) && dispatch.name !== "constructor" && !info.fields.has(dispatch.name) && !findMethodOn(lowerer, info, `get:${dispatch.name}`) && !findMethodOn(lowerer, info, `set:${dispatch.name}`))
|
|
421
938
|
continue;
|
|
422
939
|
if (dispatch.classes.has(plan.key))
|
|
423
940
|
continue;
|
|
@@ -444,16 +961,60 @@ export class ClassDynamicDispatch {
|
|
|
444
961
|
}
|
|
445
962
|
}
|
|
446
963
|
for (const dispatch of this.computed.values()) {
|
|
447
|
-
for (const plan of this.propertyReceivers(lowerer)) {
|
|
964
|
+
for (const plan of this.propertyReceivers(lowerer, true)) {
|
|
448
965
|
if (dispatch.classes.has(plan.key))
|
|
449
966
|
continue;
|
|
450
967
|
const { info } = plan;
|
|
451
968
|
dispatch.classes.add(plan.key);
|
|
452
969
|
const loc = dispatch.fn.loc;
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
970
|
+
const selected = this.propertyReceiver(plan, loc);
|
|
971
|
+
const receiverType = { kind: "object", className: info.def.name };
|
|
972
|
+
const receiver = varRef("p.receiver", receiverType, loc);
|
|
973
|
+
const helperKey = JSON.stringify([dispatch.fn.name, info.def.name]);
|
|
974
|
+
const existingHelper = this.computedClassHelpers.get(helperKey);
|
|
975
|
+
const callHelper = (helper) => {
|
|
976
|
+
const call = { kind: "call", callee: helper.name,
|
|
977
|
+
args: helper.params.map((param) => param.localId === "p.receiver" ? selected.receiver : varRef(param.localId, param.type, loc)), type: helper.returnType, loc };
|
|
978
|
+
return { kind: "if", cond: selected.condition, then: dispatch.write && !dispatch.reflect
|
|
979
|
+
? [{ kind: "exprStmt", expr: call, loc }, { kind: "return", value: null, loc }]
|
|
980
|
+
: [{ kind: "return", value: call, loc }], else_: null, loc };
|
|
981
|
+
};
|
|
982
|
+
if (existingHelper && !dispatch.dynamicKey) {
|
|
983
|
+
dispatch.fn.body.splice(dispatch.branchIndex, 0, callHelper(existingHelper));
|
|
984
|
+
changed = true;
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
const memberBody = (name) => {
|
|
988
|
+
if (dispatch.probe && (name === "message" || name === "name") && lowerer.isSubclassOf(info.def.name, "%Error") && dispatch.probe !== "in")
|
|
989
|
+
return [{
|
|
990
|
+
kind: "return", value: dispatch.probe === "own"
|
|
991
|
+
? { kind: "fieldGet", obj: lowerer.upcastTo(receiver, "%Error"), className: "%Error", field: `%${name}Present`, type: BOOL, loc }
|
|
992
|
+
: { kind: "fieldGet", obj: lowerer.upcastTo(receiver, "%Error"), className: "%Error", field: `%${name}Enumerable`, type: BOOL, loc }, loc,
|
|
993
|
+
}];
|
|
994
|
+
if (info.builtinStream !== undefined && streamProperty(name) && dispatch.probe)
|
|
995
|
+
return [{ kind: "return", value: { kind: "boolLit", value: dispatch.probe === "in", type: BOOL, loc }, loc }];
|
|
996
|
+
if (!dispatch.probe)
|
|
997
|
+
return dispatch.reflect ? this.reflectPropertyBody(lowerer, { ...dispatch, name }, info, receiver) : this.propertyBody(lowerer, { ...dispatch, name }, info, receiver);
|
|
998
|
+
const prototypeMethod = lowerer.prototypeMethodAccesses.has(name);
|
|
999
|
+
if (info.fields.has(name) || dispatch.probe === "in" && !prototypeMethod) {
|
|
1000
|
+
if (info.fields.has(name) && info.def.tracksOwnFields) {
|
|
1001
|
+
const bag = { kind: "call", callee: this.propertyBag.name, args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
|
1002
|
+
const symbol = symbolMemberKey(lowerer, info, name, loc);
|
|
1003
|
+
const fn = dispatch.probe === "in" ? "dyn.hasKey" : dispatch.probe === "own" ? "dyn.hasOwn" : "dyn.propertyIsEnumerable";
|
|
1004
|
+
return [{ kind: "return", value: { kind: "libCall", fn: symbol ? `${fn}Computed` : fn,
|
|
1005
|
+
args: [bag, symbol ?? { kind: "strLit", value: name, type: STRING, loc }], type: BOOL, loc }, loc }];
|
|
1006
|
+
}
|
|
1007
|
+
return [{ kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc }];
|
|
1008
|
+
}
|
|
1009
|
+
if (prototypeMethod)
|
|
1010
|
+
classPrototypeData(lowerer, info, loc, receiver);
|
|
1011
|
+
const bag = { kind: "call", callee: this.propertyBag.name,
|
|
1012
|
+
args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
|
1013
|
+
const symbol = name.startsWith("sym:") || name.startsWith("%symbol:");
|
|
1014
|
+
const fn = dispatch.probe === "in" ? "dyn.hasKey" : dispatch.probe === "own" ? "dyn.hasOwn" : "dyn.propertyIsEnumerable";
|
|
1015
|
+
return [{ kind: "return", value: { kind: "libCall", fn: symbol ? `${fn}Computed` : fn,
|
|
1016
|
+
args: [bag, symbol ? varRef("p.key", DYN, loc) : { kind: "strLit", value: name, type: STRING, loc }], type: BOOL, loc }, loc }];
|
|
1017
|
+
};
|
|
457
1018
|
if (dispatch.dynamicKey) {
|
|
458
1019
|
const symbols = new Map();
|
|
459
1020
|
const symbolEntries = [];
|
|
@@ -480,6 +1041,16 @@ export class ClassDynamicDispatch {
|
|
|
480
1041
|
}
|
|
481
1042
|
}
|
|
482
1043
|
const branches = [];
|
|
1044
|
+
if (!dispatch.write && !dispatch.reflect && !info.def.tracksOwnFields) {
|
|
1045
|
+
const value = varRef("p.0", DYN, loc);
|
|
1046
|
+
const key = varRef("p.key", DYN, loc);
|
|
1047
|
+
const bag = { kind: "call", callee: this.propertyBag.name, args: [value], type: DYN, loc };
|
|
1048
|
+
branches.push({ kind: "if", cond: { kind: "libCall", fn: "dyn.hasOwnComputed", args: [bag, key], type: BOOL, loc }, then: [
|
|
1049
|
+
{ kind: "return", value: dispatch.probe ? {
|
|
1050
|
+
kind: "libCall", fn: dispatch.probe === "enumerable" ? "dyn.propertyIsEnumerableComputed" : "dyn.hasOwnComputed", args: [bag, key], type: BOOL, loc,
|
|
1051
|
+
} : { kind: "dynKeyGet", value: bag, key, type: DYN, loc }, loc },
|
|
1052
|
+
], else_: null, loc });
|
|
1053
|
+
}
|
|
483
1054
|
for (const [name, symbol] of symbols) {
|
|
484
1055
|
const before = lowerer.diags.length;
|
|
485
1056
|
let then;
|
|
@@ -497,13 +1068,21 @@ export class ClassDynamicDispatch {
|
|
|
497
1068
|
branches.push({ kind: "if", cond: { kind: "dynScalarEq", left: varRef("p.key", DYN, loc), right: symbol, type: BOOL, loc }, then, else_: null, loc });
|
|
498
1069
|
}
|
|
499
1070
|
if (branches.length) {
|
|
500
|
-
dispatch.fn.body.unshift({ kind: "if", cond: condition, then: [
|
|
501
|
-
{ kind: "if", cond: { kind: "dynTest", test: "symbol", value: varRef("p.key", DYN, loc), type: BOOL, loc }, then: branches, else_: null, loc },
|
|
1071
|
+
dispatch.fn.body.unshift({ kind: "if", cond: selected.condition, then: [
|
|
1072
|
+
{ kind: "if", cond: { kind: "dynTest", test: "symbol", value: varRef("p.key", DYN, loc), type: BOOL, loc }, then: transformStmtList(branches, { stmt: (statement) => statement, expr: (expression) => expression.kind === "varRef" && expression.localId === "p.receiver" ? selected.receiver : expression }), else_: null, loc },
|
|
502
1073
|
], else_: null, loc });
|
|
503
1074
|
dispatch.branchIndex++;
|
|
504
1075
|
}
|
|
505
1076
|
}
|
|
1077
|
+
if (existingHelper) {
|
|
1078
|
+
dispatch.fn.body.splice(dispatch.branchIndex, 0, callHelper(existingHelper));
|
|
1079
|
+
changed = true;
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
506
1082
|
const names = new Set([...info.fields.keys()].filter(isClassOwnEnumerableFieldName));
|
|
1083
|
+
if (info.builtinStream)
|
|
1084
|
+
for (const name of [...STREAM_BOOL_PROPERTIES, ...STREAM_NUM_PROPERTIES])
|
|
1085
|
+
names.add(name);
|
|
507
1086
|
for (let owner = info; owner; owner = owner.base) {
|
|
508
1087
|
const methodNames = [...owner.methods.keys()];
|
|
509
1088
|
if (owner.genericMethods)
|
|
@@ -516,6 +1095,8 @@ export class ClassDynamicDispatch {
|
|
|
516
1095
|
}
|
|
517
1096
|
const branch = [];
|
|
518
1097
|
for (const name of names) {
|
|
1098
|
+
if (dispatch.numericKey && String(Number(name)) !== name)
|
|
1099
|
+
continue;
|
|
519
1100
|
const before = lowerer.diags.length;
|
|
520
1101
|
let body;
|
|
521
1102
|
try {
|
|
@@ -533,16 +1114,106 @@ export class ClassDynamicDispatch {
|
|
|
533
1114
|
kind: "strEq", left: varRef(dispatch.keyLocal, STRING, loc), right: { kind: "strLit", value: name, type: STRING, loc }, negated: false, type: BOOL, loc,
|
|
534
1115
|
}, then: body, else_: null, loc });
|
|
535
1116
|
}
|
|
536
|
-
|
|
1117
|
+
// Keep each class's key table in a separate function. A single
|
|
1118
|
+
// function containing every boxed class grows quadratically during
|
|
1119
|
+
// LLVM's control-flow optimization for large library workloads.
|
|
1120
|
+
const params = [...dispatch.fn.params, { localId: "p.receiver", name: "receiver", type: receiverType }];
|
|
1121
|
+
if (dispatch.keyLocal !== "p.key")
|
|
1122
|
+
params.push({ localId: dispatch.keyLocal, name: "key", type: STRING });
|
|
1123
|
+
const helper = {
|
|
1124
|
+
name: `%dyn.class.keys.${lowerer.liftedFns.length}`, params, returnType: dispatch.fn.returnType,
|
|
1125
|
+
locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
|
|
1126
|
+
body: [...branch, ...transformStmtList(dispatch.fn.body.slice(dispatch.write ? -2 : -1), { stmt: (statement) => statement,
|
|
1127
|
+
expr: (expression) => expression.kind === "dynKeyGet" && expression.key.type.kind === "string"
|
|
1128
|
+
? { kind: "libCall", fn: "dyn.bagGet", args: [expression.value, expression.key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }
|
|
1129
|
+
: expression.kind === "libCall" && expression.fn === "dyn.keySet"
|
|
1130
|
+
? { ...expression, fn: "dyn.bagSet", args: [...expression.args, lowerer.coerceToExpected(receiver, DYN)] }
|
|
1131
|
+
: expression,
|
|
1132
|
+
})], loc,
|
|
1133
|
+
};
|
|
1134
|
+
this.generated.add(helper);
|
|
1135
|
+
lowerer.liftedFns.push(helper);
|
|
1136
|
+
this.computedClassHelpers.set(helperKey, helper);
|
|
1137
|
+
dispatch.fn.body.splice(dispatch.branchIndex, 0, callHelper(helper));
|
|
537
1138
|
changed = true;
|
|
538
1139
|
}
|
|
539
1140
|
}
|
|
1141
|
+
if (this.enumeration)
|
|
1142
|
+
for (const plan of this.propertyReceivers(lowerer, false, false)) {
|
|
1143
|
+
if (!plan.info.def.tracksOwnFields || this.enumerationClasses.has(plan.key))
|
|
1144
|
+
continue;
|
|
1145
|
+
this.enumerationClasses.add(plan.key);
|
|
1146
|
+
const loc = this.enumeration.loc;
|
|
1147
|
+
const { condition } = this.propertyReceiver(plan, loc);
|
|
1148
|
+
const bag = { kind: "call", callee: this.propertyBag.name, args: [varRef("p.0", DYN, loc)], type: DYN, loc };
|
|
1149
|
+
this.enumeration.body.unshift({ kind: "if", cond: condition, then: [
|
|
1150
|
+
{ kind: "return", value: { kind: "libCall", fn: "dyn.forInKeys", args: [bag], type: DYN, loc }, loc },
|
|
1151
|
+
], else_: null, loc });
|
|
1152
|
+
changed = true;
|
|
1153
|
+
}
|
|
1154
|
+
if (this.constructDispatch)
|
|
1155
|
+
for (const [name, info] of this.constructors) {
|
|
1156
|
+
if (this.constructed.has(name))
|
|
1157
|
+
continue;
|
|
1158
|
+
this.constructed.add(name);
|
|
1159
|
+
const loc = this.constructDispatch.loc;
|
|
1160
|
+
const callee = varRef("p.0", DYN, loc), input = varRef("p.1", DYN, loc);
|
|
1161
|
+
const args = [];
|
|
1162
|
+
let supported = true;
|
|
1163
|
+
for (let i = 0; i < info.ctorParams.length; i++) {
|
|
1164
|
+
const param = info.ctorParams[i];
|
|
1165
|
+
if (param.mode !== "required" && param.mode !== "omittable") {
|
|
1166
|
+
supported = false;
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
const value = { kind: "dynKeyGet", value: input, key: { kind: "strLit", value: String(i), type: STRING, loc }, type: DYN, loc };
|
|
1170
|
+
const arg = lowerer.coerceToExpected(value, param.type);
|
|
1171
|
+
if (!typeEquals(arg.type, param.type)) {
|
|
1172
|
+
supported = false;
|
|
1173
|
+
break;
|
|
1174
|
+
}
|
|
1175
|
+
args.push(arg);
|
|
1176
|
+
}
|
|
1177
|
+
if (!supported || !info.decl)
|
|
1178
|
+
continue;
|
|
1179
|
+
const cls = classValueRef(lowerer, info, info.decl);
|
|
1180
|
+
const result = { kind: "new", className: name, args, type: { kind: "object", className: name }, loc };
|
|
1181
|
+
this.constructDispatch.body.unshift({ kind: "if", cond: { kind: "dynScalarEq", left: callee,
|
|
1182
|
+
right: lowerer.coerceToExpected(cls, DYN), type: BOOL, loc }, then: [
|
|
1183
|
+
{ kind: "return", value: lowerer.coerceToExpected(result, DYN), loc },
|
|
1184
|
+
], else_: null, loc });
|
|
1185
|
+
changed = true;
|
|
1186
|
+
}
|
|
1187
|
+
if (this.staticPropertyBag)
|
|
1188
|
+
for (const name of this.boxedConstructors) {
|
|
1189
|
+
if (this.staticBagClasses.has(name))
|
|
1190
|
+
continue;
|
|
1191
|
+
const info = lowerer.classes.get(name);
|
|
1192
|
+
if (!info?.decl || !isJsSourceFile(info.decl.getSourceFile()))
|
|
1193
|
+
continue;
|
|
1194
|
+
const loc = this.staticPropertyBag.loc;
|
|
1195
|
+
const bag = classStaticData(lowerer, info, loc);
|
|
1196
|
+
if (!bag)
|
|
1197
|
+
continue;
|
|
1198
|
+
this.staticBagClasses.add(name);
|
|
1199
|
+
const value = varRef("value", DYN, loc);
|
|
1200
|
+
this.staticPropertyBag.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.classIs", args: [value,
|
|
1201
|
+
{ kind: "strLit", value: typeKey({ kind: "classval", className: name }), type: STRING, loc }], type: BOOL, loc }, then: [{ kind: "return", value: bag, loc }], else_: null, loc });
|
|
1202
|
+
changed = true;
|
|
1203
|
+
}
|
|
1204
|
+
changed = this.normalizeDispatches(lowerer) || changed;
|
|
1205
|
+
for (const fn of this.generated)
|
|
1206
|
+
fn.speculativeDispatch = true;
|
|
540
1207
|
return changed;
|
|
541
1208
|
}
|
|
542
1209
|
/** A known native class can access its shared bag directly. Keep the
|
|
543
1210
|
* receiver as an owned parameter so calls and temporary instances retain
|
|
544
1211
|
* their ordinary evaluation and lifetime rules without a boxed capsule. */
|
|
545
1212
|
typedPropertyBag(lowerer, type, loc) {
|
|
1213
|
+
// A superclass constructor can be the first code to access this bag.
|
|
1214
|
+
// Its receiver still needs the actual subclass's prototype.
|
|
1215
|
+
if (lowerer.classes.get(type.className)?.subclasses.length)
|
|
1216
|
+
return null;
|
|
546
1217
|
const existing = this.typedPropertyBags.get(type.className);
|
|
547
1218
|
if (existing)
|
|
548
1219
|
return existing;
|
|
@@ -551,11 +1222,8 @@ export class ClassDynamicDispatch {
|
|
|
551
1222
|
return null;
|
|
552
1223
|
const receiver = varRef("p.0", type, loc);
|
|
553
1224
|
const bag = { kind: "fieldGet", obj: receiver, className: type.className, field: PROPERTY_BAG, type: DYN, loc };
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
const initialize = { ...initializers[0], obj: receiver, value: prototype
|
|
557
|
-
? { kind: "libCall", fn: "dyn.objCreate", args: [prototype], type: DYN, loc }
|
|
558
|
-
: { kind: "dynObjLit", fields: [], type: DYN, loc }, loc };
|
|
1225
|
+
const initialize = { ...initializers[0], obj: receiver,
|
|
1226
|
+
value: transformExpr(initializers[0].value, { stmt: (stmt) => stmt, expr: (expr) => expr.kind === "dynCheck" && expr.value.kind === "varRef" && expr.value.localId === "p.0" ? receiver : expr }), loc };
|
|
559
1227
|
const helper = {
|
|
560
1228
|
name: `%class.properties:${type.className}`, params: [{ localId: "p.0", name: "value", type }], returnType: DYN,
|
|
561
1229
|
locals: [{ id: "p.0", name: "value", type, mutable: false }], loc,
|
|
@@ -570,6 +1238,79 @@ export class ClassDynamicDispatch {
|
|
|
570
1238
|
lowerer.liftedFns.push(helper);
|
|
571
1239
|
return helper;
|
|
572
1240
|
}
|
|
1241
|
+
normalizedDispatches() {
|
|
1242
|
+
return [...(this.propertyBag ? [this.propertyBag] : []),
|
|
1243
|
+
...[...this.properties.values()].map((dispatch) => dispatch.fn),
|
|
1244
|
+
...[...this.computed.values()].map((dispatch) => dispatch.fn),
|
|
1245
|
+
...[...this.dispatches.values()].flatMap((dispatch) => [dispatch.fn, dispatch.callback])];
|
|
1246
|
+
}
|
|
1247
|
+
constructorPropertyBag(lowerer, loc) {
|
|
1248
|
+
if (!this.staticPropertyBag) {
|
|
1249
|
+
this.staticPropertyBag = { name: "%dyn.class.staticProperties", params: [{ localId: "value", name: "value", type: DYN }], returnType: DYN,
|
|
1250
|
+
locals: [{ id: "value", name: "value", type: DYN, mutable: false }],
|
|
1251
|
+
body: [{ kind: "return", value: varRef("value", DYN, loc), loc }], loc };
|
|
1252
|
+
lowerer.liftedFns.push(this.staticPropertyBag);
|
|
1253
|
+
this.generated.add(this.staticPropertyBag);
|
|
1254
|
+
}
|
|
1255
|
+
return this.staticPropertyBag;
|
|
1256
|
+
}
|
|
1257
|
+
/** A base capsule and a derived capsule refer to the same native object.
|
|
1258
|
+
* Select its actual layout once, so each member table needs one branch
|
|
1259
|
+
* per class rather than one per possible base/derived capsule pair. */
|
|
1260
|
+
normalizeDispatches(lowerer) {
|
|
1261
|
+
const entries = [];
|
|
1262
|
+
for (const name of this.boxed) {
|
|
1263
|
+
const capsule = lowerer.classes.get(name);
|
|
1264
|
+
if (!capsule || capsule.builtinEmitter || capsule.builtinStream || capsule.builtinError)
|
|
1265
|
+
continue;
|
|
1266
|
+
const children = [];
|
|
1267
|
+
const visit = (info) => {
|
|
1268
|
+
for (const child of info.subclasses) {
|
|
1269
|
+
if (lowerer.classCanBeConstructed(child))
|
|
1270
|
+
children.push(child);
|
|
1271
|
+
visit(child);
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
visit(capsule);
|
|
1275
|
+
if (children.length)
|
|
1276
|
+
entries.push({ capsule, children: children.reverse() });
|
|
1277
|
+
}
|
|
1278
|
+
if (!entries.length && !this.normalization)
|
|
1279
|
+
return false;
|
|
1280
|
+
const key = JSON.stringify(entries.map(({ capsule, children }) => [capsule.def.name, children.map((child) => child.def.name)]));
|
|
1281
|
+
let changed = key !== this.normalizationKey;
|
|
1282
|
+
if (!this.normalization) {
|
|
1283
|
+
const loc = this.propertyBag.loc;
|
|
1284
|
+
this.normalization = { name: "%dyn.class.normalize", params: [{ localId: "value", name: "value", type: DYN }], returnType: DYN,
|
|
1285
|
+
locals: [{ id: "value", name: "value", type: DYN, mutable: false }], body: [], loc };
|
|
1286
|
+
lowerer.liftedFns.push(this.normalization);
|
|
1287
|
+
this.generated.add(this.normalization);
|
|
1288
|
+
changed = true;
|
|
1289
|
+
}
|
|
1290
|
+
if (changed) {
|
|
1291
|
+
this.normalizationKey = key;
|
|
1292
|
+
const loc = this.normalization.loc;
|
|
1293
|
+
const value = varRef("value", DYN, loc);
|
|
1294
|
+
this.normalization.body = entries.map(({ capsule, children }) => {
|
|
1295
|
+
const type = { kind: "object", className: capsule.def.name };
|
|
1296
|
+
const receiver = { kind: "dynCheck", value, type, loc };
|
|
1297
|
+
return { kind: "if", cond: { kind: "libCall", fn: "dyn.typedRefIs", args: [value,
|
|
1298
|
+
{ kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc }, then: children.map((child) => ({
|
|
1299
|
+
kind: "if", cond: { kind: "instanceOf", value: receiver, className: child.def.name, type: BOOL, loc }, then: [{
|
|
1300
|
+
kind: "return", value: { kind: "dynFrom", value: { kind: "downcast", value: receiver,
|
|
1301
|
+
type: { kind: "object", className: child.def.name }, loc }, type: DYN, loc }, loc,
|
|
1302
|
+
}], else_: null, loc,
|
|
1303
|
+
})), else_: null, loc };
|
|
1304
|
+
});
|
|
1305
|
+
this.normalization.body.push({ kind: "return", value, loc });
|
|
1306
|
+
}
|
|
1307
|
+
for (const fn of this.normalizedDispatches()) {
|
|
1308
|
+
fn.locals.find((local) => local.id === "p.0").mutable = true;
|
|
1309
|
+
fn.body.unshift({ kind: "assign", localId: "p.0", value: { kind: "call", callee: this.normalization.name,
|
|
1310
|
+
args: [varRef("p.0", DYN, fn.loc)], type: DYN, loc: fn.loc }, loc: fn.loc });
|
|
1311
|
+
}
|
|
1312
|
+
return changed;
|
|
1313
|
+
}
|
|
573
1314
|
/** The hidden bag is part of the native object layout, so every capsule
|
|
574
1315
|
* shares it and normal class tracing/disposal owns its values. Insert it
|
|
575
1316
|
* at the same prefix offset throughout a hierarchy, including classes
|
|
@@ -592,14 +1333,19 @@ export class ClassDynamicDispatch {
|
|
|
592
1333
|
}
|
|
593
1334
|
/** A capsule retains its static type, while its object may be a subclass.
|
|
594
1335
|
* Preorder insertion lets later branches test the most derived layout first. */
|
|
595
|
-
propertyReceivers(lowerer) {
|
|
1336
|
+
propertyReceivers(lowerer, streams = false, normalize = true) {
|
|
596
1337
|
const plans = [];
|
|
1338
|
+
const seen = new Set();
|
|
597
1339
|
for (const name of this.boxed) {
|
|
598
1340
|
const capsule = lowerer.classes.get(name);
|
|
599
|
-
if (!capsule || capsule.builtinEmitter || capsule.builtinStream || capsule.builtinError)
|
|
1341
|
+
if (!capsule || (capsule.builtinEmitter || capsule.builtinStream) && !streams || capsule.builtinError)
|
|
600
1342
|
continue;
|
|
1343
|
+
const canonical = normalize && !capsule.builtinStream && !capsule.builtinEmitter;
|
|
601
1344
|
const visit = (info) => {
|
|
602
|
-
|
|
1345
|
+
if ((!canonical || !seen.has(info.def.name)) && (info === capsule || lowerer.classCanBeConstructed(info))) {
|
|
1346
|
+
seen.add(info.def.name);
|
|
1347
|
+
plans.push({ info, capsule: canonical ? info : capsule, key: canonical ? info.def.name : JSON.stringify([name, info.def.name]) });
|
|
1348
|
+
}
|
|
603
1349
|
for (const child of info.subclasses)
|
|
604
1350
|
visit(child);
|
|
605
1351
|
};
|
|
@@ -621,9 +1367,58 @@ export class ClassDynamicDispatch {
|
|
|
621
1367
|
className: plan.info.def.name, type: BOOL, loc }, else_: { kind: "boolLit", value: false, type: BOOL, loc }, type: BOOL, loc },
|
|
622
1368
|
};
|
|
623
1369
|
}
|
|
1370
|
+
reflectPropertyBody(lowerer, dispatch, info, receiver) {
|
|
1371
|
+
const { name, write } = dispatch;
|
|
1372
|
+
const loc = dispatch.fn.loc;
|
|
1373
|
+
const actualReceiver = varRef("p.reflectReceiver", DYN, loc);
|
|
1374
|
+
const instance = lowerer.coerceToExpected(receiver, DYN);
|
|
1375
|
+
const key = { kind: "strLit", value: name, type: STRING, loc };
|
|
1376
|
+
const field = info.fields.get(name);
|
|
1377
|
+
const member = `${write ? "set" : "get"}:${name}`;
|
|
1378
|
+
const accessor = field ? null : findMethodOn(lowerer, info, member);
|
|
1379
|
+
const bag = { kind: "call", callee: this.propertyBag.name, args: [instance], type: DYN, loc };
|
|
1380
|
+
const fallback = { kind: "libCall", fn: write ? "dyn.reflectSet" : "dyn.reflectGet",
|
|
1381
|
+
args: [bag, lowerer.coerceToExpected(key, DYN), ...(write ? [varRef("p.1", DYN, loc)] : []), actualReceiver], type: write ? BOOL : DYN, loc };
|
|
1382
|
+
if (accessor && info.decl) {
|
|
1383
|
+
const method = classMethodValue(lowerer, info.decl, info, member, loc);
|
|
1384
|
+
if (method) {
|
|
1385
|
+
const call = { kind: "libCall", fn: "dyn.reflectApply", args: [lowerer.coerceToExpected(method, DYN), actualReceiver,
|
|
1386
|
+
{ kind: "dynArrLit", elems: write ? [varRef("p.1", DYN, loc)] : [], type: DYN, loc }], type: DYN, loc };
|
|
1387
|
+
return [
|
|
1388
|
+
{ kind: "if", cond: { kind: "libCall", fn: "dyn.hasOwn", args: [bag, key], type: BOOL, loc }, then: [{ kind: "return", value: fallback, loc }], else_: null, loc },
|
|
1389
|
+
...(write ? [{ kind: "exprStmt", expr: call, loc }, { kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc }] : [{ kind: "return", value: call, loc }]),
|
|
1390
|
+
];
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (write && !field) {
|
|
1394
|
+
if (findMethodOn(lowerer, info, `get:${name}`) && !accessor)
|
|
1395
|
+
return [{ kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc }];
|
|
1396
|
+
return [{ kind: "return", value: fallback, loc }];
|
|
1397
|
+
}
|
|
1398
|
+
const body = this.propertyBody(lowerer, dispatch, info, receiver);
|
|
1399
|
+
if (!write)
|
|
1400
|
+
return transformStmtList(body, { stmt: (statement) => statement, expr: (expression) => expression.kind === "libCall" && expression.fn === "dyn.bagGet"
|
|
1401
|
+
? { ...expression, fn: "dyn.reflectGet", args: [expression.args[0], lowerer.coerceToExpected(expression.args[1], DYN), actualReceiver] } : expression });
|
|
1402
|
+
return [
|
|
1403
|
+
{ kind: "if", cond: { kind: "dynScalarEq", left: instance, right: actualReceiver, negated: true, type: BOOL, loc }, then: [{ kind: "return", value: {
|
|
1404
|
+
kind: "libCall", fn: "dyn.reflectDefine", args: [actualReceiver, lowerer.coerceToExpected(key, DYN), varRef("p.1", DYN, loc)], type: BOOL, loc,
|
|
1405
|
+
}, loc }], else_: null, loc },
|
|
1406
|
+
...transformStmtList(body, { stmt: (statement) => statement.kind === "return" && !statement.value
|
|
1407
|
+
? { ...statement, value: { kind: "boolLit", value: true, type: BOOL, loc } } : statement, expr: (expression) => expression }),
|
|
1408
|
+
];
|
|
1409
|
+
}
|
|
624
1410
|
propertyBody(lowerer, dispatch, info, receiver) {
|
|
625
1411
|
const { name, write } = dispatch;
|
|
626
1412
|
const loc = dispatch.fn.loc;
|
|
1413
|
+
if (info.builtinStream !== undefined && streamProperty(name)) {
|
|
1414
|
+
if (write)
|
|
1415
|
+
return [{ kind: "runtimeFence", code: "SC2020", message: `writing stream property '${name}' through an untyped value is not supported`, loc }];
|
|
1416
|
+
return [{ kind: "return", value: lowerer.coerceToExpected({ kind: "libCall", fn: "stream.prop", args: [receiver, { kind: "strLit", value: name, type: STRING, loc }], type: STREAM_BOOL_PROPERTIES.has(name) ? BOOL : F64, loc }, DYN), loc }];
|
|
1417
|
+
}
|
|
1418
|
+
if ((name === "message" || name === "name") && lowerer.isSubclassOf(info.def.name, "%Error"))
|
|
1419
|
+
return write
|
|
1420
|
+
? [errorPropertyWrite(lowerer, receiver, lowerer.coerceToExpected(varRef("p.1", DYN, loc), STRING), name), { kind: "return", value: null, loc }]
|
|
1421
|
+
: [{ kind: "return", value: lowerer.coerceToExpected(errorPropertyRead(lowerer, receiver, name), DYN), loc }];
|
|
627
1422
|
const field = info.fields.get(name);
|
|
628
1423
|
const member = `${write ? "set" : "get"}:${name}`;
|
|
629
1424
|
const accessor = field ? null : findMethodOn(lowerer, info, member);
|
|
@@ -631,6 +1426,14 @@ export class ClassDynamicDispatch {
|
|
|
631
1426
|
const getRecord = (id) => lowerer.shapes.get(id);
|
|
632
1427
|
const getUnion = (id) => lowerer.unions.get(id);
|
|
633
1428
|
if (!write) {
|
|
1429
|
+
if (name === "constructor" && !field && !accessor && info.decl && !info.generic && !info.localClass && !info.classDecorators) {
|
|
1430
|
+
this.constructors.set(info.def.name, info);
|
|
1431
|
+
const cls = lowerer.coerceToExpected(classValueRef(lowerer, info, info.decl), DYN);
|
|
1432
|
+
const bag = { kind: "call", callee: this.propertyBag.name, args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
|
1433
|
+
const key = { kind: "strLit", value: name, type: STRING, loc };
|
|
1434
|
+
return [{ kind: "return", value: { kind: "ternary", cond: { kind: "libCall", fn: "dyn.hasKey", args: [bag, key], type: BOOL, loc },
|
|
1435
|
+
then: { kind: "libCall", fn: "dyn.bagGet", args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }, else_: cls, type: DYN, loc }, loc }];
|
|
1436
|
+
}
|
|
634
1437
|
if (name === "toString" && findMethodOn(lowerer, info, name)?.declarer.builtinError) {
|
|
635
1438
|
return [{ kind: "return", value: errorToStringMethod(lowerer, receiver), loc }];
|
|
636
1439
|
}
|
|
@@ -655,10 +1458,25 @@ export class ClassDynamicDispatch {
|
|
|
655
1458
|
// A capsule may have the base ABI even when its native object is
|
|
656
1459
|
// a subclass (for example an inherited iterator returning this).
|
|
657
1460
|
// Select the method declaration at extraction, before detaching it.
|
|
658
|
-
const overrides = [...lowerer.classes.values()].filter((candidate) => candidate !== info && lowerer.isSubclassOf(candidate.def.name, info.def.name) &&
|
|
1461
|
+
const overrides = [...lowerer.classes.values()].filter((candidate) => candidate !== info && lowerer.classCanBeConstructed(candidate) && lowerer.isSubclassOf(candidate.def.name, info.def.name) &&
|
|
659
1462
|
(candidate.methods.has(name) || candidate.genericMethods?.has(name)));
|
|
660
1463
|
overrides.sort((a, b) => lowerer.isSubclassOf(a.def.name, b.def.name) ? -1 : lowerer.isSubclassOf(b.def.name, a.def.name) ? 1 : 0);
|
|
661
1464
|
const body = [];
|
|
1465
|
+
const symbol = symbolMemberKey(lowerer, info, name, loc);
|
|
1466
|
+
if (symbol || isClassOwnEnumerableFieldName(name) && !name.startsWith("sym:")) {
|
|
1467
|
+
const prototypeMethod = lowerer.prototypeMethodAccesses.has(name);
|
|
1468
|
+
if (prototypeMethod)
|
|
1469
|
+
classPrototypeData(lowerer, info, loc, receiver);
|
|
1470
|
+
const bag = { kind: "call", callee: this.propertyBag.name,
|
|
1471
|
+
args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
|
1472
|
+
const key = symbol ?? { kind: "strLit", value: name, type: STRING, loc };
|
|
1473
|
+
body.push({ kind: "if", cond: { kind: "libCall", fn: symbol ? "dyn.hasOwnComputed" : "dyn.hasKey", args: [bag, key], type: BOOL, loc },
|
|
1474
|
+
then: [{ kind: "return", value: { kind: "libCall", fn: symbol ? "dyn.reflectGet" : "dyn.bagGet", args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }, loc }], else_: null, loc });
|
|
1475
|
+
if (prototypeMethod) {
|
|
1476
|
+
body.push({ kind: "return", value: dynUndefinedExpr(loc), loc });
|
|
1477
|
+
return body;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
662
1480
|
for (const candidate of overrides) {
|
|
663
1481
|
const selected = classMethodValue(lowerer, info.decl, candidate, name, loc);
|
|
664
1482
|
const branch = selected && canConvertToDyn(selected.type, getRecord, getUnion)
|
|
@@ -670,8 +1488,12 @@ export class ClassDynamicDispatch {
|
|
|
670
1488
|
return body;
|
|
671
1489
|
}
|
|
672
1490
|
}
|
|
673
|
-
if (!field && !accessor)
|
|
674
|
-
|
|
1491
|
+
if (!field && !accessor) {
|
|
1492
|
+
const instance = lowerer.coerceToExpected(receiver, DYN);
|
|
1493
|
+
const bag = { kind: "call", callee: this.propertyBag.name, args: [instance], type: DYN, loc };
|
|
1494
|
+
return [{ kind: "return", value: { kind: "libCall", fn: "dyn.bagGet", args: [bag,
|
|
1495
|
+
{ kind: "strLit", value: name, type: STRING, loc }, instance], type: DYN, loc }, loc }];
|
|
1496
|
+
}
|
|
675
1497
|
const type = field ?? accessor.sig.ret;
|
|
676
1498
|
if (!canConvertToDyn(type, getRecord, getUnion))
|
|
677
1499
|
return fence();
|
|
@@ -683,8 +1505,31 @@ export class ClassDynamicDispatch {
|
|
|
683
1505
|
(t.kind === "union" && (getUnion(t.unionId)?.arms.some(mutable) ?? false));
|
|
684
1506
|
if (boxed.kind === "dynFrom" && mutable(boxed.value.type))
|
|
685
1507
|
boxed.liveRef = true;
|
|
1508
|
+
if (accessor) {
|
|
1509
|
+
const bag = { kind: "call", callee: this.propertyBag.name,
|
|
1510
|
+
args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
|
1511
|
+
const key = { kind: "strLit", value: name, type: STRING, loc };
|
|
1512
|
+
return [{ kind: "if", cond: { kind: "libCall", fn: "dyn.hasOwn", args: [bag, key], type: BOOL, loc },
|
|
1513
|
+
then: [{ kind: "return", value: { kind: "libCall", fn: "dyn.bagGet", args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }, loc }], else_: null, loc },
|
|
1514
|
+
{ kind: "return", value: boxed, loc }];
|
|
1515
|
+
}
|
|
1516
|
+
if (field && info.def.tracksOwnFields) {
|
|
1517
|
+
const instance = lowerer.coerceToExpected(receiver, DYN);
|
|
1518
|
+
const bag = { kind: "call", callee: this.propertyBag.name, args: [instance], type: DYN, loc };
|
|
1519
|
+
const symbol = symbolMemberKey(lowerer, info, name, loc);
|
|
1520
|
+
const key = symbol ?? { kind: "strLit", value: name, type: STRING, loc };
|
|
1521
|
+
return [{ kind: "return", value: { kind: "ternary", cond: { kind: "libCall", fn: symbol ? "dyn.hasOwnComputed" : "dyn.hasOwn", args: [bag, key], type: BOOL, loc },
|
|
1522
|
+
then: boxed, else_: { kind: "libCall", fn: symbol ? "dyn.reflectGet" : "dyn.bagGet", args: [bag, key, instance], type: DYN, loc }, type: DYN, loc }, loc }];
|
|
1523
|
+
}
|
|
686
1524
|
return [{ kind: "return", value: boxed, loc }];
|
|
687
1525
|
}
|
|
1526
|
+
if (!field && !accessor && isClassOwnEnumerableFieldName(name) && !name.startsWith("sym:") && !findMethodOn(lowerer, info, `get:${name}`)) {
|
|
1527
|
+
const bag = { kind: "call", callee: this.propertyBag.name,
|
|
1528
|
+
args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
|
1529
|
+
return [{ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.bagSet", args: [bag,
|
|
1530
|
+
{ kind: "strLit", value: name, type: STRING, loc }, varRef("p.1", DYN, loc), lowerer.coerceToExpected(receiver, DYN)], type: VOID, loc }, loc },
|
|
1531
|
+
{ kind: "return", value: null, loc }];
|
|
1532
|
+
}
|
|
688
1533
|
if (!field && !accessor)
|
|
689
1534
|
return [{ kind: "throw", value: {
|
|
690
1535
|
kind: "libCall", fn: "error.new", args: [{
|
|
@@ -707,6 +1552,35 @@ export class ClassDynamicDispatch {
|
|
|
707
1552
|
methodBody(lowerer, dispatch, info, receiver) {
|
|
708
1553
|
const { method, loc } = dispatch.source;
|
|
709
1554
|
const fence = () => [{ kind: "runtimeFence", code: "SC2020", message: `calling '${method}' on this native class through an untyped value is not supported yet`, loc }];
|
|
1555
|
+
if (info.builtinStream || emitterRooted(lowerer, info) && EMITTER_METHODS.has(method) && !findMethodOn(lowerer, info, method)) {
|
|
1556
|
+
const incoming = dispatch.source.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc));
|
|
1557
|
+
const registering = ["on", "addListener", "once", "prependListener", "prependOnceListener"].includes(method);
|
|
1558
|
+
const removing = method === "off" || method === "removeListener";
|
|
1559
|
+
const ret = (value) => [{ kind: "return", value: lowerer.coerceToExpected(value, DYN), loc }];
|
|
1560
|
+
if (method === "emit") {
|
|
1561
|
+
if (!incoming.length)
|
|
1562
|
+
return fence();
|
|
1563
|
+
return ret({ kind: "libCall", fn: "emitter.emitFlex", args: [receiver,
|
|
1564
|
+
lowerer.coerceToExpected(incoming[0], STRING), ...incoming.slice(1)], type: BOOL, loc });
|
|
1565
|
+
}
|
|
1566
|
+
if (registering || removing) {
|
|
1567
|
+
if (incoming.length < 2)
|
|
1568
|
+
return fence();
|
|
1569
|
+
const event = lowerer.coerceToExpected(incoming[0], STRING);
|
|
1570
|
+
const cb = incoming[1];
|
|
1571
|
+
return [{ kind: "exprStmt", expr: { kind: "libCall", fn: "emitter.checkListener", args: [cb], type: VOID, loc }, loc }, ...ret({ kind: "libCall", fn: registering ? info.builtinStream ? "stream.onDyn" : "emitter.onFlex" : "emitter.offDyn", args: [receiver, event, cb, ...(registering ? [
|
|
1572
|
+
{ kind: "boolLit", value: method === "once" || method === "prependOnceListener", type: BOOL, loc },
|
|
1573
|
+
{ kind: "boolLit", value: method.startsWith("prepend"), type: BOOL, loc },
|
|
1574
|
+
] : [])], type: receiver.type, loc })];
|
|
1575
|
+
}
|
|
1576
|
+
if (method === "read")
|
|
1577
|
+
return ret({ kind: "libCall", fn: "readable.readDyn", args: [receiver, incoming[0] ?? dynUndefinedExpr(loc)], type: DYN, loc });
|
|
1578
|
+
if (method === "pause" || method === "resume" || method === "isPaused")
|
|
1579
|
+
return ret({ kind: "libCall", fn: method === "pause" ? "readable.pause" : method === "resume" ? "readable.resume" : "readable.isPaused", args: [receiver], type: method === "isPaused" ? BOOL : receiver.type, loc });
|
|
1580
|
+
if (method === "destroy" && (!incoming.length || incoming[0]?.kind === "unitLit"))
|
|
1581
|
+
return ret({ kind: "libCall", fn: "stream.destroy", args: [receiver], type: receiver.type, loc });
|
|
1582
|
+
return fence();
|
|
1583
|
+
}
|
|
710
1584
|
const methodInfo = findMethodOn(lowerer, info, method);
|
|
711
1585
|
if (this.asyncFree && (methodInfo?.sig.gen || methodInfo?.sig.async)) {
|
|
712
1586
|
return [{ kind: "runtimeFence", code: "SC4005", message: `calling async or generator method '${method}' through an untyped library value is not supported`, loc }];
|
|
@@ -794,7 +1668,7 @@ export class ClassDynamicDispatch {
|
|
|
794
1668
|
body.unshift({ kind: "if", cond: { kind: "dynTest", test: "undefined", negated: true, value: descriptor, type: BOOL, loc }, then: [
|
|
795
1669
|
{ kind: "return", value: { kind: "dynCall", callee: { kind: "dynKeyGet", value: descriptor,
|
|
796
1670
|
key: { kind: "strLit", value: "value", type: STRING, loc }, type: DYN, loc }, receiver: varRef("p.0", DYN, loc),
|
|
797
|
-
args: incoming, calleeName: dispatch.source.calleeName, type: DYN, loc }, loc },
|
|
1671
|
+
args: incoming, calleeName: dispatch.source.calleeName, calleeNameValue: varRef("p.calleeName", STRING, loc), type: DYN, loc }, loc },
|
|
798
1672
|
], else_: null, loc });
|
|
799
1673
|
}
|
|
800
1674
|
return body;
|