Description
TypeScript Version: 2.4.1
I have a function that has 3 parameters:
function f(a: A, b: B, c: C) {
// ...
}
I was surprised to discover that my program suddenly breaks (fails to compile) if I reorder the parameters:
function f(a: A, c: C, b: B) {
// Body is completely identical
}
Of course the arguments were swapped at the call site of f
as well. The failure is due to type inference working differently.
Below is a complete example showing the issue.
leftJoin1
and leftJoin2
are identical functions, but with their parameter order swapped.
test1
and test2
call leftJoin1
and leftJoin2
respectively (with the appropriate argument ordering).
Notice that the pair of leftJoin1
+ test1
functions works fine, while the pair of leftJoin2
+ test2
functions is broken.
To really let this sink in: delete the functions leftJoin2
and test2
. Then manually swap the parameters of leftJoin1
together with swapping the arguments to it in test1
, and observe how the program will have changed from valid to broken.
// test1 + leftJoin1
//
// Both these functions compile great!
//
export function leftJoin1<s, a extends object>(
q: Q<s>,
s: (q: Q<Inner<s>>) => MakeCols<Inner<s>, a>,
pred: (p: MakeCols<s, a>) => Col<s, boolean>
): LeftCols<s, a> {
q;
pred;
throw new Error("not implemented");
}
export function test1<s>(q: Q<s>) {
leftJoin1(q,
q => select(q, addressTable),
address => stub(address.name) // Works fine! address: MakeCols<s, Address>
);
}
// test2 + leftJoin2
//
// Swap the parameters and now there is an error!
//
export function leftJoin2<s, a extends object>(
q: Q<s>,
pred: (p: MakeCols<s, a>) => Col<s, boolean>,
s: (q: Q<Inner<s>>) => MakeCols<Inner<s>, a>
): LeftCols<s, a> {
q;
pred;
throw new Error("not implemented");
}
export function test2<s>(q: Q<s>) {
leftJoin2(q,
address => stub(address.name), // Error here! address: MakeCols<s, {}>
q => select(q, addressTable)
);
}
// --------------------------------------------------------------------
export class Q<s> {
private constructor() { }
"Q": Q<s>;
s: s;
}
export interface Table<a> {
"Table": Table<a>
a: a;
}
export class Inner<s> {
private constructor() { }
"Inner": Inner<s>;
s: s;
}
export type MakeCols<s, T extends object> = {
[P in keyof T]: Col<s, T[P]>;
}
export class Col<s, a> {
private constructor() { }
"Col": Col<s, a>;
private s: s;
private a: a;
}
export type LeftCols<S, A> = {
[P in keyof A]: Col<S, A[P] | null>;
}
export interface Address {
readonly name: string;
readonly city: string;
}
export const addressTable = declareTable<Address>("address");
export function declareTable<a>(tableName: string): Table<a> {
tableName;
throw new Error("not implemented");
}
export function select<s, a extends object>(q: Q<s>, table: Table<a>): MakeCols<s, a> {
q;
table;
throw new Error("not implemented");
}
export function stub<s, a>(e: Col<s, a>): Col<s, boolean> {
e;
throw new Error("not implemented");
}
tsconfig.json
{
"compilerOptions": {
"sourceMap": true,
"inlineSources": true,
"declaration": false,
"removeComments": false,
"newLine": "LF",
"preserveConstEnums": false,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": true,
"suppressExcessPropertyErrors": false,
"suppressImplicitAnyIndexErrors": false,
"lib": [
"dom",
"es2015.collection",
"es2015.promise",
"es2015.symbol",
"es5",
"es6"
],
"noLib": false,
"target": "es2015",
"module": "commonjs",
"moduleResolution": "node",
"isolatedModules": false
}
}