Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript equivalent to C#'s NotNullWhen

When using non-nullable reference types in C# you can annotate an input parameter to show that it isn't null, when the method returns false:

// From string
bool IsNullOrEmpty([NotNullWhen(false)] string? value)

// User code
string? userInput = GetUserInput();
if (!string.IsNullOrEmpty(userInput))
{
    int messageLength = userInput.Length; // no null check needed.
}
// null check needed on userInput here.

Say I have something similar in Typescript:

// Helper method
isNullOrEmpty(s: string | undefined | null): boolean

// User code
const userInput = getUserInput();
if (!isNullOrEmpty(userInput))
{
    const messageLength = userInput.length; // Typescript complains here
}                         ^^^^^^^^^^^^^^^^

Is there a way to define the isNullOrEmpty to help the type system in Typescript figuring out that the input parameter isn't null or undefined, when the method returns true?

like image 781
Mikkel R. Lund Avatar asked Nov 18 '25 12:11

Mikkel R. Lund


1 Answers

Typescript refers to code that narrows a type (as in, determines it is a particular subset of its possible type) as “type guards,” for example, typeof can be used as a type guard:

const userInput = getUserInput();
if (typeof userInput === 'string')
{
    const messageLength = userInput.length; // Typescript has no complaints
}

This can work positive or negative:

const userInput = getUserInput();
if (typeof userInput !== 'object' && typeof userInput !== 'undefined')
{
    const messageLength = userInput.length; // Typescript has no complaints
}

And of course there are a bunch of ways to narrow your types, such as checking for falsy values with if (userInput) (though that will reject an empty string, which may be undesirable—you could use if (userInput || userInput === '') if you wanted), or if (userInput !== null && userInput !== undefined).

And yes, there are also “user-defined type guards” that allow you define when these things are true. The equivalent here would be:

isNullOrEmpty(s: string | undefined | null): s is undefined | null {
  return !s && s !== ''; // or however you want to define it
}

const userInput = getUserInput();
if (!isNullOrEmpty(userInput))
{
    const messageLength = userInput.length; // Typescript has no complaints
}

The s is undefined | null syntax indicates a user-defined type guard, and replaces your boolean return type. The function is still expected to return a boolean value, where true indicates that the variable has the indicated type (i.e. undefined | null here), and false indicates that it does not (which will result in the type being narrowed to string in this case).

Note that Typescript does not check to confirm that the code you have written in the body of the user-defined type guard actually proves the value has the type you say it has. From a type-safety perspective, it’s basically the same as casting—the only thing Typescript will check is that the wider type and the narrow type are sufficiently related to one another that it could be true. For this reason, it may be considered better to use non-user-defined type guards when possible.

like image 155
KRyan Avatar answered Nov 21 '25 03:11

KRyan