@swf2es/runtime 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/avm2/cache-fill.d.ts +4 -1
  2. package/dist/avm2/cache-fill.d.ts.map +1 -1
  3. package/dist/avm2/cache-fill.js +5 -6
  4. package/dist/avm2/cache-fill.js.map +1 -1
  5. package/dist/avm2/descriptors.d.ts +6 -1
  6. package/dist/avm2/descriptors.d.ts.map +1 -1
  7. package/dist/avm2/descriptors.js.map +1 -1
  8. package/dist/avm2/errors.d.ts +14 -0
  9. package/dist/avm2/errors.d.ts.map +1 -1
  10. package/dist/avm2/errors.js +17 -0
  11. package/dist/avm2/errors.js.map +1 -1
  12. package/dist/avm2/index.d.ts +1 -0
  13. package/dist/avm2/index.d.ts.map +1 -1
  14. package/dist/avm2/index.js +1 -0
  15. package/dist/avm2/index.js.map +1 -1
  16. package/dist/avm2/names.d.ts +11 -0
  17. package/dist/avm2/names.d.ts.map +1 -1
  18. package/dist/avm2/names.js +12 -0
  19. package/dist/avm2/names.js.map +1 -1
  20. package/dist/avm2/natives/proxy.d.ts.map +1 -1
  21. package/dist/avm2/natives/proxy.js +9 -4
  22. package/dist/avm2/natives/proxy.js.map +1 -1
  23. package/dist/avm2/options.d.ts +5 -0
  24. package/dist/avm2/options.d.ts.map +1 -1
  25. package/dist/avm2/options.js.map +1 -1
  26. package/dist/avm2/property-cache.d.ts +17 -13
  27. package/dist/avm2/property-cache.d.ts.map +1 -1
  28. package/dist/avm2/property-cache.js +50 -10
  29. package/dist/avm2/property-cache.js.map +1 -1
  30. package/dist/avm2/runtime.d.ts +62 -0
  31. package/dist/avm2/runtime.d.ts.map +1 -1
  32. package/dist/avm2/runtime.js +245 -25
  33. package/dist/avm2/runtime.js.map +1 -1
  34. package/dist/avm2/traits.d.ts +7 -0
  35. package/dist/avm2/traits.d.ts.map +1 -1
  36. package/dist/avm2/traits.js +6 -0
  37. package/dist/avm2/traits.js.map +1 -1
  38. package/package.json +2 -2
  39. package/src/avm2/cache-fill.ts +5 -7
  40. package/src/avm2/descriptors.ts +6 -2
  41. package/src/avm2/errors.ts +18 -0
  42. package/src/avm2/index.ts +1 -0
  43. package/src/avm2/names.ts +13 -0
  44. package/src/avm2/natives/proxy.ts +20 -4
  45. package/src/avm2/options.ts +5 -0
  46. package/src/avm2/property-cache.ts +57 -11
  47. package/src/avm2/runtime.ts +280 -26
  48. package/src/avm2/traits.ts +7 -1
@@ -47,7 +47,7 @@ import {
47
47
  pairOf,
48
48
  walkless,
49
49
  } from "./enumeration.js";
50
- import { AsError, describe, errorMessage, stackOf } from "./errors.js";
50
+ import { AsError, describe, detailOf, errorMessage, stackOf, UnsupportedError } from "./errors.js";
51
51
  import { FloatValue, floatValue } from "./float.js";
52
52
  import { type ClassHook, hookedBinding, type NativesProvider, type PropertyHook } from "./hooks.js";
53
53
  import {
@@ -70,6 +70,8 @@ import {
70
70
  CONSTANT_RTQnameA,
71
71
  CONSTANT_RTQnameL,
72
72
  CONSTANT_RTQnameLA,
73
+ MADE_Number,
74
+ MADE_Value,
73
75
  Multiname,
74
76
  Namespace,
75
77
  NS_Private,
@@ -113,7 +115,7 @@ import {
113
115
  IC_Slot,
114
116
  invalidate,
115
117
  NO_CACHE,
116
- REPLACEMENTS,
118
+ ScopeFinds,
117
119
  UNFILLED,
118
120
  } from "./property-cache.js";
119
121
  import {
@@ -153,6 +155,18 @@ class ScopePack {
153
155
  ) {}
154
156
  }
155
157
 
158
+ /** Whether a chain or a pack, `scopes` with bits `w`, has a with scope, in a pack in it too. */
159
+ function hasWith(scopes: Value[], w: number): boolean {
160
+ for (let i = 0; i < scopes.length; i++) {
161
+ const o = scopes[i];
162
+ if (o instanceof ScopePack ? hasWith(o.scopes, o.w) : (w >> i) & 1) {
163
+ return true;
164
+ }
165
+ }
166
+
167
+ return false;
168
+ }
169
+
156
170
  /** The value of `o`'s dynamic property `name`, or NOT_FOUND. */
157
171
  function dynamicValue(o: AsObject, name: string): Value {
158
172
  const d: Map<string, Value> | null | undefined = o.$d;
@@ -167,6 +181,7 @@ function dynamicValue(o: AsObject, name: string): Value {
167
181
  export class Runtime {
168
182
  readonly print: (line: string) => void;
169
183
  readonly debugger: boolean;
184
+ private readonly onUnsupported: ((message: string) => void) | undefined;
170
185
  readonly compileAbc:
171
186
  | ((abc: Uint8Array, unit: CompileUnit) => ((rt: Runtime) => Abc) | number)
172
187
  | null;
@@ -249,7 +264,7 @@ export class Runtime {
249
264
  /** Special traits: an activation's or a catch scope's, by descriptor. */
250
265
  private readonly scopeTraits = new WeakMap<object, Traits>();
251
266
  readonly specialized = new Map<AsObject, AsObject>();
252
- readonly empty: Scope = Object.assign([], { w: 0 });
267
+ readonly empty: Scope = Object.assign([], { w: 0, found: undefined });
253
268
 
254
269
  /**
255
270
  * `natives` are the natives by name, or a provider that makes them for
@@ -264,6 +279,7 @@ export class Runtime {
264
279
  ) {
265
280
  this.print = options.print ?? defaultPrint;
266
281
  this.debugger = options.debugger ?? false;
282
+ this.onUnsupported = options.onUnsupported;
267
283
  this.compileAbc = options.compileAbc ?? null;
268
284
  this.files = options.files ?? memoryFiles();
269
285
  this.workerHost = options.workers ?? null;
@@ -429,6 +445,7 @@ export class Runtime {
429
445
  // Its string only once a lookup needs it: a Dictionary does not.
430
446
  const keyed = Multiname.keyed(kind, namespaces, versions, part, mn.attribute, this.keyName);
431
447
  keyed.domain = mn.domain;
448
+ keyed.made = MADE_Value;
432
449
  return keyed;
433
450
  } else {
434
451
  name = typeof part === "string" ? part : this.toString(part);
@@ -437,6 +454,42 @@ export class Runtime {
437
454
 
438
455
  const named = new Multiname(kind, namespaces, versions, name, mn.attribute);
439
456
  named.domain = mn.domain;
457
+ // A number here is one no element access made: a Proxy tells it by its string.
458
+ if (mn.runtimeName && typeof last !== "number") {
459
+ named.made = MADE_Value;
460
+ }
461
+
462
+ return named;
463
+ }
464
+
465
+ /**
466
+ * The name runtimeName makes of `mn`, a MultinameL, and a string the
467
+ * code itself holds, `obj["name"]`: a module makes it once as it loads
468
+ * and keeps it as it keeps `mn`, so it has a cache as a module's own
469
+ * names have, however many such names the module has and however long
470
+ * the string is.
471
+ */
472
+ literalName(mn: Multiname, name: string): Multiname {
473
+ const named = new Multiname(mn.kind, mn.namespaces, mn.versions, name, mn.attribute);
474
+ named.domain = mn.domain;
475
+ named.made = MADE_Value;
476
+ named.cache = UNFILLED;
477
+
478
+ return named;
479
+ }
480
+
481
+ /**
482
+ * The name number `i` makes of `mn` in an element's read or write, which
483
+ * avmplus' JIT hands an object as a number (getpropertylate_i and _d), a
484
+ * Proxy as its string: where the name has no public namespace, it is a
485
+ * name as any other.
486
+ */
487
+ private indexName(mn: Multiname, i: number): Multiname {
488
+ const named = this.runtimeName(mn, i);
489
+ if (mn.kind === CONSTANT_MultinameL && mn.elementName) {
490
+ named.made = MADE_Number;
491
+ }
492
+
440
493
  return named;
441
494
  }
442
495
 
@@ -462,6 +515,7 @@ export class Runtime {
462
515
 
463
516
  const named = new Multiname(base.kind, base.namespaces, base.versions, name, base.attribute);
464
517
  named.domain = base.domain;
518
+ named.made = MADE_Value;
465
519
  if (!cache) {
466
520
  cache = { names: new Map(), misses: 0 };
467
521
  this.stringNames.set(base, cache);
@@ -578,6 +632,7 @@ export class Runtime {
578
632
  const traits = new Traits("global", this.objectTraits);
579
633
  traits.dynamic = true;
580
634
  traits.isGlobal = true;
635
+ traits.abc = script.abc;
581
636
  traits.describe(script.desc.traits);
582
637
  const g = traits.instance();
583
638
  // As the script's initializer, its methods run in an empty scope chain,
@@ -671,6 +726,7 @@ export class Runtime {
671
726
  scope(outer: Scope, locals: Value[], withs: number): Scope {
672
727
  const chain = outer.concat(locals) as Scope;
673
728
  chain.w = outer.w | (withs << outer.length);
729
+ chain.found = undefined;
674
730
  return chain;
675
731
  }
676
732
 
@@ -686,6 +742,7 @@ export class Runtime {
686
742
  const packed = chain.slice(0, at) as Scope;
687
743
  packed.push(new ScopePack(chain.slice(at), chain.w >>> at) as never);
688
744
  packed.w = (chain.w & ((1 << at) - 1)) | (1 << at);
745
+ packed.found = undefined;
689
746
  return packed;
690
747
  }
691
748
 
@@ -724,6 +781,47 @@ export class Runtime {
724
781
  }
725
782
  }
726
783
 
784
+ const found = this.outerScopeOf(outer, mn);
785
+ if (found !== null) {
786
+ return found;
787
+ }
788
+
789
+ return this.global(mn, outer.length ? outer[0] : locals[0], strict);
790
+ }
791
+
792
+ /**
793
+ * The innermost object of a captured chain that has `mn`, or null: kept
794
+ * on a chain without a with scope (ScopeFinds), as a function whose
795
+ * chain the compiler could not type, such as one made where chains of
796
+ * different lengths meet, looks its names up each time it runs.
797
+ */
798
+ private outerScopeOf(outer: Scope, mn: Multiname): AsObject | null {
799
+ let finds = outer.found;
800
+ if (finds === undefined) {
801
+ finds = hasWith(outer, outer.w) ? null : new ScopeFinds();
802
+ outer.found = finds;
803
+ }
804
+
805
+ // A name made at run time is a new one each time: keeping it would only grow the map.
806
+ if (finds === null || mn.cache === NO_CACHE) {
807
+ return this.searchOuter(outer, mn);
808
+ }
809
+
810
+ if (finds.epoch !== epoch()) {
811
+ finds.clear();
812
+ finds.epoch = epoch();
813
+ }
814
+
815
+ let o = finds.get(mn);
816
+ if (o === undefined) {
817
+ o = this.searchOuter(outer, mn);
818
+ finds.set(mn, o);
819
+ }
820
+
821
+ return o;
822
+ }
823
+
824
+ private searchOuter(outer: Scope, mn: Multiname): AsObject | null {
727
825
  for (let i = outer.length - 1; i >= 0; i--) {
728
826
  const o = outer[i];
729
827
  if (o instanceof ScopePack) {
@@ -736,7 +834,7 @@ export class Runtime {
736
834
  }
737
835
  }
738
836
 
739
- return this.global(mn, outer.length ? outer[0] : locals[0], strict);
837
+ return null;
740
838
  }
741
839
 
742
840
  findPropertyStrict(mn: Multiname, outer: Scope, locals: Value[], withs: number): AsObject {
@@ -887,16 +985,24 @@ export class Runtime {
887
985
  traitsOf(v: Value): Traits {
888
986
  switch (typeof v) {
889
987
  case "object":
890
- case "function":
988
+ case "function": {
891
989
  if (v === null) {
892
990
  throw this.error("TypeError", 1009);
893
991
  }
894
992
 
993
+ // An object's own first: instanceof walks the whole prototype
994
+ // chain of an object, and a class's is as deep as its bases.
995
+ const traits = v.$traits;
996
+ if (traits !== undefined) {
997
+ return traits;
998
+ }
999
+
895
1000
  return v instanceof FloatValue
896
1001
  ? this.builtinTraits("float")
897
1002
  : v instanceof Namespace
898
1003
  ? this.builtinTraits("Namespace")
899
- : v.$traits;
1004
+ : traits;
1005
+ }
900
1006
  // As avmplus' toVTable: every number is a Number at run time, int or
901
1007
  // not; int's and uint's traits only bind early, from static types.
902
1008
  case "number":
@@ -918,12 +1024,19 @@ export class Runtime {
918
1024
  private receiverTraits(v: Value): Traits | undefined {
919
1025
  switch (typeof v) {
920
1026
  case "object":
921
- case "function":
922
- return v instanceof FloatValue
923
- ? this.builtinTraits("float")
924
- : v === null
925
- ? undefined
926
- : v.$traits;
1027
+ case "function": {
1028
+ if (v === null) {
1029
+ return undefined;
1030
+ }
1031
+
1032
+ // A float and a Namespace have none of their own (see traitsOf).
1033
+ const traits: Traits | undefined = v.$traits;
1034
+ if (traits !== undefined) {
1035
+ return traits;
1036
+ }
1037
+
1038
+ return v instanceof FloatValue ? this.builtinTraits("float") : undefined;
1039
+ }
927
1040
  case "string":
928
1041
  return this.stringTraits;
929
1042
  case "number":
@@ -1126,7 +1239,7 @@ export class Runtime {
1126
1239
  }
1127
1240
  }
1128
1241
 
1129
- return this.getProperty(o, this.runtimeName(mn, i));
1242
+ return this.getProperty(o, this.indexName(mn, i));
1130
1243
  }
1131
1244
 
1132
1245
  /** obj[i] = v for a number i, as avmplus' setUintProperty. */
@@ -1140,13 +1253,103 @@ export class Runtime {
1140
1253
  return;
1141
1254
  }
1142
1255
 
1143
- if (o.$a !== undefined && o.$a !== SEALED_ELEMENTS && i !== 0xffffffff) {
1144
- o.$a[i] = v;
1145
- return;
1256
+ const a: Value[] | undefined = o.$a;
1257
+ if (a !== undefined) {
1258
+ if (a !== SEALED_ELEMENTS && i !== 0xffffffff) {
1259
+ a[i] = v;
1260
+ return;
1261
+ }
1262
+ } else if (traits?.dynamic && traits.properties === null && !traits.refusesNames) {
1263
+ // As ScriptObject::setUintProperty: an object with no elements of its own keeps the
1264
+ // number among its dynamic properties, with no binding asked for.
1265
+ const d: Map<string, Value> | null | undefined = o.$d;
1266
+ if (d) {
1267
+ d.set(`${i}`, v);
1268
+ return;
1269
+ }
1146
1270
  }
1147
1271
  }
1148
1272
 
1149
- this.setProperty(o, this.runtimeName(mn, i), v);
1273
+ this.setProperty(o, this.indexName(mn, i), v);
1274
+ }
1275
+
1276
+ /**
1277
+ * obj[k] for a key whose type the compiler does not know, by a name with
1278
+ * no namespace from the stack, as avmplus' getprop_index and
1279
+ * getpropertyHelper: a number is an index (getIndexed), an object a
1280
+ * Dictionary has as a key is read by itself, and anything else, a QName
1281
+ * and a Dictionary's absent key among them, is looked up by the name it
1282
+ * makes.
1283
+ */
1284
+ getKeyed(o: Value, mn: Multiname, k: Value): Value {
1285
+ let r: Value;
1286
+ if (typeof k === "number") {
1287
+ r = this.getIndexed(o, mn, k);
1288
+ return r;
1289
+ }
1290
+
1291
+ if (typeof k === "object" && k !== null) {
1292
+ const keys: DictionaryKeys | undefined = o?.$keys;
1293
+ if (keys !== undefined) {
1294
+ if (!mn.elementName) {
1295
+ r = this.getProperty(o, this.runtimeName(mn, this.unkeyed(k)));
1296
+ return r;
1297
+ }
1298
+
1299
+ // What is there was put there by a name with the object as its key, which a QName's is not.
1300
+ const v = keys.get(k);
1301
+ if (v !== undefined) {
1302
+ return v;
1303
+ }
1304
+ }
1305
+ }
1306
+
1307
+ r = this.getProperty(o, this.runtimeName(mn, k));
1308
+ return r;
1309
+ }
1310
+
1311
+ /**
1312
+ * An object given as the key of a Dictionary by a name with no public
1313
+ * namespace, as the name is made of it: avmplus' JIT asks a Dictionary
1314
+ * for an object only through getprop_index and setprop_index, which such
1315
+ * a name does not reach, so the object is its string, as on any other
1316
+ * receiver (initMultinameLate); a QName names what it names. Its
1317
+ * interpreter asks the Dictionary by any name, as a name made without
1318
+ * this does: the compiler leaves script and class initializers to that.
1319
+ */
1320
+ private unkeyed(k: AsObject): Value {
1321
+ return k.$traits !== undefined && k.$local === undefined ? this.toString(k) : k;
1322
+ }
1323
+
1324
+ /** obj[k] = v for a key whose type the compiler does not know, as avmplus' setprop_index and setpropertyHelper. */
1325
+ setKeyed(o: Value, mn: Multiname, k: Value, v: Value): void {
1326
+ if (typeof k === "number") {
1327
+ this.setIndexed(o, mn, k, v);
1328
+ return;
1329
+ }
1330
+
1331
+ if (typeof k === "object" && k !== null) {
1332
+ const keys: DictionaryKeys | undefined = o?.$keys;
1333
+ if (keys !== undefined) {
1334
+ if (!mn.elementName) {
1335
+ this.setProperty(o, this.runtimeName(mn, this.unkeyed(k)), v);
1336
+ return;
1337
+ }
1338
+
1339
+ if (keys.replace(k, v)) {
1340
+ return;
1341
+ }
1342
+
1343
+ // A new key: an object of traits, which a float and a Namespace are not, and no QName,
1344
+ // which names what it names. Those are added, or not, by the name they make.
1345
+ if (k.$traits !== undefined && k.$local === undefined) {
1346
+ keys.add(k, v);
1347
+ return;
1348
+ }
1349
+ }
1350
+ }
1351
+
1352
+ this.setProperty(o, this.runtimeName(mn, k), v);
1150
1353
  }
1151
1354
 
1152
1355
  /**
@@ -1385,11 +1588,7 @@ export class Runtime {
1385
1588
  /** fillCache (cache-fill.ts), for a name that caches on traits a cache knows `o` by. */
1386
1589
  private fill(o: Value, traits: Traits, b: number, mn: Multiname): void {
1387
1590
  const head = mn.cache;
1388
- if (
1389
- head === NO_CACHE ||
1390
- (head.replaced === REPLACEMENTS && head.epoch === epoch()) ||
1391
- this.receiverTraits(o) !== traits
1392
- ) {
1591
+ if (head === NO_CACHE || this.receiverTraits(o) !== traits) {
1393
1592
  return;
1394
1593
  }
1395
1594
 
@@ -1424,6 +1623,8 @@ export class Runtime {
1424
1623
  f.$length = (method as CountedMethod).$length ?? method.length;
1425
1624
  f.$id = (method as Method & { $id?: number }).$id ?? 0;
1426
1625
  f.$closure = true;
1626
+ // Whose method it is, for moduleOf.
1627
+ f.$of = traits;
1427
1628
  if (typeof o === "object") {
1428
1629
  byMethod.set(method, f);
1429
1630
  }
@@ -1740,8 +1941,7 @@ export class Runtime {
1740
1941
  if (
1741
1942
  typeof o === "object" &&
1742
1943
  o !== null &&
1743
- !(o instanceof Namespace) &&
1744
- !(o instanceof FloatValue)
1944
+ (o.$traits !== undefined || !(o instanceof Namespace || o instanceof FloatValue))
1745
1945
  ) {
1746
1946
  return o.$p;
1747
1947
  }
@@ -2242,6 +2442,21 @@ export class Runtime {
2242
2442
  return f;
2243
2443
  }
2244
2444
 
2445
+ /**
2446
+ * The module a function's code is from, as avmplus has a method's code
2447
+ * context: a function's by the global object its scope begins with, a
2448
+ * bound method's by its receiver's class; null for a native, a class's
2449
+ * own method, or anything but a function.
2450
+ */
2451
+ moduleOf(f: Value): object | null {
2452
+ const o = f as AsObject | null;
2453
+ if (o?.$of) {
2454
+ return (o.$of as Traits).abc;
2455
+ }
2456
+
2457
+ return o?.$global ? this.traitsOf(o.$global).abc : null;
2458
+ }
2459
+
2245
2460
  /** A Function object calling `f`, with `global` as its receiver when it has none. */
2246
2461
  newFunctionObject(f: Method, global: AsObject | null): AsObject {
2247
2462
  const o = this.functionTraits.instance();
@@ -3124,7 +3339,13 @@ export class Runtime {
3124
3339
  return new AsError(`${name}: ${message}`);
3125
3340
  }
3126
3341
 
3127
- return this.constructClass(cls, [message, id]);
3342
+ const made = this.constructClass(cls, [message, id]);
3343
+ // What the number stands for, kept beside the message for a host's report (detailOf).
3344
+ if (!this.debugger) {
3345
+ Object.defineProperty(made, "$detail", { value: errorMessage(true, id, args) });
3346
+ }
3347
+
3348
+ return made;
3128
3349
  }
3129
3350
 
3130
3351
  /** See stackOf in errors.ts. */
@@ -3132,6 +3353,11 @@ export class Runtime {
3132
3353
  return stackOf(error);
3133
3354
  }
3134
3355
 
3356
+ /** See detailOf in errors.ts. */
3357
+ detailOf(error: Value): string | null {
3358
+ return detailOf(error);
3359
+ }
3360
+
3135
3361
  /** See errorMessage in errors.ts. */
3136
3362
  errorMessage(id: number, args: Value[] = []): string {
3137
3363
  return errorMessage(this.debugger, id, args);
@@ -3162,6 +3388,10 @@ export class Runtime {
3162
3388
  return this.error("RangeError", 1506);
3163
3389
  }
3164
3390
 
3391
+ if (e instanceof UnsupportedError) {
3392
+ return this.unsupportedCaught(e);
3393
+ }
3394
+
3165
3395
  if (e instanceof Error && !(e instanceof AsError)) {
3166
3396
  throw e;
3167
3397
  }
@@ -3169,6 +3399,30 @@ export class Runtime {
3169
3399
  return e;
3170
3400
  }
3171
3401
 
3402
+ /** What a catch was told of already (unsupportedCaught). */
3403
+ private readonly unsupportedTold = new Set<string>();
3404
+
3405
+ /**
3406
+ * What is not supported as the AS3 error a catch gets: an
3407
+ * IllegalOperationError with its message, or an Error where the player's
3408
+ * classes are not loaded; said once to the host, as the code may say nothing.
3409
+ */
3410
+ private unsupportedCaught(e: UnsupportedError): Value {
3411
+ if (!this.unsupportedTold.has(e.message)) {
3412
+ this.unsupportedTold.add(e.message);
3413
+ this.onUnsupported?.(e.message);
3414
+ }
3415
+
3416
+ let cls: AsObject;
3417
+ try {
3418
+ cls = this.resolve(this.cls(namespace(NS_Public, "flash.errors"), "IllegalOperationError"));
3419
+ } catch {
3420
+ cls = this.builtinClass("Error");
3421
+ }
3422
+
3423
+ return this.constructClass(cls, [e.message, 0]);
3424
+ }
3425
+
3172
3426
  catches(e: Value, type: TypeRef): boolean {
3173
3427
  return this.isInstanceOf(e, this.traitsOfType(type));
3174
3428
  }
@@ -3178,7 +3432,7 @@ export class Runtime {
3178
3432
  }
3179
3433
 
3180
3434
  unsupported(what: string): Error {
3181
- return new Error(`swf2es: ${what} is not supported yet`);
3435
+ return new UnsupportedError(`swf2es: ${what} is not supported yet`);
3182
3436
  }
3183
3437
 
3184
3438
  // Class aliases and the classes AMF makes (amf-classes.ts).
@@ -16,7 +16,7 @@ import type { Domain } from "./domain.js";
16
16
  import { DynamicProperties } from "./dynamic-properties.js";
17
17
  import type { ClassHook, IndexHook, PropertyHook } from "./hooks.js";
18
18
  import type { Multiname, Namespace } from "./names.js";
19
- import { invalidate } from "./property-cache.js";
19
+ import { invalidate, type PropertyCache } from "./property-cache.js";
20
20
 
21
21
  // Binding kinds, as the compiler encodes them: kind | id << 3.
22
22
  export const BIND_Method = 1;
@@ -160,6 +160,12 @@ export class Traits {
160
160
  interfaceKeys: Map<Traits, (string | undefined)[]> | null = null;
161
161
  lastInterface: Traits | null = null;
162
162
  lastInterfaceKeys: (string | undefined)[] = [];
163
+ /**
164
+ * The cache entries for these traits of names looked up on more traits
165
+ * than their own caches keep (property-cache.ts), by name; emptied with
166
+ * every cache.
167
+ */
168
+ manyTraitsEntries: Map<Multiname, PropertyCache> | null = null;
163
169
 
164
170
  constructor(
165
171
  readonly name: string,