Alexander Abakumov
Alexander Abakumov

Reputation: 14539

Return type strengthening depending on whether optional param is passed

Suppose we need to implement a method which returns something, but when this something is undefined, it should return some default value passed in instead. But we don't want to make the default value param required, so are declaring it as an optional one:

private returnSomething(someParam: string, defaultValue?: string) : string | undefined {
    const result: string | undefined = ...; // Some result-producing logic
    return result !== undefined ? result : defaultValue;
}

And we expect the following to compile in strict mode (notice something1: string type which is declared without the undefined part):

const something1: string = this.returnSomething("someParam1", "?"); // Error, no undefined in the type of something1
const something2: string | undefined = this.returnSomething("someParam2");

Is it possible to declare a method in TypeScript so that when an optional parameter is not passed the return type of the method is considered as string | undefined whereas when it's passed, the return type becomes string?

Upvotes: 1

Views: 102

Answers (2)

Nurbol Alpysbayev
Nurbol Alpysbayev

Reputation: 21881

So if I got you correctly, this is easy with a generic type:

const returnSomething = <D extends string | undefined>(someParam: string, defaultValue?: D) : D extends undefined ? string | undefined : string => {

  const result = {} as any;

  return result !== undefined ? result : defaultValue;
}

See the playground

UPD to avoid error when using without any :

const returnSomething = <D extends string | undefined, R extends D extends undefined ? string | undefined : string>(someParam: string, defaultValue?: D) : R => {

  const result = 'str';

  return (result !== undefined ? result : defaultValue) as R;
}

UPD2. The correct approach (and my apologies for messing this up).

I want to give my apologies because I've introduced generics and conditional types, when they were unnecessary. I mean, I didn't know they can cause such an error. But I also was misguided by the original question. So the normal way, how I would make it, looks like this:

const returnSomething = (someParam: string, defaultValue?: string): string | undefined => {

    const result = '';

    return result !== undefined ? result : defaultValue;
}

const something1 = returnSomething("someParam1", "?");
const something2 = returnSomething("someParam2");

That's it. And yes, you should not assign types to something1 and something2. Instead, you should runtime-check (and typescript infers types from the checking):

if (typeof something1 === 'undefined') { 
    /* oops the value is undefined, do something to make the program continue working properly */ 
} 
else {
    something1 // has type "string"
}

Regarding the error, I've submitted an issue.

Upvotes: 2

y2bd
y2bd

Reputation: 6466

Nurbol was most of the way there, I had to make one additional change to get it fully working:

const returnSomething = <D extends string | undefined>(someParam: string, defaultValue?: D) : D extends undefined ? string | undefined : string => {
  const result = {} as any;
  return result !== undefined ? result : defaultValue;
}

const something1: string = returnSomething("someParam1", "d"); // No error
const something2: string | undefined = returnSomething("someParam2"); // No error
const something3: string = returnSomething("someParam2"); // Yes error
const something4: string = returnSomething("someParam2", undefined); // Yes error

We need to define D to accept undefined initially, otherwise the conditional will always return just string.

The side effect of this though is that you are now allowed to pass undefined as a default value. It still errors as expected though.

You could also do this with function overloads:

function returnSomething(param: string): string | undefined;
function returnSomething(param: string, defaultValue: string): string;
function returnSomething(param: string, defaultValue?: string) {
  const result = {} as any;
  return result !== undefined ? result: defaultValue;
}

This avoids the undefined issue.

Upvotes: 1

Related Questions