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 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions src/plugins/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,45 @@ pp.flowParseTypeInitialiser = function (tok, allowLeadingPipeOrAnd) {
return type;
};

pp.flowParsePredicate = function() {
let node = this.startNode();
let moduloLoc = this.state.startLoc;
let moduloPos = this.state.start;
this.expect(tt.modulo);
let 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, "Unexpected token");
Copy link
Member

Choose a reason for hiding this comment

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

This line is not covered. Do you think we can have two tests that check if there are parse errors in these cases?

Copy link
Member

@danez danez Sep 2, 2016

Choose a reason for hiding this comment

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

You could also use this.unexpected(moduloPos); here. It's a little bit shorter but otherwise isn't really important if not changed.

}
if (this.match(tt.parenL)) {
this.next();
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.

Instead of

if (this.match(tt.parenL)) {
    this.next();

you can do

if (this.eat(tt.parenL)) {

Which is doing the exact same thing but is shorter. :)

node.expression = this.parseExpression();
this.expect(tt.parenR);
return this.finishNode(node, "DeclaredPredicate");
} else {
return this.finishNode(node, "InferredPredicate");
}
};

pp.flowParseTypeAndPredicateInitialiser = function () {
let 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)) {
predicate = this.flowParsePredicate();
} else {
type = this.flowParseType();
if (this.match(tt.modulo)) {
predicate = this.flowParsePredicate();
}
}
this.state.inType = oldInType;
return [type, predicate];
};

pp.flowParseDeclareClass = function (node) {
this.next();
this.flowParseInterfaceish(node, true);
Expand All @@ -46,7 +85,7 @@ pp.flowParseDeclareFunction = function (node) {
typeNode.params = tmp.params;
typeNode.rest = tmp.rest;
this.expect(tt.parenR);
typeNode.returnType = this.flowParseTypeInitialiser();
[typeNode.returnType, node.predicate] = this.flowParseTypeAndPredicateInitialiser();

typeContainer.typeAnnotation = this.finishNode(typeNode, "FunctionTypeAnnotation");
id.typeAnnotation = this.finishNode(typeContainer, "TypeAnnotation");
Expand Down Expand Up @@ -674,6 +713,13 @@ pp.flowParseTypeAnnotation = function () {
return this.finishNode(node, "TypeAnnotation");
};

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

pp.flowParseTypeAnnotatableIdentifier = function (requireTypeAnnotation, canBeOptionalParam) {

let ident = this.parseIdentifier();
Expand Down Expand Up @@ -715,7 +761,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, node.predicate] = this.flowParseTypeAndPredicateAnnotation();
}

return inner.call(this, node, allowExpression);
Expand Down Expand Up @@ -1161,10 +1207,11 @@ export default function (instance) {
if (this.match(tt.colon)) {
let state = this.state.clone();
try {
let returnType = this.flowParseTypeAnnotation();
let [returnType, predicate] = this.flowParseTypeAndPredicateAnnotation();
if (!this.match(tt.arrow)) this.unexpected();
// assign after it is clear it is an arrow
node.returnType = returnType;
node.predicate = predicate;
} catch (err) {
if (err instanceof SyntaxError) {
this.state = state;
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/flow/predicates/1/actual.js
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
var f = (x: mixed): %checks => typeof x === "string";
Loading