@scriptc/compiler 0.2.1 → 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 +59 -4
- package/dist/backend/llvm/dyn.d.ts +3 -0
- package/dist/backend/llvm/dyn.js +84 -12
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +4 -1
- package/dist/backend/llvm/emitter.js +20 -5
- 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-containers.js +5 -0
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +30 -5
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +70 -9
- 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-shared.js +35 -1
- package/dist/backend/llvm/lib-shared.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/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/coverage/surface-manifest.js +5 -3
- package/dist/coverage/surface-manifest.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 +1 -1
- package/dist/frontend/lowering/class-callbacks.js +7 -6
- package/dist/frontend/lowering/class-callbacks.js.map +1 -1
- package/dist/frontend/lowering/class-construction.d.ts +2 -0
- package/dist/frontend/lowering/class-construction.js +34 -5
- package/dist/frontend/lowering/class-construction.js.map +1 -1
- package/dist/frontend/lowering/class-descriptors.d.ts +1 -1
- package/dist/frontend/lowering/class-descriptors.js +12 -5
- package/dist/frontend/lowering/class-descriptors.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +25 -0
- package/dist/frontend/lowering/class-dynamic-dispatch.js +597 -78
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.test.js +64 -2
- 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 +11 -41
- package/dist/frontend/lowering/class-instance-constructor.js.map +1 -1
- package/dist/frontend/lowering/class-method-values.d.ts +16 -0
- package/dist/frontend/lowering/class-method-values.js +60 -29
- package/dist/frontend/lowering/class-method-values.js.map +1 -1
- package/dist/frontend/lowering/class-prototypes.d.ts +5 -1
- package/dist/frontend/lowering/class-prototypes.js +69 -10
- package/dist/frontend/lowering/class-prototypes.js.map +1 -1
- package/dist/frontend/lowering/class-runtime-statics.d.ts +4 -4
- package/dist/frontend/lowering/class-runtime-statics.js +26 -8
- package/dist/frontend/lowering/class-runtime-statics.js.map +1 -1
- package/dist/frontend/lowering/class-static-data.d.ts +1 -0
- package/dist/frontend/lowering/class-static-data.js +4 -2
- package/dist/frontend/lowering/class-static-data.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.js +17 -4
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/containers/string-and-regexp.js +6 -1
- 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/expressions/object-literals.js +31 -1
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
- package/dist/frontend/lowering/expressions/optional-chains.js +3 -3
- package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +4 -9
- package/dist/frontend/lowering/lower-builtins.js +291 -51
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +283 -73
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +5 -0
- package/dist/frontend/lowering/lower-classes.js +439 -25
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +1 -1
- package/dist/frontend/lowering/lower-containers.js +39 -17
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +10 -6
- package/dist/frontend/lowering/lower-exprs.js +294 -129
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.js +5 -1
- 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.js +4 -0
- 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 +186 -82
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.test.js +69 -1
- package/dist/frontend/lowering/lower-modules.test.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +100 -35
- 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 +2 -0
- 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 +26 -9
- package/dist/frontend/lowering/lowerer.js +218 -27
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/lowerer.test.js +38 -0
- 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 +1 -0
- package/dist/frontend/lowering/surfaces.js +24 -12
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/lowering/text-decoder-encoding.js +3 -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/program.js +4 -2
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/type-mapper.js +83 -14
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/frontend/union-discriminants.js +22 -7
- 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/ir.d.ts +27 -13
- package/dist/ir/ir.js +26 -2
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/serialize.js +21 -1
- package/dist/ir/serialize.js.map +1 -1
- package/dist/ir/serialize.test.js +21 -0
- 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 +56 -5
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +17 -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/package.json +20 -20
- package/surface-manifest.json +115 -13
|
@@ -4,20 +4,48 @@ import { varRef } from "../../ir/build.js";
|
|
|
4
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, classValueRef, findGenericMethodOn, findMethodOn, findStaticOn, genericOverrideBelow, upcastTo } from "./lower-classes.js";
|
|
7
|
+
import { accessorCall, classValueRef, findGenericMethodOn, findGenericStaticOn, findMethodOn, findStaticOn, genericOverrideBelow, upcastTo } from "./lower-classes.js";
|
|
8
8
|
import { classPrototypeData, hasClassPrototypeData, reflectClassPrototype } from "./class-prototypes.js";
|
|
9
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
17
|
import { refreshDescriptorGuards } from "./class-descriptors.js";
|
|
16
18
|
import { ClassConstructionDispatch } from "./class-construction.js";
|
|
19
|
+
import { emitterRooted } from "./lower-event-emitter.js";
|
|
17
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"]);
|
|
18
22
|
const STREAM_BOOL_PROPERTIES = new Set(["readable", "readableEnded", "writable", "writableEnded", "writableFinished", "writableNeedDrain", "destroyed", "closed", "readableObjectMode", "writableObjectMode", "allowHalfOpen"]);
|
|
19
23
|
const STREAM_NUM_PROPERTIES = new Set(["readableLength", "readableHighWaterMark", "writableLength", "writableHighWaterMark", "writableCorked"]);
|
|
20
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
|
+
}
|
|
21
49
|
export function classInstanceOf(lowerer, value, info, loc, classValue) {
|
|
22
50
|
const name = `%dyn.class.${classValue ? "valueInstanceof" : "instanceof"}:${info.def.name}`;
|
|
23
51
|
const params = [{ localId: "value", name: "value", type: DYN },
|
|
@@ -29,7 +57,7 @@ export function classInstanceOf(lowerer, value, info, loc, classValue) {
|
|
|
29
57
|
{ kind: "varDecl", localId: "candidate", init: varRef("value", DYN, loc), loc },
|
|
30
58
|
{ kind: "while", cond: { kind: "ternary", cond: { kind: "dynTest", test: "nullish", value: candidate, type: BOOL, loc },
|
|
31
59
|
then: { kind: "boolLit", value: false, type: BOOL, loc }, else_: { kind: "dynTest", test: "object", value: candidate, type: BOOL, loc }, type: BOOL, loc }, body: [
|
|
32
|
-
{ kind: "assign", localId: "candidate", value: { kind: "libCall", fn: "dyn.getPrototype", args: [candidate], type: DYN, loc }, loc },
|
|
60
|
+
{ kind: "assign", localId: "candidate", value: { kind: "libCall", fn: "dyn.getPrototype", args: [candidate], type: DYN, loc, prototypeIdentityOnly: true }, loc },
|
|
33
61
|
{ kind: "if", cond: { kind: "dynScalarEq", left: candidate, right: prototype, type: BOOL, loc }, then: [
|
|
34
62
|
{ kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc },
|
|
35
63
|
], else_: null, loc },
|
|
@@ -57,6 +85,82 @@ export function classPropertiesHelper(lowerer, loc) {
|
|
|
57
85
|
lowerer.liftedFns.push(helper);
|
|
58
86
|
return helper;
|
|
59
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
|
+
}
|
|
60
164
|
/** Calls on native class capsules keep the instance's compiled methods. The
|
|
61
165
|
* reachable-body fixed point discovers both the boxed classes and method
|
|
62
166
|
* names before generating checked native dispatch; ordinary dyn receivers
|
|
@@ -73,6 +177,11 @@ export class ClassDynamicDispatch {
|
|
|
73
177
|
dispatches = new Map();
|
|
74
178
|
properties = new Map();
|
|
75
179
|
computed = new Map();
|
|
180
|
+
computedClassHelpers = new Map();
|
|
181
|
+
normalization = null;
|
|
182
|
+
normalizationKey = "";
|
|
183
|
+
staticPropertyBag = null;
|
|
184
|
+
staticBagClasses = new Set();
|
|
76
185
|
propertyBag = null;
|
|
77
186
|
errorStrings = new Map();
|
|
78
187
|
bagClasses = new Set();
|
|
@@ -84,12 +193,26 @@ export class ClassDynamicDispatch {
|
|
|
84
193
|
constructed = new Set();
|
|
85
194
|
instanceTests = new Map();
|
|
86
195
|
jsonPrototypes = new Set();
|
|
196
|
+
serializesClasses = false;
|
|
87
197
|
descriptorHelper = null;
|
|
88
198
|
prototypeHelper = null;
|
|
89
199
|
prototypeClasses = new Set();
|
|
200
|
+
prototypeIdentityHelper = null;
|
|
201
|
+
prototypeIdentityClasses = new Set();
|
|
90
202
|
instancePrototypes = new Map();
|
|
203
|
+
checkedCasts = new Map();
|
|
204
|
+
enumeration = null;
|
|
205
|
+
enumerationClasses = new Set();
|
|
91
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
|
+
}
|
|
92
214
|
let changed = refreshErrorMethodDispatch(lowerer, functions);
|
|
215
|
+
changed = refreshClassMethodValueSelections(lowerer) || changed;
|
|
93
216
|
changed = refreshErrorPropertyDispatch(lowerer, functions) || changed;
|
|
94
217
|
changed = this.generatorDispatch.process(lowerer, functions, this.generated) || changed;
|
|
95
218
|
changed = this.constructionDispatch.process(lowerer, functions) || changed;
|
|
@@ -123,12 +246,27 @@ export class ClassDynamicDispatch {
|
|
|
123
246
|
};
|
|
124
247
|
for (const fn of functions)
|
|
125
248
|
everyStmtList(fn.body, {
|
|
126
|
-
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
|
+
},
|
|
127
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
|
+
}
|
|
128
262
|
switch (expr.kind) {
|
|
129
263
|
case "dynFrom":
|
|
130
264
|
discover(expr.value.type);
|
|
131
265
|
break;
|
|
266
|
+
case "fieldIncDec":
|
|
267
|
+
if (lowerer.classes.get(expr.className)?.def.tracksOwnFields && isClassOwnEnumerableFieldName(expr.field))
|
|
268
|
+
discover(expr.obj.type);
|
|
269
|
+
break;
|
|
132
270
|
case "call":
|
|
133
271
|
if (expr.callee === "%dyn.class.properties" && expr.args[0]?.kind === "dynFrom" && isDynTypedRefType(expr.args[0].value.type))
|
|
134
272
|
rewrite.add(fn);
|
|
@@ -137,9 +275,13 @@ export class ClassDynamicDispatch {
|
|
|
137
275
|
case "dynInvoke":
|
|
138
276
|
rewrite.add(fn);
|
|
139
277
|
break;
|
|
278
|
+
case "dynCheck":
|
|
279
|
+
if (isDynTypedRefType(expr.type))
|
|
280
|
+
rewrite.add(fn);
|
|
281
|
+
break;
|
|
140
282
|
case "libCall":
|
|
141
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" ||
|
|
142
|
-
expr.fn === "dyn.hasKeyComputed" || expr.fn === "dyn.hasOwnComputed" || expr.fn === "dyn.propertyIsEnumerableComputed" || expr.fn === "dyn.defineProperty" || expr.fn === "dyn.getPrototype")
|
|
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")
|
|
143
285
|
rewrite.add(fn);
|
|
144
286
|
break;
|
|
145
287
|
}
|
|
@@ -203,7 +345,19 @@ export class ClassDynamicDispatch {
|
|
|
203
345
|
then: [{ kind: "return", value: prototype, loc }], else_: null, loc });
|
|
204
346
|
changed = true;
|
|
205
347
|
}
|
|
206
|
-
if (!this.
|
|
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")) {
|
|
207
361
|
this.jsonPrototypes.add(className);
|
|
208
362
|
const loc = this.propertyBag.loc;
|
|
209
363
|
const method = classMethodValue(lowerer, info.decl, info, "toJSON", loc);
|
|
@@ -225,7 +379,7 @@ export class ClassDynamicDispatch {
|
|
|
225
379
|
}
|
|
226
380
|
}
|
|
227
381
|
}
|
|
228
|
-
for (const plan of this.propertyReceivers(lowerer)) {
|
|
382
|
+
for (const plan of this.propertyReceivers(lowerer, false, false)) {
|
|
229
383
|
const info = plan.info;
|
|
230
384
|
const className = info.def.name;
|
|
231
385
|
let instancePrototype = this.instancePrototypes.get(plan.capsule.def.name);
|
|
@@ -258,13 +412,15 @@ export class ClassDynamicDispatch {
|
|
|
258
412
|
cond: { kind: "instanceOf", value: receiver, className, type: BOOL, loc }, then: [returned], else_: null, loc });
|
|
259
413
|
changed = true;
|
|
260
414
|
}
|
|
261
|
-
const needsPrototype = (current) => hasClassPrototypeData(current) || current.subclasses.some(needsPrototype);
|
|
415
|
+
const needsPrototype = (current) => hasClassPrototypeData(current) || current.subclasses.some((child) => lowerer.classCanBeConstructed(child) && needsPrototype(child));
|
|
262
416
|
const prototypeFor = (current, receiver, loc) => {
|
|
263
417
|
const currentReceiver = receiver.type.kind === "object" && receiver.type.className !== current.def.name
|
|
264
418
|
? { kind: "downcast", value: receiver, type: { kind: "object", className: current.def.name }, loc }
|
|
265
419
|
: receiver;
|
|
266
420
|
let result = classPrototypeData(lowerer, current, loc, currentReceiver);
|
|
267
421
|
for (const child of current.subclasses) {
|
|
422
|
+
if (!lowerer.classCanBeConstructed(child))
|
|
423
|
+
continue;
|
|
268
424
|
const selected = prototypeFor(child, receiver, loc);
|
|
269
425
|
if (selected && result)
|
|
270
426
|
result = { kind: "ternary", cond: { kind: "instanceOf", value: receiver, className: child.def.name, type: BOOL, loc },
|
|
@@ -285,6 +441,18 @@ export class ClassDynamicDispatch {
|
|
|
285
441
|
continue;
|
|
286
442
|
this.bagClasses.add(plan.key);
|
|
287
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
|
+
}
|
|
288
456
|
const loc = this.propertyBag.loc;
|
|
289
457
|
const { receiver, condition } = this.propertyReceiver(plan, loc);
|
|
290
458
|
const bag = { kind: "fieldGet", obj: receiver, className, field: PROPERTY_BAG, type: DYN, loc };
|
|
@@ -314,10 +482,11 @@ export class ClassDynamicDispatch {
|
|
|
314
482
|
const found = byMethod.get(method);
|
|
315
483
|
if (found)
|
|
316
484
|
return found;
|
|
317
|
-
const matching =
|
|
318
|
-
|
|
319
|
-
if (!info || info.fields.has(method) || info.builtinEmitter || info.builtinError)
|
|
485
|
+
const matching = this.propertyReceivers(lowerer, true).flatMap(({ info }) => {
|
|
486
|
+
if (!info || info.fields.has(method) || info.builtinError)
|
|
320
487
|
return [];
|
|
488
|
+
if (emitterRooted(lowerer, info) && EMITTER_METHODS.has(method))
|
|
489
|
+
return [info];
|
|
321
490
|
if (info.builtinStream)
|
|
322
491
|
return STREAM_METHODS.has(method) ? [info] : [];
|
|
323
492
|
return findMethodOn(lowerer, info, method) || findGenericMethodOn(lowerer, info, method) ? [info] : [];
|
|
@@ -331,10 +500,71 @@ export class ClassDynamicDispatch {
|
|
|
331
500
|
// every reachability pass. Recompute this set as new bodies appear.
|
|
332
501
|
if (this.generated.has(fn) || !rewrite.has(fn))
|
|
333
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
|
+
};
|
|
334
535
|
fn.body = transformStmtList(fn.body, {
|
|
335
536
|
stmt: (stmt) => stmt,
|
|
336
537
|
expr: (expr) => {
|
|
337
|
-
if (expr.kind === "
|
|
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
|
+
}
|
|
338
568
|
if (!this.prototypeHelper) {
|
|
339
569
|
const loc = expr.loc;
|
|
340
570
|
this.prototypeHelper = { name: "%dyn.class.getPrototype", params: [{ localId: "value", name: "value", type: DYN }],
|
|
@@ -394,6 +624,19 @@ export class ClassDynamicDispatch {
|
|
|
394
624
|
}
|
|
395
625
|
return { kind: "call", callee: this.constructDispatch.name, args: expr.args, type: DYN, loc: expr.loc };
|
|
396
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
|
+
}
|
|
397
640
|
if (expr.kind === "libCall" && expr.fn === "dyn.defineProperty") {
|
|
398
641
|
const loc = expr.loc;
|
|
399
642
|
if (!this.descriptorHelper) {
|
|
@@ -418,13 +661,24 @@ export class ClassDynamicDispatch {
|
|
|
418
661
|
}
|
|
419
662
|
return { kind: "call", callee: this.descriptorHelper.name, args: expr.args, type: DYN, loc };
|
|
420
663
|
}
|
|
421
|
-
|
|
422
|
-
|
|
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;
|
|
423
673
|
const computedProbe = expr.kind === "libCall" && ["dyn.hasKeyComputed", "dyn.hasOwnComputed", "dyn.propertyIsEnumerableComputed"].includes(expr.fn) ? expr : null;
|
|
424
674
|
if (computedRead || computedWrite || computedProbe) {
|
|
425
675
|
const probe = computedProbe ? computedProbe.fn === "dyn.hasKeyComputed" ? "in" : computedProbe.fn === "dyn.hasOwnComputed" ? "own" : "enumerable" : undefined;
|
|
426
|
-
const dynamicKey = !!computedProbe || computedWrite?.fn === "dyn.keySetComputed" || computedRead?.key.type.kind === "dyn";
|
|
427
|
-
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]);
|
|
428
682
|
let dispatch = this.computed.get(key);
|
|
429
683
|
if (!dispatch) {
|
|
430
684
|
const loc = expr.loc;
|
|
@@ -432,23 +686,28 @@ export class ClassDynamicDispatch {
|
|
|
432
686
|
{ localId: "p.0", name: "value", type: DYN },
|
|
433
687
|
{ localId: "p.key", name: "key", type: dynamicKey ? DYN : STRING },
|
|
434
688
|
...(computedWrite ? [{ localId: "p.1", name: "stored", type: DYN }] : []),
|
|
689
|
+
...(reflect ? [{ localId: "p.reflectReceiver", name: "receiver", type: DYN }] : []),
|
|
435
690
|
];
|
|
436
691
|
const keyLocal = dynamicKey ? "key.string" : "p.key";
|
|
437
692
|
const bag = { kind: "call", callee: this.propertyBag.name, args: [varRef("p.0", DYN, loc)], type: DYN, loc };
|
|
438
|
-
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
|
|
439
696
|
? { ...computedProbe, fn: probe === "in" ? "dyn.hasKey" : probe === "own" ? "dyn.hasOwn" : "dyn.propertyIsEnumerable", args: [bag, varRef(keyLocal, STRING, loc)] }
|
|
440
697
|
: computedRead
|
|
441
698
|
? { ...computedRead, value: bag, key: varRef(keyLocal, STRING, loc) }
|
|
442
699
|
: { ...computedWrite, fn: "dyn.keySet", args: [bag, varRef(keyLocal, STRING, loc), varRef("p.1", DYN, loc)] };
|
|
443
700
|
const helper = {
|
|
444
|
-
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,
|
|
445
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 }] : [])],
|
|
446
|
-
body: computedWrite
|
|
703
|
+
body: reflect ? [{ kind: "return", value: fallback, loc }] : computedWrite
|
|
447
704
|
? [{ kind: "exprStmt", expr: fallback, loc }, { kind: "return", value: null, loc }]
|
|
448
705
|
: [{ kind: "return", value: fallback, loc }], loc,
|
|
449
706
|
};
|
|
450
707
|
if (dynamicKey)
|
|
451
|
-
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 ? [
|
|
452
711
|
{ kind: "return", value: { ...computedProbe, args: [bag, varRef("p.key", DYN, loc)] }, loc },
|
|
453
712
|
] : computedRead ? [
|
|
454
713
|
{ kind: "return", value: { ...computedRead, value: bag, key: varRef("p.key", DYN, loc) }, loc },
|
|
@@ -456,18 +715,18 @@ export class ClassDynamicDispatch {
|
|
|
456
715
|
{ kind: "exprStmt", expr: { ...computedWrite, args: [bag, varRef("p.key", DYN, loc), varRef("p.1", DYN, loc)] }, loc },
|
|
457
716
|
{ kind: "return", value: null, loc },
|
|
458
717
|
], else_: null, loc }, { kind: "if", cond: { kind: "dynTest", test: "nullish", value: varRef("p.0", DYN, loc), type: BOOL, loc }, then: [
|
|
459
|
-
...(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 }] : [
|
|
460
719
|
{ kind: "exprStmt", expr: { ...computedWrite, args: [varRef("p.0", DYN, loc), varRef("p.key", DYN, loc), varRef("p.1", DYN, loc)] }, loc },
|
|
461
720
|
{ kind: "return", value: null, loc },
|
|
462
721
|
]),
|
|
463
722
|
], else_: null, loc }, { kind: "varDecl", localId: keyLocal, init: { kind: "libCall", fn: "dyn.toStringCoerce", args: [varRef("p.key", DYN, loc)], type: STRING, loc }, loc });
|
|
464
|
-
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 };
|
|
465
724
|
this.computed.set(key, dispatch);
|
|
466
725
|
this.generated.add(helper);
|
|
467
726
|
lowerer.liftedFns.push(helper);
|
|
468
727
|
changed = true;
|
|
469
728
|
}
|
|
470
|
-
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 };
|
|
471
730
|
}
|
|
472
731
|
const read = expr.kind === "dynKeyGet" && expr.key.kind === "strLit" ? expr : null;
|
|
473
732
|
const write = expr.kind === "libCall" && expr.fn === "dyn.keySet" && expr.args[1]?.kind === "strLit" ? expr : null;
|
|
@@ -478,9 +737,14 @@ export class ClassDynamicDispatch {
|
|
|
478
737
|
if (!dispatch) {
|
|
479
738
|
const loc = expr.loc;
|
|
480
739
|
const params = (write ? [0, 1] : [0]).map((i) => ({ localId: `p.${i}`, name: `p${i}`, type: DYN }));
|
|
481
|
-
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;
|
|
482
744
|
const fallback = read
|
|
483
|
-
? {
|
|
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 }
|
|
484
748
|
: { ...write, args: [bag, write.args[1], varRef("p.1", DYN, loc)] };
|
|
485
749
|
const helper = {
|
|
486
750
|
name: `%dyn.class.property.${this.properties.size}`, params, returnType: write ? VOID : DYN,
|
|
@@ -507,13 +771,19 @@ export class ClassDynamicDispatch {
|
|
|
507
771
|
const invoke = iterator
|
|
508
772
|
? { kind: "dynInvoke", recv: iterator.args[0], method: "sym:iterator", calleeName: "value[Symbol.iterator]", args: [], type: DYN, loc: iterator.loc }
|
|
509
773
|
: expr.kind === "dynInvoke" ? expr : null;
|
|
510
|
-
|
|
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")))
|
|
511
779
|
return expr;
|
|
512
|
-
const key = JSON.stringify([invoke.method, invoke.
|
|
780
|
+
const key = JSON.stringify([invoke.method, invoke.args.length, iterator?.fn, iterator?.args[1]]);
|
|
513
781
|
let dispatch = this.dispatches.get(key);
|
|
514
782
|
if (!dispatch) {
|
|
515
783
|
const params = [invoke.recv, ...invoke.args].map((_, i) => ({ localId: `p.${i}`, name: `p${i}`, type: DYN }));
|
|
516
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 });
|
|
517
787
|
const callback = {
|
|
518
788
|
name: `%dyn.class.callback.${this.dispatches.size}`, params: [params[0]], returnType: DYN,
|
|
519
789
|
locals: [{ id: "p.0", name: "receiver", type: DYN, mutable: false }],
|
|
@@ -521,10 +791,10 @@ export class ClassDynamicDispatch {
|
|
|
521
791
|
};
|
|
522
792
|
const helper = {
|
|
523
793
|
name: `%dyn.class.call.${this.dispatches.size}`, params, returnType: DYN,
|
|
524
|
-
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 })),
|
|
525
795
|
body: [{ kind: "return", value: iterator
|
|
526
|
-
? { ...iterator, args: [varRef("p.0", DYN, expr.loc), ...iterator.args.slice(1)] }
|
|
527
|
-
: { ...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 }],
|
|
528
798
|
loc: expr.loc,
|
|
529
799
|
};
|
|
530
800
|
if (invoke.method === "toString" && lowerer.classes.has("%Error")) {
|
|
@@ -533,7 +803,7 @@ export class ClassDynamicDispatch {
|
|
|
533
803
|
helper.body.unshift({ kind: "if", cond: { kind: "dynTest", test: "error", value, type: BOOL, loc }, then: [
|
|
534
804
|
{ kind: "return", value: {
|
|
535
805
|
kind: "dynCall", callee: errorToStringMethod(lowerer, { kind: "dynCheck", value, type: { kind: "object", className: "%Error" }, loc }),
|
|
536
|
-
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,
|
|
537
807
|
}, loc },
|
|
538
808
|
], else_: null, loc });
|
|
539
809
|
}
|
|
@@ -550,27 +820,67 @@ export class ClassDynamicDispatch {
|
|
|
550
820
|
const receiver = varRef(local.id, DYN, expr.loc);
|
|
551
821
|
return { kind: "seqExpr", stmts: [{ kind: "varDecl", localId: local.id, init: invoke.recv, loc: expr.loc }], result: {
|
|
552
822
|
kind: "call", callee: dispatch.fn.name, args: [receiver,
|
|
553
|
-
{ 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,
|
|
554
824
|
}, type: DYN, loc: expr.loc };
|
|
555
825
|
},
|
|
556
826
|
});
|
|
557
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
|
+
}
|
|
558
846
|
for (const dispatch of this.dispatches.values()) {
|
|
559
|
-
for (const
|
|
560
|
-
|
|
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)
|
|
561
855
|
continue;
|
|
562
|
-
dispatch.classes.add(
|
|
856
|
+
dispatch.classes.add(key);
|
|
563
857
|
const loc = dispatch.source.loc;
|
|
564
|
-
const type = { kind: "object", className: info.def.name };
|
|
565
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);
|
|
566
878
|
if (isClassCallback(lowerer, info, dispatch.source.method)) {
|
|
567
879
|
const prototypeMethod = lowerer.prototypeMethodAccesses.has(dispatch.source.method);
|
|
568
880
|
if (prototypeMethod)
|
|
569
881
|
classPrototypeData(lowerer, info, loc);
|
|
570
|
-
const bag = { kind: "call", callee: this.propertyBag.name, args: [
|
|
571
|
-
dispatch.callback.body.unshift({ kind: "if", cond: {
|
|
572
|
-
kind: "libCall", fn: "dyn.typedRefIs", args: [receiver, { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc,
|
|
573
|
-
}, then: [{ kind: "return", value: { kind: "ternary", cond: prototypeMethod ? { kind: "boolLit", value: true, type: BOOL, loc } : { kind: "libCall", fn: "dyn.hasKey", args: [bag,
|
|
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,
|
|
574
884
|
{ kind: "strLit", value: dispatch.source.method, type: STRING, loc }], type: BOOL, loc }, then: { kind: "dynObjLit", fields: [
|
|
575
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 } },
|
|
576
886
|
], type: DYN, loc }, else_: dynUndefinedExpr(loc), type: DYN, loc }, loc }], else_: null, loc });
|
|
@@ -578,7 +888,7 @@ export class ClassDynamicDispatch {
|
|
|
578
888
|
const before = lowerer.diags.length;
|
|
579
889
|
let branch;
|
|
580
890
|
try {
|
|
581
|
-
branch = this.methodBody(lowerer, dispatch, info,
|
|
891
|
+
branch = this.methodBody(lowerer, dispatch, info, receiver);
|
|
582
892
|
}
|
|
583
893
|
catch (error) {
|
|
584
894
|
if (!(error instanceof PoisonError) || !info.decl)
|
|
@@ -589,9 +899,7 @@ export class ClassDynamicDispatch {
|
|
|
589
899
|
branch = [fence];
|
|
590
900
|
}
|
|
591
901
|
dispatch.fn.body.unshift({
|
|
592
|
-
kind: "if", cond:
|
|
593
|
-
kind: "libCall", fn: "dyn.typedRefIs", args: [receiver, { kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc,
|
|
594
|
-
}, then: branch, else_: null, loc,
|
|
902
|
+
kind: "if", cond: condition, then: branch, else_: null, loc,
|
|
595
903
|
});
|
|
596
904
|
changed = true;
|
|
597
905
|
}
|
|
@@ -600,17 +908,26 @@ export class ClassDynamicDispatch {
|
|
|
600
908
|
if (!dispatch.write)
|
|
601
909
|
for (const name of this.boxedConstructors) {
|
|
602
910
|
const info = lowerer.classes.get(name);
|
|
603
|
-
const
|
|
911
|
+
const found = info && findStaticOn(lowerer, info, dispatch.name);
|
|
912
|
+
const field = found?.field;
|
|
604
913
|
const key = `constructor:${name}`;
|
|
605
|
-
if (!
|
|
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)
|
|
606
918
|
continue;
|
|
607
|
-
dispatch.classes.add(key);
|
|
608
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);
|
|
609
926
|
const value = varRef("p.0", DYN, loc);
|
|
610
927
|
const type = { kind: "classval", className: name };
|
|
611
928
|
dispatch.fn.body.unshift({ kind: "if", cond: { kind: "libCall", fn: "dyn.classIs", args: [value,
|
|
612
929
|
{ kind: "strLit", value: typeKey(type), type: STRING, loc }], type: BOOL, loc }, then: [
|
|
613
|
-
{ kind: "return", value: lowerer.coerceToExpected(
|
|
930
|
+
{ kind: "return", value: lowerer.coerceToExpected(result, DYN), loc },
|
|
614
931
|
], else_: null, loc });
|
|
615
932
|
changed = true;
|
|
616
933
|
}
|
|
@@ -650,7 +967,23 @@ export class ClassDynamicDispatch {
|
|
|
650
967
|
const { info } = plan;
|
|
651
968
|
dispatch.classes.add(plan.key);
|
|
652
969
|
const loc = dispatch.fn.loc;
|
|
653
|
-
const
|
|
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
|
+
}
|
|
654
987
|
const memberBody = (name) => {
|
|
655
988
|
if (dispatch.probe && (name === "message" || name === "name") && lowerer.isSubclassOf(info.def.name, "%Error") && dispatch.probe !== "in")
|
|
656
989
|
return [{
|
|
@@ -661,9 +994,16 @@ export class ClassDynamicDispatch {
|
|
|
661
994
|
if (info.builtinStream !== undefined && streamProperty(name) && dispatch.probe)
|
|
662
995
|
return [{ kind: "return", value: { kind: "boolLit", value: dispatch.probe === "in", type: BOOL, loc }, loc }];
|
|
663
996
|
if (!dispatch.probe)
|
|
664
|
-
return this.propertyBody(lowerer, { ...dispatch, name }, info, receiver);
|
|
997
|
+
return dispatch.reflect ? this.reflectPropertyBody(lowerer, { ...dispatch, name }, info, receiver) : this.propertyBody(lowerer, { ...dispatch, name }, info, receiver);
|
|
665
998
|
const prototypeMethod = lowerer.prototypeMethodAccesses.has(name);
|
|
666
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
|
+
}
|
|
667
1007
|
return [{ kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc }];
|
|
668
1008
|
}
|
|
669
1009
|
if (prototypeMethod)
|
|
@@ -701,7 +1041,7 @@ export class ClassDynamicDispatch {
|
|
|
701
1041
|
}
|
|
702
1042
|
}
|
|
703
1043
|
const branches = [];
|
|
704
|
-
if (!dispatch.write) {
|
|
1044
|
+
if (!dispatch.write && !dispatch.reflect && !info.def.tracksOwnFields) {
|
|
705
1045
|
const value = varRef("p.0", DYN, loc);
|
|
706
1046
|
const key = varRef("p.key", DYN, loc);
|
|
707
1047
|
const bag = { kind: "call", callee: this.propertyBag.name, args: [value], type: DYN, loc };
|
|
@@ -728,12 +1068,17 @@ export class ClassDynamicDispatch {
|
|
|
728
1068
|
branches.push({ kind: "if", cond: { kind: "dynScalarEq", left: varRef("p.key", DYN, loc), right: symbol, type: BOOL, loc }, then, else_: null, loc });
|
|
729
1069
|
}
|
|
730
1070
|
if (branches.length) {
|
|
731
|
-
dispatch.fn.body.unshift({ kind: "if", cond: condition, then: [
|
|
732
|
-
{ 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 },
|
|
733
1073
|
], else_: null, loc });
|
|
734
1074
|
dispatch.branchIndex++;
|
|
735
1075
|
}
|
|
736
1076
|
}
|
|
1077
|
+
if (existingHelper) {
|
|
1078
|
+
dispatch.fn.body.splice(dispatch.branchIndex, 0, callHelper(existingHelper));
|
|
1079
|
+
changed = true;
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
737
1082
|
const names = new Set([...info.fields.keys()].filter(isClassOwnEnumerableFieldName));
|
|
738
1083
|
if (info.builtinStream)
|
|
739
1084
|
for (const name of [...STREAM_BOOL_PROPERTIES, ...STREAM_NUM_PROPERTIES])
|
|
@@ -750,6 +1095,8 @@ export class ClassDynamicDispatch {
|
|
|
750
1095
|
}
|
|
751
1096
|
const branch = [];
|
|
752
1097
|
for (const name of names) {
|
|
1098
|
+
if (dispatch.numericKey && String(Number(name)) !== name)
|
|
1099
|
+
continue;
|
|
753
1100
|
const before = lowerer.diags.length;
|
|
754
1101
|
let body;
|
|
755
1102
|
try {
|
|
@@ -770,25 +1117,40 @@ export class ClassDynamicDispatch {
|
|
|
770
1117
|
// Keep each class's key table in a separate function. A single
|
|
771
1118
|
// function containing every boxed class grows quadratically during
|
|
772
1119
|
// LLVM's control-flow optimization for large library workloads.
|
|
773
|
-
const params = [...dispatch.fn.params];
|
|
1120
|
+
const params = [...dispatch.fn.params, { localId: "p.receiver", name: "receiver", type: receiverType }];
|
|
774
1121
|
if (dispatch.keyLocal !== "p.key")
|
|
775
1122
|
params.push({ localId: dispatch.keyLocal, name: "key", type: STRING });
|
|
776
1123
|
const helper = {
|
|
777
1124
|
name: `%dyn.class.keys.${lowerer.liftedFns.length}`, params, returnType: dispatch.fn.returnType,
|
|
778
1125
|
locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
|
|
779
|
-
body: [...branch, ...dispatch.fn.body.slice(dispatch.write ? -2 : -1)
|
|
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,
|
|
780
1133
|
};
|
|
781
1134
|
this.generated.add(helper);
|
|
782
1135
|
lowerer.liftedFns.push(helper);
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
const then = dispatch.write
|
|
786
|
-
? [{ kind: "exprStmt", expr: call, loc }, { kind: "return", value: null, loc }]
|
|
787
|
-
: [{ kind: "return", value: call, loc }];
|
|
788
|
-
dispatch.fn.body.splice(dispatch.branchIndex, 0, { kind: "if", cond: condition, then, else_: null, loc });
|
|
1136
|
+
this.computedClassHelpers.set(helperKey, helper);
|
|
1137
|
+
dispatch.fn.body.splice(dispatch.branchIndex, 0, callHelper(helper));
|
|
789
1138
|
changed = true;
|
|
790
1139
|
}
|
|
791
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
|
+
}
|
|
792
1154
|
if (this.constructDispatch)
|
|
793
1155
|
for (const [name, info] of this.constructors) {
|
|
794
1156
|
if (this.constructed.has(name))
|
|
@@ -822,6 +1184,26 @@ export class ClassDynamicDispatch {
|
|
|
822
1184
|
], else_: null, loc });
|
|
823
1185
|
changed = true;
|
|
824
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;
|
|
825
1207
|
return changed;
|
|
826
1208
|
}
|
|
827
1209
|
/** A known native class can access its shared bag directly. Keep the
|
|
@@ -856,6 +1238,79 @@ export class ClassDynamicDispatch {
|
|
|
856
1238
|
lowerer.liftedFns.push(helper);
|
|
857
1239
|
return helper;
|
|
858
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
|
+
}
|
|
859
1314
|
/** The hidden bag is part of the native object layout, so every capsule
|
|
860
1315
|
* shares it and normal class tracing/disposal owns its values. Insert it
|
|
861
1316
|
* at the same prefix offset throughout a hierarchy, including classes
|
|
@@ -878,14 +1333,19 @@ export class ClassDynamicDispatch {
|
|
|
878
1333
|
}
|
|
879
1334
|
/** A capsule retains its static type, while its object may be a subclass.
|
|
880
1335
|
* Preorder insertion lets later branches test the most derived layout first. */
|
|
881
|
-
propertyReceivers(lowerer, streams = false) {
|
|
1336
|
+
propertyReceivers(lowerer, streams = false, normalize = true) {
|
|
882
1337
|
const plans = [];
|
|
1338
|
+
const seen = new Set();
|
|
883
1339
|
for (const name of this.boxed) {
|
|
884
1340
|
const capsule = lowerer.classes.get(name);
|
|
885
|
-
if (!capsule || capsule.builtinEmitter || capsule.builtinStream && !streams || capsule.builtinError)
|
|
1341
|
+
if (!capsule || (capsule.builtinEmitter || capsule.builtinStream) && !streams || capsule.builtinError)
|
|
886
1342
|
continue;
|
|
1343
|
+
const canonical = normalize && !capsule.builtinStream && !capsule.builtinEmitter;
|
|
887
1344
|
const visit = (info) => {
|
|
888
|
-
|
|
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
|
+
}
|
|
889
1349
|
for (const child of info.subclasses)
|
|
890
1350
|
visit(child);
|
|
891
1351
|
};
|
|
@@ -907,6 +1367,46 @@ export class ClassDynamicDispatch {
|
|
|
907
1367
|
className: plan.info.def.name, type: BOOL, loc }, else_: { kind: "boolLit", value: false, type: BOOL, loc }, type: BOOL, loc },
|
|
908
1368
|
};
|
|
909
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
|
+
}
|
|
910
1410
|
propertyBody(lowerer, dispatch, info, receiver) {
|
|
911
1411
|
const { name, write } = dispatch;
|
|
912
1412
|
const loc = dispatch.fn.loc;
|
|
@@ -932,7 +1432,7 @@ export class ClassDynamicDispatch {
|
|
|
932
1432
|
const bag = { kind: "call", callee: this.propertyBag.name, args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
|
933
1433
|
const key = { kind: "strLit", value: name, type: STRING, loc };
|
|
934
1434
|
return [{ kind: "return", value: { kind: "ternary", cond: { kind: "libCall", fn: "dyn.hasKey", args: [bag, key], type: BOOL, loc },
|
|
935
|
-
then: { kind: "
|
|
1435
|
+
then: { kind: "libCall", fn: "dyn.bagGet", args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }, else_: cls, type: DYN, loc }, loc }];
|
|
936
1436
|
}
|
|
937
1437
|
if (name === "toString" && findMethodOn(lowerer, info, name)?.declarer.builtinError) {
|
|
938
1438
|
return [{ kind: "return", value: errorToStringMethod(lowerer, receiver), loc }];
|
|
@@ -958,19 +1458,20 @@ export class ClassDynamicDispatch {
|
|
|
958
1458
|
// A capsule may have the base ABI even when its native object is
|
|
959
1459
|
// a subclass (for example an inherited iterator returning this).
|
|
960
1460
|
// Select the method declaration at extraction, before detaching it.
|
|
961
|
-
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) &&
|
|
962
1462
|
(candidate.methods.has(name) || candidate.genericMethods?.has(name)));
|
|
963
1463
|
overrides.sort((a, b) => lowerer.isSubclassOf(a.def.name, b.def.name) ? -1 : lowerer.isSubclassOf(b.def.name, a.def.name) ? 1 : 0);
|
|
964
1464
|
const body = [];
|
|
965
|
-
|
|
1465
|
+
const symbol = symbolMemberKey(lowerer, info, name, loc);
|
|
1466
|
+
if (symbol || isClassOwnEnumerableFieldName(name) && !name.startsWith("sym:")) {
|
|
966
1467
|
const prototypeMethod = lowerer.prototypeMethodAccesses.has(name);
|
|
967
1468
|
if (prototypeMethod)
|
|
968
1469
|
classPrototypeData(lowerer, info, loc, receiver);
|
|
969
1470
|
const bag = { kind: "call", callee: this.propertyBag.name,
|
|
970
1471
|
args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
|
971
|
-
const key = { kind: "strLit", value: name, type: STRING, loc };
|
|
972
|
-
body.push({ kind: "if", cond: { kind: "libCall", fn: "dyn.hasKey", args: [bag, key], type: BOOL, loc },
|
|
973
|
-
then: [{ kind: "return", value: { kind: "
|
|
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 });
|
|
974
1475
|
if (prototypeMethod) {
|
|
975
1476
|
body.push({ kind: "return", value: dynUndefinedExpr(loc), loc });
|
|
976
1477
|
return body;
|
|
@@ -987,8 +1488,12 @@ export class ClassDynamicDispatch {
|
|
|
987
1488
|
return body;
|
|
988
1489
|
}
|
|
989
1490
|
}
|
|
990
|
-
if (!field && !accessor)
|
|
991
|
-
|
|
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
|
+
}
|
|
992
1497
|
const type = field ?? accessor.sig.ret;
|
|
993
1498
|
if (!canConvertToDyn(type, getRecord, getUnion))
|
|
994
1499
|
return fence();
|
|
@@ -1005,16 +1510,24 @@ export class ClassDynamicDispatch {
|
|
|
1005
1510
|
args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
|
1006
1511
|
const key = { kind: "strLit", value: name, type: STRING, loc };
|
|
1007
1512
|
return [{ kind: "if", cond: { kind: "libCall", fn: "dyn.hasOwn", args: [bag, key], type: BOOL, loc },
|
|
1008
|
-
then: [{ kind: "return", value: { kind: "
|
|
1513
|
+
then: [{ kind: "return", value: { kind: "libCall", fn: "dyn.bagGet", args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc }, loc }], else_: null, loc },
|
|
1009
1514
|
{ kind: "return", value: boxed, loc }];
|
|
1010
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
|
+
}
|
|
1011
1524
|
return [{ kind: "return", value: boxed, loc }];
|
|
1012
1525
|
}
|
|
1013
1526
|
if (!field && !accessor && isClassOwnEnumerableFieldName(name) && !name.startsWith("sym:") && !findMethodOn(lowerer, info, `get:${name}`)) {
|
|
1014
1527
|
const bag = { kind: "call", callee: this.propertyBag.name,
|
|
1015
1528
|
args: [lowerer.coerceToExpected(receiver, DYN)], type: DYN, loc };
|
|
1016
|
-
return [{ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.
|
|
1017
|
-
{ kind: "strLit", value: name, type: STRING, loc }, varRef("p.1", DYN, loc)], type: VOID, loc }, 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 },
|
|
1018
1531
|
{ kind: "return", value: null, loc }];
|
|
1019
1532
|
}
|
|
1020
1533
|
if (!field && !accessor)
|
|
@@ -1039,17 +1552,23 @@ export class ClassDynamicDispatch {
|
|
|
1039
1552
|
methodBody(lowerer, dispatch, info, receiver) {
|
|
1040
1553
|
const { method, loc } = dispatch.source;
|
|
1041
1554
|
const fence = () => [{ kind: "runtimeFence", code: "SC2020", message: `calling '${method}' on this native class through an untyped value is not supported yet`, loc }];
|
|
1042
|
-
if (info.builtinStream) {
|
|
1555
|
+
if (info.builtinStream || emitterRooted(lowerer, info) && EMITTER_METHODS.has(method) && !findMethodOn(lowerer, info, method)) {
|
|
1043
1556
|
const incoming = dispatch.source.args.map((_, i) => varRef(`p.${i + 1}`, DYN, loc));
|
|
1044
1557
|
const registering = ["on", "addListener", "once", "prependListener", "prependOnceListener"].includes(method);
|
|
1045
1558
|
const removing = method === "off" || method === "removeListener";
|
|
1046
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
|
+
}
|
|
1047
1566
|
if (registering || removing) {
|
|
1048
1567
|
if (incoming.length < 2)
|
|
1049
1568
|
return fence();
|
|
1050
1569
|
const event = lowerer.coerceToExpected(incoming[0], STRING);
|
|
1051
1570
|
const cb = incoming[1];
|
|
1052
|
-
return [{ kind: "exprStmt", expr: { kind: "libCall", fn: "emitter.checkListener", args: [cb], type: VOID, loc }, loc }, ...ret({ kind: "libCall", fn: registering ? "stream.onDyn" : "emitter.offDyn", args: [receiver, event, cb, ...(registering ? [
|
|
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 ? [
|
|
1053
1572
|
{ kind: "boolLit", value: method === "once" || method === "prependOnceListener", type: BOOL, loc },
|
|
1054
1573
|
{ kind: "boolLit", value: method.startsWith("prepend"), type: BOOL, loc },
|
|
1055
1574
|
] : [])], type: receiver.type, loc })];
|
|
@@ -1149,7 +1668,7 @@ export class ClassDynamicDispatch {
|
|
|
1149
1668
|
body.unshift({ kind: "if", cond: { kind: "dynTest", test: "undefined", negated: true, value: descriptor, type: BOOL, loc }, then: [
|
|
1150
1669
|
{ kind: "return", value: { kind: "dynCall", callee: { kind: "dynKeyGet", value: descriptor,
|
|
1151
1670
|
key: { kind: "strLit", value: "value", type: STRING, loc }, type: DYN, loc }, receiver: varRef("p.0", DYN, loc),
|
|
1152
|
-
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 },
|
|
1153
1672
|
], else_: null, loc });
|
|
1154
1673
|
}
|
|
1155
1674
|
return body;
|