I tried to call another overloaded function within an overloaded function in typescript. Since the type Func2 is identical to the type Func1, it is certain that the arguements passed onto func1 from func2 will be typed correctly. However, typescript seems to be unable to pick that up, and throwing an error.
type Func1 = {
(a: string, b: string): void
(a: undefined, b: undefined): void
}
const func1: Func1 = (a, b) => {
console.log(a, b)
}
type Func2 = {
(a: string, b: string): void
(a: undefined, b: undefined): void
}
const func2: Func2 = (a, b) => {
func1(a, b)
console.log(a, b)
}
/*
No overload matches this call.
Overload 1 of 2, '(a: string, b: string): void', gave the following error.
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
Type 'undefined' is not assignable to type 'string'.
Overload 2 of 2, '(a: undefined, b: undefined): void', gave the following error.
Argument of type 'string | undefined' is not assignable to parameter of type 'undefined'.
Type 'string' is not assignable to type 'undefined'.
*/
How can I resolve this?
EDIT: Here's the actual implementation that I was attempting:
type ParseRandomArgs = {
(a: undefined, b: undefined): [number, number]
(a: number, b: undefined): [number, number]
(a: number, b: number): [number, number]
(a: [number, number], b: undefined): [number, number]
(a: [number], b: undefined): [number, number]
}
type RandomNumber = {
(a: undefined, b: undefined): number
(a: number, b: undefined): number
(a: number, b: number): number
(a: [number, number], b: undefined): number
(a: [number], b: undefined): number
}
const isNullish = (value: any) => value === undefined || value === null
const parseRandomArgs: ParseRandomArgs = (a, b) => {
if (Array.isArray(a)) {
if (a.length === 2) return a
return [0, a[0]]
}
else if (isNullish(b)) return [0, isNullish(a) ? 1 : a as number]
else return [a as number, b as number]
}
const randomFloat: RandomNumber = (a, b) => {
let [min, max] = parseRandomArgs(a, b) // [min, max]
return Math.random() * (max - min) min
}
const randomInt: RandomNumber = (a, b) => {
let [min, max] = parseRandomArgs(a, b) // [min, max]
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min) min)
}
CodePudding user response:
As @Mike mentioned, making undefined arguments optional makes overloading simpler to work out. This should work:
type RandomGenerator = {
(start: number, end?: number): number
(range: [number, number]): number;
(range: [number]): number;
}
const randomFloat: RandomGenerator = (start, end?) => {
const [min, max] = parseRandomArgs(start, end as number | undefined);
return computeRandom(min, max);
}
const randomInt: RandomGenerator = (start, end?) => {
const [min, max] = parseRandomArgs(start, end as number | undefined);
return Math.floor(computeRandom(min, max));
}
const computeRandom = (min: number, max: number): number => {
return Math.random() * (max - min) min;
}
const parseRandomArgs = (first: number | [number, number] | [number], second: number | undefined): [number, number] => {
let args: [number, number];
if (Array.isArray(first) && first.length === 1) {
args = [0, first[0]];
} else if (Array.isArray(first) && first.length === 2) {
args = first;
} else {
args = Number.isFinite(second) ? [first, second as number] : [0, first];
}
const [min, max] = args;
return [Math.ceil(min), Math.floor(max)];
}
CodePudding user response:
I think you try make too clean code
Considering the second example (with a random number): typescript actually join all possible variants of arguments
const randomInt: RandomNumber = (a, b) => {
//a: number | [number, number] | [number] | undefined
//b: number | undefined
}
and your RandomNumber/ParseRandomArgs definitions can be used only for call validation
so the first solution is to extend ParseRandomArgs with the union of all args
type ParseRandomArgs = {
...
(a: [number]| [number, number] | number | undefined, b: number | undefined): [number, number]
}
second, move such union to protected function and convert ParseRandomArgs to a proxy
const _parseRandomArgs = function(a: [number] | [number,number] | number | undefined ,b?: number | undefined): [number,number]{
...
}
const parseRandomArgs: ParseRandomArgs = (a, b) => {
return _parseRandomArgs(a,b);
}
const randomFloat: RandomNumber = (a, b) => {
let [min, max] = _parseRandomArgs(a, b) // [min, max]
return Math.random() * (max - min) min
}
and I suggest you make all undefined arguments - optional
type Fn<
Params extends unknown[] = any[],
Result = any,
> = (...args: Params) => Result;
type CommonParams = [
[max?: number],
[min: number, max?: number],
[minAndMax: [min: number, max: number]],
[maxOnly: [max: number]],
];
type ParseFn = Fn<CommonParams[number], [number, number]>;
type RandomFn = Fn<CommonParams[number], number>;
function isNullish <T>(value: T): value is Exclude<T, NonNullable<T>> {
return value === undefined || value === null;
}
const parseRandomArgs: ParseFn = (...args) => {
const [a, b] = args;
if (Array.isArray(a)) return a.length === 2 ? a : [0, a[0]];
if (isNullish(a)) return [0, 1];
return isNullish(b) ? [0, a] : [a, b];
};
const randomFloat: RandomFn = (...args) => {
const [min, max] = parseRandomArgs(...args);
return Math.random() * (max - min) min;
};
const randomInt: RandomFn = (...args) => {
let [min, max] = parseRandomArgs(...args);
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) min);
};

