basickarl
basickarl

Reputation: 40561

TypeScript not following code logic: Cannot invoke an object which is possibly 'undefined'

The following code results in the error:

example/not-following.ts:15:1 - error TS2722: Cannot invoke an object which is possibly 'undefined'.

15 run(true).maybe();
   ~~~~~~~~~~~~~~~

Code:

interface Something {
    maybe?: () => void;
}

function run(isTrue: boolean): Something {
    const object: Something = {};
    if (isTrue) {
        object.maybe = (): void => {
            console.log('maybe');
        };
    }
    return object;
}

run(true).maybe();

Since this is deterministic code, why doesn't TypeScript follow it?

Upvotes: 2

Views: 568

Answers (3)

basickarl
basickarl

Reputation: 40561

The solution was something called generics: https://www.typescriptlang.org/docs/handbook/generics.html

interface Something {
    maybe?: () => void;
}

function run<T extends boolean>(isTrue: T):
    T extends true
        ? Something & Required<Pick<Something, "maybe">>
        : Something {
    const object: Something = {};
    if (isTrue) {
        object.maybe = () => {
            console.log('maybe');
        };
    }

    // @ts-ignore
    return object;
}

run(true).maybe();

// @ts-expect-error
run(false).maybe();

Upvotes: 3

Bart Hofland
Bart Hofland

Reputation: 3905

I guess TypeScript is pretty smart, but probably not so smart that it completely validates these kind of runtime execution paths.

As an alternative, you could make use of closures, but I'm not sure if such a solution is appropriate for you.

interface Something {
    maybe: () => void;
}

function run(isTrue: boolean): Something {
  const object: Something = {
    maybe: () => {
      if (isTrue) {
        console.log('maybe');
      }
    }
  };
  return object;
}

run(true).maybe();

Edit:

Due to the downvote I received (which I do not want to object to), I want to additionally remark that for avoiding the error in TypeScript, you need to either:

  • make the maybe function mandatory or
  • check if the function is set prior to each invocation.

I personally dislike optional fields and I personally do not want to tediously wrap each call in a checking if-block. So I personally still prefer my current solution.

Upvotes: 0

user13258211
user13258211

Reputation:

Cannot invoke an object which is possibly 'undefined'.

You get this error because maybe is optional, typescript has no way of knowing whether maybe is defined or not.

To fix it you'll have to check if the property exists first:

   interface Something {
        maybe?: () => void;
    }

    function run(isTrue: boolean): Something {
        const object: Something = { };
        if (isTrue) {
            object.maybe = (): void => {
                console.log('maybe');
            };
        }
        return object;
    }

    const oSomething: Something = run(true);
    if (oSomething.maybe) {
        oSomething.maybe();
    }

Upvotes: 0

Related Questions