TL;DR Difference between link1 (Working) vs link2 (not working)
TypeGuard
function hasAllProperties<T>(obj: any, props: (keyof T)[]): obj is T {
return props.every((prop) => obj.hasOwnProperty(prop))
}
Assume a variable msg
that can be of 2 types - kind1
or kind2
.
interface kind1 {
propertyA: number
}
interface kind2 {
propertyB: number
}
let msg: any = {
propertyA: 1
}
In link1 the type guard is accessed directly and is working expected.
In link2 a boolean variable isKind1
is initially set to false and only set to true if the type guard check is passed.
let isKind1 = false
let isKind2 = false
// Check msg properties
if (
hasAllProperties<kind1>(msg, [
"propertyA",
])
) {
console.log("Kind1")
isKind1 = true
} else if (
hasAllProperties<kind2>(msg, [
"propertyB",
])
) {
console.log("Kind2")
isKind2=true
} else {
throw `invalid ❌`
}
console.log("Check passed ✅")
Thus in link2 , I cannot think of a condition where if isKind1 === true
but msg
is not of type kind1
if (isKind1) {
// Why is msg not of only type kind1
console.log(msg.propertyA)
}
CodePudding user response:
This is one of many situations where your code is correct, but Typescript just doesn't have a rule that would allow the compiler to prove it. The compiler cannot generally prove every provable property of your code.
To explain this case more specifically, your first example works in Typescript 4.4 because of a new feature, referred to as Control Flow Analysis of Aliased Conditions and Discriminants in the change notes. Essentially, the compiler has a specific rule which applies in your first example because in every execution path, the type guard's result is assigned directly to the variable isKind1
; but the rule does not apply in your second example, because the value assigned to isKind1
is the literal value true
, which is not a type guard.
CodePudding user response:
In the given context , if isKind1 === true
then msg
will not always be of kind1
as isKind1
is a let
can be set to true outside of the given if
check.
Example
const msg = {
"foo": "bar"
}
let isKind1 = false
if (
hasAllProperties<kind1>(msg, [
"propertyA",
])
) {
console.log("Kind1")
isKind1 = true
}
// Assume if check didnt pass, thus isKind1 = false and msg is not of kind1
// Changing isKind1 externally
isKind1 = true
if (isKind1) {
// Now even if isKind1 === true but msg will not be of type kind1
console.log(msg.propertyA)
}