Reputation: 43
I created the following types from a constant array <const>['a', 'b]
:
const paths = <const>['a', 'b']
type Path = typeof paths[number]
type PathMap = {
[path in Path]: path
}
Path
equals to "a" | "b"
PathMap
equals to {a: "a", b: "b"}
Then the following code compiles fine:
const BASE_PATHS = paths.reduce((map: PathMap, p: Path) => {
map['a'] = 'a'
return map
}, <PathMap>{})
This also works:
const BASE_PATHS = paths.reduce((map: PathMap, p: Path) => {
return { ...map, [p]: p }
}, <PathMap>{})
But the following code does not compile:
const BASE_PATHS = paths.reduce((map: PathMap, p: Path) => {
map[p] = p
return map
}, <PathMap>{})
Which gave me this error at map[p] = p
:
TS2322: Type 'string' is not assignable to type 'never'. Type 'string' is not assignable to type 'never'.
Why is this the case?
Thanks for helping!
Upvotes: 4
Views: 562
Reputation: 33091
I believe this is because objects are contravariant in their key types.
For more information see this answer.
Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.
const paths = ['a', 'b'] as const
type Path = typeof paths[number]
type PathMap = {
[path in Path]: path
}
type a = 'a'
type b = 'b'
type c = a & b // never
{
const BASE_PATHS = paths.reduce((map: PathMap, p: Path) => {
let x = map[p]
map[p] = p // same here
return map
}, {} as PathMap)
Intersection of a
and b
produces never
.
If you remove as const
from paths
it will compile, because string & string = string
Btw, since you are using functional approach try to avoid object mutations.
Here, in my blog, you can find more information about mutations in TS
Credits to @aleksxor
Here you can find official explanation
Upvotes: 4
Reputation: 3140
This is because map[p]
will give you either a
or b
and type of a
or b
is definitely a string
. For type Path
, as it is a union type, type string is never
for Path
because Path
must satisfy a
or b
. You can do something like this
const BASE_PATHS = paths.reduce((map: PathMap, p: Path) => {
// enforce the compiler to treat map[p] as one of Path
(map[p] as Path) = p;
return map;
}, {} as PathMap);
Upvotes: 0