0

I don't understand why this very short TypeScript snippet:

type R<B> = (b: B | undefined) => {};

type W = (z: Z) => Z;
type Z = <B>(r: R<B>) => B;

interface BB {someKey: number}

const result: Z = (_r: R<BB>) => ({ someKey: 123 });

is causing this error:

Type '(_r: R<BB>) => { someKey: number; }' is not assignable to type 'Z'.
  Types of parameters '_r' and 'r' are incompatible.
    Types of parameters 'b' and 'b' are incompatible.
      Type 'BB' is not assignable to type 'B'.

Can anyone help me understand how TypeScript's type inference works here? Here's the link to the snippet on TypeScript playground

Huy Nguyen
  • 2,840
  • 1
  • 14
  • 18

1 Answers1

0

(For ease of understanding for others, I'm going to use Array<T> instead of R<B> and number instead of BB, and I will rename Z to GenericArrayPicker. And W doesn't enter in so I'm removing it. If you want to switch them back you can. The error is exactly the same, but hopefully the terminology below is more intuitive.)

Your main error is trying to say that a non-generic function is generic. Observe the following type:

type GenericArrayPicker = <T>(arr: Array<T>) => T;

That's a generic function type, where the type parameter T is anything the caller of the function wants it to be. Let's say we actually had a function of that type:

declare const genericArrayPicker: GenericArrayPicker;

The caller is free to choose T either explicitly:

genericArrayPicker<string>(["foo", "bar"]); // returns a string

or implicitly by allowing the compiler to infer it from the call site:

genericArrayPicker(["foo", "bar"]); // T still inferred as string

Now the following function is not generic:

const numberArrayPicker = 
  (arr: Array<number>) => 17;

It only works on arrays of numbers. If you inspect its type, it is (arr: Array<number>) => number. You cannot call it with strings the way you can with genericArrayPicker:

numberArrayPicker(["foo", "bar"]); // error!

This should be a hint that numberArrayPicker does not match the GenericArrayPicker type. If you try to assign it to a GenericArrayPicker, you get a warning:

const result: GenericArrayPicker = numberArrayPicker; // error!
// T is not assignable to number

The statement "T is not assignable to number" might seem weird. A more explicit statement is that numberArrayPicker only works for a value of the type parameter T chosen by the implementer of the function (number). But it is the caller, not the implementer, who gets to choose the value of T. This is the difference between universal and existential type quantification; TypeScript only supports universal type quantification (caller picks a type, implementer has to accept whatever the caller picks), not existential type quantification (implementer picks a type, caller has to accept whatever the implementer picks).

Hope that makes sense. Good luck.

jcalz
  • 264,269
  • 27
  • 359
  • 360