Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

[Flow] Function predicate declaration #103

Merged
merged 6 commits into from Feb 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 50 additions & 4 deletions src/plugins/flow.js
Expand Up @@ -16,6 +16,45 @@ pp.flowParseTypeInitialiser = function (tok) {
return type;
};

pp.flowParsePredicate = function() {
const node = this.startNode();
const moduloLoc = this.state.startLoc;
const moduloPos = this.state.start;
this.expect(tt.modulo);
const checksLoc = this.state.startLoc;
this.expectContextual("checks");
// Force '%' and 'checks' to be adjacent
if (moduloLoc.line !== checksLoc.line || moduloLoc.column !== checksLoc.column - 1) {
this.raise(moduloPos, "Spaces between ´%´ and ´checks´ are not allowed here.");
}
if (this.eat(tt.parenL)) {
node.expression = this.parseExpression();
this.expect(tt.parenR);
return this.finishNode(node, "DeclaredPredicate");
} else {
return this.finishNode(node, "InferredPredicate");
}
};

pp.flowParseTypeAndPredicateInitialiser = function () {
const oldInType = this.state.inType;
this.state.inType = true;
Copy link
Member

@danez danez Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.state.inType seems to be only necessary for this.flowParseType(), so maybe we could just do this two lines in the else branch? Then the reset only has to happen there either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this.state.inType needs to be true for this.expect(tt.colon) to succeed (I tried to move it further down, but other tests failed). So, oldInType needs to be recorded in the beginning in order to be restored before this.flowParsePredicate() is called (note that flowParsePredicate possibly parses expressions).

Bottom line is I wasn't able to perform the optimization you suggested.

this.expect(tt.colon);
let type = null;
let predicate = null;
if (this.match(tt.modulo)) {
this.state.inType = oldInType;
predicate = this.flowParsePredicate();
} else {
type = this.flowParseType();
this.state.inType = oldInType;
if (this.match(tt.modulo)) {
predicate = this.flowParsePredicate();
}
}
return [type, predicate];
};

pp.flowParseDeclareClass = function (node) {
this.next();
this.flowParseInterfaceish(node, true);
Expand All @@ -41,9 +80,10 @@ pp.flowParseDeclareFunction = function (node) {
typeNode.params = tmp.params;
typeNode.rest = tmp.rest;
this.expect(tt.parenR);
typeNode.returnType = this.flowParseTypeInitialiser();

let predicate = null;
[typeNode.returnType, predicate] = this.flowParseTypeAndPredicateInitialiser();
typeContainer.typeAnnotation = this.finishNode(typeNode, "FunctionTypeAnnotation");
typeContainer.predicate = predicate;
id.typeAnnotation = this.finishNode(typeContainer, "TypeAnnotation");

this.finishNode(id, id.type);
Expand Down Expand Up @@ -758,6 +798,12 @@ pp.flowParseTypeAnnotation = function () {
return this.finishNode(node, "TypeAnnotation");
};

pp.flowParseTypeAndPredicateAnnotation = function () {
const node = this.startNode();
[node.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser();
return this.finishNode(node, "TypeAnnotation");
};

pp.flowParseTypeAnnotatableIdentifier = function () {
const ident = this.parseIdentifier();
if (this.match(tt.colon)) {
Expand Down Expand Up @@ -798,7 +844,7 @@ export default function (instance) {
if (this.match(tt.colon) && !allowExpression) {
// if allowExpression is true then we're parsing an arrow function and if
// there's a return type then it's been handled elsewhere
node.returnType = this.flowParseTypeAnnotation();
node.returnType = this.flowParseTypeAndPredicateAnnotation();
}

return inner.call(this, node, allowExpression);
Expand Down Expand Up @@ -1340,7 +1386,7 @@ export default function (instance) {
try {
const oldNoAnonFunctionType = this.state.noAnonFunctionType;
this.state.noAnonFunctionType = true;
const returnType = this.flowParseTypeAnnotation();
const returnType = this.flowParseTypeAndPredicateAnnotation();
this.state.noAnonFunctionType = oldNoAnonFunctionType;

if (this.canInsertSemicolon()) this.unexpected();
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/flow/predicates/1/actual.js
@@ -0,0 +1 @@
declare function foo(x: mixed): boolean %checks(x !== null);
226 changes: 226 additions & 0 deletions test/fixtures/flow/predicates/1/expected.json
@@ -0,0 +1,226 @@
{
"type": "File",
"start": 0,
"end": 60,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 60
}
},
"program": {
"type": "Program",
"start": 0,
"end": 60,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 60
}
},
"sourceType": "module",
"body": [
{
"type": "DeclareFunction",
"start": 0,
"end": 60,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 60
}
},
"id": {
"type": "Identifier",
"start": 17,
"end": 59,
"loc": {
"start": {
"line": 1,
"column": 17
},
"end": {
"line": 1,
"column": 59
},
"identifierName": "foo"
},
"name": "foo",
"typeAnnotation": {
"type": "TypeAnnotation",
"start": 20,
"end": 59,
"loc": {
"start": {
"line": 1,
"column": 20
},
"end": {
"line": 1,
"column": 59
}
},
"typeAnnotation": {
"type": "FunctionTypeAnnotation",
"start": 20,
"end": 59,
"loc": {
"start": {
"line": 1,
"column": 20
},
"end": {
"line": 1,
"column": 59
}
},
"typeParameters": null,
"params": [
{
"type": "FunctionTypeParam",
"start": 21,
"end": 29,
"loc": {
"start": {
"line": 1,
"column": 21
},
"end": {
"line": 1,
"column": 29
}
},
"name": {
"type": "Identifier",
"start": 21,
"end": 22,
"loc": {
"start": {
"line": 1,
"column": 21
},
"end": {
"line": 1,
"column": 22
},
"identifierName": "x"
},
"name": "x"
},
"optional": false,
"typeAnnotation": {
"type": "MixedTypeAnnotation",
"start": 24,
"end": 29,
"loc": {
"start": {
"line": 1,
"column": 24
},
"end": {
"line": 1,
"column": 29
}
}
}
}
],
"rest": null,
"returnType": {
"type": "BooleanTypeAnnotation",
"start": 32,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 32
},
"end": {
"line": 1,
"column": 39
}
}
}
},
"predicate": {
"type": "DeclaredPredicate",
"start": 40,
"end": 59,
"loc": {
"start": {
"line": 1,
"column": 40
},
"end": {
"line": 1,
"column": 59
}
},
"expression": {
"type": "BinaryExpression",
"start": 48,
"end": 58,
"loc": {
"start": {
"line": 1,
"column": 48
},
"end": {
"line": 1,
"column": 58
}
},
"left": {
"type": "Identifier",
"start": 48,
"end": 49,
"loc": {
"start": {
"line": 1,
"column": 48
},
"end": {
"line": 1,
"column": 49
},
"identifierName": "x"
},
"name": "x"
},
"operator": "!==",
"right": {
"type": "NullLiteral",
"start": 54,
"end": 58,
"loc": {
"start": {
"line": 1,
"column": 54
},
"end": {
"line": 1,
"column": 58
}
}
}
}
}
}
}
}
],
"directives": []
}
}
1 change: 1 addition & 0 deletions test/fixtures/flow/predicates/2/actual.js
@@ -0,0 +1 @@
var f = (x: mixed): %checks => typeof x === "string";