admin管理员组文章数量:1321051
I'm trying to do a recursive method similar to this:
#foo(bar: number | number[]): (number | undefined) | (number | undefined)[] {
if (Array.isArray(bar)) {
return bar.map(this.#foo, this);
}
if (bar <= 10 ) {
return 20
}
return undefined;
}
But I can't get the correct return type, I always get errors similar to this:
error TS2322: Type '(number | (number | undefined)[] | undefined)[]' is not assignable to type 'number | (number | undefined)[] | undefined'.
Type '(number | (number | undefined)[] | undefined)[]' is not assignable to type '(number | undefined)[]'.
Type 'number | (number | undefined)[] | undefined' is not assignable to type 'number | undefined'.
Type '(number | undefined)[]' is not assignable to type 'number'.
58 return algo.map(this.#algo, this);
~~~~~~
Found 1 error.
I'm trying to do a recursive method similar to this:
#foo(bar: number | number[]): (number | undefined) | (number | undefined)[] {
if (Array.isArray(bar)) {
return bar.map(this.#foo, this);
}
if (bar <= 10 ) {
return 20
}
return undefined;
}
But I can't get the correct return type, I always get errors similar to this:
error TS2322: Type '(number | (number | undefined)[] | undefined)[]' is not assignable to type 'number | (number | undefined)[] | undefined'.
Type '(number | (number | undefined)[] | undefined)[]' is not assignable to type '(number | undefined)[]'.
Type 'number | (number | undefined)[] | undefined' is not assignable to type 'number | undefined'.
Type '(number | undefined)[]' is not assignable to type 'number'.
58 return algo.map(this.#algo, this);
~~~~~~
Found 1 error.
Share
Improve this question
asked Jan 17 at 19:34
alexojegualexojegu
7967 silver badges23 bronze badges
2
|
1 Answer
Reset to default 1If the return type of this.#foo
is number | undefined | (number | undefined)[]
independent of the input, then TypeScript cannot conclude that the output will not be an array if the input is not an array. For all it knows, every call might output an array of numbers.
So then the type of bar.map(this.#foo, this)
is an array of possibly-an-array-of-numbers (that is, (number | undefined | (number | undefined))[]
), which means it's inappropriate to return that directly.
There are various ways to approach this. The simplest is to just assert that you know what you're doing when calling map()
. For example, you can tell TypeScript that you are sure the function returns number | undefined
when the input is number
:
#foo(bar: number | number[]): number | undefined | (number | undefined)[] {
if (Array.isArray(bar)) {
return bar.map(
this.#foo as (x: number) => number | undefined, // assert
this);
}
if (bar <= 10) {
return 20
}
return undefined;
}
Or that the type of bar.map()
is will just be a (number | undefined)[]
:
#foo(bar: number | number[]): number | undefined | (number | undefined)[] {
if (Array.isArray(bar)) {
return bar.map(this.#foo, this) as (number | undefined)[] // assert
}
if (bar <= 10) {
return 20
}
return undefined;
}
On the other hand, if you're more worried about type safety than convenience, you could perform a redundant runtime work so that TypeScript is convinced that you're returning the right thing, such as filter()
-ing the result of bar.map()
so that any rogue nested arrays are suppressed:
#foo(bar: number | number[]): number | undefined | (number | undefined)[] {
if (Array.isArray(bar)) {
return bar.map(this.#foo, this
).filter(x => typeof x !== "object"); // runime filter
}
if (bar <= 10) {
return 20
}
return undefined;
}
TypeScript understands that the callback x => typeof x !== "object"
acts as a type guard function, and will eliminate (number | undefined)[]
from the possible elements of bar.map()
, so what's left is all number | undefined
elements.
Similarly you could throw an error if any of the elements of the intended return value are themselves arrays:
#foo(bar: number | number[]): number | undefined | (number | undefined)[] {
if (Array.isArray(bar)) {
const ret = bar.map(this.#foo, this);
if (ret.every(x => typeof x !== "object")) return ret;
throw new Error("OH NOEZ");
}
if (bar <= 10) {
return 20
}
return undefined;
}
There are other possible approaches as well. If you want callers of this.#foo
(presumably other class methods) to know that arrays only come out if arrays go in, then you can give this.#foo
a definition hat represents this, such as with overloads:
// overload call signatures
#foo(bar: number): number | undefined;
#foo(bar: number[]): (number | undefined)[];
// implementation
#foo(bar: number | number[]): number | undefined | (number | undefined)[] {
if (Array.isArray(bar)) {
// help TS see which call signature you're using
const f: (bar: number) => number | undefined = this.#foo;
return bar.map(f, this);
}
if (bar <= 10) {
return 20
}
return undefined;
}
Indeed, once you start thinking of this as a method that has two fundamentally different call signatures, it might even make more sense to refactor to two methods, each of which just does one thing directly:
#fooNum(bar: number): number | undefined {
if (bar <= 10) return 20;
return undefined;
}
#fooArr(bar: number[]): (number | undefined)[] {
return bar.map(this.#fooNum, this);
}
And then if you must have a method that does both of these, you can use the two specialized methods to make it:
#foo(bar: number | number[]) {
return (Array.isArray(bar)) ? this.#fooArr(bar) : this.#fooNum(bar);
}
Now you have a #foo
that does exactly what you want and TypeScript can verify the types without need for assertions or redundant runtime work.
Playground link to code
本文标签: typescriptCorrect return type for function recursive if param is arrayStack Overflow
版权声明:本文标题:typescript - Correct return type for function recursive if param is array - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742094227a2420447.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
number
in, then anumber | undefined
comes out. As far as it knows, you get anumber | undefined | (number | undefined)[]
, and if it's an array, thenmap()
will return a nested array, which isn't good. You can either overload#foo
so it knows these things, or assert that it returns the right thing, or do a (redundant) runtime check to convince it. The three options are shown in this playground link. Does this fully address the question? If so I'll write an answer; if not, what's missing? – jcalz Commented Jan 17 at 19:45