Search code examples
javascripttypescripttsc

Typescript - Inline undefined check not working (Object is possibly 'undefined'.ts(2532))


I get the following typescript error:

const myFunction = (
  param1: string | undefined,
  param2: { someProp: string } | undefined
) => {
  if (!param1 && !param2) {
    return;
  }

  // Here I get the following Typescript error:
  //  (parameter) param2: { someProp: string } | undefined
  //  Object is possibly 'undefined'.ts(2532)
  const param3 = param1 ? param1 : param2.someProp;
};

the following works:

const param4 = param1 ? param1 : param2 ? param2.someProp : null;

but seems redundant to check for null or undefined twice.

I have to mention that the strictNullChecks option is set to true in the compilerOptions and want to keep it like this.

Any idea why I get this error?

Here's a CodeSandbox with the code: https://codesandbox.io/s/jn2mp01q2v


Solution

  • The sad truth about the TypeScript compiler is that it just isn't as smart as a human being (as of TypeScript 3.4 anyway) and so its control flow analysis is but a pale shadow of the sort of analysis you can perform yourself. Of course, it is very consistent about its analysis, whereas mine tends to get worse when I haven't eaten recently.

    If you perform a check on a variable of a union type which completely eliminates one or more of the constituents of that union, the compiler will happily narrow the type of the variable for you:

    param1.charAt(0); // error, possibly undefined
    if (!param1) return;
    param1.charAt(0); // okay now
    

    But one thing the compiler just doesn't do is keep track of correlated variables outside of discriminated unions. What you've eliminated by checking

    if (!param1 && !param2) return;
    

    is the possibility that both param1 and param2 can be undefined at the same time. You've taken two previously independent variables, and made them correlated to each other. Which the compiler doesn't keep track of. Since param1 and param2 can both still be undefined (just not at the same time), the compiler treats them as still independent and you are left with your problem.

    You can do what the other answer suggested and use a type assertion, which is meant for occasions where you're smarter than the compiler and don't want to try to lead the compiler through the task of understanding what you already know:

    const param3 = param1 ? param1 : param2!.someProp; // I'm smarter than the compiler 🤓
    

    Note that I've used the non-null assertion operator ! which is probably the most concise way possible to assert your superiority over the machine. Also beware that such assertions are not safe, since you might be mistaken about your superiority. So only do something like this after you double and triple check that there is no way for param2 to be undefined.


    Another thing you can do is restructure your code so as to lead the compiler through an analysis that it can do.

    const param3 = param1 || (param2 ? param2.someProp : undefined);
    if (!param3) return;
    param3.charAt(0); // string
    

    This will work for you and you are only checking each parameter once. The variable param3 is of type string | undefined, and is only undefined if both param1 and param2 are falsy. Each step in that code completely eliminates union constituents for each variable, without leaving any correlated types lying around to confuse the compiler.

    Either solution should work for you. Hope that helps; good luck!