From 322842abe7b9fd0ece731eaa2238edeaaa315876 Mon Sep 17 00:00:00 2001 From: "William C. Johnson" Date: Mon, 9 Oct 2017 00:10:28 -0400 Subject: [PATCH] Flow typecasts require parentheses Addresses https://github.com/wcjohnson/lightscript/issues/22 --- src/parser/expression.js | 5 +- src/plugins/flow.js | 44 +++-- test/fixtures/flow/typecasts/array/actual.js | 1 + .../flow/typecasts/array/expected.json | 150 ++++++++++++++++ test/fixtures/flow/typecasts/call/actual.js | 1 + .../flow/typecasts/call/expected.json | 167 ++++++++++++++++++ .../flow/typecasts/illegal-array/actual.js | 1 + .../flow/typecasts/illegal-array/options.json | 3 + .../flow/typecasts/illegal-call-2/actual.js | 1 + .../typecasts/illegal-call-2/options.json | 3 + .../flow/typecasts/illegal-call/actual.js | 1 + .../flow/typecasts/illegal-call/options.json | 3 + 12 files changed, 356 insertions(+), 24 deletions(-) create mode 100644 test/fixtures/flow/typecasts/array/actual.js create mode 100644 test/fixtures/flow/typecasts/array/expected.json create mode 100644 test/fixtures/flow/typecasts/call/actual.js create mode 100644 test/fixtures/flow/typecasts/call/expected.json create mode 100644 test/fixtures/flow/typecasts/illegal-array/actual.js create mode 100644 test/fixtures/flow/typecasts/illegal-array/options.json create mode 100644 test/fixtures/flow/typecasts/illegal-call-2/actual.js create mode 100644 test/fixtures/flow/typecasts/illegal-call-2/options.json create mode 100644 test/fixtures/flow/typecasts/illegal-call/actual.js create mode 100644 test/fixtures/flow/typecasts/illegal-call/options.json diff --git a/src/parser/expression.js b/src/parser/expression.js index 7428740576..29d25a8b7e 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -944,6 +944,10 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow if (refShorthandDefaultPos.start) this.unexpected(refShorthandDefaultPos.start); if (refNeedsArrowPos.start) this.unexpected(refNeedsArrowPos.start); + if (this.hasPlugin("flow")) { + this.flowExprListToCast(exprList); + } + if (exprList.length > 1) { val = this.startNodeAt(innerStartPos, innerStartLoc); val.expressions = exprList; @@ -953,7 +957,6 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow val = exprList[0]; } - this.addExtra(val, "parenthesized", true); this.addExtra(val, "parenStart", startPos); diff --git a/src/plugins/flow.js b/src/plugins/flow.js index bc1fd5ec44..142070986f 100644 --- a/src/plugins/flow.js +++ b/src/plugins/flow.js @@ -862,6 +862,21 @@ pp.flowParseVariance = function() { return variance; }; +pp.flowExprListToCast = function(exprList) { + // A flow typecast is a parenthesized expr list of exactly length 1 with + // a TypeCastish in the first slot; anything else involving TypeCastish + // is illegal. + if (exprList.length === 1 && exprList[0].type === "TypeCastish") { + exprList[0].type = "TypeCastExpression"; + } else { + exprList.forEach((expr) => { + if (expr.type === "TypeCastish") { + this.unexpected(expr.typeAnnotation.pos); + } + }); + } +}; + export default function (instance) { // plain function return types: function name(): string {} instance.extend("parseFunctionBody", function (inner) { @@ -962,7 +977,7 @@ export default function (instance) { typeCastNode.expression = node; typeCastNode.typeAnnotation = this.flowParseTypeAnnotation(); - return this.finishNode(typeCastNode, "TypeCastExpression"); + return this.finishNode(typeCastNode, "TypeCastish"); } return node; @@ -1058,7 +1073,7 @@ export default function (instance) { instance.extend("toAssignable", function (inner) { return function (node, isBinding, contextDescription) { - if (node.type === "TypeCastExpression") { + if (node.type === "TypeCastish") { return inner.call(this, this.typeCastToParameter(node), isBinding, contextDescription); } else if (this.hasPlugin("existentialExpression") && node.type === "ExistentialExpression") { // Existential expression looks like an optional flow parameter @@ -1074,7 +1089,7 @@ export default function (instance) { return function (exprList, isBinding, contextDescription) { for (let i = 0; i < exprList.length; i++) { const expr = exprList[i]; - if (expr && expr.type === "TypeCastExpression") { + if (expr && expr.type === "TypeCastish") { exprList[i] = this.typeCastToParameter(expr); } else if (this.hasPlugin("existentialExpression") && expr && expr.type === "ExistentialExpression") { // Existential expression looks like an optional flow parameter @@ -1091,8 +1106,8 @@ export default function (instance) { return function (exprList) { for (let i = 0; i < exprList.length; i++) { const expr = exprList[i]; - if (expr && expr._exprListItem && expr.type === "TypeCastExpression") { - this.raise(expr.start, "Unexpected type cast"); + if (expr && expr.type === "TypeCastish") { + this.raise(expr.typeAnnotation.start, "Unexpected type cast"); } } @@ -1100,26 +1115,9 @@ export default function (instance) { }; }); - // parse an item inside a expression list eg. `(NODE, NODE)` where NODE represents - // the position where this function is called - instance.extend("parseExprListItem", function (inner) { - return function (...args) { - const container = this.startNode(); - const node = inner.call(this, ...args); - if (this.match(tt.colon)) { - container._exprListItem = true; - container.expression = node; - container.typeAnnotation = this.flowParseTypeAnnotation(); - return this.finishNode(container, "TypeCastExpression"); - } else { - return node; - } - }; - }); - instance.extend("checkLVal", function (inner) { return function (node) { - if (node.type !== "TypeCastExpression") { + if (node.type !== "TypeCastish") { return inner.apply(this, arguments); } }; diff --git a/test/fixtures/flow/typecasts/array/actual.js b/test/fixtures/flow/typecasts/array/actual.js new file mode 100644 index 0000000000..3ca0685c31 --- /dev/null +++ b/test/fixtures/flow/typecasts/array/actual.js @@ -0,0 +1 @@ +[(castee: CastTo)] diff --git a/test/fixtures/flow/typecasts/array/expected.json b/test/fixtures/flow/typecasts/array/expected.json new file mode 100644 index 0000000000..357d9e0a91 --- /dev/null +++ b/test/fixtures/flow/typecasts/array/expected.json @@ -0,0 +1,150 @@ +{ + "type": "File", + "start": 0, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "sourceType": "module", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "expression": { + "type": "ArrayExpression", + "start": 0, + "end": 18, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "elements": [ + { + "type": "TypeCastExpression", + "start": 2, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "expression": { + "type": "Identifier", + "start": 2, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 8 + }, + "identifierName": "castee" + }, + "name": "castee" + }, + "typeAnnotation": { + "type": "TypeAnnotation", + "start": 8, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "typeAnnotation": { + "type": "GenericTypeAnnotation", + "start": 10, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "typeParameters": null, + "id": { + "type": "Identifier", + "start": 10, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 16 + }, + "identifierName": "CastTo" + }, + "name": "CastTo" + } + } + }, + "extra": { + "parenthesized": true, + "parenStart": 1 + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/flow/typecasts/call/actual.js b/test/fixtures/flow/typecasts/call/actual.js new file mode 100644 index 0000000000..58934c7a32 --- /dev/null +++ b/test/fixtures/flow/typecasts/call/actual.js @@ -0,0 +1 @@ +callee((castee: CastTo)) diff --git a/test/fixtures/flow/typecasts/call/expected.json b/test/fixtures/flow/typecasts/call/expected.json new file mode 100644 index 0000000000..b42a1f4d3d --- /dev/null +++ b/test/fixtures/flow/typecasts/call/expected.json @@ -0,0 +1,167 @@ +{ + "type": "File", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "sourceType": "module", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "expression": { + "type": "CallExpression", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "callee": { + "type": "Identifier", + "start": 0, + "end": 6, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 6 + }, + "identifierName": "callee" + }, + "name": "callee" + }, + "arguments": [ + { + "type": "TypeCastExpression", + "start": 8, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 22 + } + }, + "expression": { + "type": "Identifier", + "start": 8, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 14 + }, + "identifierName": "castee" + }, + "name": "castee" + }, + "typeAnnotation": { + "type": "TypeAnnotation", + "start": 14, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 14 + }, + "end": { + "line": 1, + "column": 22 + } + }, + "typeAnnotation": { + "type": "GenericTypeAnnotation", + "start": 16, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 22 + } + }, + "typeParameters": null, + "id": { + "type": "Identifier", + "start": 16, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 22 + }, + "identifierName": "CastTo" + }, + "name": "CastTo" + } + } + }, + "extra": { + "parenthesized": true, + "parenStart": 7 + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/flow/typecasts/illegal-array/actual.js b/test/fixtures/flow/typecasts/illegal-array/actual.js new file mode 100644 index 0000000000..fbb280c67e --- /dev/null +++ b/test/fixtures/flow/typecasts/illegal-array/actual.js @@ -0,0 +1 @@ +[castee: CastTo] diff --git a/test/fixtures/flow/typecasts/illegal-array/options.json b/test/fixtures/flow/typecasts/illegal-array/options.json new file mode 100644 index 0000000000..f95a009d6a --- /dev/null +++ b/test/fixtures/flow/typecasts/illegal-array/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Unexpected type cast (1:7)" +} diff --git a/test/fixtures/flow/typecasts/illegal-call-2/actual.js b/test/fixtures/flow/typecasts/illegal-call-2/actual.js new file mode 100644 index 0000000000..b9dbb27d70 --- /dev/null +++ b/test/fixtures/flow/typecasts/illegal-call-2/actual.js @@ -0,0 +1 @@ +callee(castee: CastTo, castee2: CastTo2) diff --git a/test/fixtures/flow/typecasts/illegal-call-2/options.json b/test/fixtures/flow/typecasts/illegal-call-2/options.json new file mode 100644 index 0000000000..2377a3924c --- /dev/null +++ b/test/fixtures/flow/typecasts/illegal-call-2/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Unexpected type cast (1:13)" +} diff --git a/test/fixtures/flow/typecasts/illegal-call/actual.js b/test/fixtures/flow/typecasts/illegal-call/actual.js new file mode 100644 index 0000000000..cd1cd4e0ea --- /dev/null +++ b/test/fixtures/flow/typecasts/illegal-call/actual.js @@ -0,0 +1 @@ +callee(castee: CastTo) diff --git a/test/fixtures/flow/typecasts/illegal-call/options.json b/test/fixtures/flow/typecasts/illegal-call/options.json new file mode 100644 index 0000000000..2377a3924c --- /dev/null +++ b/test/fixtures/flow/typecasts/illegal-call/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Unexpected type cast (1:13)" +}