Reputation: 25820
What is the correct way in TypeScript to copy properties from source to destination, using the same key-type?
interface ITest {
first?(val: number, msg: string): void;
second?(): void
}
function copy(e: keyof ITest, source: ITest, dest: ITest) {
// TS2322:
// Type '(val: number, msg: string) => void' is not assignable to type '() => void'.
dest[e] = source[e];
}
I can't understand why TypeScript fails to see that source and destination are the same type and that we use the same key-type. But how else are we supposed to do it then?
I'm using TypeScript 3.6.4, with tsconfig.json
as below:
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"strict": true
}
Upvotes: 3
Views: 115
Reputation: 2860
Look at the signatures of your two properties:
(val: number, msg: string) => void
and
() => void
The error you are getting looks like the compiler is saying you are assigning across keys. The problem is they're both functions but with different signatures. Your type for e is a union of those two signatures:
e: "first" | "second"
If Typescript supported reified types this would be easy but for now this example is the best I can do. Essentially pulling Typescripts type info kicking and screaming into the Runtime environment.
type ITest = IFirst & ISecond
interface IFirst extends IType {
type: 'first'
first?(val: number, msg: string): void;
}
interface ISecond extends IType {
type: 'second'
second?(): void
}
interface IType {
type: 'first' | 'second'
}
function copy(e: IType, source: ITest, dest: ITest) {
switch (e.type) {
case 'first':
const first = e as IFirst
dest[first.type] = source[first.type];
break;
case 'second':
const second = e as ISecond
dest[second.type] = source[second.type];
break;
}
}
Upvotes: 1
Reputation: 327624
TS3.5 introduced a change to enforce more type safety when writing to properties whose key is a union. In terms of just the types, the compiler can't tell the difference between what you're trying to do, and the definitely unsafe code here:
function badCopy(k1: keyof ITest, k2: keyof ITest, o: ITest) {
o[k2] = o[k1]; // error!
}
Here, the key k1
and k2
are exactly the same type: keyof ITest
. But there's a good chance you're trying to blindly copy a property from one key to an incompatible property at another key. Because the key values in k1
and k2
might not be the same, despite being the same type. That's unsafe, so you get an error.
Unfortunately that change also causes what you're doing to get deemed as "possibly unsafe" even though you know for sure that e
is the same exact value in both the read and the write. This is currently considered a design limitation in TypeScript, although it's possible that they might consider special-casing identical key identifiers. If you care about that, you might want to go to the related GitHub issue (it looks like microsoft/TypeScript#32693 is still listed as "in discussion") and give it a 👍 or describe your use case if it's more compelling than what's listed there.
That being said there are workarounds until and unless that gets resolved:
The compiler still allows the loophole where an identical type is assignable to itself, as long as the compiler can't see that you are using a union-type as your key type. Often you can make your functions more generic, like this:
function copy<K extends keyof ITest>(e: K, source: ITest, dest: ITest) {
dest[e] = source[e]; // no error now
}
Or, if you don't want to do that, just accept that you know better than the compiler here and use a type assertion to tell it not to worry:
function copyAssert(e: keyof ITest, source: ITest, dest: ITest) {
dest[e] = source[e] as any; // one way
dest[e] = source[e] as ITest["first"] & ITest["second"]; // more explicit
}
Hopefully one of those works for you. Good luck!
Upvotes: 2
Reputation: 7344
I can't understand why TypeScript fails to see that source and destination are the same type and that we use the same key-type.
I don't truly know if this case can't be solved by TypeScript, but what I know is that TypeScript's compiler (or any other language under certain constraints) can't check every possible truth (in this case, that those elements are equal). This seems to be your case.
But how else are we supposed to do it then?
Sometimes, humans need to give hints to the compiler. In this case, I'd use any
to stop the compiler to report errors.
Upvotes: 0