From 32fce771f340839fdf28ffc6622dd09fc88db016 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 14:38:45 -0500 Subject: [PATCH] Return undefined from overload calls whose generic body misses an index A call that selects an overload signature of a generic function now gives its instance the same result type as an implementation-signature call: when the body can return an indexed read that misses, the result includes undefined, as in JavaScript. Before, such a call failed at the missing read instead of returning undefined. --- .../frontend/lowering/generic-functions.ts | 23 +++++++++++-------- .../corpus/6497-generic-overload-bindings.ts | 13 +++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index 446ffa2bd..ca4664f20 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -303,14 +303,7 @@ export function genericCallInstance( fenceGenericSignatureResult(lowerer, expr, retTs); lowerer.badType(expr, retTs); } - // A body that can return an indexed read that misses returns undefined - // there, as in JavaScript, whatever the declared result says. An async - // body's promise is always present and settles with undefined there. - const returnType = lowerer.runtimeOptionalGenericReturns.has(info.decl) - ? mappedReturn.kind === "promise" - ? { kind: "promise" as const, inner: lowerer.runtimeOptionalType(mappedReturn.inner) } - : lowerer.runtimeOptionalType(mappedReturn) - : mappedReturn; + const returnType = runtimeReturnType(lowerer, info, mappedReturn); // keyof-constrained type parameters (`K extends keyof T`): the bound // LITERAL is semantic — the instance body reads the named field — so @@ -417,7 +410,8 @@ function overloadCallInstance( if (!returnType) return null; // keyof-constrained parameters key their instance on the bound literal, // as on the implementation-signature path. - return internGenericInstance(lowerer, expr, info, params, returnType, () => bindings, { + const runtimeReturn = runtimeReturnType(lowerer, info, returnType); + return internGenericInstance(lowerer, expr, info, params, runtimeReturn, () => bindings, { ...(keyofConstrainedTypeParams(info).size > 0 ? { extraKey: keyofLiteralKey(info, tsBindings) } : {}), @@ -425,6 +419,17 @@ function overloadCallInstance( }); } +/** An instance's result type: a body that can return an indexed read + * that misses returns undefined there, as in JavaScript, whatever the + * declared result says. An async body's promise is always present and + * settles with undefined there. */ +function runtimeReturnType(lowerer: Lowerer, info: GenericFnInfo, mapped: IrType): IrType { + if (!lowerer.runtimeOptionalGenericReturns.has(info.decl)) return mapped; + return mapped.kind === "promise" + ? { kind: "promise", inner: lowerer.runtimeOptionalType(mapped.inner) } + : lowerer.runtimeOptionalType(mapped); +} + /** A generic factory whose result is one of its own local classes * (`function base(v: T) { return class { value: T = v }; }`). Each * instance has its own class, so the instance is keyed on its bindings and diff --git a/tests/corpus/6497-generic-overload-bindings.ts b/tests/corpus/6497-generic-overload-bindings.ts index e27e94e20..0d33acaae 100644 --- a/tests/corpus/6497-generic-overload-bindings.ts +++ b/tests/corpus/6497-generic-overload-bindings.ts @@ -32,3 +32,16 @@ function read(row: T, key: K, fallback?: T[K]): T[K] { const row: Row = { a: 1, b: 2, name: "r" }; console.log(read(row, "a"), read(row, "b"), read(row, "name")); console.log(read(row, "b", 0) + read(row, "a", 0)); + +// An overload call whose implementation returns an indexed read that +// misses returns undefined, as the implementation's own calls do. +function at(items: T[], index: number): T; +function at(items: T[], index: number, label: string): T; +function at(items: T[], index: number, label?: string): T { + if (label !== undefined) console.log(label); + return items[index]; +} +console.log(at([1, 2, 3], 1), at([1, 2, 3], 5)); +console.log(at(["a", "b"], 0, "strings"), at(["a", "b"], 9, "strings")); +const missing = at([{ id: 1 }], 3); +console.log(missing === undefined, typeof missing);