I tried to implement the following pattern
type ClassProps = {
a: number,
b: string,
}
class Example implements ClassProps {
public a: number
public b: string
constructor(props: ClassProps) {
let key: keyof ClassProps
for(key in props) {
this[key] = props[key]
}
}
}
It compiles and runs just fine as far as I can tell.
But VSCode (Code - OSS 1.73.0) raises an error at the assignment because it infers the type of this[key] to be never.
Type 'string | number' is not assignable to type 'never'.
I would like to understand why this happens and how to fix it.
The linter is also not clever enough to figure out that a and b here are in fact definitely assigned in the constructor and complains about it - but that is easily remedied with the ! operator.
But for the other error, the only way I found so far to fix it was implicitly or explicitly typing the class properties to any - losing all typing support.
This is a limitation or missing feature of TypeScript. The specific problem with copying a property between two objects of the same type is the subject of microsoft/TypeScript#32693, but the general problem is the lack of support for "correlated unions" as described in microsoft/TypeScript#30581. If you look at the assignment
The compiler essentially analyzes only the types of these values, and not their identities. On the right hand side, it sees you indexing into a
ClassPropswith a union-typed key of type"a" | "b"for reading (also known as the "source"), and thus you have a value of typestring | number. On the left hand side, it sees you indexing into anExamplewith a union-typed key of type"a" | "b"for writing (also known as the "target"). For that to be considered safe, according to microsoft/TypeScript#30769, it requires the target to be the intersection of the relevant property types: so it must be of typestring & number(which is immediately reduced to the impossiblenevertype, since there are no values which are bothstringandnumberat the same time). And sincestring | numberis not assginable tostring & number, the compiler issues an error.That error would be completely reasonable if you were faced with the following assignment instead:
After all, you can't write a random property from
propsto a random property inthis. What if you are reading astringand writing to anumber?And, sadly, the compiler does not distinguish between that case and
this[key] = props[key]. It doesn't realize that the types on either side of the assignment are correlated and cannot possibly be mismatched.Until and unless anything changes here, we need to use workarounds, or to refactor. The recommended refactorings usually involve generics. One exception to the union-source-to-intersection-target rule is if the source and intersection are both identical generic types. So instead of
keybeing of typekeyof ClassProps, you can make it of typeK extends keyof ClassProps.For example, if you refactor to a generic function like:
That compiles because both sides of the equation are type
T[K]. And then you can call this function:If you don't want to spend time refactoring, you could just use a type assertion and move on with your life:
It's up to you and what your use case is.
Playground link to code