Skip to content

Incorrect contextual type inference #8461

Closed

Description

Hi,

TypeScript Version: 1.8.5
Visual Studio 2013

interface Foo { a: string }
interface Bar { b: string }

getData<T>(): T {
   return null;
}

var funcArray = [this.getData<Foo>(), this.getData<Bar>()];  //Inferred as (Foo | Bar)[]

Shouldn't this be inferred as [Foo, Bar]?
Is this intended behavior? Looks reasonable since funcArray can be explicitly typed to [Foo, Bar]

PS: This could be a duplicate of other issues for contextual typing, which is/are already fixed (fix included in milestone 2.0?)

Activity

  1. malibuzios commented on May 4, 2016

    @malibuzios

    I tried to simplify the example:

    function getData<T>(arg: T): T {
       return arg;
    }
    
    let resultArray = [getData("abcd"), getData(1234)]; // Inferred as (string | number)[]
    
    // compare to
    let simpleArray = ["abcd", 1234]; // Inferred as (string | number)[]

    This seems consistent with how the type of an array initialized to constant values would behave. Having it inferred as a tuple would create a problem if it is extended or modified later. If it was immutable or somehow its length was fixed then I believe it would be reasonable to infer it as a tuple.

    One workaround is to annotate the return type explicitly:

    let resultArray: [string, number] = [getData("abcd"), getData(1234)];

    Another is to use a helper like createTuple:

    function createTuple<T, V>(arg1: T, arg2: V): [T, V] {
        return [arg1, arg2];
    }
    
    let resultArray = createTuple(getData("abcd"), getData(1234)); // Inferred as [string, number]
  2. mhegazy commented on May 4, 2016

    @mhegazy
    Contributor

    Tuple types are never inferred from a literal unless specified. so:

    var a = [1, "string"];  // a is (number | string)[]
    var t: [number, string] = [1, "string"]; // t is [number, string]

    If tuples were inferred automatically that could be a huge breaking change, and would make array uses for a lot of valid scenarios errors.

  3. AbhishekGarg commented on May 5, 2016

    @AbhishekGarg
    Author

    Thank you for the explanation. This makes sense.
    However, to elaborate on the use case, an issue occurs when funcArray has to be used in a function expecting a tuple.

    interface Foo { a: string }
    interface Bar { b: string }
    
    getData<T>(): T {
       return null;
    }
    
    var funcArray = [this.getData<Foo>(), this.getData<Bar>()];  //Inferred as (Foo | Bar)[]
    
    myFunc<T1, T2>(values: [T1, T2]): boolean {
        return values[0] !== undefined && values[1] !== null;
    }
    
    // Error: Argument of type '(Foo | Bar)[]' is not assignable to parameter of type '[Foo, Bar]'
    // Property '0' is missing in type '(Foo | Bar)[]'
    myFunc<Foo, Bar>(funcArray); 
    1. What is property '0'?
    2. Explicitly annotating the return type is a valid workaround, but it is cumbersome to do every time the method is used. Is there a better and clean solution?
  4. kitsonk commented on May 5, 2016

    @kitsonk
    Contributor
    1. In a tuple, the types are treated more internally treat like this:
    interface FuncArrayTuple<T1, T2> {
      0: T1;
      1: T2;
    }
    1. Tuples are the least JavaScripty like type that TypeScript supports, basically intended to allow multiple values to be returned from a function. So not using them is one option. You could create a type alias, something like:
    interface Foo { a: string }
    interface Bar { b: string }
    
    function getData<T>(): T {
       return null;
    }
    
    type TwoTuple = [ any, any ];
    
    var funcArray: TwoTuple = [ getData<Foo>(),  getData<Bar>()];
    
    function myFunc(values: TwoTuple): boolean {
        return values[0] !== undefined && values[1] !== null;
    }
    
    myFunc(funcArray);

    Or probably better, have something return a tuple type, like:

    interface Foo { a: string }
    interface Bar { b: string }
    
    function getData<T>(): T {
       return null;
    }
    
    function returnFuncArray<T1, T2>(a: T1, b: T2): [T1, T2] {
        return [a, b];
    }
    
    var funcArray = returnFuncArray(getData<Foo>(),  getData<Bar>());
    
    function myFunc<T1, T2>(values: [T1, T2]): boolean {
        return values[0] !== undefined && values[1] !== null;
    }
    
    myFunc(funcArray); 
  5. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    By DesignDeprecated - use "Working as Intended" or "Design Limitation" instead

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions