From 004034e4e9d3925e98f9f195c3cacd4cad0a6682 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 01:10:46 -0700 Subject: [PATCH 1/2] Monomorphize generic overload calls and call generic methods through Pick views - A call that resolves to an overload signature of a generic function or method with an implementation (`load(k, map: Mapper): Promise; load(k): Promise; load(k, map?) {...}`) monomorphizes the implementation under the bindings the overload's resolved signature proves; a type parameter the overload never mentions is unknown, and parameters the overload omits must be optional in the implementation. It was refused. - `view.m(...)` where `view` is a `Pick`/`Omit` view of a program class and `m` is a generic (or generically overloaded) method extracts the instance from the view's live receiver and calls the method statically, instead of a runtime refusal reading the method through the untyped view. - Binding inference pairs the arms of `T | Promise` with an instantiated `R | Promise`, and `unknown | Promise` maps to the checked-dynamic value the checker reduces it to. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/generic-functions.ts | 92 +++++++++++++++++++ .../src/frontend/lowering/lower-calls.ts | 34 +++++++ packages/compiler/src/frontend/type-mapper.ts | 4 + .../6495-generic-method-overloads.ts.json | 6 ++ .../corpus/6496-overload-signatures.ts.json | 6 ++ .../tests/diagnostics/overload-fences.ts.json | 6 -- tests/corpus/6495-generic-method-overloads.ts | 54 +++++++++++ .../6496-overload-signatures.ts} | 8 +- .../__snapshots__/overload-fences.ts.txt | 6 -- 9 files changed, 200 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6495-generic-method-overloads.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6496-overload-signatures.ts.json delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overload-fences.ts.json create mode 100644 tests/corpus/6495-generic-method-overloads.ts rename tests/{diagnostics/overload-fences.ts => corpus/6496-overload-signatures.ts} (66%) delete mode 100644 tests/harness/__snapshots__/overload-fences.ts.txt diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index f588faf6d2..b6eb52a8af 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -268,10 +268,13 @@ export function genericCallInstance( { const rdecl = rsig ? rsig.declaration : undefined; if ( + rsig && rdecl && (ts.isFunctionDeclaration(rdecl) || ts.isMethodDeclaration(rdecl)) && !rdecl.body ) { + const overload = overloadCallInstance(lowerer, expr, info, rsig, rdecl); + if (overload) return overload; lowerer.unsupported( "SC1090", expr, @@ -342,6 +345,74 @@ export function genericCallInstance( ); } +/** A call that resolved to an OVERLOAD signature of a generic function + * or method with an implementation (`request(p: WithHandler): + * Promise; request(p: Plain): Promise; request(p: Params) + * {...}`): the implementation monomorphizes under the bindings the + * overload's resolved signature proves for its type parameters (the + * structural unification every generic call uses, run against the + * overload; unbound parameters take their declared defaults). The + * instance's parameters and result are the IMPLEMENTATION's under those + * bindings — the body was checked against them — and the call's + * arguments and result coerce across, exactly as a non-generic overload + * call reconciles with its implementation (parameters the overload omits + * must be optional in the implementation). Null when the overload does + * not belong to this implementation or the arities do not line up. */ +function overloadCallInstance( + lowerer: Lowerer, + expr: ts.CallExpression, + info: GenericFnInfo, + rsig: ts.Signature, + rdecl: ts.FunctionDeclaration | ts.MethodDeclaration, +): GenericInstance | null { + const impl = info.decl; + if ( + rdecl.parent !== impl.parent || + rdecl.name === undefined || + impl.name === undefined || + rdecl.name.getText() !== impl.name.getText() || + rdecl.parameters.nodes.length > impl.parameters.nodes.length || + impl.parameters.nodes + .slice(rdecl.parameters.nodes.length) + .some((p) => p.questionToken === undefined && p.initializer === undefined) || + rdecl.parameters.nodes.some((p) => p.dotDotDotToken !== undefined) + ) + return null; + const declSig = lowerer.checker.getSignatureFromDeclaration(impl); + if (!declSig) return null; + const tsBindings = new Map(); + const bindings = lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings); + // A type parameter the overload never mentions (a handler the overload + // omits) is `unknown`, the checker's own answer for an uninferred one. + for (const tp of info.typeParams) { + if (bindings.has(tp)) continue; + bindings.set(tp, DYN); + tsBindings.set(tp, lowerer.checker.getUnknownType()); + } + const previous = lowerer.typeParamBindings; + const previousTs = lowerer.typeParamTsBindings; + lowerer.typeParamBindings = bindings; + lowerer.typeParamTsBindings = tsBindings; + let params: ParamShape[]; + let returnType: IrType | null; + try { + params = paramShapes( + lowerer, + impl.parameters.nodes, + declSig, + (_param, i) => expr.arguments.nodes[i] ?? expr, + ); + returnType = lowerer.mapTypeOf(lowerer.checker.getReturnTypeOfSignature(declSig)); + } finally { + lowerer.typeParamBindings = previous; + lowerer.typeParamTsBindings = previousTs; + } + if (!returnType) return null; + return internGenericInstance(lowerer, expr, info, params, returnType, () => bindings, { + tsBindings, + }); +} + /** 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 @@ -705,6 +776,27 @@ function unifySignatureBindings( // so match it against the whole remaining union. Removing null // too is safe only when no undeclared null arm remains. unify(dParts[0]!, lowerer.checker.getNonNullableType(inst), depth + 1); + } else if (dParts.length > 1 && iParts.length === dParts.length) { + // `T | Promise` against `Res | Promise`: each declared + // part that names a generic target (Promise, an interface, an + // alias) pairs with the one instantiated part of the same target; + // a single remaining bare type parameter takes the single + // remaining instantiated part. + const remaining = [...iParts]; + const bare: ts.Type[] = []; + for (const d of dParts) { + if (d.flags & ts.TypeFlags.TypeParameter) { + bare.push(d); + continue; + } + const target = d.getSymbol() ?? d.getAliasSymbol(); + if (target === undefined) return; + const matches = remaining.filter((i) => (i.getSymbol() ?? i.getAliasSymbol()) === target); + if (matches.length !== 1) return; + remaining.splice(remaining.indexOf(matches[0]!), 1); + unify(d, matches[0]!, depth + 1); + } + if (bare.length === 1 && remaining.length === 1) unify(bare[0]!, remaining[0]!, depth + 1); } return; } diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 5a48387825..40ffcdb936 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -3309,6 +3309,8 @@ function lowerDynReceiverMethodCall( // any-elemented array). Typed receivers keep their own lowerings. const recvTs = lowerer.typeOf(access.expression); if (lowerer.classImplementedProtocol(recvTs)) { + const viewed = pickViewGenericMethodCall(lowerer, call, access, recvTs); + if (viewed) return viewed; const property = lowerer.checker.getPropertyOfType(recvTs, access.name.text); if ( property && @@ -10092,3 +10094,35 @@ function maybePromiseHandlerResult( } return stmt; } + +/** `view.m(args)` where `view` is a `Pick` / `Omit` view of a + * program class C and `m` is one of C's GENERIC methods (its overloads + * included): generic methods have no dispatchable method value, so the + * view's live receiver is extracted as the C instance it holds (a checked + * extraction — a value of another class throws Node-style TypeError + * text naming C) and the call monomorphizes statically against C, the + * same lowering a C-typed receiver gets. Null for other receivers. */ +function pickViewGenericMethodCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, + recvTs: ts.Type, +): IrExpr | null { + const alias = recvTs.getAliasSymbol(); + if (alias === undefined || (alias.name !== "Pick" && alias.name !== "Omit")) return null; + if (!alias.declarations.every((d) => d.getSourceFile().isDeclarationFile)) return null; + const source = recvTs.getAliasTypeArguments()[0]; + const symbol = source?.getSymbol(); + const info = symbol ? lowerer.classBySymbol.get(symbol) : undefined; + if (!info || call.questionDotToken || access.questionDotToken) return null; + const found = findGenericMethodOn(lowerer, info, access.name.text); + if (!found) return null; + const loc = locOf(access.expression); + const lowered = lowerer.lowerExpr(access.expression); + const instance: IrType = { kind: "object", className: info.def.name }; + const receiver: IrExpr = + lowered.type.kind === "object" + ? lowered + : { kind: "dynCheck", value: lowerer.coerceToExpected(lowered, DYN), type: instance, loc }; + return lowerClassGenericMethodCall(lowerer, call, access, info, found, receiver); +} diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 58434ef6b8..2f054fb715 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -6340,6 +6340,10 @@ function dynSubsumableUnionArm(arm: IrType, ctx: TypeMapperCtx): boolean { return true; case "record": case "array": + // `T | Promise` with T bound to `unknown`: the checked-dynamic value + // holds a promise by reference, so `unknown` absorbs the promise arm + // exactly as the checker's own union reduction does. + case "promise": return canConvertToDyn( arm, (id) => ctx.shapes.get(id), diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6495-generic-method-overloads.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6495-generic-method-overloads.ts.json new file mode 100644 index 0000000000..98806a53c4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6495-generic-method-overloads.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6495-generic-method-overloads.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6496-overload-signatures.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6496-overload-signatures.ts.json new file mode 100644 index 0000000000..8bc601bd63 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6496-overload-signatures.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6496-overload-signatures.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overload-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overload-fences.ts.json deleted file mode 100644 index 1515ceeb9b..0000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overload-fences.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/diagnostics/overload-fences.ts" - ], - "diags": [] -} diff --git a/tests/corpus/6495-generic-method-overloads.ts b/tests/corpus/6495-generic-method-overloads.ts new file mode 100644 index 0000000000..34bcfa712c --- /dev/null +++ b/tests/corpus/6495-generic-method-overloads.ts @@ -0,0 +1,54 @@ +// Calls that resolve to OVERLOAD signatures of a generic method or +// function: the implementation monomorphizes under what the overload +// proves for its type parameters (a parameter the overload never mentions +// is unknown), directly and through a `Pick` view of the class, whose +// live receiver keeps the instance's state. `T | Promise` callbacks +// bind T from either arm. +interface Reply { + code: number; + text: string; +} +type Mapper = (reply: Reply) => T | Promise; + +class Store { + loads = 0; + async load(key: string, map: Mapper): Promise; + async load(key: string): Promise; + async load(key: string, map?: Mapper): Promise { + this.loads++; + const reply: Reply = { code: 200, text: key }; + if (map === undefined) return reply; + return await map(reply); + } +} + +interface Services { + store: Pick; +} + +function pick(items: T[], index: number): T; +function pick(items: T[]): T; +function pick(items: T[], index = 0): T { + return items[index]!; +} + +async function settle(reply: Reply, map?: Mapper): Promise { + return map ? await map(reply) : reply; +} + +async function main(): Promise { + const store = new Store(); + const plain = await store.load("alpha"); + console.log(plain.code, plain.text); + const length = await store.load("beta", (reply) => reply.text.length); + console.log(length * 2); + const services: Services = { store }; + const viaView = await services.store.load("gamma"); + const upper = await services.store.load("delta", async (reply) => reply.text.toUpperCase()); + console.log(viaView.text, upper, store.loads); + console.log(pick(["x", "y"]), pick([1, 2, 3], 2)); + console.log(JSON.stringify(await settle({ code: 1, text: "s" }))); + console.log(await settle({ code: 2, text: "tt" }, (reply) => reply.code + reply.text.length)); +} + +main(); diff --git a/tests/diagnostics/overload-fences.ts b/tests/corpus/6496-overload-signatures.ts similarity index 66% rename from tests/diagnostics/overload-fences.ts rename to tests/corpus/6496-overload-signatures.ts index 0cf1f7e157..5902d8c190 100644 --- a/tests/diagnostics/overload-fences.ts +++ b/tests/corpus/6496-overload-signatures.ts @@ -1,7 +1,7 @@ -// The overload fences that REMAIN now that overload signatures lower: -// generic overload calls (the per-instantiation body would be typed -// against a signature it was never checked under), and the multi- -// signature type surfacing where a binding needs it spelled. +// Overload signatures: a generic overload call monomorphizes the +// implementation under the bindings the overload proves (a parameter the +// overload omits is optional in the implementation), and an immutable +// alias of an overloaded declaration projects onto the implementation. function twice(x: T): T[]; function twice(x: T, n: number): T[]; function twice(x: T, n?: number): T[] { diff --git a/tests/harness/__snapshots__/overload-fences.ts.txt b/tests/harness/__snapshots__/overload-fences.ts.txt deleted file mode 100644 index 6c4b2f405f..0000000000 --- a/tests/harness/__snapshots__/overload-fences.ts.txt +++ /dev/null @@ -1,6 +0,0 @@ -overload-fences.ts:12:13 - error SC1090: calls selecting an overload signature of a generic function (only the implementation signature monomorphizes) is not supported yet - - 11 | } - 12 | console.log(twice("q").length); - | ^~~~~~~~~~ - 13 | function pick(x: "a"): string; \ No newline at end of file From 19835975eae1b9f1c140965581c5d8e31dbaae80 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 12:25:57 -0500 Subject: [PATCH 2/2] Bind generic overload calls by the overload's own type parameters --- .../frontend/lowering/generic-functions.ts | 61 ++++++++++++++----- .../src/frontend/lowering/lower-calls.ts | 14 +++++ .../6497-generic-overload-bindings.ts.json | 6 ++ .../pick-view-generic-methods.ts.json | 6 ++ .../corpus/6497-generic-overload-bindings.ts | 34 +++++++++++ .../diagnostics/pick-view-generic-methods.ts | 16 +++++ .../pick-view-generic-methods.ts.txt | 6 ++ .../6495-generic-method-overloads.ts.json | 1 + .../6496-overload-signatures.ts.json | 1 + .../6497-generic-overload-bindings.ts.json | 1 + 10 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6497-generic-overload-bindings.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pick-view-generic-methods.ts.json create mode 100644 tests/corpus/6497-generic-overload-bindings.ts create mode 100644 tests/diagnostics/pick-view-generic-methods.ts create mode 100644 tests/harness/__snapshots__/pick-view-generic-methods.ts.txt create mode 100644 tests/harness/corpus-costs/6495-generic-method-overloads.ts.json create mode 100644 tests/harness/corpus-costs/6496-overload-signatures.ts.json create mode 100644 tests/harness/corpus-costs/6497-generic-overload-bindings.ts.json diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index b6eb52a8af..446ffa2bdb 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -320,23 +320,11 @@ export function genericCallInstance( // different fields). Non-literal bindings (a key union, plain string) // share one runtime-keyed instance per IR signature, exactly the // widened discipline. - const keyofTps = keyofConstrainedTypeParams(info); - if (keyofTps.size > 0) { + if (keyofConstrainedTypeParams(info).size > 0) { const tsBindings = new Map(); const bindings = lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings); - const litKey = info.typeParams - .filter((tp) => keyofTps.has(tp)) - .map((tp) => { - const bound = tsBindings.get(tp); - return bound?.isStringLiteralType() - ? JSON.stringify(bound.value) - : bound?.isNumberLiteralType() - ? String(bound.value) - : "*"; - }) - .join(","); return internGenericInstance(lowerer, expr, info, params, returnType, () => bindings, { - extraKey: `@${litKey}`, + extraKey: keyofLiteralKey(info, tsBindings), tsBindings, }); } @@ -381,7 +369,26 @@ function overloadCallInstance( const declSig = lowerer.checker.getSignatureFromDeclaration(impl); if (!declSig) return null; const tsBindings = new Map(); - const bindings = lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings); + const bindings = new Map(); + unifySignatureBindings(lowerer, info, rsig, bindings, tsBindings); + // Explicit type arguments name the OVERLOAD's type parameters, which + // need not match the implementation's in order: the resolved overload + // already carries them, so unification binds first, and an explicit + // argument binds only a still-unbound implementation parameter of the + // same name. + const overloadTps = rdecl.typeParameters?.nodes ?? []; + expr.typeArguments?.nodes.forEach((ta, i) => { + const name = overloadTps[i]?.name.text; + const tp = info.typeParams.find((p) => p.name === name); + if (!tp || bindings.has(tp)) return; + const taT = lowerer.checker.getTypeFromTypeNode(ta); + const mapped = lowerer.mapTypeOf(taT); + if (mapped) { + bindings.set(tp, mapped); + tsBindings.set(tp, taT); + } + }); + bindDefaultTypeParams(lowerer, info.typeParams, impl.typeParameters?.nodes, bindings, tsBindings); // A type parameter the overload never mentions (a handler the overload // omits) is `unknown`, the checker's own answer for an uninferred one. for (const tp of info.typeParams) { @@ -408,7 +415,12 @@ function overloadCallInstance( lowerer.typeParamTsBindings = previousTs; } 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, { + ...(keyofConstrainedTypeParams(info).size > 0 + ? { extraKey: keyofLiteralKey(info, tsBindings) } + : {}), tsBindings, }); } @@ -641,6 +653,25 @@ function keyofConstrainedTypeParams(info: GenericFnInfo): Set { return out; } +/** The instance-key suffix of the literals bound to `info`'s + * keyof-constrained type parameters (`@"a"`; `*` for a non-literal + * binding): two literals can map to one IR signature while their bodies + * read different fields. */ +function keyofLiteralKey(info: GenericFnInfo, tsBindings: Map): string { + const keyofTps = keyofConstrainedTypeParams(info); + const literals = info.typeParams + .filter((tp) => keyofTps.has(tp)) + .map((tp) => { + const bound = tsBindings.get(tp); + return bound?.isStringLiteralType() + ? JSON.stringify(bound.value) + : bound?.isNumberLiteralType() + ? String(bound.value) + : "*"; + }); + return `@${literals.join(",")}`; +} + /** Type-parameter symbol → concrete IR type for one instantiation. * Explicit type arguments bind directly; the rest come from structurally * matching each DECLARED param/return type (which mentions the type diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 40ffcdb936..316ebadf92 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -10120,6 +10120,20 @@ function pickViewGenericMethodCall( const loc = locOf(access.expression); const lowered = lowerer.lowerExpr(access.expression); const instance: IrType = { kind: "object", className: info.def.name }; + // A view that statically holds an instance of another class (a + // structurally compatible one) calls that class's method in Node, not + // C's: the call is refused rather than extracted as a C instance. + if ( + lowered.type.kind === "object" && + lowered.type.className !== info.def.name && + !lowerer.isSubclassOf(lowered.type.className, info.def.name) + ) { + lowerer.unsupported( + "SC1090", + access, + `calling the generic method '${access.name.text}' through a '${alias.name}' view of '${info.def.name}' that holds a '${lowerer.fmt(lowered.type)}'`, + ); + } const receiver: IrExpr = lowered.type.kind === "object" ? lowered diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6497-generic-overload-bindings.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6497-generic-overload-bindings.ts.json new file mode 100644 index 0000000000..06e7bb17f9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6497-generic-overload-bindings.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6497-generic-overload-bindings.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pick-view-generic-methods.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pick-view-generic-methods.ts.json new file mode 100644 index 0000000000..1dafe9a3ab --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pick-view-generic-methods.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/pick-view-generic-methods.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6497-generic-overload-bindings.ts b/tests/corpus/6497-generic-overload-bindings.ts new file mode 100644 index 0000000000..e27e94e20e --- /dev/null +++ b/tests/corpus/6497-generic-overload-bindings.ts @@ -0,0 +1,34 @@ +// Bindings of generic overload calls: explicit type arguments name the +// overload's type parameters, which can be declared in another order than +// the implementation's, and keyof-constrained parameters bound to +// different literals through an overload read their own fields. +function tag(value: A, label: B): string; +function tag(value: A, label?: B): string { + return `${typeof value}:${typeof label}:${String(value)}${String(label)}`; +} +console.log(tag(5, "x")); +console.log(tag("y", true)); + +class Labels { + prefix = "#"; + render(value: A, label: B): string; + render(value: A, label?: B): string { + return `${this.prefix}${typeof value}/${typeof label}`; + } +} +console.log(new Labels().render("z", 7)); + +interface Row { + a: number; + b: number; + name: string; +} +function read(row: T, key: K): T[K]; +function read(row: T, key: K, fallback: T[K]): T[K]; +function read(row: T, key: K, fallback?: T[K]): T[K] { + const value = row[key]; + return value === undefined ? fallback! : value; +} +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)); diff --git a/tests/diagnostics/pick-view-generic-methods.ts b/tests/diagnostics/pick-view-generic-methods.ts new file mode 100644 index 0000000000..912867dc63 --- /dev/null +++ b/tests/diagnostics/pick-view-generic-methods.ts @@ -0,0 +1,16 @@ +// A generic method called through a `Pick` view of a class compiles +// against that class. A view that holds an instance of a structurally +// compatible, unrelated class is refused: Node calls that class's own +// method. +class Store { + load(key: string, map: (text: string) => T): T { + return map(key); + } +} +class Mirror { + load(key: string, map: (text: string) => T): T { + return map(`${key}!`); + } +} +const view: Pick = new Mirror(); +console.log(view.load("k", (text) => text.length)); diff --git a/tests/harness/__snapshots__/pick-view-generic-methods.ts.txt b/tests/harness/__snapshots__/pick-view-generic-methods.ts.txt new file mode 100644 index 0000000000..59e7ed6422 --- /dev/null +++ b/tests/harness/__snapshots__/pick-view-generic-methods.ts.txt @@ -0,0 +1,6 @@ +pick-view-generic-methods.ts:16:13 - error SC1090: calling the generic method 'load' through a 'Pick' view of 'Store' that holds a 'Mirror' is not supported yet + + 15 | const view: Pick = new Mirror(); + 16 | console.log(view.load("k", (text) => text.length)); + | ^~~~~~~~~ + 17 | \ No newline at end of file diff --git a/tests/harness/corpus-costs/6495-generic-method-overloads.ts.json b/tests/harness/corpus-costs/6495-generic-method-overloads.ts.json new file mode 100644 index 0000000000..89eeb5b0c8 --- /dev/null +++ b/tests/harness/corpus-costs/6495-generic-method-overloads.ts.json @@ -0,0 +1 @@ +{ "plain": 2783, "san": 2211 } diff --git a/tests/harness/corpus-costs/6496-overload-signatures.ts.json b/tests/harness/corpus-costs/6496-overload-signatures.ts.json new file mode 100644 index 0000000000..5b1f193e3e --- /dev/null +++ b/tests/harness/corpus-costs/6496-overload-signatures.ts.json @@ -0,0 +1 @@ +{ "plain": 2032, "san": 1924 } diff --git a/tests/harness/corpus-costs/6497-generic-overload-bindings.ts.json b/tests/harness/corpus-costs/6497-generic-overload-bindings.ts.json new file mode 100644 index 0000000000..a94adc8beb --- /dev/null +++ b/tests/harness/corpus-costs/6497-generic-overload-bindings.ts.json @@ -0,0 +1 @@ +{ "plain": 2061, "san": 1900 }