Michael Lorton
Michael Lorton

Reputation: 44416

Why isn't spreading working with intersecting a generic type?

I'm trying to wrap a function that take an argument of some specific type to create a function that take an argument of a slightly different type. Here is the code, slightly simplified:

type DifferentArgs<T> = {
    myNewArg: number;
} & T;


function f<T, U>({ myNewArg, ...t }: DifferentArgs<T>, g: (q: T) => U) {
    return g(t);
}

This seems logical to me: t will necessarily be assignable to T.

The compiler disagrees:

 'Pick<DifferentArgs<T>, Exclude<keyof T, "myNewArg">>' is 
  assignable to the constraint of type 'T', but 'T' could 
  be instantiated with a different subtype of constraint '{}'

Can someone explain how there could be a different T and how I can fix this?

Upvotes: 2

Views: 31

Answers (1)

Michael Lorton
Michael Lorton

Reputation: 44416

Wow, that took me a long time.

Here's the problem. Imagine the above code did compile. Further imagine you are trying to call f() like so:

type KillerType = {
  myNewArg(): void;
}

f( k, (kt: KillerType) => kt.myNewArgs());

Now there is no value you can use for k that makes sense. It would have to have the property myNewArg both be a number and a void-valued function, a clear impossibility. Even ignoring the type-checking problem, f() would call the anonymous function without any myNewArg value at all.

It can be fixed:

type DifferentArgs<T> = {
    myNewArg: number;
} & T; 

type SameArgs<T> = Omit<T, 'myNewArg'>;

function f<T, U>({ myNewArg, ...t }: DifferentArgs<T>,
                 g: (q: SameArgs<T>) => U): U {
    return g(t);
}

Basically, you have to promise the compiler that the parameter to g will never need a property called myNewArg so the conflict can never arise.

Upvotes: 1

Related Questions