mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Discard the result of void generic methods called through class-backed interfaces (#2275)
This commit is contained in:
@@ -6794,24 +6794,41 @@ export class ClassDynamicDispatch {
|
||||
type: result,
|
||||
loc,
|
||||
};
|
||||
// A void method dispatches its overrides with statements: a ternary
|
||||
// has no void form.
|
||||
let voidDispatch: IrStmt[] = [{ kind: "exprStmt", expr: call, loc }];
|
||||
for (const override of overrides.toReversed()) {
|
||||
const subType: IrType = { kind: "object", className: override.cls.def.name };
|
||||
const isOverride: IrExpr = {
|
||||
kind: "instanceOf",
|
||||
value: receiver,
|
||||
className: override.cls.def.name,
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
const overrideCall: IrExpr = {
|
||||
kind: "call",
|
||||
callee: override.callee,
|
||||
args: [{ kind: "downcast", value: receiver, type: subType, loc }, ...args],
|
||||
type: result,
|
||||
loc,
|
||||
};
|
||||
if (result.kind === "void") {
|
||||
voidDispatch = [
|
||||
{
|
||||
kind: "if",
|
||||
cond: isOverride,
|
||||
then: [{ kind: "exprStmt", expr: overrideCall, loc }],
|
||||
else_: voidDispatch,
|
||||
loc,
|
||||
},
|
||||
];
|
||||
continue;
|
||||
}
|
||||
call = {
|
||||
kind: "ternary",
|
||||
cond: {
|
||||
kind: "instanceOf",
|
||||
value: receiver,
|
||||
className: override.cls.def.name,
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
then: {
|
||||
kind: "call",
|
||||
callee: override.callee,
|
||||
args: [{ kind: "downcast", value: receiver, type: subType, loc }, ...args],
|
||||
type: result,
|
||||
loc,
|
||||
},
|
||||
cond: isOverride,
|
||||
then: overrideCall,
|
||||
else_: call,
|
||||
type: result,
|
||||
loc,
|
||||
@@ -6819,10 +6836,7 @@ export class ClassDynamicDispatch {
|
||||
}
|
||||
let body: IrStmt[];
|
||||
if (result.kind === "void")
|
||||
body = [
|
||||
{ kind: "exprStmt", expr: call, loc },
|
||||
{ kind: "return", value: dynUndefinedExpr(loc), loc },
|
||||
];
|
||||
body = [...voidDispatch, { kind: "return", value: dynUndefinedExpr(loc), loc }];
|
||||
else {
|
||||
const boxed = lowerer.coerceToExpected(call, DYN);
|
||||
if (boxed.type.kind !== "dyn") return fence();
|
||||
|
||||
@@ -10107,7 +10107,13 @@ function lowerObjLitGenericMethodCall(
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
return lowerer.coerceInto(call, invoked, lowerer.irTypeOf(call));
|
||||
const resultType = lowerer.irTypeOf(call);
|
||||
// A void method's result stays the runtime's untyped value, as a
|
||||
// call through a non-generic void member's dispatch keeps it: a class
|
||||
// may implement the member with a method that returns a value, which
|
||||
// JavaScript passes on (`console.log(c.publish(m))`).
|
||||
if (resultType.kind === "void") return invoked;
|
||||
return lowerer.coerceInto(call, invoked, resultType);
|
||||
}
|
||||
}
|
||||
if (onInterface) {
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/generic-interface-void-calls.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Calling a void generic method through an interface value that classes
|
||||
// implement (the receiver is a checked-dynamic object, and the call
|
||||
// dispatches by name to the class's erased method) discards the result, as
|
||||
// JavaScript does, instead of converting it to `void`.
|
||||
|
||||
interface Message<T = unknown> {
|
||||
content: string;
|
||||
details?: T;
|
||||
}
|
||||
|
||||
interface Channel {
|
||||
name: string;
|
||||
publish<T = unknown>(message: Message<T>): void;
|
||||
count<T>(items: T[]): number;
|
||||
}
|
||||
|
||||
const delivered: string[] = [];
|
||||
|
||||
class LogChannel implements Channel {
|
||||
name: string;
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
publish<T = unknown>(message: Message<T>): void {
|
||||
delivered.push(`${this.name}:${message.content}:${message.details === undefined ? "-" : JSON.stringify(message.details)}`);
|
||||
}
|
||||
count<T>(items: T[]): number {
|
||||
return items.length;
|
||||
}
|
||||
}
|
||||
|
||||
class LoudChannel extends LogChannel {
|
||||
publish<T = unknown>(message: Message<T>): void {
|
||||
super.publish({ content: message.content.toUpperCase(), details: message.details });
|
||||
}
|
||||
}
|
||||
|
||||
const channels: Channel[] = [new LogChannel("quiet"), new LoudChannel("loud")];
|
||||
for (const channel of channels) {
|
||||
channel.publish({ content: "hello", details: [1, 2] });
|
||||
channel.publish({ content: "bare" });
|
||||
const result = channel.publish<string>({ content: "typed", details: "x" });
|
||||
console.log(channel.name, result === undefined, channel.count(["a", "b", "c"]));
|
||||
}
|
||||
console.log(delivered.join(" | "));
|
||||
|
||||
// A class may implement the void member with a method that returns a value;
|
||||
// JavaScript passes that value on.
|
||||
class CountingChannel implements Channel {
|
||||
name = "counting";
|
||||
sent = 0;
|
||||
publish<T = unknown>(message: Message<T>): number {
|
||||
this.sent++;
|
||||
return message.content.length;
|
||||
}
|
||||
count<T>(items: T[]): number {
|
||||
return items.length * 2;
|
||||
}
|
||||
}
|
||||
|
||||
const counted: Channel[] = [new CountingChannel(), new LogChannel("plain")];
|
||||
for (const channel of counted) {
|
||||
console.log(channel.name, channel.publish({ content: "four" }));
|
||||
console.log(`sent ${channel.publish<number>({ content: "again", details: 2 })}`);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4000 }
|
||||
Reference in New Issue
Block a user