Home > Software engineering >  Typescript Type Guard wont affect the variable type if set indirectly
Typescript Type Guard wont affect the variable type if set indirectly

Time:11-13

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)
}
  • Related