TypeScript Version: 2.0.3 (and 1.8.10)
Code
class Greeter<TData> {
constructor(options: {
a: (x: any) => TData,
b: (y: TData) => any
}) { }
}
let greeter = new Greeter({
a: () => ({ asger: 'hej' }), // no parameter
b: (y) => y.asger // TData is of type { asger: string } and this line compiles
});
let greeter = new Greeter({
a: (x) => ({ asger: 'hej' }), // with parameter
b: (y) => y.asger // TData is of type { } and this line does not compile
});
Expected behavior:
I expected type inference to be the same regardless of number of parameters on the function assigned to a.
Actual behavior:
A function with one parameter makes type inference fail and type of TData be {}.
This behavior is an artifact of which parts of an expression we consider _producers_ of inferences and which we consider _consumers_ of inference (because they may need to know the results of previous inferences to give type to their parameters). In the first example, we classify the first lambda as a producer of inferences because it has no parameters that need contextual types. For that reason we can make an inference for TData from its return value and then apply that inference as a contextual parameter type in the second lambda. However, in the second example, we classify both lambdas as consumers of inferences because they both have contextually typed parameters. Thus we fail to make any inferences for TData before we consume it in the second lambda.
It is _possible_ to fix this, e.g. by realizing that a contextually typed lambda only consumes types that don't reference type parameters, but it isn't simple. Meanwhile, a possible workaround is to explicitly type the x parameter in the second example:
let greeter = new Greeter({
a: (x: any) => ({ asger: 'hej' }),
b: (y) => y.asger
});
I should mention that another possible workaround is to separate the functions into discrete parameters:
class Greeter<TData> {
constructor(a: (x: any) => TData, b: (y: TData) => any) { }
}
let greeter = new Greeter(
x => ({ asger: 'hej' }),
y => y.asger
);
This works because we finish inferring from the first constructor parameter before we consume the inference as a contextual parameter type for y.
@ahejlsberg Thanks a lot for this thorough explanation! The second workaround will work for me.
I wonder if it would be possible to emit an error message saying why inference failed? Though that would probably require that you could indicate that inference of a given generic type must not fail/fallback to {}.
On another note: should I close this issue if I feel sufficiently enlightened?
@asgerhallas It's tough to improve inference error messages, particularly because inference is just supposed to _infer_ but not to _error_. But we'll continue to think about it.
Yes, feel free to close this issue.
Most helpful comment
This behavior is an artifact of which parts of an expression we consider _producers_ of inferences and which we consider _consumers_ of inference (because they may need to know the results of previous inferences to give type to their parameters). In the first example, we classify the first lambda as a producer of inferences because it has no parameters that need contextual types. For that reason we can make an inference for
TDatafrom its return value and then apply that inference as a contextual parameter type in the second lambda. However, in the second example, we classify both lambdas as consumers of inferences because they both have contextually typed parameters. Thus we fail to make any inferences forTDatabefore we consume it in the second lambda.It is _possible_ to fix this, e.g. by realizing that a contextually typed lambda only consumes types that don't reference type parameters, but it isn't simple. Meanwhile, a possible workaround is to explicitly type the
xparameter in the second example: