What is the exact parsing precedence of arrow function (fat arrow =>) in Javascript?
As you say, =>
is not an operator. Arrow functions are primary syntax.
The rules for them are defined in the specification, starting with the ArrowFunction production. ArrowFunction is defined as ArrowParameters followed by =>
followed by the misleadingly-named ConciseBody. ConciseBody has two forms. You're asking about the form using ExpressionBody, which is where the first non-whitespace token after =>
isn't {
. If there were an opening curly brace there it would denote the opening of a block called a FunctionBody instead.
The ExpressionBody definition is quite simple: it's an AssignmentExpression.
Which takes us into very familiar territory, because AssignmentExpression is the syntax for the right-hand side of an assignment (or variable initializer), entries in an array initializer, the value part of a property initializer, an argument to a function, etc. So whatever follows the =>
in a concise body has the same parsing rules as what we put where AssignmentExpression is below:
x = AssignmentExpression;
y = AssignmentExpression, z = AssignmentExpression;
a1 = [AssignmentExpression];
a2 = [AssignmentExpression, AssignmentExpression];
o1 = {foo: AssignmentExpression};
o2 = {foo: AssignmentExpression, bar: AssignmentExpression};
doSomething(AssignmentExpression);
doSomething(AssignmentExpression, AssignmentExpression);
Just for detail, an AssignmentExpression is any of:
- ConditionalExpression (as in your example)
- YieldExpression
- ArrowFunction
- AsyncArrowFunction
- LeftHandSideExpression = AssignmentExpression
- LeftHandSideExpression AssignmentOperator AssignmentExpression
(You may wonder, as I did, how the y
in x = y
can match AssignmentExpression given the definition of it above, since y
is clearly an Identifier and none of those looks like it will lead to the Identifier production. This is where specs can be really hard to read. It turns out that you get there if you keep going long enough. The path is (deep breath): AssignmentExpression →
ConditionalExpression →
LogicalORExpression →
LogicalANDExpression →
BitwiseORExpression →
BitwiseXORExpression →
BitwiseANDExpression →
EqualityExpression →
RelationalExpression →
ShiftExpression →
AdditiveExpression →
MultiplicativeExpression →
ExponentiationExpression →
UnaryExpression →
UpdateExpression →
LeftHandSideExpression →
NewExpression →
MemberExpression →
PrimaryExpression →
IdentifierReference →
Identifier — whew! [mops brow]. Thank you Oriol!)
It does not further elaborate on the special parsing rules.
They're not that special, they're just different. A function
expression is a so-called primary expression that can occur anywhere in code. It's very clear from its syntactical form function …(…) {…}
what elements it consists of, explicitly delimiting start and end.
In contrast, an arrow function can have an expression body without using a block, so it is not delimited explicitly. We therefore need restrictions on what things can appear inside the body of an arrow function and what things can not, to resolve ambiguity in a sequence of tokens containing a =>
.
So my question is, what is the rule of precedence regarding arrow functions?
Basically, =>
has the same "precedence" as the =
assignment operator.
(We just don't like to call =>
an operator, as it constructs a value instead of computing a result after evaluating its operands)
This means that we can put an arrow function in any place where an assignment could occur:
- as element values in array or object literals
- as default initialisers (destructuring, function parameters)
- as variable initialisers
- as expression statements or in loop headers
- as function arguments
- as grouped expressions
Those are all unambiguous. The ambiguous cases are more interesting:
- at the right hand side of an assignment
- at the right hand side of the conditional operator
- at the right hand side of the
yield
operator - at any side of the comma operator
Having to put an arrow function in a place where an assignment would be valid also means that we cannot put it (directly, without grouping) as an operand of any other (higher-precedence) operator - most notably not as an operand of logical expressions (||
, &&
).
The body of the arrow function can be an assignment expression (including anything of higher precedence) again, so the =>
is right-associative (just like =
). The body will be parsed greedily (the expression is made as long as possible) so that it will contain basically all expressions on the right hand side of the =>
.
The only things that cannot form an arrow function's concise body are a yield
expression (not allowed outside generator functions anyway) and a comma expression.