diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-constrained-lambda-impls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-constrained-lambda-impls.ts.json new file mode 100644 index 000000000..4c7bdccc5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-constrained-lambda-impls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/generic-member-constrained-lambda-impls.ts" + ], + "diags": [] +} diff --git a/tests/corpus/generic-member-constrained-lambda-impls.ts b/tests/corpus/generic-member-constrained-lambda-impls.ts new file mode 100644 index 000000000..b34d46c15 --- /dev/null +++ b/tests/corpus/generic-member-constrained-lambda-impls.ts @@ -0,0 +1,73 @@ +// A generic method member whose own type parameter is CONSTRAINED by the +// enclosing type's type parameter (`run(item: Item)`), +// 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`'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 { + id: string; + api: TApi; + options?: TApi extends "alpha" ? AlphaOptions : TApi extends "beta" ? BetaOptions : never; +} + +interface Runner { + name: string; + run(item: Item): string; +} + +function describe(item: Item): string { + const options = item.options === undefined ? "-" : JSON.stringify(item.options); + return `${item.id}:${item.api}:${options}`; +} + +function makeRunner(name: string): Runner { + 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("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 implements Runner { + name: string; + constructor(name: string) { + this.name = name; + } + run(item: Item): 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); diff --git a/tests/harness/corpus-costs/generic-member-constrained-lambda-impls.ts.json b/tests/harness/corpus-costs/generic-member-constrained-lambda-impls.ts.json new file mode 100644 index 000000000..fe5fca1ae --- /dev/null +++ b/tests/harness/corpus-costs/generic-member-constrained-lambda-impls.ts.json @@ -0,0 +1 @@ +{ "plain": 4100 }