The answer is contained within the question itself:
F<T & U>
is equivalent to F<T> | F<U>
which means that
type F1 = ((s: string) => any) | ((n: number) => any);
(sorry I find your helper type a little distracting, YMMV) is equal to
type F2 = (x: string & number) => any;
And now the reason for never
is clear: there is no value that can satisfy both string
and number
, so there can't be a function that accepts a non-existent string-and-number.
More abstractly, I think your understanding of variance is a bit off: covariance and contravariance are about subtype relationships, and how they affect functions on the types.
interface Animal ...;
interface Cat extends Animal ...;
So far so good. But now we have functions that take a parameter, and functions that return a thing:
const getAnimal: () => Animal;
const getCat: () => Cat;
const feedAnimal: ((a: Animal) => void) => void;
const feedCat: (cat: Cat) => void;
feedCat(getAnimal()); // ERROR
feedAnimal(feedCat); // ERROR
and here we see variance at play with the substitutability of the types: we can pass a Cat
to any function that expects an Animal
and we can pass a function that returns a Cat
to anything that expects () => Animal
but crucially you can't do the reverse: the farmer might try to feed the cat grain or return a goat instead of a cat.
Playground
For how that plays out generically I will defer to this explanation by jcalz here:
Contravariance means that F<T>
and T
contra-vary. That is, F<T>
varies counter to (in the opposite direction from) T
. In other words, if T
extends U
, then F<U>
extends F<T>
which we see in the example above, albeit with the concrete type and subtype rather than generically: Cat
extends Animal
so (a: Animal) => any
extends (c: Cat) => any
, not the other way around. To be fair this seems counter intuitive (or at least it did to me) until one actually sees it walked through with concrete examples in code, as I've tried to do here.