diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index 9d9c9668c..557b1a462 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -408,7 +408,15 @@ export function paramShapes( return { type: mapped, mode: "rest" }; } if (declParam.initializer) return defaultParameterShape(lowerer, declParam, mapped); - if (declParam.questionToken && !lowerer.bareUndefinedArmedUnion(mapped)) { + // A checked-value slot holds undefined directly, so an omitted + // argument needs no union arm (paramShape's declared-signature rule; + // an erased type parameter and a class-implemented interface view + // both map here). + if ( + declParam.questionToken && + !lowerer.bareUndefinedArmedUnion(mapped) && + mapped.kind !== "dyn" + ) { lowerer.unsupported( "SC1090", declParam, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6340-prototype-reflection-optional-view-param.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6340-prototype-reflection-optional-view-param.ts.json new file mode 100644 index 000000000..89aef0f85 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6340-prototype-reflection-optional-view-param.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6340-prototype-reflection-optional-view-param.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6340-prototype-reflection-optional-view-param.ts b/tests/corpus/6340-prototype-reflection-optional-view-param.ts new file mode 100644 index 000000000..58e21164d --- /dev/null +++ b/tests/corpus/6340-prototype-reflection-optional-view-param.ts @@ -0,0 +1,42 @@ +// A generic method whose optional parameter is an interface view (or an +// erased type parameter) reflects through the prototype of an instance +// that crossed into checked-dynamic code, and calls directly with the +// argument omitted. +interface Handle { + close(): string; +} +class Plain implements Handle { + close(): string { + return "closed"; + } +} +class Registry { + private current: Handle | null = null; + use(handle: Handle | null): void { + this.current = handle; + } + wrap(fn: F, handle?: Handle | null): F { + if (handle) this.current = handle; + return fn; + } + tag(value: T, label?: T): string { + return `${String(value)}:${label === undefined ? "none" : String(label)}`; + } + size(): number { + return this.current === null ? 0 : 1; + } +} + +const registry = new Registry(); +const inc = registry.wrap((x: number) => x + 1); +console.log(inc(1), registry.size()); +const dec = registry.wrap((x: number) => x - 1, new Plain()); +console.log(dec(1), registry.size()); +console.log(registry.tag(1), registry.tag("a", "b")); + +const boxed: unknown = registry; +const proto = Object.getPrototypeOf(boxed); +console.log(Object.getOwnPropertyNames(proto).join(",")); +console.log(typeof proto.wrap, typeof proto.tag, proto.size.call(registry)); +const loose: any = registry; +console.log(loose.size(), proto.tag.call(registry, 2), proto.tag.call(registry, 2, 3));