diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index 8e30e8b82..65a013bf3 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -13,7 +13,12 @@ import { typeEquals, } from "../../ir/ir.js"; import { isJsSourceFile, isNodeEsmFile } from "../program.js"; -import { genResultRecord, jsOpenObjectType } from "../type-mapper.js"; +import { + genResultRecord, + isUnitOnlyTsType, + jsOpenObjectType, + unitOnlyUnion, +} from "../type-mapper.js"; import { dynFallbackType } from "./lowerer.js"; import { builtinModuleFnOf } from "./surfaces.js"; import { mixinFnShapeOf } from "./lower-mixins.js"; @@ -373,7 +378,7 @@ export function paramShapes( const tsType = symbol ? lowerer.checker.getTypeOfSymbol(symbol) : lowerer.typeOf(declParam.name); - const mapped = jsOpenObjectType( + let mapped = jsOpenObjectType( declParam, lowerer.runtimeOptionalBindingType( declParam.name, @@ -382,6 +387,11 @@ export function paramShapes( lowerer.shapes, lowerer.unions, ); + // An instantiation with a literal `undefined` argument (`id(undefined)` + // binds T = undefined): standalone undefined maps to VOID (the return + // convention), and a parameter is a value position, so it takes the + // unit-only slot like a declared `v: undefined` parameter. + if (mapped?.kind === "void" && isUnitOnlyTsType(tsType)) mapped = unitOnlyUnion(lowerer.unions); if (!mapped || mapped.kind === "void") lowerer.badType(blameOf?.(declParam, i) ?? declParam.name, tsType); if (declParam.dotDotDotToken) { diff --git a/tests/corpus/generic-undefined-argument.ts b/tests/corpus/generic-undefined-argument.ts new file mode 100644 index 000000000..97d438549 --- /dev/null +++ b/tests/corpus/generic-undefined-argument.ts @@ -0,0 +1,19 @@ +// A generic call with a literal `undefined` argument binds T = undefined; +// the parameter holds undefined like a declared `v: undefined` parameter. +function kind(v: T): string { + return typeof v; +} +console.log(kind(undefined), kind(null), kind(1), kind("s")); +function same(a: T, b: T): boolean { + return a === b; +} +console.log(same(undefined, undefined)); +function wrap(v: T): { v: T } { + return { v }; +} +const w = wrap(undefined); +console.log(JSON.stringify(w), w.v === undefined, "v" in w); +function describe(label: string, v: T): string { + return `${label}=${String(v)}`; +} +console.log(describe("u", undefined));