Typescript Type Guards and fat arrow function
Asked Answered
C

2

6

Shouldn't this compile correctly? I get an error "Property 'hello' does not exist on type 'object'." in the highlighted line.

I can access g.hello outside the fat arrow function without problems.

class Test {
    constructor() {
    }
    hello() : string {
        return "Hello";
    }
}

let g : object;

if (g instanceof Test) {
    () => {
        g.hello();    ////// ERROR HERE /////
    };
}
Calva answered 5/12, 2018 at 22:1 Comment(0)
L
4

The narrowing that a type-guard does on a variable (or anything else) will not cross fucntion boundaries. This is a design limitation.

A way to work around this issue is to assign g to a new variable, which will have it's type inferred based on the narrowing. Accessing the new variable in the arrow function will work as expected:

class Test {
    constructor() {
    }
    hello() : string {
        return "Hello";
    }
}

let g : object;

if (g instanceof Test) {
    const gTest = g;
    () => {
        gTest.hello();
    };
}

Another way to work around this issue if g does not change, is to declare g with const. This will let the compiler preserve the narrowing:

let g : object;

if (g instanceof Test) {
    const gTest = g;
    () => {
        gTest.hello();
    };
}

Playground Link

Loutitia answered 5/12, 2018 at 22:8 Comment(6)
Ah the 'ol that = this +1Damara
@ChrisW. old solution with new use cases ;)Loutitia
I think this answer is either partially incorrect or outdated. In TypeScript 3+, type guards can cross function boundaries for const-declared variables. Changing line 9 in the original question to const g: object = new Test(); allows it to compile without errors. (It makes sense that let narrowing would not cross function boundaries because the variable could be reassigned back to an object before the function is called.)Poltroonery
@Poltroonery Nope, the issue is still there, specifically it has to do with the arrow function () => typescriptlang.org/play?#code/…Loutitia
You're using let, I said const: typescriptlang.org/play?#code/… That looks like narrowing crossing function boundaries to me.Poltroonery
@Poltroonery You are right, this seems to have changed, or I missed it at the time. I will add this workaroudn as wellLoutitia
C
0

let + arrow function

class Test {
    constructor() {
    }
    hello() : string {
        return "Hello";
    }
}

declare let g : object;

if (g instanceof Test) {
    () => {
        g.hello(); // error
    };
}

g is a Test when the function is defined, but g may not be Test when the function runs.

const + arrow function

class Test {
    constructor() {
    }
    hello() : string {
        return "Hello";
    }
}

declare const g : object;

if (g instanceof Test) {
    () => {
        g.hello(); // ok
    };
}

g is immutable, g is always a Test after the function is defined.

const + regular function

class Test {
    constructor() {
    }
    hello() : string {
        return "Hello";
    }
}

declare const g : object;

if (g instanceof Test) {
    function f() {
        g.hello(); // error
    };
}

Within underlying implementation, regular function f is defined by ES5 var, instead of ES6 let. variable hoisting hoists declaration of f, when g may not be a Test.

Confound answered 12/7, 2021 at 14:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.