Home > Back-end >  How to define the return type to be something or undefined only if some generic parameter may be und
How to define the return type to be something or undefined only if some generic parameter may be und

Time:01-16

I'd like to express a custom utility function to have its return type to be nullable only if the input is nullable too.

The actual goal is to limit the need to check for return type or to hard code type assertion (working in strict mode)

For exemple, imagine a dummy toUpper function that accept a string.

  • If my incoming parameter is string, I'm sure my return will never be undefined
  • If my incoming parameter is string | undefined, my function may return undefined

Like this:

// actually this strings are returned from other code
const str1 : string = 'foo'; 
const str2 : string | undefined = 'bar';
const str3 : string | undefined = undefined;

const upper1 = toUpper(str1); // upper1 should be typed as string
const upper2 = toUpper(str2); // upper2 should be typed as string | undefined
const upper3 = toUpper(str3); // upper3 should be typed as string | undefined

I tried this:

const toUpper = <TInput extends string | undefined>(
    input: TInput
    ): TInput extends string ? string : (string | undefined) => {
    if(!input)  return undefined;

    return input.toUpperCase();
}

But it does compile with the error Type 'undefined' is not assignable to type 'TInput extends string ? string : string | undefined'. (on line return undefined)

How can I fix this ?

Here's a playground link that illustrate my need

PS: using TS 4.5

[Edit] As suggested by @eldar, I tried using overloads:

function toUpper(input:string) :string;
function toUpper(input : undefined) :undefined;
function toUpper(input :string | undefined){
     if(!input)  return undefined;

    return input.toUpperCase();

}

However, it does not compile

link to updated playground

CodePudding user response:

You can try:

type PreventLiteralString<T> = T extends string ? string : T;

function toUpper<T>(arg: T): PreventLiteralString<T> | T {
  if (typeof arg === "string") {
    return arg.toLocaleUpperCase() as PreventLiteralString<T>;
  } else {
    return arg;
  }
}

const a = toUpper("test"); \\ a: string
const b = toUpper(undefined); \\ b: undefined

Point of PreventLiteralString is that just using T doesn't work. It also cannot be replaced by string, since we get assignability error:

function toUpper<T>(arg: T): T {
  if (typeof arg === "string") {
    return arg.toLocaleUpperCase() as T; // If `as string` compiler errors here.
  } else {
    return arg;
  }
}

const a = toUpper("test"); \\ a: "test" !!!
const b = toUpper(undefined);

Sadly use of as is needed, due to compiler defering evaluation of extends.

  • Related