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?
Intersection types are closely related to union types, but they are used very differently. An intersection type combines multiple types into one. This allows you to add together existing types to get a single type that has all the features you need.
Using infer in TypeScript The infer keyword compliments conditional types and cannot be used outside an extends clause. Infer allows us to define a variable within our constraint to be referenced or returned.
An intersection type creates a new type by combining multiple existing types. The new type has all features of the existing types. To combine types, you use the & operator as follows: type typeAB = typeA & typeB; Code language: TypeScript (typescript)
TypeScript Union Type Narrowing To narrow a variable to a specific type, implement a type guard. Use the typeof operator with the variable name and compare it with the type you expect for the variable.
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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With