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 beundefined
- If my incoming parameter is
string | undefined
, my function may returnundefined
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
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
.