I am unsure as to how to formulate my question, so bear with me if the question title looks incorrect to you.
function uselessFunction(value: number): string | number {
if (value > 5) {
return "false"
}
return 2
}
const uselessVar1 = uselessFunction(1);
// type is `string | number`
// expected would be `number`
const uselessVar2 = uselessFunction(10);
// type is `string | number`
// expected would be `string`.
I know about typeguard
concept, and how I could use it, but that's not my use-case.
function isString(value: string | number): value is string {
if (typeof value === "string") {
return true
}
return false
}
if (isString(uselessVar1) {
// uselessVar1 type is `string`.
}
I would like to know if it's possible for typescript to know if uselessVar1
is either a string
or a number
immediatly after the return, and how could I achieve this.
Also, as to the how
is interesting to me, I'd also like to know if it's a bad practice and why.
If you're more interested in the original use-case...
/**
* Takes an input of type <T>[].
* And returns bins of this list, of length binSize.
* Example:
*
* makeBinsFromArray([1, 2, 3, 4, 5], 3);
* // [[1, 2, 3], [4, 5]]
*
* makeBinsFromArray([1, 2, 3, 4, 5, 6, 7], 3);
* // [[1, 2, 3], [4, 5, 6], [7]]
*
* makeBinsFromArray([1, 2, 3, 4, 5, 6, 7], 3, true);
* // [[1, 2, 3], [4, 5, 6], [7, undefined, undefined]]
*/
function makeBinsFromArray<T>(
value: T[],
binSize: number,
fillLast?: boolean
): (T | (T | undefined))[][] {
...
}
const result = makeBinsFromArray([1, 2, 3], 2, true);
// expected type: (T | undefined)[][]
const result = makeBinsFromArray([1, 2, 3], 2);
// expected type: T[][];
Using this function, I want to know if I have a type T[][]
or (T | undefined)[][]
at compile time, depending on my fillLast
argument.
CodePudding user response:
Yes.
Function overloads are typically what you want here where you have different signature for each way that you want the function to be called.
function makeBinsFromArray<T>(
value: T[],
binSize: number,
fillLast: true
): (T | undefined)[][]
function makeBinsFromArray<T>(
value: T[],
binSize: number,
fillLast?: false
): T[][]
function makeBinsFromArray<T>(
value: T[],
binSize: number,
fillLast?: boolean
): (T | (T | undefined))[][] {
/// implementation
}
const resultA = makeBinsFromArray([1, 2, 3], 2, true);
// (T | undefined)[][]
const resultB = makeBinsFromArray([1, 2, 3], 2);
// T[][];
However, this isn't going to work:
function uselessFunction(value: number): string | number {}
const uselessVar1 = uselessFunction(1); // `number`
const uselessVar2 = uselessFunction(10); // `string`.
You can can change the return based on the type of a parameter, but this performs logic based on a value. And a number type cannot be mathematically compared to another number type in Typescript, so this can't work. string | number
is the best you'll get here.
Well, not impossible, but this path is not for the feint of heart. Nor, probably, a good idea.
CodePudding user response:
function foo<Param extends boolean>(param?: Param): Param extends false | undefined ? T[][] : (T | undefined)[][]
It is possible using a generic to “store” what the parameter is. Then in the return type annotation we use a conditional to get the correct return type.
I have left out the body and other parameters for brevity.