简体   繁体   中英

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

I want to dynamically map the keys and values from list into obj . However, TS gives me an error message:

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

where I have no idea about what's wrong. Below is the code snippet:

interface T {
    // uncomment the next line makes the error go away
    // [k: string]: any
    a: string;
    b?: string;
    c?: number;
}

const obj: T = {
    a: 'something',
};

const list: Array<{
    foo: keyof T;
    bar: string | number;
}> = [
    { foo: 'b', bar: 'str' },
    { foo: 'c', bar: 1 },
];

list.forEach(item => {
    const { foo, bar } = item;

    // The error message comes from the next line
    obj[foo] = bar;
});

I notice that if I include the typing [k: string]: any into the interface T , the error message goes away.

However, I am reluctant to do that because I can then add other key/value pairs into the obj , such as obj.d = 'error' without TS warning me.

Also, I am curious about why TS would gives me this error message and what is the type never thing is all about.

For the tsconfig.json , I am using the default values by running tsc --init with version 3.5.1

Thank you.

TypeScript 3.5 closed a loophole whereby index-access writes on unions of keys were not being properly checked . If I have an object obj of type T , and a key foo of generic type keyof T , then although you can safely read a property of type T[keyof T] from obj[foo] ,like const baz: T[keyof T] = obj[foo] , it might not be safe to write such a property, like const bar: T[keyof T] = ...; obj[foo] = bar; const bar: T[keyof T] = ...; obj[foo] = bar; In your code, foo might be "a" and bar might be 1 , and that would be unsafe to write.

The way the loophole got closed: if I read a value from a union of keys, it becomes a union of the property types, as before. but if I write a value to a union of keys, it becomes an intersection of property types. So say I have an object o of type {a: string | number, b: number | boolean} {a: string | number, b: number | boolean} {a: string | number, b: number | boolean} and I want to write something to o[Math.random()<0.5 ? "a" : "b"] o[Math.random()<0.5 ? "a" : "b"] ... what is safe to write? Only something which works for both oa and ob ... that is, (string | number) & (number | boolean) , which (when you fiddle with distributing unions across intersections and reducing) becomes just number . You can only safely write a number .

In your case, though, the intersection is string & string & number . And unfortunately, there's no value which is both a string and a number ... so that gets reduced to never . Oops.


To fix this case I'd probably refactor this code so that list is more narrowly typed, only allowing "matching" foo and bar properties, and then pass the forEach method a generic callback where foo and bar are annotated so that obj[foo] and bar are seen as identical types:

type KV = { [K in keyof T]-?: { foo: K, bar: NonNullable<T[K]> } }[keyof T]
/* type KV = {
    foo: "a";
    bar: string;
} | {
    foo: "b";
    bar: string;
} | {
    foo: "c";
    bar: number;
} */

const list: Array<KV> = [
    { foo: 'b', bar: 'str' },
    { foo: 'c', bar: 1 },
];

list.forEach(<K extends keyof T>(item: { foo: K, bar: NonNullable<T[K]> }) => {
    const { foo, bar } = item;
    obj[foo] = bar; // okay
});

The KV type does a little type juggling with mapped and lookup types to produce a union of all acceptable foo / bar pairs, which you can verify by using IntelliSense on the KV definition.

And the forEach() callback acts on a value of type item: { foo: K, bar: NonNullable<T[K]> } for generic K extends keyof T . So obj[foo] will be seen as type T[K] , and you'll assign a NonNullable<T[K]> to it, which is acceptable according to a rule that isn't quite sound but convenient enough to be allowed .

Does that make sense? Hope that helps; good luck!

Link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM