Skip to content

Commit

Permalink
Update coalesce precedence (#11017)
Browse files Browse the repository at this point in the history
* refactor: reimplement nullish coalescing precedence tracking

Co-authored-by: Toru Nagashima <public@mysticatea.dev>

* fix: Coalesce has same precedence with LogicalOR

* fix flow errors

Co-authored-by: Toru Nagashima <public@mysticatea.dev>
  • Loading branch information
2 people authored and nicolo-ribaudo committed Jan 17, 2020
1 parent 6648d62 commit 45301c5
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 59 deletions.
61 changes: 22 additions & 39 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -320,7 +320,7 @@ export default class ExpressionParser extends LValParser {
minPrec: number,
noIn: ?boolean,
): N.Expression {
const prec = this.state.type.binop;
let prec = this.state.type.binop;
if (prec != null && (!noIn || !this.match(tt._in))) {
if (prec > minPrec) {
const operator = this.state.value;
Expand All @@ -343,11 +343,17 @@ export default class ExpressionParser extends LValParser {
}

const op = this.state.type;
const logical = op === tt.logicalOR || op === tt.logicalAND;
const coalesce = op === tt.nullishCoalescing;

if (op === tt.pipeline) {
this.expectPlugin("pipelineOperator");
this.state.inPipeline = true;
this.checkPipelineAtInfixOperator(left, leftStartPos);
} else if (coalesce) {
// Handle the precedence of `tt.coalesce` as equal to the range of logical expressions.
// In other words, `node.right` shouldn't contain logical expressions in order to check the mixed error.
prec = ((tt.logicalAND: any): { binop: number }).binop;
}

this.next();
Expand All @@ -369,49 +375,26 @@ export default class ExpressionParser extends LValParser {
}

node.right = this.parseExprOpRightExpr(op, prec, noIn);

this.finishNode(
node,
logical || coalesce ? "LogicalExpression" : "BinaryExpression",
);
/* this check is for all ?? operators
* a ?? b && c for this example
* b && c => This is considered as a logical expression in the ast tree
* a => Identifier
* so for ?? operator we need to check in this case the right expression to have parenthesis
* second case a && b ?? c
* here a && b => This is considered as a logical expression in the ast tree
* c => identifier
* so now here for ?? operator we need to check the left expression to have parenthesis
* if the parenthesis is missing we raise an error and throw it
* when op is coalesce and nextOp is logical (&&), throw at the pos of nextOp that it can not be mixed.
* Symmetrically it also throws when op is logical and nextOp is coalesce
*/
if (op === tt.nullishCoalescing) {
if (
left.type === "LogicalExpression" &&
left.operator !== "??" &&
!(left.extra && left.extra.parenthesized)
) {
throw this.raise(
left.start,
`Nullish coalescing operator(??) requires parens when mixing with logical operators`,
);
} else if (
node.right.type === "LogicalExpression" &&
node.right.operator !== "??" &&
!(node.right.extra && node.right.extra.parenthesized)
) {
throw this.raise(
node.right.start,
`Nullish coalescing operator(??) requires parens when mixing with logical operators`,
);
}
const nextOp = this.state.type;
if (
(coalesce && (nextOp === tt.logicalOR || nextOp === tt.logicalAND)) ||
(logical && nextOp === tt.nullishCoalescing)
) {
throw this.raise(
this.state.start,
`Nullish coalescing operator(??) requires parens when mixing with logical operators`,
);
}

this.finishNode(
node,
op === tt.logicalOR ||
op === tt.logicalAND ||
op === tt.nullishCoalescing
? "LogicalExpression"
: "BinaryExpression",
);

return this.parseExprOp(
node,
leftStartPos,
Expand Down
30 changes: 15 additions & 15 deletions packages/babel-parser/src/tokenizer/types.js
Expand Up @@ -139,22 +139,22 @@ export const types: { [name: string]: TokenType } = {
tilde: new TokenType("~", { beforeExpr, prefix, startsExpr }),
pipeline: createBinop("|>", 0),
nullishCoalescing: createBinop("??", 1),
logicalOR: createBinop("||", 2),
logicalAND: createBinop("&&", 3),
bitwiseOR: createBinop("|", 4),
bitwiseXOR: createBinop("^", 5),
bitwiseAND: createBinop("&", 6),
equality: createBinop("==/!=/===/!==", 7),
relational: createBinop("</>/<=/>=", 8),
bitShift: createBinop("<</>>/>>>", 9),
plusMin: new TokenType("+/-", { beforeExpr, binop: 10, prefix, startsExpr }),
logicalOR: createBinop("||", 1),
logicalAND: createBinop("&&", 2),
bitwiseOR: createBinop("|", 3),
bitwiseXOR: createBinop("^", 4),
bitwiseAND: createBinop("&", 5),
equality: createBinop("==/!=/===/!==", 6),
relational: createBinop("</>/<=/>=", 7),
bitShift: createBinop("<</>>/>>>", 8),
plusMin: new TokenType("+/-", { beforeExpr, binop: 9, prefix, startsExpr }),
// startsExpr: required by v8intrinsic plugin
modulo: new TokenType("%", { beforeExpr, binop: 11, startsExpr }),
star: createBinop("*", 11),
slash: createBinop("/", 11),
modulo: new TokenType("%", { beforeExpr, binop: 10, startsExpr }),
star: createBinop("*", 10),
slash: createBinop("/", 10),
exponent: new TokenType("**", {
beforeExpr,
binop: 12,
binop: 11,
rightAssociative: true,
}),

Expand Down Expand Up @@ -189,8 +189,8 @@ export const types: { [name: string]: TokenType } = {
_null: createKeyword("null", { startsExpr }),
_true: createKeyword("true", { startsExpr }),
_false: createKeyword("false", { startsExpr }),
_in: createKeyword("in", { beforeExpr, binop: 8 }),
_instanceof: createKeyword("instanceof", { beforeExpr, binop: 8 }),
_in: createKeyword("in", { beforeExpr, binop: 7 }),
_instanceof: createKeyword("instanceof", { beforeExpr, binop: 7 }),
_typeof: createKeyword("typeof", { beforeExpr, prefix, startsExpr }),
_void: createKeyword("void", { beforeExpr, prefix, startsExpr }),
_delete: createKeyword("delete", { beforeExpr, prefix, startsExpr }),
Expand Down
Expand Up @@ -457,7 +457,7 @@
"isAssign": false,
"prefix": true,
"postfix": false,
"binop": 10,
"binop": 9,
"updateContext": null
},
"value": "+",
Expand Down
@@ -1,4 +1,4 @@
{
"plugins": ["estree"],
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:0)"
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:7)"
}
@@ -1,3 +1,3 @@
{
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:5)"
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:7)"
}
@@ -1,3 +1,3 @@
{
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:10)"
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:12)"
}
@@ -1,4 +1,4 @@
{
"plugins": ["estree"],
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:0)"
"throws": "Nullish coalescing operator(??) requires parens when mixing with logical operators (1:7)"
}

0 comments on commit 45301c5

Please sign in to comment.