3

in typescript 2.6 I want to write a function that does a null check. When I enable strict null-checks, typescript 2.6 complains about the following code. (Note that when using the null check directly works)

edited: corrected notNullOrUndefined since it didn't check for foo

interface A {
  foo: string | undefined;
}
const notNullOrUndefined = (a: A): boolean => {
  return a.foo != null;
}
const len = (a: A): number => {
  //if (a.foo != null) {
  if (notNullOrUndefined(a)){
    return a.foo.length;
  }
  return 0;
} 

here is the example to play with: example

What's the typescript way to solve this?

oliver
  • 9,235
  • 4
  • 34
  • 39
  • seems like you should check if both `a` and `a.foo` are null or undefined. Something like `const len = (a: A): number => a && a.foo && a.foo.length || 0; ` – Slai Dec 08 '17 at 13:35
  • What if you use (a && a.foo != null) – nipuna-g Dec 08 '17 at 14:08

4 Answers4

10

EDIT: updated to reflect fixing a typo in question: The question is a little confusing, since your notNullOrUndefined() doesn't check a.foo at all, so it's not surprising that those would be different.

Note that with --strictNullChecks on, you have defined len() so that the a parameter is an A, and therefore cannot be null or undefined. So you don't have to check a itself inside the len() function implementation; instead you need to make sure that anything you pass to len() is a valid A. So notNullOrUndefined() is kind of a bad name, since you're checking the foo value of the parameter, not the parameter itself. Feel free to change it to something like fooPropertyIsNotNull(); I will leave it for now.

The main issue here is that TypeScript recognizes that if (a.foo != null) { ... } is a type guard, and narrows a.foo to string inside the { ... } clause. But type guards do not propagate out of functions automatically, so TypeScript doesn't understand that notNullOrUndefined() itself acts as a type guard.

Luckily, this issue is common enough that TypeScript provides user-defined type guards: if you have a function that returns a boolean which narrows the type of one of its parameters, you can change the boolean return type to a type predicate, using the x is T syntax. Here it is for notNullOrUndefined():

const notNullOrUndefined = (a: A): a is { foo: string } => {
  return a.foo != null;
}

So the function signature says: if you pass in an A, it will return a boolean value. If it returns true, then the passed-in parameter is narrowed to { foo: string }. Now you will get no errors, as you wanted:

interface A {
  foo: string | undefined;
}
const notNullOrUndefined = (a: A): a is { foo: string } => {
  return a.foo != null; // checking a.foo
}
const len = (a: A): number => {
  if (notNullOrUndefined(a)){
    return a.foo.length; // okay
  }
  return 0;
} 

Hope that helps, good luck!

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • thanks...you exactly answered my question. I was playing around so much so I pasted a broken example...corrected that as well. While your solution solves my question, do you have any recommendation how this might scale when the object you check is nested? – oliver Dec 08 '17 at 14:30
  • The obvious answer is to check only the nested property and not the whole object (`isDefined(a.foo.bar)` instead of `isFooBarDefined(a)`). Otherwise you might want to have two interfaces, one with all the properties non-null, and the other with all properties nullable, like in [this answer](https://stackoverflow.com/a/45380315/2887218). There's no *great* solution right now without type guard propagation or conditional mapped types. – jcalz Dec 08 '17 at 14:37
0

This code will check for null.

"use strict";

function isNull(something: any) {
    return something === null;
}

function checkForNull() {
    console.log(isNull(''));
    console.log(isNull(null));
    console.log(isNull(undefined));
}

checkForNull();

Output:

false
true
false
Chris Sharp
  • 2,005
  • 1
  • 18
  • 32
  • 1
    I know this checks for null...the question is how can I call a function that checks for null as a prerequisite and then access the potentially null fields so that typescript 2.6 does not complain – oliver Dec 08 '17 at 13:50
  • I guess I'm just not getting the question. I'm going to edit my answer, at least temporarily, to see if you can tell me what part of your question I'm missing. – Chris Sharp Dec 08 '17 at 14:05
  • The OP is asking about compile-time errors of TypeScript, not run-time behavior of the generated JavaScript. – jcalz Dec 08 '17 at 14:18
  • I'm still not understanding. Sorry. I get no compile errors at all with this code. I read your answer @jcalz and all I can see is that you can't set something to null during runtime so can't write code that tries to do it. I'm happy to remove the mountain of useless code I posted but I still would appreciate understanding what the question actually was. If you define a type you can't later assign it to null. Is there something else? – Chris Sharp Dec 08 '17 at 14:24
0

You need to check both a and a.foo. This code, for example, will work even when strict null checking is not enabled:

const len = (a: A): number => {
  if (a != null && a.foo != null){
    return a.foo.length;
  }
  return 0;
} 

If you do have strict null checking enabled you already know that a is not undefined and then you can just use your commented out line:

const len = (a: A): number => {
  if (a.foo != null){
    return a.foo.length;
  }
  return 0;
} 

If you want to perform the test in a separate function then you should make that function a type assertion. You could for example remove the undefined from the type of foo but use Partial<A> anywhere that you don't know whether or not foo is present:

interface A {
  foo: string;
}
const notNullOrUndefined = (a: Partial<A>): a is A => {
  return a != null && a.foo != null;
}
const len = (a: Partial<A>): number => {
  if (notNullOrUndefined(a)){
    return a.foo.length;
  }
  return 0;
} 
Duncan
  • 92,073
  • 11
  • 122
  • 156
0

This is why I hate strictNullChecks at the moment. It does not support typechecking from within a function to 'bubble up' to where the check is called at.

if (a && a.foo) {
   return a.foo.length;
}

works as expected. For me, wrapping the following in a function...

const hasFoo(a: A) => !!a && 'foo' in a && typeof a.foo === 'string';

...

if (hasFoo(a)) {
   return a.foo.length;
}

...

...should work as the initial check. However, this is not the case.

A solution (and afaik the only one) is to use the new !. postfix expression. This claims that the value is indeed declared as the expected type.

if (hasFoo(a))
{
   return a.foo!.length;
}
Shane
  • 3,049
  • 1
  • 17
  • 18
  • Are you aware of [user-defined type guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards)? – jcalz Dec 08 '17 at 14:16