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.
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>
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.
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.
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 `{}`
Yes, I think that would be the best approach, but only if the array is empty.
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);
Most helpful comment
One option here is to make no inferences form a fresh array literal. the parallel in an object literal case here would be: