Reputation: 602
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;
};
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;
};
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
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
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