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.