Typescript: Type inference of return type changes when function arity changes

Created on 27 Sep 2016  路  5Comments  路  Source: microsoft/TypeScript

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 {}.

Design Limitation

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 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
});

All 5 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

CyrusNajmabadi picture CyrusNajmabadi  路  3Comments

jbondc picture jbondc  路  3Comments

MartynasZilinskas picture MartynasZilinskas  路  3Comments

siddjain picture siddjain  路  3Comments

remojansen picture remojansen  路  3Comments