Reputation: 1707
I am trying to write a function which takes a parameter of type boolean and returns one of two types, depending on the value of the input. I have found two approaches:
function dependsOnParameter<B extends boolean>(x: B): B extends true ? number : string {
if (x) {
return 3;
} else {
return "string";
}
}
Here, TypeScript says that Type '3'/'"string"' is not assignable to type 'B extends true ? number : string'.
My other approach looks like this:
function dependsOnParameter(x: true): number;
function dependsOnParameter(x: false): string;
function dependsOnParameter(x: boolean): number | string {
if (x) {
return 3;
} else {
return "string";
}
}
This compiles; however, if I try to use my function:
function calling(x: boolean) {
dependsOnParameter(x);
}
I get Argument of type 'boolean' is not assignable to parameter of type 'false'
.
Is there any way to achieve what I want without using any
?
Upvotes: 98
Views: 69395
Reputation: 139
whenever i use a boolean type, because it almost aways you can just not set it, which result in undefined. i always give it a default value of false
function test<T extends boolean = false>(isOn?:T);
if you call test(); it will assume the type is false, instead of undefined.
btw typescript doesn't handle well undefine type for some reason. which gave me alot of headache in the past
which you can see in this example
async function ke() {
const k = await HaiV2();
k.usage.hello();
}
async function test1() {
return {
hello() {
return "yeeepii";
},
};
}
async function HaiV2<NoLimit extends boolean | undefined>(
noLimit?: NoLimit
): Promise<{ usage: NoLimit extends true ? {} : { hello(): string } }> {
return {
usage: noLimit === true ? {} : await test1(),
};
}
Upvotes: 0
Reputation: 1018
I faced a similar issue, except that the dictating boolean parameter is optional and defaults to false
. In this case, the conditional type approach described by @Titian Cernicova-Dragomir breaks down (as @AndyO observed).
There are 8 possible cases to consider:
true
false
undefined
boolean
(= true | false
)true | undefined
false | undefined
boolean | undefined
I failed to produce a single function declaration that reliably covers all of them. However, 2 declarations are sufficient; one with a parameter (which we use in our conditional check), and one without.
function foo(): string
function foo<B extends boolean | undefined>(x: B): B extends true ? number : string
function foo(x?: boolean): number | string {
return x ? 123 : 'asdf'
}
function calling(
bool: boolean,
boolOrUndefined: boolean | undefined,
trueOrUndefined: true | undefined,
falseOrUndefined: false | undefined,
) {
foo(true) // number
foo(false) // string
foo() // string
foo(undefined) // string
foo(bool) // string | number
foo(boolOrUndefined) // string | number
foo(trueOrUndefined) // string | number
foo(falseOrUndefined) // string
}
Things become complicated when we use an option object, as the number of cases grow exponentially. It's better to make three declarations--one for the truthy case, one for the falsy case, and one for the catch-all:
function bar(x: { y: true }): number
function bar(x?: { y?: false }): string
function bar(x?: { y?: boolean }): number | string
function bar(x?: { y?: boolean }): number | string {
return x?.y ? 123 : 'asdf'
}
function calling2(
bool: boolean,
boolOrUndefined: boolean | undefined,
trueOrUndefined: true | undefined,
falseOrUndefined: false | undefined,
z: undefined | { y: false } | { y: undefined },
w: { y?: true }
) {
bar() // string
bar(undefined) // string
bar({}) // string
bar({ y: undefined }) // string
bar({ y: true }) // number
bar({ y: false }) // string
bar({ y: bool }) // string | number
bar({ y: boolOrUndefined }) // string | number
bar({ y: trueOrUndefined }) // string | number
bar({ y: falseOrUndefined }) // string
bar(z) // string
bar(w) // string | number
}
Upvotes: 5
Reputation: 295
This is one way:
function dependsOnParameter<B extends boolean>(x: B): B extends true ? number : string {
return (x === true ? 3 : "string") as B extends true ? number : string;
}
Here, the condition itself (B extends true ? number : string) is considered as a type. This type is called a Conditional Type.
Upvotes: 7
Reputation: 249466
Both approaches are valid. If your function uses conditional types in the return it will need to use type assertions, as typescript will not try to reason about the conditional type since it contains a free type parameter:
function dependsOnParameter<B extends boolean>(x: B): B extends true ? number : string {
if (x) {
return 3 as any;
} else {
return "string"as any;
}
}
This approach uses any
which you want to avoid.
The second approach we can get to work without resorting to type assertions by just duplicating the last signature:
function dependsOnParameter(x: true): number;
function dependsOnParameter(x: false): string;
function dependsOnParameter(x: boolean): number | string
function dependsOnParameter(x: boolean): number | string {
if (x) {
return 3;
} else {
return "string";
}
}
function calling(x: boolean) {
dependsOnParameter(x); // returns number| string
dependsOnParameter(true); // returns number
dependsOnParameter(false); // returns string
}
The last signature is the implementation signature and is not publicly accessible. You can make it accessible by duplicating it. The compiler is not smart enough to combine the two overloads with true
/false
and decide the return type is string|number
Edit
We can also combine the two approaches for fewer signatures:
function dependsOnParameter<B extends boolean>(x: B): B extends true ? number : string
function dependsOnParameter(x: boolean): number | string{
if (x) {
return 3;
} else {
return "string";
}
}
Upvotes: 118
Reputation: 21851
You can write it like this
function dependsOnParameter<B extends boolean, C = B extends true ? number : string>(x: B): C {
if (x) {
return 3 as unknown as C;
} else {
return "string" as unknown as C;
}
}
Upvotes: -4