I am seeking to define a simple function that checks a condition and returns either a value or a default. In most cases, the default is constant, so it would be beneficial to return a union of expected and default value types to properly narrow it down at the call site.
First, let's look at the function and its expected use cases.
const func = (
value: unknown, defaultValue?: string | null | undefined
): string | null | undefined => {
if (typeof value === 'string') {
return value;
}
return defaultValue;
};
const case1: string = func('', 's');
const case2: string | null = func('', null);
const case3: string | undefined = func('', undefined);
const case4: string | undefined = func('');
const x: string | null | undefined = '';
const case5: string | null | undefined = func('', x);
The first argument type always remains the same and does not affect discrimination, included here for illustration purposes. The output type always contains a string. Discrimination should only impact whether the output type contains null and undefined based on the default parameter type at the call site.
With the specified function type, the compiler raises errors for cases 1-4.
Now, I have tried various approaches to defining a discriminating type.
interface CustomType {
(value: unknown, defaultValue: string): string;
(value: unknown, defaultValue: null): string | null;
(value: unknown, defaultValue?: undefined): string | undefined;
}
type Type1 = ((value: unknown, defaultValue: string) => string) |
((value: unknown, defaultValue: null) => string | null) |
((value: unknown, defaultValue?: undefined) => string | undefined) |
((value: unknown, defaultValue: string | null | undefined) => string | null | undefined)
type Type2 = <U extends string | null | undefined> (
value: unknown, defaultValue?: U
) => string | U;
The interface "CustomType" with overloads covers all use cases, but the function "func" cannot be assigned to it even when the last overload exactly matches "func"'s type. Overloads tend to be verbose in nature.
"func" can be assigned to Type1, however, it does not function as overloads.
Type2 offers a concise solution that satisfies all use cases, yet "func" still cannot be assigned to it. Moreover, Type2 allows incorrect use cases like
const incorrect: number = func('', <any>1);
, due to the forgiving nature of extends
.
Is there a way to create a type that describes the same overloads as the aforementioned interface? Can we declare the function in a manner that makes it assignable to both the interface and the type?