Issue
Why value
is of type unknown
again after assigning a string
to it?
Example in TypeScript Playground
function example(): boolean {
let value: unknown = 'something' // maybe here is some function call, which returns unknown
if (!(typeof value === 'string') && !(typeof value === 'number')) {
return false
}
value // string | number
value = 'test'
value // unknown
return true
}
Solution
You've just run into the issue in microsoft/TypeScript#27706, which is currently marked as a feature request and not a bug. But it's definitely confusing.
TypeScript performs narrowing based on some heuristic rules. Some of these rules are able to narrow values of the unknown
type, while others only work properly on values of union types. (And unknown
is not a union).
For example, typeof
type guards work on unknown
, as mentioned in microsoft/TypeScript#24439, the pull request introducing unknown
. And they work on unions. So these look the same:
let value: string | number | boolean =
["abc", 123, true][Math.floor(Math.random() * 3)];
if (!(typeof value === 'string') && !(typeof value === 'number')) {
return false
}
value // string | number
and
let value: unknown =
["abc", 123, true][Math.floor(Math.random() * 3)];
if (!(typeof value === 'string') && !(typeof value === 'number')) {
return false
}
value // string | number
But assignment narrowing doesn't narrow unknown
. Assignments first widen the variable back to its original type (so any prior narrowings will be reset) and then filters the variable to just those union members to which the new value is assignable. It's like you're using the Extract<T, U>
utility type.
If you have a variable of type string | number | boolean
and you assign a string
to it, then the variable will be narrowed to Extract<string | number | boolean, string>
which is string
:
let value: string | number | boolean = "test";
value.toUpperCase(); // okay
But unknown
is not a union, if you assign a string
to it, the resulting type will be Extract<unknown, string>
which is just unknown
, and the compiler does not understand that it's a string
:
let value: unknown = 'something'
value.toUpperCase(); // error!
And this is responsible for the behavior you're seeing. You can use typeof
to narrow unknown
to string | number
, but when you do an assignment it gets widened back to unknown
. It is suggested at microsoft/TypeScript#27706 that assignments to unknown
variables should narrow. If you agree you might want to give that issue a 👍, but it's a fairly longstanding open issue so I wouldn't count on anything changing here soon. (The recent improvements to narrowing don't seem to affect this.)
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.