mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Pin generic method members constrained by the enclosing type parameter
An interface's generic method whose own type parameter is constrained by the enclosing type's parameter (run<T extends TApi>(item: Item<T>)), implemented by a plain contextually typed lambda, compiles since #2274: the lambda takes the contextual signature's type parameters and lowers once, erased. The merged corpus only covers unconstrained contextual generics, so this program pins the constrained shape with a conditional member on the argument type, explicit and inferred call type arguments, the class-body form, and record-held receivers. Verified against the 2026-10-23 base checkout, where the same shape refused with SC2009 (Item<T>'s member 'api' has type 'T'), and the pi models.ts:1159 Model<T> 'compat' shape refused the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6e5f21c580
commit
ec973e7221
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/generic-member-constrained-lambda-impls.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// A generic method member whose own type parameter is CONSTRAINED by the
|
||||
// enclosing type's type parameter (`run<T extends TApi>(item: Item<T>)`),
|
||||
// implemented by a plain lambda: the lambda takes the interface signature's
|
||||
// type parameters as its own and lowers once, erased like a generic lambda.
|
||||
// `Item<T>`'s conditional member depends on the type argument each call
|
||||
// passes, so the erased parameter must hold whatever the call sends.
|
||||
|
||||
type Api = "alpha" | "beta" | "gamma";
|
||||
|
||||
interface AlphaOptions {
|
||||
fast?: boolean;
|
||||
}
|
||||
interface BetaOptions {
|
||||
depth?: number;
|
||||
}
|
||||
|
||||
interface Item<TApi extends Api> {
|
||||
id: string;
|
||||
api: TApi;
|
||||
options?: TApi extends "alpha" ? AlphaOptions : TApi extends "beta" ? BetaOptions : never;
|
||||
}
|
||||
|
||||
interface Runner<TApi extends Api> {
|
||||
name: string;
|
||||
run<T extends TApi>(item: Item<T>): string;
|
||||
}
|
||||
|
||||
function describe(item: Item<Api>): string {
|
||||
const options = item.options === undefined ? "-" : JSON.stringify(item.options);
|
||||
return `${item.id}:${item.api}:${options}`;
|
||||
}
|
||||
|
||||
function makeRunner<TApi extends Api>(name: string): Runner<TApi> {
|
||||
return {
|
||||
name,
|
||||
run: (item) => `${name}:${describe(item)}`,
|
||||
};
|
||||
}
|
||||
|
||||
const both = makeRunner<"alpha" | "beta">("both");
|
||||
console.log(both.name);
|
||||
console.log(both.run({ id: "a", api: "alpha", options: { fast: true } }));
|
||||
console.log(both.run({ id: "b", api: "beta", options: { depth: 3 } }));
|
||||
console.log(both.run({ id: "c", api: "alpha" }));
|
||||
console.log(both.run<"beta">({ id: "d", api: "beta" }));
|
||||
|
||||
const all = makeRunner<Api>("all");
|
||||
console.log(all.run({ id: "e", api: "gamma" }));
|
||||
console.log(all.run({ id: "f", api: "beta", options: { depth: 1 } }));
|
||||
|
||||
// The same contract through a class body: the generic method lowers under
|
||||
// the class instantiation's binding joined with its own type parameter.
|
||||
class ClassRunner<TApi extends Api> implements Runner<TApi> {
|
||||
name: string;
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
run<T extends TApi>(item: Item<T>): string {
|
||||
return `${this.name}:${describe(item)}`;
|
||||
}
|
||||
}
|
||||
|
||||
const cls = new ClassRunner<"alpha" | "beta">("cls");
|
||||
console.log(cls.run({ id: "g", api: "alpha", options: { fast: false } }));
|
||||
console.log(cls.run({ id: "h", api: "beta" }));
|
||||
|
||||
// Record-held receivers call the erased slot the literal stored.
|
||||
const runners: Runner<"alpha" | "beta">[] = [both, cls];
|
||||
let combined = "";
|
||||
for (const runner of runners) {
|
||||
combined += `[${runner.run({ id: "i", api: "alpha" })}]`;
|
||||
}
|
||||
console.log(combined);
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4100 }
|
||||
Reference in New Issue
Block a user