Map any parameters of interface method and call signatures as checked values (#2273)

This commit is contained in:
Chris Tate
2026-10-25 18:14:23 -05:00
committed by GitHub
4 changed files with 54 additions and 4 deletions
@@ -5297,10 +5297,12 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null
// A parameter a TypeScript program types `any` takes whatever the
// caller passes: the checked-dynamic slot, as an `any`-typed binding
// already is (dynFallbackType). Declaration files keep their paths.
// Only function declarations, spelled function types and lambda
// parameters whose default is the only source of their type (`(n =
// lookup() ?? 5) => ...` with an `any` lookup): other lambdas and
// methods in object literals keep their existing checked-dynamic story.
// Only function declarations, spelled function types, method and call
// signatures of interfaces and type literals (`execute(params: any):
// Promise<R>`), and lambda parameters whose default is the only source
// of their type (`(n = lookup() ?? 5) => ...` with an `any` lookup):
// other lambdas and methods in object literals keep their existing
// checked-dynamic story.
if (
!pt &&
!rest &&
@@ -5309,6 +5311,8 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null
decl.parent !== undefined &&
(ts.isFunctionDeclaration(decl.parent) ||
ts.isFunctionTypeNode(decl.parent) ||
ts.isMethodSignatureDeclaration(decl.parent) ||
ts.isCallSignatureDeclaration(decl.parent) ||
((ts.isArrowFunction(decl.parent) || ts.isFunctionExpression(decl.parent)) &&
decl.initializer !== undefined &&
decl.type === undefined &&
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/method-signature-any-params.ts"
],
"diags": []
}
@@ -0,0 +1,39 @@
// A method or call signature of an interface whose parameter is typed `any`
// (directly, or through an alias that resolves to `any`) takes whatever the
// caller passes, as a function type's `any` parameter already does: the
// interface compiles, and implementations receive the checked-dynamic value.
type Loose = any;
interface Tool {
name: string;
execute(id: string, params: any, extra?: Loose): Promise<string>;
describe(value: Loose): string;
}
interface Formatter {
(value: any): string;
}
const tools: Tool[] = [
{
name: "count",
execute: async (id, params) => `${id} has ${Object.keys(params ?? {}).length} keys`,
describe: (value) => `count(${Array.isArray(value) ? "array" : typeof value})`,
},
];
const format: Formatter = (value) => (typeof value === "number" ? value.toFixed(1) : String(value));
async function main(): Promise<void> {
const inputs: unknown[] = [{ a: 1, b: [2, 3] }, "text", 42, null, [1, 2]];
for (const tool of tools) {
for (const input of inputs) {
console.log(tool.name, await tool.execute("call", input), tool.describe(input));
}
console.log(tool.name, await tool.execute("extra", { k: true }, { more: "yes" }));
}
console.log(format(3), format("x"), format(undefined));
}
main().catch((e: unknown) => console.log("failed", e instanceof Error ? e.message : String(e)));
@@ -0,0 +1 @@
{ "plain": 4000 }