Skip to content

Bad type inference under --strictFunctionTypes #19576

Description

@gcnew

TypeScript Version: 2.7.0-dev.20171029

Code

type Comparator<T> = (x: T, y: T) => number;

interface LinkedList<T> {
    comparator: Comparator<T>,
    nodes: Node<T>
}

type Node<T> = { value: T, next: Node<T> } | null

declare function compareNumbers(x: number, y: number): number;
declare function mkList<T>(items: T[], comparator: Comparator<T>): LinkedList<T>;

const list: LinkedList<number> = mkList([], compareNumbers); // `T` inferred as `never`,
                                                             //     should   be `number`

Actual behavior:

The implicit evolving never[] type of the array literal is taking precedence, leading to unexpected results.

Activity

  1. HerringtonDarkholme commented on Oct 31, 2017

    @HerringtonDarkholme
    Contributor

    I think this is an intended behavior. compareNumbers type argument is covariant. This means number must be a supertype of T. Empty array is typed initially as never, a subtype of every other type. So, type checker should choose never, so number is correctly typed covariantly.

    I would suggest annotate mkList<number>

  2. kitsonk commented on Oct 31, 2017

    @kitsonk
    Contributor

    That does feel like never is infecting the inference though. While it might be strictly sound it feels like resolution should only assign never when there is no other suitable type.

  3. gcnew commented on Oct 31, 2017

    @gcnew
    ContributorAuthor

    I think the main problem is that an unresolved contextually typed literal takes precedence. The array is not a variable reference, it's a literal. What surfaces is the presumably evolving implicit never[] type, which is unexpected because it trumps user provided annotations.

  4. mhegazy commented on Nov 1, 2017

    @mhegazy
    Contributor

    One option here is to make no inferences form a fresh array literal. the parallel in an object literal case here would be:

    declare function mkList<T>(items: { 0?: T }, comparator: Comparator<T>): LinkedList<T>;
    mkList({}, compareNumbers); // `number`, since no inferences are made from `{}`
  5. gcnew commented on Nov 1, 2017

    @gcnew
    ContributorAuthor

    Yes, I think that would be the best approach, but only if the array is empty.

  6. locked and limited conversation to collaborators on Jun 14, 2018
  7. unlocked this conversation on Aug 20, 2018
  8. RyanCavanaugh commented on Aug 20, 2018

    @RyanCavanaugh
    Member

    Example of breaking soundness with an aliased never[]

    type Comparator<T> = (x: T, y: T) => number;
    
    declare const compareStrings: Comparator<string>;
    declare const compareNumber: Comparator<number>;
    
    function makeArray<T1, T2>(arr1: T1[], arr2: T2[], c1: Comparator<T1>, c2: Comparator<T2>) {
        return {
            arr1,
            arr2
        }
    }
    
    const arr: never[] = [];
    const res = makeArray(arr, arr, compareStrings, compareNumber);
    
    res.arr1.push("");
    res.arr2.push(10);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

FixedA PR has been merged for this issueSuggestionAn idea for TypeScript

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions