AndiH
AndiH

Reputation: 602

Why does Typescript infer 'never' instead of intersection type?

Given the following example:

interface Data {
    name: string;
    value: number;
}

const data :Data = {
    name: 'name',
    value: 1,
}

const updateValue = (key: keyof Data, value: string | number): void => {
    data[key] = value;
};

link to ts-playgound

Typescript shows the following error:

Type 'string | number' is not assignable to type 'string & number'.
  Type 'string' is not assignable to type 'string & number'.
    Type 'string' is not assignable to type 'number'.

Which is clear and understandable. However if I add a union type to the interface like so:

type MultiType = 'x' | 'y';

interface Data {
    name: string;
    value: number;
    multiType: MultiType | null;
}

const data :Data = {
    name: 'name',
    value: 1,
    multiType: null,
}

const updateValue = (key: keyof Data, value: string | number): void => {
    data[key] = value;
};

link to ts-playgound

I get the following error:

Type 'string | number' is not assignable to type 'never'.
  Type 'string' is not assignable to type 'never'.

Typescript accepts it if I use the intersection type string & number & MultiType but it also accepts never.

This seems inconsistent to me. Is this maybe a bug?

Upvotes: 7

Views: 5411

Answers (2)

jcalz
jcalz

Reputation: 328362

string & number is equivalent to never, and so is string & number & (MultiType | null). There are no values which are both a string and a number, so no values satisfy string & number or string & number & AnythingElse.

Right now the latter explicitly reduces to never because it contains unions of these equivalent-to-never types, which is really ugly to leave like that. Specifically, the compiler distributes intersections over unions, so

string & number & ('x' | 'y' | null)

becomes

(string & number & 'x') | (string & number & 'y') | (string & number & null)

That type isn't particularly enlightening to people, so the compiler checks that each of those union constituents is equivalent to never and reduces the type to

never | never | never

which is just

never

as you saw.


So why doesn't string & number by itself get immediately reduced to never? Well originally the idea was that it would help people understand where the bug in their code came from, since string is not assignable to number is more enlightening that string is not assignable to never.

Unfortunately while string & number is equivalent to never in terms of what values are assignable to and from it, in TS3.5 and below the compiler doesn't always treat them the same, which is confusing.

Therefore it looks like, starting in TS3.6, empty intersections like string & number will be reduced to never explicitly. Once TS3.6 is released, your above code will act the same in both cases, and you'll get the string | number is not assignable to never error.


Okay, hope that helps; good luck!

Upvotes: 14

Kewin Dousse
Kewin Dousse

Reputation: 4027

Two things :

  • It's only logical that TypeScript doesn't accept string | number as type for your value parameter, because all of the following :

    • string | number is not assignable to string because number is not assignable to string in case the key is 'name'

    • string | number is not assignable to number because string is not assignable to number in case the key is 'value' (both of there were already true in your first example)

    • string | number is not assignable to MultiType | null because both number and string are not assignable to 'x' | 'y' | null in case the key is 'multiType'

For some reason however, in your first example TypeScript simply stops on the first "wrong" case and gives you this error (Even though there are really two things wrong already).

In the second case, you could have seen the same error message because the incompatibility of types is still there, it seems like the inference goes a little deeper and tells you that the problem is deeper than this. As to why is the error message formatted like this, I don't really know and I guess it would require going deeper into how are things inferred by the compiler here. In the end, the compiler is right, but the error message could be clearer.


For your second question, the never type documentation says :

The never type is a subtype of, and assignable to, every type

So that's why you can specify your value to be of type never, and assign it to your data. Because it will never happen.

Upvotes: 1

Related Questions