I have the following code, which leads to a type error TS2322 at the commented position:
interface Environment<Position, Result, Results> {
listResults(results : Results) : Iterable<[Position, Iterable<Result>]>
}
function* flatListResults<E extends Environment<P, R, Rs>, P, R, Rs>(
env : E, results : Rs) : Iterable<[P, R]>
{
for (const [p, rs] of env.listResults(results)) {
for (const r of rs) {
yield [p, r];
}
}
}
function test(env : Environment<number, string, [number, string[]]>, results : [number, string[]]) {
for (const [p, rs] of env.listResults(results)) {
let q : number = p // type checks successfully
}
for (const [p, r] of flatListResults(env, results)) {
let q : number = p // yields error TS2322: Type 'unknown' is not assignable to type 'number'
}
}
I would consider this type error a bug. Would you agree?
It's not considered a TypeScript bug; instead, it's more of a missing feature.
The problem with a call signature like
declare function flatListResults<E extends Environment<P, R, Rs>, P, R, Rs>(
env: E, results: Rs): Iterable<[P, R]>;
is that there are no inference sites for the P or R type parameters. The env and results parameters are of types E and Rs, respectively, so when you call the function the compiler can infer E from the type of the env argument and Rs from the type of the results argument. But there is nowhere for the compiler to infer P and R.
You might object that the generic constraint on E should provide the needed information: since E extends Environment<P, R, Rs>, couldn't the compiler use the inferred type of E and the constraint to infer P and R? Maybe, but such a feature was never implemented; generic constraints are not considered inferences sites. This is the subject of microsoft/TypeScript#7234, a bug report that was re-classified as a feature request (so it's officially Not A Bug), and that was eventually closed as not planned.
According to that issue, if you find yourself needing this, you should use an intersection type instead so that the constraint information appears explicitly in the function parameter types:
declare function flatListResults<E extends Environment<P, R, Rs>, P, R, Rs>(
env: E & Environment<P, R, Rs>, results: Rs): Iterable<[P, R]>;
Now env can be used to infer P and R as well as Em and inference works as desired:
for (const [p, r] of flatListResultsBetter(env, results)) {
let q: number = p // okay
}
Of course that refactoring presumes you actually need E in your type to begin with. If E was just supposed to be a vehicle for inferring P and R (which it can't do) then you could just dispense with E entirely and write env in terms of P, R, and Rs:
declare function flatListResults<P, R, Rs>(
env: Environment<P, R, Rs>, results: Rs
): Iterable<[P, R]>;
Inference also works here, and you're not carrying around an extra type parameter. And so this is how I would give your function its call signature, at least given the information in the example.
Playground like to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With