user1852503
user1852503

Reputation: 5607

Specify that a param type is T | null but not null

I wrote a function that can handle being passed a null or undefined value instead of a T, and in that case does nothing and returns some default value.

However, in many cases the compiler is smart enough to know during compile time that the value being passed in is not T or null, but null 100% of the time, and I would like there to be a compiler error in that case.

Example:

const functionThatTakeTheUnionType = <T>(p: T | null){
 return p && typeof p.name === 'string' ? p.name : 'default';
}

let obj = functionReturningTOrNull();
let name:string;
if(obj === null){
 // This line should not compile
 name = functionThatTakeTheUnionType(obj)
} else{
 name = 'different default name';
}

In this case the developer did not want to pass null into the function and depend on its default value but instead use some other value, but the developer mixed up the branches of the if statement. the compiler is smart enough to know that obj is null, but a definite null is just as valid as maybe a null.

Can this be done? I'm using typescript version 2.9.2. can upgrade to 3 if that would help.

ps. I looking for a solution within the type system, not a 'maybe monad' object replacing the 'T | null' typed value.

Upvotes: 2

Views: 120

Answers (2)

artem
artem

Reputation: 51629

You can use overloaded function declaration which, when the compiler definitely knows that the argument is null, is declared to return something inappropriate, like void:

function functionThatTakeTheUnionType(p: null): void;
function functionThatTakeTheUnionType<T extends { name: string }>(p: T | null): string;
function functionThatTakeTheUnionType<T extends { name: string }>(p: T | null): string {
    return p && typeof p.name === 'string' ? p.name : 'default';
}

declare function functionReturningTOrNull(): { name: string } | null;

function a() {
    let obj = functionReturningTOrNull();
    let name: string;
    if (obj === null) {
        // error: Type 'void' is not assignable to type 'string'.
        name = functionThatTakeTheUnionType(obj)
    } else {
        name = 'different default name';
    }

    let obj2 = functionReturningTOrNull();
    let name2 = functionThatTakeTheUnionType(obj2); // string
}

But this only works if functionThatTakeTheUnionType is declared as function, not an object which has functional value.

Also, it gives the error only when someone uses the result of functionThatTakeTheUnionType - I don't know if there is any way to make compiler to produce error on its argument.

Upvotes: 0

Matt McCutchen
Matt McCutchen

Reputation: 30919

Gotta love type hackery...

const functionThatTakeTheUnionType = <T>(p: T & ([T] extends [null] ? never : T)) => {
    // ...
}

The first T in the parameter type makes inference work. Then if T is inferred to be definitely null at compile time, we intersect with a type of never, which is sure to cause an error. If T is inferred to be something like MyObject | null, we intersect with just T again, which has no effect; note, it isn't necessary to write T | null because T will already include the null if applicable.

Upvotes: 2

Related Questions