I am in the following situation:
interface Rec {
key: string;
children: this[];
}
type Recursive<D extends string> = {
[K in D]: string;
} & Rec;
type FlattenRecursive =
<D extends string, R extends Recursive<D>>(rs: R[]) => Omit<R, "children">[]
const flatten: FlattenRecursive =
rs => rs.flatMap(r => flatten(r.children))
I expect the recursive call to the flatten
function to be inferred as flatten<D, R>
instead of the current <string, R>
. Therefore, I'm forced to explicitly annotate the type arguments when calling it:
type RValueAnimal = Recursive<"value"> & { animal: string }
const rvalues: RValueAnimal[] = [
// ...
]
flatten(rvalues) // <- error
flatten<"value", RValueAnimal>(rvalues)
How can I solve this problem?
CodePudding user response:
The problem with a [generic]https://www.typescriptlang.org/docs/handbook/2/generics.html) function type like
<D extends string, R extends Recursive<D>>(rs: R[]) => Omit<R, "children">[]
is that there is no inference site for the type parameter D
. You might hope that the compiler would be able to infer D
using the generic constraint of Recursive<D>
for the type parameter R
. Unfortunately, generic constraints do not serve as inference sites for other type parameters. There was a suggestion at microsoft/TypeScript#7234 to do this, but it was never implemented.
That means when you call such a function, inference will fail for D
. It will therefore fall back to its own constraint of string
, and then R
will be constrained to Recursive<string>
, which breaks things.
My suggestion is to remove D
entirely, and express the constraint you care about purely in terms of R
. For example:
type FlattenRecursive =
<R extends Recursive<Extract<Exclude<keyof R, keyof Rec>, string>>>(
rs: R[]) => Omit<R, "children">[]
This is a bit convoluted, but the idea is that what you were calling D
can be computed from keyof R
(it should be just those string
keys of R
which are not also keys of Rec
). And you can use it as desired:
flatten(rvalues); // okay