Instantiate generics with a literal undefined argument

`id(undefined)` binds T = undefined, which the type mapper answers as VOID
(the return convention), so the instance's parameter was refused (SC2001)
although a declared `v: undefined` parameter compiles. Instantiated
parameters are value positions and now take the unit-only slot, as
declared ones do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Malte Ubl
2026-10-18 15:16:18 -07:00
co-authored by Claude Opus 5.5
parent dd0c255fa7
commit cd63b935e0
2 changed files with 31 additions and 2 deletions
@@ -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) {
@@ -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<T>(v: T): string {
return typeof v;
}
console.log(kind(undefined), kind(null), kind(1), kind("s"));
function same<T>(a: T, b: T): boolean {
return a === b;
}
console.log(same(undefined, undefined));
function wrap<T>(v: T): { v: T } {
return { v };
}
const w = wrap(undefined);
console.log(JSON.stringify(w), w.v === undefined, "v" in w);
function describe<T>(label: string, v: T): string {
return `${label}=${String(v)}`;
}
console.log(describe("u", undefined));