Make property non-optional if it was passed in object literal

Consider the following example (Playground link)

interface Foo<T> {
    kind: string;
    value?: T;
}
 
function foo<T>(props: Omit<Foo<T>, "kind">) : Foo<T> {
    return {kind:"foo", ...props};
}

function testOptUnknown(v?: unknown) {

}

function testNumber(v: number) {

}


const foo1 = foo({});
const foo2 = foo({value: 42});

testOptUnknown(foo1.value);
testNumber(foo2.value);             // <--- does not compile

What I am trying to achieve is that the "builder"-function foo specialises Foo's value to be non-optional if a given value for value was passed.

BTW. Turning value?: T into value: T will not work for me because I am using exactOptionalPropertyTypes.


You can use a type parameter for the actually passed-in input parameter and use that type parameter to construct your return type. That way, the compiler will propagate its knowledge of the type of the actual argument:

interface Foo<T> {
    kind: string;
    value?: T;
}
 
function foo<X, T extends Omit<Foo<X>, "kind">>(props: T) : T & {kind: 'foo'} {
    return {kind:"foo", ...props};
}

function testOptUnknown(v?: unknown) {

}

function testNumber(v: number) {

}

const x = {
    'a': 1
}

const y = {...x, b: '2'}


const foo1 = foo({});
const foo2: Foo<string> = foo({});
const foo3 = foo({value: 42});

testOptUnknown(foo1.value); 
testOptUnknown(foo2.value);
testNumber(foo3.value);

Playground link