I'm trying to understand why Typescript cannot correctly get the type of an object property parameter, that can be undefined, inside an inner function but when that same property is assigned to its own variable in the outer function scope the narrowing happens seamlessly.
I have this snippet to reproduce the problem:
type MyType = {
optional?: string
}
(arr: string[], param: MyType) => {
if (param.optional) {
const includes = arr.includes(param.optional) // <-- Fine
arr.filter(function (item) {
return item.includes(param.optional) // <-- Wrong! Type undefined is not assignable...
})
}
}
Within the if branch, Typescript warns me about the use of param.optional
inside the filter inner function because it could be string | undefined
, whether when it is used inside the arr.includes(param.optional)
there are no warnings at all.
It seems very odd to me because if instead of using param.optional
I assigned that same value to its own variable Typescript is able to narrow its type (string
) perfectly.
type MyType = {
optional?: string
}
(arr: string[], param: MyType) => {
const variable = param.optional
if (variable) {
const includes = arr.includes(variable) // <-- Fine
arr.filter(function (item) {
return item.includes(variable) // <-- Fine
})
}
}
Why is this behaviour happening? What does it have to do with the object itself (because if the parameter is just a primitive optional value string | undefined
this problem does not occur)?
Because it's not safe to do so in the general case.
TypeScript doesn't know what filter
does, so it doesn't know when its callback is called (synchronously now, or asynchronously later) and thus doesn't know whether the object gets changed by other code outside the scope of the function. Changes by code outside the function could invalidate the type guard.
But with your (amusingly-named) variable
constant, TypeScript knows that the value can't change, so the type guard can be applied. It even works if you use let
instead of const
, because TypeScript can see that there aren't any assignments to variable
. (But if you used let
and added variable = undefined;
after the filter
call, you'd get the same error you get with param.optional
, because again TypeScript doesn't know when the callback's code runs relative to that assignment: example.)