I am trying to write some code that does something to a set of string property names from an arbitrary interface/class (supplied as a generic parameter).
My first attempt looked like this:
function doSomething<T extends object>(props: (keyof T)[]) {
for (let prop of props) {
const x: string = prop;
}
}
This causes a compiler error in the line starting with const
:
TS2322: Type 'string | number | symbol' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
Now, the first line seems clear - keyof T
may be a string, but may also be a number, or a symbol.
The second line is, however, unexpected to me - why would the compiler assume that prop
is a number
and nothing else? Or is it just an example for a possible, conflicting case?
In any case, I thought I could simply restrict my loop body to treat only those property names that are strings:
function doSomething<T extends object>(props: (keyof T)[]) {
for (let prop of props) {
if (typeof prop === 'string') {
const x: string = prop;
}
}
}
But now, my typechecking condition typeof prop === 'string'
is highlighted, with a message saying:
Invalid 'typeof' check: 'prop' cannot have type 'string'
Why wouldn't prop
ever be a string? When I call my function with a sample class, I sure can pass an array of strings (as long as those strings match property names of the sample class, obviously).
Or are those strings somehow implicitly cast to symbol
? As a matter of fact, if I change the typecheck condition to typeof prop === 'symbol'
, there is no warning anymore that this would never match. But then, in the first error message, keyof T
was explicitly said to be either string or number or symbol, so in what case would it be a string at all?
My code is now in a state to be run in a Jest unit test, and - surprise, surprise - turns out prop
actually is a string. The suggestion that it cannot have type string is effectively wrong. (Unfortunately, I cannot turn it off because WebStorm does not give away what part in the toolchain generated the message.)
This might be related to bug WEB-49787.
The second line is, however, unexpected to me - why would the compiler assume that prop is a number and nothing else?
It's not assuming that, it's just walking through all the possibilities of the union. Typescript considers the case where it's a string
and sees no problem. Then it considers the case where it's a number
, and that one results in an error, so it shows that. symbol
would also cause an error, but for brevity, typescript just shows the first problem it finds.
Put another way: line 2 is explaining how typescript deduced the error on line 1
But now, my typechecking condition typeof prop === 'string' is highlighted, with a message saying:
Invalid 'typeof' check: 'prop' cannot have type 'string'
Why wouldn't prop ever be a string?
That's not a typescript error. For example, you won't see it in this typescript playground. It's probably a lint error. A google search led me to this question which may be related.