mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-07 08:33:50 +08:00
Call the receiver copy for direct calls whose result is discarded
A direct call to an untyped method that returns `this` on every path, used as a statement (`builder.add(part);`), boxed its receiver into a capsule that nothing read. Such a call now runs the method's copy that returns nothing, as the class dispatch helpers already do.
This commit is contained in:
@@ -7,8 +7,10 @@
|
||||
* (`%dyn.class.call.N`) that calls such a method already holds a capsule
|
||||
* of the same instance at its exact class: its receiver after
|
||||
* normalization. The helper now calls a copy of the method that returns
|
||||
* nothing and answers its own receiver, so the call boxes nothing. Other
|
||||
* callers (vtables, typed code, method values) keep the method itself. */
|
||||
* nothing and answers its own receiver, so the call boxes nothing. A
|
||||
* direct call whose result nothing reads calls the copy too. Other callers
|
||||
* (vtables, method values, calls that use the result) keep the method
|
||||
* itself. */
|
||||
import type { IrExpr, IrFunction, IrStmt } from "../../ir/ir.js";
|
||||
import { VOID } from "../../ir/ir.js";
|
||||
import { varRef } from "../../ir/build.js";
|
||||
@@ -57,6 +59,20 @@ export function returnDispatchReceivers(lowerer: Lowerer, functions: IrFunction[
|
||||
];
|
||||
}
|
||||
}
|
||||
// A direct call whose result nothing reads (`builder.set(1);` in typed
|
||||
// code) needs no capsule at all.
|
||||
for (const fn of [...functions]) {
|
||||
everyStmtList(fn.body, {
|
||||
stmt: (s) => {
|
||||
if (s.kind === "exprStmt" && s.expr.kind === "call") {
|
||||
const copy = copyOf(s.expr.callee);
|
||||
if (copy) s.expr = { ...s.expr, callee: copy.name, type: VOID };
|
||||
}
|
||||
return true;
|
||||
},
|
||||
expr: () => true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** The helper's receiver is assigned only by its normalization, so it is
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/dynamic-method-receiver-statements.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Untyped methods that return their receiver, called as statements on
|
||||
// receivers whose class is known: side effects, throws, overrides, and
|
||||
// results that are used.
|
||||
class Builder {
|
||||
constructor() {
|
||||
this.parts = [];
|
||||
this.count = 0;
|
||||
}
|
||||
add(part) {
|
||||
if (part === "") throw new TypeError("empty part");
|
||||
this.parts.push(part);
|
||||
this.count++;
|
||||
return this;
|
||||
}
|
||||
reset() {
|
||||
this.parts.length = 0;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
class LoudBuilder extends Builder {
|
||||
add(part) {
|
||||
super.add(part.toUpperCase());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
const b = new Builder();
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
b.add("p" + (i % 3));
|
||||
if (i % 100 === 99) b.reset();
|
||||
}
|
||||
console.log(b.count, b.parts.join(","));
|
||||
b.add("x").add("y");
|
||||
const same = b.add("z");
|
||||
console.log(same === b, b.parts.join(","));
|
||||
try {
|
||||
b.add("");
|
||||
} catch (e) {
|
||||
console.log(e instanceof TypeError, e.message, b.count);
|
||||
}
|
||||
const loud = new LoudBuilder();
|
||||
loud.add("a");
|
||||
loud.add("b").add("c");
|
||||
console.log(loud.parts.join(","), loud.count, loud instanceof Builder);
|
||||
const builders = [new Builder(), new LoudBuilder()];
|
||||
for (const each of builders) each.add("q");
|
||||
console.log(builders.map((each) => each.parts.join("")).join("|"));
|
||||
Reference in New Issue
Block a user