Home > Back-end >  TypeScript compiler is not guessing variable type in if statement
TypeScript compiler is not guessing variable type in if statement

Time:01-20

I am currently working on this question called 'enter image description here

What I don't understand is, I clearly covered every case and TS should be guessing that root2 can't be null at the point where it's yelling 'root2 is possibly null'.

Am I missing any case or it's just me not understanding how the compiler behave..?

It would be much appreciated if you could point me in the right direction.

My solution

const mergeTrees = (
  root1: TreeNode | null,
  root2: TreeNode | null
): TreeNode | null => {
  if (!root1 && !root2) return null;
  if (root1 && root2)
    return new TreeNode(
      root1.val   root2.val,
      mergeTrees(root1.left, root2.left),
      mergeTrees(root1.right, root2.right)
    );
  if (root1 && !root2)
    return new TreeNode(
      root1.val,
      mergeTrees(root1.left, null),
      mergeTrees(root1.right, null)
    );
  return new TreeNode(
    root2.val,
    mergeTrees(null, root2.left),
    mergeTrees(null, root2.right)
  );
}

CodePudding user response:

There are limits to the flow analysis that TypeScript's compiler does. Although you (and we) know from the code logic that root2 can't be null there at the end, TypeScript doesn't.

You have at least three options:

  1. Use a non-nullish type assertion on root2
  2. Write an explicit test that throws an assertion error if root2 is null, inline in that function
  3. Use a type assertion function

Use a non-nullish type assertion

The ! operator at the end of a value expression is an assertion saying "I know this value will never be null or undefined ('nullish')." So you could use it in all three places:

return new TreeNode(
    root2!.val,
    mergeTrees(null, root2!.left),
    mergeTrees(null, root2!.right)
);

or in just one place, by assigning to a new identifier:

const r = root2!;
return new TreeNode(
    r.val,
    mergeTrees(null, r.left),
    mergeTrees(null, r.right)
);

Like all type assertions, if you get your logic wrong, TypeScript won't be able to help you because it will believe what you tell it. So if the logic is wrong, you'll get an unexpected error accessing root2 when it's nullish.

Explicit inline test of root2

You could also add a runtime check and throw an assertion error:

if (!root2) {
    throw new Error("Assertion failure, 'root2' can't be nullish here");
}
return new TreeNode(
    root2.val,
    mergeTrees(null, root2.left),
    mergeTrees(null, root2.right)
);

That has the advantage of both testing your assertion and providing a clear error about it, and reassuring TypeScript's compiler.

A type assertion function

This is pretty much the same as above, but in a reusable package. You can write a reusable type assertion function that tests for nullish values and throws the error:

// In some utilities module...
function assertIsNotNullish<T>(value: T | null | undefined): asserts value is T {
    if (value === null || value === undefined) {
        throw new Error("Value shouldn't be nullish here");
    }
}

// In your mergeTrees function:
assertIsNotNullish(root2);
return new TreeNode(
    root2.val,
    mergeTrees(null, root2.left),
    mergeTrees(null, root2.right)
);

This has the same advantage that it both tests the assertion and also reassures the compiler.

CodePudding user response:

You could shorten your code with nullish coalescing and ternaries, which should also allow the compiler to resolve types

const mergeTrees = (
  root1: TreeNode | null,
  root2: TreeNode | null
): TreeNode | null => {
  if (!root1 && !root2) return null;
  else {
    return new TreeNode(
      (root1.val ?? 0)   (root2.val ?? 0),
      mergeTrees(root1 ? root1.left : null, root2 ? root2.left : null),
      mergeTrees(root1 ? root1.right : null, root2 ? root2.right : null)
    );
  }
}

CodePudding user response:

I'm not familiar with typescript, but I can help in the right direction: In C# there's a case where you can have nullable variables and non-nullable variables. and usually before you're able to use these nullable variables, you have to make them non-nullable instead.

As example in C#:

DateTime? date = null //nullable variable 
int seconds = 0;

date = DateTime.Now();

if (date != null)
{
    //it's ensured that date is not null, but this still won't compile, 
    //because it's still a nullable variable
    seconds = date.Second;

    //instead, it should be converted to a non-nullable variable. In C#, this goes with '.Value'.
    seconds = date.Value.Second;
}
  • Related