martiansnoop
martiansnoop

Reputation: 2326

Typescript strictNullChecks checking across functions

With --strictNullChecks enabled, tsc seems unable to infer that an optional property is not undefined if the check happens in a separate function. (See example, because I'm having trouble wording this clearly).

Given an interface with an optional property like

interface Foo {
    bar?: {
        baz: string;
    };
}

The following code compiles:

//compiles
function doStuff(foo: Foo) {
    if (foo.bar === undefined) {
        throw new Error("foo.bar must be defined");
    }
    foo.bar.baz;
}

And this code does not because tsc thinks foo.bar can be undefined:

//does not compile, foo.bar can be undefined
function doStuff2(foo: Foo) {
    validateFoo(foo);
    foo.bar.baz;
}

function validateFoo(foo: Foo) {
    if (foo.bar === undefined) {
        throw new Error("foo.bar must be defined");
    }
}

Why is this? Is there way to mark a function as a thing that can check for undefined and null? I'd like to avoid foo.bar!.baz if possible. In the example it's easy to inline the if/throw but if Foo has multiple optional properties that need to be checked in multiple functions it gets repetitive.

Upvotes: 0

Views: 281

Answers (1)

Daniel Rosenwasser
Daniel Rosenwasser

Reputation: 23433

Why is this?

You could imagine TypeScript trying to inline the function for this, but to then get an accurate result you need to inline every function that validateFoo calls as well - this can go arbitrarily deep.

There's actually an entire discussion about this here on TypeScript's issue tracker.

Is there way to mark a function as a thing that can check for undefined and null?

Sort of - there are type predicates, which are functions that tell TypeScript what a parameter's type if the function returns true.

function validateFoo(foo: Foo): foo is { bar: { baz: string } } {
    if (foo.bar === undefined) {
        throw new Error("foo.bar must be defined");
    }
    return true;
}

You could use it as follows:

function doStuff(foo: Foo) {
    if (!validateFoo(foo)) {
        throw foo;
    }
    foo.bar.baz;
}

Upvotes: 2

Related Questions