mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Call the declared base method for super calls in JavaScript method values
Since 76735a8d9 (#1975), a `super.m()` call in a JavaScript class method
whose `this` is the call-site receiver reflected the base class's whole
prototype and read `m` from it. An instance's member lookup first tests
its property bag, which inherits from its class's prototype data, so once
the base's prototype was reflected, `child[k]` for a method the subclass
overrides found the base's method there: `value[name](arg)` on a subclass
instance called the base method.
While no code reads a prototype method by name, `super.m()` now calls the
declaring class's receiver-independent method value with the receiver, as
the method's [[HomeObject]] lookup answers, without reflecting the base
prototype. Programs that read prototype methods by name keep the
reflected lookup.
This commit is contained in:
@@ -194,7 +194,10 @@ import { classPrototypeData, reflectClassPrototype } from "./class-prototypes.js
|
||||
import { constructionEscape } from "./construction-escapes.js";
|
||||
import { checkedClassConstruction, checkedClassConstructionPacked } from "./class-construction.js";
|
||||
import { initializeRuntimeStatics, hasRuntimeStatics } from "./class-runtime-statics.js";
|
||||
import { reflectedClassStaticMethodValue } from "./class-method-values.js";
|
||||
import {
|
||||
reflectedClassMethodValue,
|
||||
reflectedClassStaticMethodValue,
|
||||
} from "./class-method-values.js";
|
||||
import { identifierReadSymbol } from "./surfaces.js";
|
||||
|
||||
export function returnsOnlyThis(member: ts.MethodDeclaration): boolean {
|
||||
@@ -9560,19 +9563,31 @@ export function lowerSuperMethodCall(
|
||||
// method up on the base class's complete prototype, as `super` reads
|
||||
// [[HomeObject]].[[Prototype]], and calls it with that receiver.
|
||||
const dynamicThis = thisRef.type.kind === "dyn";
|
||||
// While no code reads a prototype method by name (which could replace it),
|
||||
// that method is the declaring class's own: its receiver-independent value
|
||||
// is called directly. Reflecting the base's whole prototype instead would
|
||||
// put its members on the prototype chain of every subclass instance's
|
||||
// property bag, ahead of the subclasses' own compiled overrides.
|
||||
const declared =
|
||||
dynamicThis && !lowerer.prototypeMethodAccesses.has(access.name.text)
|
||||
? reflectedClassMethodValue(lowerer, found.declarer, access.name.text)
|
||||
: null;
|
||||
if (dynamicThis || lowerer.prototypeMethodAccesses.has(access.name.text)) {
|
||||
const prototype =
|
||||
dynamicThis && cls.base.decl
|
||||
const prototype = declared
|
||||
? null
|
||||
: dynamicThis && cls.base.decl
|
||||
? reflectClassPrototype(lowerer, cls.base, loc, classValueRef(lowerer, cls.base, access))
|
||||
: classPrototypeData(lowerer, cls.base, loc);
|
||||
if (prototype) {
|
||||
const callee: IrExpr = {
|
||||
kind: "dynKeyGet",
|
||||
value: prototype,
|
||||
key: { kind: "strLit", value: access.name.text, type: STRING, loc },
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
if (declared || prototype) {
|
||||
const callee: IrExpr = declared
|
||||
? lowerer.coerceToExpected(declared, DYN)
|
||||
: {
|
||||
kind: "dynKeyGet",
|
||||
value: prototype!,
|
||||
key: { kind: "strLit", value: access.name.text, type: STRING, loc },
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
// Spread arguments are spread into the dynamic call, as in a dynamic method call.
|
||||
const spreads: { arg: number; what: string }[] = [];
|
||||
const args = call.arguments.nodes.map((arg, index) => {
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8302-class-super-method-values/lib.cjs",
|
||||
"<repo>/tests/corpus/8302-class-super-method-values/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
"use strict";
|
||||
// Subclasses whose methods call super, read and called through computed and
|
||||
// literal keys, as values called with call(), and directly.
|
||||
class Base {
|
||||
constructor(n) { this.n = n; }
|
||||
scale(k) { return this.n * k; }
|
||||
label() { return "base:" + this.n; }
|
||||
plain(k) { return k; }
|
||||
}
|
||||
class Child extends Base {
|
||||
scale(k) { return super.scale(k) * 10; }
|
||||
label() { return "child(" + super.label() + ")"; }
|
||||
plain(k) { return k * 100; }
|
||||
}
|
||||
class Grandchild extends Child {
|
||||
scale(k) { return super.scale(k) + 1; }
|
||||
}
|
||||
class Sibling extends Base {
|
||||
scale(k) { return -k; }
|
||||
plain(k) { return super.plain(k) + 0.5; }
|
||||
}
|
||||
exports.make = function (i) {
|
||||
switch (i % 4) {
|
||||
case 0: return new Base(i);
|
||||
case 1: return new Child(i);
|
||||
case 2: return new Grandchild(i);
|
||||
default: return new Sibling(i);
|
||||
}
|
||||
};
|
||||
exports.computed = function (value, name, arg) { return value[name](arg); };
|
||||
exports.viaCall = function (value, name, arg) { const f = value[name]; return f.call(value, arg); };
|
||||
exports.literal = function (value, arg) { return [value["scale"](arg), value["plain"](arg), value["label"]()].join(" "); };
|
||||
exports.direct = function (value, arg) { return [value.scale(arg), value.plain(arg), value.label()].join(" "); };
|
||||
@@ -0,0 +1,26 @@
|
||||
// Methods of JavaScript subclasses that call super, read from instances
|
||||
// through computed keys, literal keys and as values, and called directly.
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
interface Lib {
|
||||
make(i: number): unknown;
|
||||
computed(value: unknown, name: string, arg: number): unknown;
|
||||
viaCall(value: unknown, name: string, arg: number): unknown;
|
||||
literal(value: unknown, arg: number): string;
|
||||
direct(value: unknown, arg: number): string;
|
||||
}
|
||||
const require = createRequire(import.meta.url);
|
||||
const lib: Lib = require("./lib.cjs");
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const value = lib.make(i);
|
||||
console.log(
|
||||
i,
|
||||
lib.computed(value, "scale", 3),
|
||||
lib.computed(value, "plain", 3),
|
||||
lib.computed(value, "label", 0),
|
||||
lib.viaCall(value, "scale", 2),
|
||||
lib.viaCall(value, "label", 0),
|
||||
);
|
||||
console.log(i, lib.literal(value, 5), "|", lib.direct(value, 5));
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "type": "module" }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4797 }
|
||||
Reference in New Issue
Block a user