One of Flow’s original goals was to be able to understand idiomatic JavaScript.
In JavaScript, you can call a function with more arguments than the function
expects. Therefore, Flow never complained about calling a function with
extraneous arguments.
We are changing this behavior.
What is arity?
A function’s arity is the number of arguments it expects. Since some functions
have optional parameters and some use rest parameters, we can define the
minimum arity as the smallest number of arguments it expects and the maximum
arity as the largest number of arguments it expects.
1
2
3
4
|
function no_args() {}
function two_args(a, b) {}
function optional_args(a, b?) {}
function many_args(a, ...rest) {}
|
{"value":"function no_args() {} // arity of 0\nfunction two_args(a, b) {} // arity of 2\nfunction optional_args(a, b?) {} // min arity of 1, max arity of 2\nfunction many_args(a, ...rest) {} // min arity of 1, no max arity\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"no_args","line":1,"start":9,"end":16},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":16,"end":17},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":17,"end":18},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":19,"end":20},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":20,"end":21},{"type":"Line","context":"comment","value":"// arity of 0","line":1,"start":22,"end":35},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":36,"end":44},{"type":"T_IDENTIFIER","context":"normal","value":"two_args","line":2,"start":45,"end":53},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":53,"end":54},{"type":"T_IDENTIFIER","context":"normal","value":"a","line":2,"start":54,"end":55},{"type":"T_COMMA","context":"normal","value":",","line":2,"start":55,"end":56},{"type":"T_IDENTIFIER","context":"normal","value":"b","line":2,"start":57,"end":58},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":58,"end":59},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":60,"end":61},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":61,"end":62},{"type":"Line","context":"comment","value":"// arity of 2","line":2,"start":63,"end":76},{"type":"T_FUNCTION","context":"normal","value":"function","line":3,"start":77,"end":85},{"type":"T_IDENTIFIER","context":"normal","value":"optional_args","line":3,"start":86,"end":99},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":99,"end":100},{"type":"T_IDENTIFIER","context":"normal","value":"a","line":3,"start":100,"end":101},{"type":"T_COMMA","context":"normal","value":",","line":3,"start":101,"end":102},{"type":"T_IDENTIFIER","context":"normal","value":"b","line":3,"start":103,"end":104},{"type":"T_PLING","context":"normal","value":"?","line":3,"start":104,"end":105},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":105,"end":106},{"type":"T_LCURLY","context":"normal","value":"{","line":3,"start":107,"end":108},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":108,"end":109},{"type":"Line","context":"comment","value":"// min arity of 1, max arity of 2","line":3,"start":110,"end":143},{"type":"T_FUNCTION","context":"normal","value":"function","line":4,"start":144,"end":152},{"type":"T_IDENTIFIER","context":"normal","value":"many_args","line":4,"start":153,"end":162},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":162,"end":163},{"type":"T_IDENTIFIER","context":"normal","value":"a","line":4,"start":163,"end":164},{"type":"T_COMMA","context":"normal","value":",","line":4,"start":164,"end":165},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":4,"start":166,"end":169},{"type":"T_IDENTIFIER","context":"normal","value":"rest","line":4,"start":169,"end":173},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":173,"end":174},{"type":"T_LCURLY","context":"normal","value":"{","line":4,"start":175,"end":176},{"type":"T_RCURLY","context":"normal","value":"}","line":4,"start":176,"end":177},{"type":"Line","context":"comment","value":"// min arity of 1, no max arity","line":4,"start":178,"end":209}],"errors":[]}
Motivation
Consider the following code:
1
2
|
function add(a, b) { return a + b; }
const sum = add(1, 1, 1, 1);
|
{"value":"function add(a, b) { return a + b; }\nconst sum = add(1, 1, 1, 1);\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"add","line":1,"start":9,"end":12},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":12,"end":13},{"type":"T_IDENTIFIER","context":"normal","value":"a","line":1,"start":13,"end":14},{"type":"T_COMMA","context":"normal","value":",","line":1,"start":14,"end":15},{"type":"T_IDENTIFIER","context":"normal","value":"b","line":1,"start":16,"end":17},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":17,"end":18},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":19,"end":20},{"type":"T_RETURN","context":"normal","value":"return","line":1,"start":21,"end":27},{"type":"T_IDENTIFIER","context":"normal","value":"a","line":1,"start":28,"end":29},{"type":"T_PLUS","context":"normal","value":"+","line":1,"start":30,"end":31},{"type":"T_IDENTIFIER","context":"normal","value":"b","line":1,"start":32,"end":33},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":33,"end":34},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":35,"end":36},{"type":"T_CONST","context":"normal","value":"const","line":2,"start":37,"end":42},{"type":"T_IDENTIFIER","context":"normal","value":"sum","line":2,"start":43,"end":46},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":47,"end":48},{"type":"T_IDENTIFIER","context":"normal","value":"add","line":2,"start":49,"end":52},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":52,"end":53},{"type":"T_NUMBER","context":"normal","value":"1","line":2,"start":53,"end":54},{"type":"T_COMMA","context":"normal","value":",","line":2,"start":54,"end":55},{"type":"T_NUMBER","context":"normal","value":"1","line":2,"start":56,"end":57},{"type":"T_COMMA","context":"normal","value":",","line":2,"start":57,"end":58},{"type":"T_NUMBER","context":"normal","value":"1","line":2,"start":59,"end":60},{"type":"T_COMMA","context":"normal","value":",","line":2,"start":60,"end":61},{"type":"T_NUMBER","context":"normal","value":"1","line":2,"start":62,"end":63},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":63,"end":64},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":64,"end":65}],"errors":[]}
The author apparently thought the add()
function adds up all its
arguments, and that sum
will have the value 4
. However, only the first two
arguments are summed, and sum
actually will have the value 2
. This is
obviously a bug, so why doesn’t JavaScript or Flow complain?
And while the error in the above example is easy to see, in real code it’s often
a lot harder to notice. For example, what is the value of total
here:
1
|
const total = parseInt("10", 2) + parseFloat("10.1", 2);
|
{"value":"const total = parseInt(\"10\", 2) + parseFloat(\"10.1\", 2);\n","tokens":[{"type":"T_CONST","context":"normal","value":"const","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"total","line":1,"start":6,"end":11},{"type":"T_ASSIGN","context":"normal","value":"=","line":1,"start":12,"end":13},{"type":"T_IDENTIFIER","context":"normal","value":"parseInt","line":1,"start":14,"end":22},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":22,"end":23},{"type":"T_STRING","context":"normal","value":"\"10\"","line":1,"start":23,"end":27},{"type":"T_COMMA","context":"normal","value":",","line":1,"start":27,"end":28},{"type":"T_NUMBER","context":"normal","value":"2","line":1,"start":29,"end":30},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":30,"end":31},{"type":"T_PLUS","context":"normal","value":"+","line":1,"start":32,"end":33},{"type":"T_IDENTIFIER","context":"normal","value":"parseFloat","line":1,"start":34,"end":44},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":44,"end":45},{"type":"T_STRING","context":"normal","value":"\"10.1\"","line":1,"start":45,"end":51},{"type":"T_COMMA","context":"normal","value":",","line":1,"start":51,"end":52},{"type":"T_NUMBER","context":"normal","value":"2","line":1,"start":53,"end":54},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":54,"end":55},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":55,"end":56}],"errors":[]}
"10"
in base 2 is 2
in decimal and "10.1"
in base 2 is 2.5
in decimal.
So the author probably thought that total
would be 4.5
. However, the correct
answer is 12.1
. parseInt("10", 2)
does evaluates to 2
, as expected.
However, parseFloat("10.1", 2)
evaluates to 10.1
. parseFloat()
only takes
a single argument. The second argument is ignored!
At this point, you might feel like this is just an example of JavaScript making
terrible life decisions. However, this behavior is very convenient in a bunch of
situations!
Callbacks
If you couldn’t call a function with more arguments than it handles, then
mapping over an array would look like
1
|
const doubled_arr = [1, 2, 3].map((element, index, arr) => element * 2);
|
{"value":"const doubled_arr = [1, 2, 3].map((element, index, arr) => element * 2);\n","tokens":[{"type":"T_CONST","context":"normal","value":"const","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"doubled_arr","line":1,"start":6,"end":17},{"type":"T_ASSIGN","context":"normal","value":"=","line":1,"start":18,"end":19},{"type":"T_LBRACKET","context":"normal","value":"[","line":1,"start":20,"end":21},{"type":"T_NUMBER","context":"normal","value":"1","line":1,"start":21,"end":22},{"type":"T_COMMA","context":"normal","value":",","line":1,"start":22,"end":23},{"type":"T_NUMBER","context":"normal","value":"2","line":1,"start":24,"end":25},{"type":"T_COMMA","context":"normal","value":",","line":1,"start":25,"end":26},{"type":"T_NUMBER","context":"normal","value":"3","line":1,"start":27,"end":28},{"type":"T_RBRACKET","context":"normal","value":"]","line":1,"start":28,"end":29},{"type":"T_PERIOD","context":"normal","value":".","line":1,"start":29,"end":30},{"type":"T_IDENTIFIER","context":"normal","value":"map","line":1,"start":30,"end":33},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":33,"end":34},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":34,"end":35},{"type":"T_IDENTIFIER","context":"normal","value":"element","line":1,"start":35,"end":42},{"type":"T_COMMA","context":"normal","value":",","line":1,"start":42,"end":43},{"type":"T_IDENTIFIER","context":"normal","value":"index","line":1,"start":44,"end":49},{"type":"T_COMMA","context":"normal","value":",","line":1,"start":49,"end":50},{"type":"T_IDENTIFIER","context":"normal","value":"arr","line":1,"start":51,"end":54},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":54,"end":55},{"type":"T_ARROW","context":"normal","value":"=>","line":1,"start":56,"end":58},{"type":"T_IDENTIFIER","context":"normal","value":"element","line":1,"start":59,"end":66},{"type":"T_MULT","context":"normal","value":"*","line":1,"start":67,"end":68},{"type":"T_NUMBER","context":"normal","value":"2","line":1,"start":69,"end":70},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":70,"end":71},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":71,"end":72}],"errors":[]}
When you call Array.prototype.map
, you pass in a callback. For each element in
the array, that callback is invoked and passed 3 arguments:
- The element
- The index of the element
- The array over which you’re mapping
However, your callback often only needs to reference the first argument: the
element. It’s really nice that you can write
1
|
const doubled_arr = [1, 2, 3].map(element => element * 2);
|
{"value":"const doubled_arr = [1, 2, 3].map(element => element * 2);\n","tokens":[{"type":"T_CONST","context":"normal","value":"const","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"doubled_arr","line":1,"start":6,"end":17},{"type":"T_ASSIGN","context":"normal","value":"=","line":1,"start":18,"end":19},{"type":"T_LBRACKET","context":"normal","value":"[","line":1,"start":20,"end":21},{"type":"T_NUMBER","context":"normal","value":"1","line":1,"start":21,"end":22},{"type":"T_COMMA","context":"normal","value":",","line":1,"start":22,"end":23},{"type":"T_NUMBER","context":"normal","value":"2","line":1,"start":24,"end":25},{"type":"T_COMMA","context":"normal","value":",","line":1,"start":25,"end":26},{"type":"T_NUMBER","context":"normal","value":"3","line":1,"start":27,"end":28},{"type":"T_RBRACKET","context":"normal","value":"]","line":1,"start":28,"end":29},{"type":"T_PERIOD","context":"normal","value":".","line":1,"start":29,"end":30},{"type":"T_IDENTIFIER","context":"normal","value":"map","line":1,"start":30,"end":33},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":33,"end":34},{"type":"T_IDENTIFIER","context":"normal","value":"element","line":1,"start":34,"end":41},{"type":"T_ARROW","context":"normal","value":"=>","line":1,"start":42,"end":44},{"type":"T_IDENTIFIER","context":"normal","value":"element","line":1,"start":45,"end":52},{"type":"T_MULT","context":"normal","value":"*","line":1,"start":53,"end":54},{"type":"T_NUMBER","context":"normal","value":"2","line":1,"start":55,"end":56},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":56,"end":57},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":57,"end":58}],"errors":[]}
Stubbing
Sometimes I come across code like this
1
2
3
4
5
|
let log = () => {};
if (DEBUG) {
log = (message) => console.log(message);
}
log("Hello world");
|
{"value":"let log = () => {};\nif (DEBUG) {\n log = (message) => console.log(message);\n}\nlog(\"Hello world\");\n","tokens":[{"type":"T_LET","context":"normal","value":"let","line":1,"start":0,"end":3},{"type":"T_IDENTIFIER","context":"normal","value":"log","line":1,"start":4,"end":7},{"type":"T_ASSIGN","context":"normal","value":"=","line":1,"start":8,"end":9},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":10,"end":11},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":11,"end":12},{"type":"T_ARROW","context":"normal","value":"=>","line":1,"start":13,"end":15},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":16,"end":17},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":17,"end":18},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":18,"end":19},{"type":"T_IF","context":"normal","value":"if","line":2,"start":20,"end":22},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":23,"end":24},{"type":"T_IDENTIFIER","context":"normal","value":"DEBUG","line":2,"start":24,"end":29},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":29,"end":30},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":31,"end":32},{"type":"T_IDENTIFIER","context":"normal","value":"log","line":3,"start":35,"end":38},{"type":"T_ASSIGN","context":"normal","value":"=","line":3,"start":39,"end":40},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":41,"end":42},{"type":"T_IDENTIFIER","context":"normal","value":"message","line":3,"start":42,"end":49},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":49,"end":50},{"type":"T_ARROW","context":"normal","value":"=>","line":3,"start":51,"end":53},{"type":"T_IDENTIFIER","context":"normal","value":"console","line":3,"start":54,"end":61},{"type":"T_PERIOD","context":"normal","value":".","line":3,"start":61,"end":62},{"type":"T_IDENTIFIER","context":"normal","value":"log","line":3,"start":62,"end":65},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":65,"end":66},{"type":"T_IDENTIFIER","context":"normal","value":"message","line":3,"start":66,"end":73},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":73,"end":74},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":74,"end":75},{"type":"T_RCURLY","context":"normal","value":"}","line":4,"start":76,"end":77},{"type":"T_IDENTIFIER","context":"normal","value":"log","line":5,"start":78,"end":81},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":81,"end":82},{"type":"T_STRING","context":"normal","value":"\"Hello world\"","line":5,"start":82,"end":95},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":95,"end":96},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":96,"end":97}],"errors":[]}
The idea is that in a development environment, calling log()
will output a
message, but in production it does nothing. Since you can call a
function with more arguments than it expects, it is easy to stub out log()
in
production.
Variadic functions using arguments
A variadic function is a function that can take an indefinite number of
arguments. The old-school way to write variadic functions in JavaScript is by
using arguments
. For example
1
2
3
4
5
6
|
function sum_all() {
let ret = 0;
for (let i = 0; i < arguments.length; i++) { ret += arguments[i]; }
return ret;
}
const total = sum_all(1, 2, 3);
|
{"value":"function sum_all() {\n let ret = 0;\n for (let i = 0; i < arguments.length; i++) { ret += arguments[i]; }\n return ret;\n}\nconst total = sum_all(1, 2, 3); // returns 6\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"sum_all","line":1,"start":9,"end":16},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":16,"end":17},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":17,"end":18},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":19,"end":20},{"type":"T_LET","context":"normal","value":"let","line":2,"start":23,"end":26},{"type":"T_IDENTIFIER","context":"normal","value":"ret","line":2,"start":27,"end":30},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":31,"end":32},{"type":"T_NUMBER","context":"normal","value":"0","line":2,"start":33,"end":34},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":34,"end":35},{"type":"T_FOR","context":"normal","value":"for","line":3,"start":38,"end":41},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":42,"end":43},{"type":"T_LET","context":"normal","value":"let","line":3,"start":43,"end":46},{"type":"T_IDENTIFIER","context":"normal","value":"i","line":3,"start":47,"end":48},{"type":"T_ASSIGN","context":"normal","value":"=","line":3,"start":49,"end":50},{"type":"T_NUMBER","context":"normal","value":"0","line":3,"start":51,"end":52},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":52,"end":53},{"type":"T_IDENTIFIER","context":"normal","value":"i","line":3,"start":54,"end":55},{"type":"T_LESS_THAN","context":"normal","value":"<","line":3,"start":56,"end":57},{"type":"T_IDENTIFIER","context":"normal","value":"arguments","line":3,"start":58,"end":67},{"type":"T_PERIOD","context":"normal","value":".","line":3,"start":67,"end":68},{"type":"T_IDENTIFIER","context":"normal","value":"length","line":3,"start":68,"end":74},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":74,"end":75},{"type":"T_IDENTIFIER","context":"normal","value":"i","line":3,"start":76,"end":77},{"type":"T_INCR","context":"normal","value":"++","line":3,"start":77,"end":79},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":79,"end":80},{"type":"T_LCURLY","context":"normal","value":"{","line":3,"start":81,"end":82},{"type":"T_IDENTIFIER","context":"normal","value":"ret","line":3,"start":83,"end":86},{"type":"T_PLUS_ASSIGN","context":"normal","value":"+=","line":3,"start":87,"end":89},{"type":"T_IDENTIFIER","context":"normal","value":"arguments","line":3,"start":90,"end":99},{"type":"T_LBRACKET","context":"normal","value":"[","line":3,"start":99,"end":100},{"type":"T_IDENTIFIER","context":"normal","value":"i","line":3,"start":100,"end":101},{"type":"T_RBRACKET","context":"normal","value":"]","line":3,"start":101,"end":102},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":102,"end":103},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":104,"end":105},{"type":"T_RETURN","context":"normal","value":"return","line":4,"start":108,"end":114},{"type":"T_IDENTIFIER","context":"normal","value":"ret","line":4,"start":115,"end":118},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":118,"end":119},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":120,"end":121},{"type":"T_CONST","context":"normal","value":"const","line":6,"start":122,"end":127},{"type":"T_IDENTIFIER","context":"normal","value":"total","line":6,"start":128,"end":133},{"type":"T_ASSIGN","context":"normal","value":"=","line":6,"start":134,"end":135},{"type":"T_IDENTIFIER","context":"normal","value":"sum_all","line":6,"start":136,"end":143},{"type":"T_LPAREN","context":"normal","value":"(","line":6,"start":143,"end":144},{"type":"T_NUMBER","context":"normal","value":"1","line":6,"start":144,"end":145},{"type":"T_COMMA","context":"normal","value":",","line":6,"start":145,"end":146},{"type":"T_NUMBER","context":"normal","value":"2","line":6,"start":147,"end":148},{"type":"T_COMMA","context":"normal","value":",","line":6,"start":148,"end":149},{"type":"T_NUMBER","context":"normal","value":"3","line":6,"start":150,"end":151},{"type":"T_RPAREN","context":"normal","value":")","line":6,"start":151,"end":152},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":152,"end":153},{"type":"Line","context":"comment","value":"// returns 6","line":6,"start":154,"end":166}],"errors":[]}
For all intents and purposes, sum_all
appears like it takes no arguments. So
even though it appears to have an arity of 0, it is convenient that we can call
it with more arguments.
Changes to Flow
We think we have found a compromise which catches the motivating bugs without
breaking the convenience of JavaScript.
Calling a function
If a function has a maximum arity of N, then Flow will start complaining if you
call it with more than N arguments.
1
2
3
4
5
|
test:1
1: const num = parseFloat("10.5", 2);
^ unused function argument
19: declare function parseFloat(string: mixed): number;
^^^^^^^^^^^^^^^^^^^^^^^ function type expects no more than 1 argument. See lib: <BUILTINS>/core.js:19
|
{"value":"test:1\n 1: const num = parseFloat(\"10.5\", 2);\n ^ unused function argument\n 19: declare function parseFloat(string: mixed): number;\n ^^^^^^^^^^^^^^^^^^^^^^^ function type expects no more than 1 argument. See lib: <BUILTINS>/core.js:19\n","tokens":[{"type":"T_IDENTIFIER","context":"normal","value":"test","line":1,"start":0,"end":4},{"type":"T_COLON","context":"normal","value":":","line":1,"start":4,"end":5},{"type":"T_NUMBER","context":"normal","value":"1","line":1,"start":5,"end":6},{"type":"T_NUMBER","context":"normal","value":"1","line":2,"start":9,"end":10},{"type":"T_COLON","context":"normal","value":":","line":2,"start":10,"end":11},{"type":"T_CONST","context":"normal","value":"const","line":2,"start":12,"end":17},{"type":"T_IDENTIFIER","context":"normal","value":"num","line":2,"start":18,"end":21},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":22,"end":23},{"type":"T_IDENTIFIER","context":"normal","value":"parseFloat","line":2,"start":24,"end":34},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":34,"end":35},{"type":"T_STRING","context":"normal","value":"\"10.5\"","line":2,"start":35,"end":41},{"type":"T_COMMA","context":"normal","value":",","line":2,"start":41,"end":42},{"type":"T_NUMBER","context":"normal","value":"2","line":2,"start":43,"end":44},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":44,"end":45},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":45,"end":46},{"type":"T_BIT_XOR","context":"normal","value":"^","line":3,"start":83,"end":84},{"type":"T_IDENTIFIER","context":"normal","value":"unused","line":3,"start":85,"end":91},{"type":"T_FUNCTION","context":"normal","value":"function","line":3,"start":92,"end":100},{"type":"T_IDENTIFIER","context":"normal","value":"argument","line":3,"start":101,"end":109},{"type":"T_NUMBER","context":"normal","value":"19","line":4,"start":113,"end":115},{"type":"T_COLON","context":"normal","value":":","line":4,"start":115,"end":116},{"type":"T_DECLARE","context":"normal","value":"declare","line":4,"start":117,"end":124},{"type":"T_FUNCTION","context":"normal","value":"function","line":4,"start":125,"end":133},{"type":"T_IDENTIFIER","context":"normal","value":"parseFloat","line":4,"start":134,"end":144},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":144,"end":145},{"type":"T_IDENTIFIER","context":"normal","value":"string","line":4,"start":145,"end":151},{"type":"T_COLON","context":"normal","value":":","line":4,"start":151,"end":152},{"type":"T_IDENTIFIER","context":"normal","value":"mixed","line":4,"start":153,"end":158},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":158,"end":159},{"type":"T_COLON","context":"type","value":":","line":4,"start":159,"end":160},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":4,"start":161,"end":167},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":167,"end":168},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":203,"end":204},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":204,"end":205},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":205,"end":206},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":206,"end":207},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":207,"end":208},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":208,"end":209},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":209,"end":210},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":210,"end":211},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":211,"end":212},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":212,"end":213},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":213,"end":214},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":214,"end":215},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":215,"end":216},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":216,"end":217},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":217,"end":218},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":218,"end":219},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":219,"end":220},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":220,"end":221},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":221,"end":222},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":222,"end":223},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":223,"end":224},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":224,"end":225},{"type":"T_BIT_XOR","context":"normal","value":"^","line":5,"start":225,"end":226},{"type":"T_FUNCTION","context":"normal","value":"function","line":5,"start":227,"end":235},{"type":"T_TYPE","context":"normal","value":"type","line":5,"start":236,"end":240},{"type":"T_IDENTIFIER","context":"normal","value":"expects","line":5,"start":241,"end":248},{"type":"T_IDENTIFIER","context":"normal","value":"no","line":5,"start":249,"end":251},{"type":"T_IDENTIFIER","context":"normal","value":"more","line":5,"start":252,"end":256},{"type":"T_IDENTIFIER","context":"normal","value":"than","line":5,"start":257,"end":261},{"type":"T_NUMBER","context":"normal","value":"1","line":5,"start":262,"end":263},{"type":"T_IDENTIFIER","context":"normal","value":"argument","line":5,"start":264,"end":272},{"type":"T_PERIOD","context":"normal","value":".","line":5,"start":272,"end":273},{"type":"T_IDENTIFIER","context":"normal","value":"See","line":5,"start":274,"end":277},{"type":"T_IDENTIFIER","context":"normal","value":"lib","line":5,"start":278,"end":281},{"type":"T_COLON","context":"normal","value":":","line":5,"start":281,"end":282},{"type":"T_LESS_THAN","context":"normal","value":"<","line":5,"start":283,"end":284},{"type":"T_IDENTIFIER","context":"normal","value":"BUILTINS","line":5,"start":284,"end":292},{"type":"T_GREATER_THAN","context":"normal","value":">","line":5,"start":292,"end":293},{"type":"T_DIV","context":"normal","value":"/","line":5,"start":293,"end":294},{"type":"T_IDENTIFIER","context":"normal","value":"core","line":5,"start":294,"end":298},{"type":"T_PERIOD","context":"normal","value":".","line":5,"start":298,"end":299},{"type":"T_IDENTIFIER","context":"normal","value":"js","line":5,"start":299,"end":301},{"type":"T_COLON","context":"normal","value":":","line":5,"start":301,"end":302},{"type":"T_NUMBER","context":"normal","value":"19","line":5,"start":302,"end":304}],"errors":[]}
Function subtyping
Flow will not change its function subtyping behavior. A function
with a smaller maximum arity is still a subtype of a function with a larger
maximum arity. This allows callbacks to still work as before.
1
2
3
4
5
6
|
class Array<T> {
...
map<U>(callbackfn: (value: T, index: number, array: Array<T>) => U, thisArg?: any): Array<U>;
...
}
const arr = [1,2,3].map(() => 4);
|
{"value":"class Array<T> {\n ...\n map<U>(callbackfn: (value: T, index: number, array: Array<T>) => U, thisArg?: any): Array<U>;\n ...\n}\nconst arr = [1,2,3].map(() => 4); // No error, evaluates to [4,4,4]\n","tokens":[{"type":"T_CLASS","context":"normal","value":"class","line":1,"start":0,"end":5},{"type":"T_IDENTIFIER","context":"normal","value":"Array","line":1,"start":6,"end":11},{"type":"T_LESS_THAN","context":"type","value":"<","line":1,"start":11,"end":12},{"type":"T_IDENTIFIER","context":"type","value":"T","line":1,"start":12,"end":13},{"type":"T_GREATER_THAN","context":"type","value":">","line":1,"start":13,"end":14},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":15,"end":16},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":2,"start":19,"end":22},{"type":"T_IDENTIFIER","context":"normal","value":"map","line":3,"start":25,"end":28},{"type":"T_LESS_THAN","context":"type","value":"<","line":3,"start":28,"end":29},{"type":"T_IDENTIFIER","context":"type","value":"U","line":3,"start":29,"end":30},{"type":"T_GREATER_THAN","context":"type","value":">","line":3,"start":30,"end":31},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":31,"end":32},{"type":"T_IDENTIFIER","context":"normal","value":"callbackfn","line":3,"start":32,"end":42},{"type":"T_COLON","context":"type","value":":","line":3,"start":42,"end":43},{"type":"T_LPAREN","context":"type","value":"(","line":3,"start":44,"end":45},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":3,"start":45,"end":50},{"type":"T_COLON","context":"type","value":":","line":3,"start":50,"end":51},{"type":"T_IDENTIFIER","context":"type","value":"T","line":3,"start":52,"end":53},{"type":"T_COMMA","context":"type","value":",","line":3,"start":53,"end":54},{"type":"T_IDENTIFIER","context":"normal","value":"index","line":3,"start":55,"end":60},{"type":"T_COLON","context":"type","value":":","line":3,"start":60,"end":61},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":3,"start":62,"end":68},{"type":"T_COMMA","context":"type","value":",","line":3,"start":68,"end":69},{"type":"T_IDENTIFIER","context":"normal","value":"array","line":3,"start":70,"end":75},{"type":"T_COLON","context":"type","value":":","line":3,"start":75,"end":76},{"type":"T_IDENTIFIER","context":"type","value":"Array","line":3,"start":77,"end":82},{"type":"T_LESS_THAN","context":"type","value":"<","line":3,"start":82,"end":83},{"type":"T_IDENTIFIER","context":"type","value":"T","line":3,"start":83,"end":84},{"type":"T_GREATER_THAN","context":"type","value":">","line":3,"start":84,"end":85},{"type":"T_RPAREN","context":"type","value":")","line":3,"start":85,"end":86},{"type":"T_ARROW","context":"type","value":"=>","line":3,"start":87,"end":89},{"type":"T_IDENTIFIER","context":"type","value":"U","line":3,"start":90,"end":91},{"type":"T_COMMA","context":"normal","value":",","line":3,"start":91,"end":92},{"type":"T_IDENTIFIER","context":"normal","value":"thisArg","line":3,"start":93,"end":100},{"type":"T_PLING","context":"normal","value":"?","line":3,"start":100,"end":101},{"type":"T_COLON","context":"type","value":":","line":3,"start":101,"end":102},{"type":"T_ANY_TYPE","context":"type","value":"any","line":3,"start":103,"end":106},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":106,"end":107},{"type":"T_COLON","context":"type","value":":","line":3,"start":107,"end":108},{"type":"T_IDENTIFIER","context":"type","value":"Array","line":3,"start":109,"end":114},{"type":"T_LESS_THAN","context":"type","value":"<","line":3,"start":114,"end":115},{"type":"T_IDENTIFIER","context":"type","value":"U","line":3,"start":115,"end":116},{"type":"T_GREATER_THAN","context":"type","value":">","line":3,"start":116,"end":117},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":117,"end":118},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":4,"start":121,"end":124},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":125,"end":126},{"type":"T_CONST","context":"normal","value":"const","line":6,"start":127,"end":132},{"type":"T_IDENTIFIER","context":"normal","value":"arr","line":6,"start":133,"end":136},{"type":"T_ASSIGN","context":"normal","value":"=","line":6,"start":137,"end":138},{"type":"T_LBRACKET","context":"normal","value":"[","line":6,"start":139,"end":140},{"type":"T_NUMBER","context":"normal","value":"1","line":6,"start":140,"end":141},{"type":"T_COMMA","context":"normal","value":",","line":6,"start":141,"end":142},{"type":"T_NUMBER","context":"normal","value":"2","line":6,"start":142,"end":143},{"type":"T_COMMA","context":"normal","value":",","line":6,"start":143,"end":144},{"type":"T_NUMBER","context":"normal","value":"3","line":6,"start":144,"end":145},{"type":"T_RBRACKET","context":"normal","value":"]","line":6,"start":145,"end":146},{"type":"T_PERIOD","context":"normal","value":".","line":6,"start":146,"end":147},{"type":"T_IDENTIFIER","context":"normal","value":"map","line":6,"start":147,"end":150},{"type":"T_LPAREN","context":"normal","value":"(","line":6,"start":150,"end":151},{"type":"T_LPAREN","context":"normal","value":"(","line":6,"start":151,"end":152},{"type":"T_RPAREN","context":"normal","value":")","line":6,"start":152,"end":153},{"type":"T_ARROW","context":"normal","value":"=>","line":6,"start":154,"end":156},{"type":"T_NUMBER","context":"normal","value":"4","line":6,"start":157,"end":158},{"type":"T_RPAREN","context":"normal","value":")","line":6,"start":158,"end":159},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":159,"end":160},{"type":"Line","context":"comment","value":"// No error, evaluates to [4,4,4]","line":6,"start":161,"end":194}],"errors":[]}
In this example, () => number
is a subtype of (number, number, Array<number>) => number
.
Stubbing and variadic functions
This will, unfortunately, cause Flow to complain about stubs and variadic
functions which are written using arguments
. However, you can fix these by
using rest parameters
1
2
3
4
5
6
7
|
let log (...rest) => {};
function sum_all(...rest) {
let ret = 0;
for (let i = 0; i < rest.length; i++) { ret += rest[i]; }
return ret;
}
|
{"value":"let log (...rest) => {};\n\nfunction sum_all(...rest) {\n let ret = 0;\n for (let i = 0; i < rest.length; i++) { ret += rest[i]; }\n return ret;\n}\n","tokens":[{"type":"T_LET","context":"normal","value":"let","line":1,"start":0,"end":3},{"type":"T_IDENTIFIER","context":"normal","value":"log","line":1,"start":4,"end":7},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":8,"end":9},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":1,"start":9,"end":12},{"type":"T_IDENTIFIER","context":"normal","value":"rest","line":1,"start":12,"end":16},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":16,"end":17},{"type":"T_ARROW","context":"normal","value":"=>","line":1,"start":18,"end":20},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":21,"end":22},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":22,"end":23},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":23,"end":24},{"type":"T_FUNCTION","context":"normal","value":"function","line":3,"start":26,"end":34},{"type":"T_IDENTIFIER","context":"normal","value":"sum_all","line":3,"start":35,"end":42},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":42,"end":43},{"type":"T_ELLIPSIS","context":"normal","value":"...","line":3,"start":43,"end":46},{"type":"T_IDENTIFIER","context":"normal","value":"rest","line":3,"start":46,"end":50},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":50,"end":51},{"type":"T_LCURLY","context":"normal","value":"{","line":3,"start":52,"end":53},{"type":"T_LET","context":"normal","value":"let","line":4,"start":56,"end":59},{"type":"T_IDENTIFIER","context":"normal","value":"ret","line":4,"start":60,"end":63},{"type":"T_ASSIGN","context":"normal","value":"=","line":4,"start":64,"end":65},{"type":"T_NUMBER","context":"normal","value":"0","line":4,"start":66,"end":67},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":67,"end":68},{"type":"T_FOR","context":"normal","value":"for","line":5,"start":71,"end":74},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":75,"end":76},{"type":"T_LET","context":"normal","value":"let","line":5,"start":76,"end":79},{"type":"T_IDENTIFIER","context":"normal","value":"i","line":5,"start":80,"end":81},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":82,"end":83},{"type":"T_NUMBER","context":"normal","value":"0","line":5,"start":84,"end":85},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":85,"end":86},{"type":"T_IDENTIFIER","context":"normal","value":"i","line":5,"start":87,"end":88},{"type":"T_LESS_THAN","context":"normal","value":"<","line":5,"start":89,"end":90},{"type":"T_IDENTIFIER","context":"normal","value":"rest","line":5,"start":91,"end":95},{"type":"T_PERIOD","context":"normal","value":".","line":5,"start":95,"end":96},{"type":"T_IDENTIFIER","context":"normal","value":"length","line":5,"start":96,"end":102},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":102,"end":103},{"type":"T_IDENTIFIER","context":"normal","value":"i","line":5,"start":104,"end":105},{"type":"T_INCR","context":"normal","value":"++","line":5,"start":105,"end":107},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":107,"end":108},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":109,"end":110},{"type":"T_IDENTIFIER","context":"normal","value":"ret","line":5,"start":111,"end":114},{"type":"T_PLUS_ASSIGN","context":"normal","value":"+=","line":5,"start":115,"end":117},{"type":"T_IDENTIFIER","context":"normal","value":"rest","line":5,"start":118,"end":122},{"type":"T_LBRACKET","context":"normal","value":"[","line":5,"start":122,"end":123},{"type":"T_IDENTIFIER","context":"normal","value":"i","line":5,"start":123,"end":124},{"type":"T_RBRACKET","context":"normal","value":"]","line":5,"start":124,"end":125},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":125,"end":126},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":127,"end":128},{"type":"T_RETURN","context":"normal","value":"return","line":6,"start":131,"end":137},{"type":"T_IDENTIFIER","context":"normal","value":"ret","line":6,"start":138,"end":141},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":141,"end":142},{"type":"T_RCURLY","context":"normal","value":"}","line":7,"start":143,"end":144}],"errors":[]}
Rollout plan
Flow v0.46.0 will ship with strict function call arity turned off by default. It
can be enabled via your .flowconfig
with the flag
experimental.strict_call_arity=true
Flow v0.47.0 will ship with strict function call arity turned on and the
experimental.strict_call_arity
flag will be removed.
Why turn this on over two releases?
This decouples the switch to strict checking of function call arity from the
release.
Why not keep the experimental.strict_call_arity
flag?
This is a pretty core change. If we kept both behaviors, we’d have to test that
everything works with and without this change. As we add more flags, the number
of combinations grows exponentially, and Flow’s behavior gets harder to reason
about. For this reason, we’re choosing only one behavior: strict checking of
function call arity.
What do you think?
This change was motivated by feedback from Flow users. We really appreciate
all the members of our community who take the time to share their feedback with
us. This feedback is invaluable and helps us make Flow better, so please keep
it coming!