Skip to content

Commit

Permalink
[fix] Parsing function predicates
Browse files Browse the repository at this point in the history
Summary:
Function predicates are parsed as part of the return type (following the `:`).
This simplifies things cause now predicates are NOT part of the language, but
only the type language. The accepted relevant grammar is now:

  FunctionReturnType :=
    Type
    Predicate
    Type Predicate

  Predicate :=
    %checks
    %checks ( ConditionalExpression )

Reviewed By: gabelevi

Differential Revision: D3704831

fbshipit-source-id: 545a77846fd1e6597e194dca39d902dd53427adb
  • Loading branch information
panagosg7 authored and Facebook Github Bot 8 committed Sep 2, 2016
1 parent b6c7f3d commit 1ae591f
Show file tree
Hide file tree
Showing 37 changed files with 192 additions and 161 deletions.
2 changes: 1 addition & 1 deletion src/parser/estree_translator.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1221,7 +1221,7 @@ end with type t = Impl.t) = struct
|]
)

and predicate (loc, p) = Ast.Predicate.(
and predicate (loc, p) = Ast.Type.Predicate.(
let _type, value = match p with
| Declared e -> "DeclaredPredicate", [|"value", expression e|]
| Inferred -> "InferredPredicate", [||]
Expand Down
3 changes: 3 additions & 0 deletions src/parser/lexer_flow.mll
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ module Token = struct
| T_OF
| T_ASYNC
| T_AWAIT
| T_CHECKS
(* Operators *)
| T_RSHIFT3_ASSIGN
| T_RSHIFT_ASSIGN
Expand Down Expand Up @@ -211,6 +212,7 @@ module Token = struct
| T_OF -> "T_OF"
| T_ASYNC -> "T_ASYNC"
| T_AWAIT -> "T_AWAIT"
| T_CHECKS -> "T_CHECKS"
| T_LCURLY -> "T_LCURLY"
| T_RCURLY -> "T_RCURLY"
| T_LCURLYBAR -> "T_LCURLYBAR"
Expand Down Expand Up @@ -1090,6 +1092,7 @@ and type_token env = parse
try env, Hashtbl.find type_keywords word
with Not_found -> env, T_IDENTIFIER
}
| "%checks" { env, T_CHECKS }
(* Syntax *)
| "[" { env, T_LBRACKET }
| "]" { env, T_RBRACKET }
Expand Down
108 changes: 55 additions & 53 deletions src/parser/parser_flow.ml
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,6 @@ let string_starts_with long short =
with Invalid_argument _ ->
false

(* NOTE: temporary support for predicated function definitions requires
that the name starts with `$pred`. In later iterations we would like to
add support for a more compact solutions (e.g. `function?(...)`)
*)
let is_predicate = function
| Some id -> string_starts_with (name_of_id id) "$pred$"
| _ -> false

module rec Parse : sig
val program : env -> Ast.program
val statement : env -> Ast.Statement.t
Expand All @@ -76,6 +68,7 @@ module rec Parse : sig
val statement_list_with_directives : term_fn:(Token.t -> bool) -> env -> Ast.Statement.t list * bool
val module_body : term_fn:(Token.t -> bool) -> env -> Ast.Statement.t list
val expression : env -> Ast.Expression.t
val conditional : env -> Ast.Expression.t
val assignment : env -> Ast.Expression.t
val object_initializer : env -> Loc.t * Ast.Expression.Object.t
val array_initializer : env -> Loc.t * Ast.Expression.Array.t
Expand All @@ -91,7 +84,6 @@ module rec Parse : sig
val class_declaration : env -> Ast.Expression.t list -> Ast.Statement.t
val class_expression : env -> Ast.Expression.t
val is_assignable_lhs : Ast.Expression.t -> bool
val predicate : env -> Ast.Predicate.t option
end = struct
module Type : sig
val _type : env -> Ast.Type.t
Expand All @@ -103,6 +95,8 @@ end = struct
val function_param_list : env -> Type.Function.Param.t option * Type.Function.Param.t list
val annotation : env -> Ast.Type.annotation
val annotation_opt : env -> Ast.Type.annotation option
val predicate_opt : env -> Ast.Type.Predicate.t option
val annotation_and_predicate_opt : env -> Ast.Type.annotation option * Ast.Type.Predicate.t option
end = struct
type param_list_or_type =
| ParamList of (Type.Function.Param.t option * Type.Function.Param.t list)
Expand Down Expand Up @@ -643,6 +637,37 @@ end = struct
| T_COLON -> Some (annotation env)
| _ -> None

let predicate env =
let checks_loc = Peek.loc env in
Expect.token env T_CHECKS;
if Peek.token env = T_LPAREN then begin
Expect.token env T_LPAREN;
Eat.push_lex_mode env Lex_mode.NORMAL;
let exp = Parse.conditional env in
Eat.pop_lex_mode env;
let rparen_loc = Peek.loc env in
Expect.token env T_RPAREN;
let loc = Loc.btwn checks_loc rparen_loc in
(loc, Ast.Type.Predicate.Declared exp)
end else
(checks_loc, Ast.Type.Predicate.Inferred)

let predicate_opt env =
match Peek.token env with
| T_CHECKS -> Some (predicate env)
| _ -> None

let annotation_and_predicate_opt env =
match Peek.token env, Peek.token ~i:1 env with
| T_COLON, T_CHECKS ->
Expect.token env T_COLON;
(None, predicate_opt env)
| T_COLON, _ ->
let annotation = annotation_opt env in
let predicate = predicate_opt env in
(annotation, predicate)
| _ -> None, None

let wrap f env =
let env = env |> with_strict true in
Eat.push_lex_mode env Lex_mode.TYPE;
Expand All @@ -661,6 +686,8 @@ end = struct
let function_param_list = wrap function_param_list
let annotation = wrap annotation
let annotation_opt = wrap annotation_opt
let predicate_opt = wrap predicate_opt
let annotation_and_predicate_opt = wrap annotation_and_predicate_opt
let generic = wrap generic
end

Expand Down Expand Up @@ -835,8 +862,7 @@ end = struct
(Type.type_parameter_declaration env, Some id)
) in
let params, defaults, rest = function_params env in
let returnType = Type.annotation_opt env in
let predicate = Parse.predicate env in
let (returnType, predicate) = Type.annotation_and_predicate_opt env in
let _, body, strict = function_body env ~async ~generator in
let simple = is_simple_function_params params defaults rest in
strict_post_check env ~strict ~simple id params;
Expand Down Expand Up @@ -997,31 +1023,26 @@ end = struct
| T_ARROW (* x => 123 *)
| T_COLON -> (* (x): number => 123 *)
raise Try.Rollback
| _ when Peek.is_identifier env ->
(* (x): number checks => 123 *)
if Peek.value env = "checks" then
(* async x => 123 -- and we've already parsed async as an identifier
* expression *)
| _ when Peek.is_identifier env -> begin match snd ret with
| Expression.Identifier (_, {Identifier.name = "async"; _ })
when not (Peek.is_line_terminator env) ->
raise Try.Rollback
(* async x => 123 -- and we've already parsed async as an identifier
* expression *)
else begin match snd ret with
| Expression.Identifier (_, {Identifier.name = "async"; _ })
when not (Peek.is_line_terminator env) ->
raise Try.Rollback
| _ -> ret
end
| _ -> ret
end
| _ -> ret

in fun env ->
match Peek.token env, Peek.is_identifier env with
| T_YIELD, _ when (allow_yield env) -> yield env
| T_LPAREN, _
| T_LESS_THAN, _
| _, true ->

(* Ok, we don't know if this is going to be an arrow function of a
* regular assignment expression. Let's first try to parse it as an
* assignment expression. If that fails we'll try an arrow function.
*)
(* Ok, we don't know if this is going to be an arrow function or a
* regular assignment expression. Let's first try to parse it as an
* assignment expression. If that fails we'll try an arrow function.
*)
(match Try.to_parse env try_assignment_but_not_arrow_function with
| Try.ParsedSuccessfully expr -> expr
| Try.FailedToParse ->
Expand Down Expand Up @@ -1493,8 +1514,7 @@ end = struct
id, Type.type_parameter_declaration env
end in
let params, defaults, rest = Declaration.function_params env in
let returnType = Type.annotation_opt env in
let predicate = Parse.predicate env in
let returnType, predicate = Type.annotation_and_predicate_opt env in
let end_loc, body, strict =
Declaration.function_body env ~async ~generator in
let simple = Declaration.is_simple_function_params params defaults rest in
Expand Down Expand Up @@ -1739,19 +1759,18 @@ end = struct
* that it's an async function *)
let async = Peek.token ~i:1 env <> T_ARROW && Declaration.async env in
let typeParameters = Type.type_parameter_declaration env in
let params, defaults, rest, returnType =
let params, defaults, rest, returnType, predicate =
(* Disallow all fancy features for identifier => body *)
if Peek.is_identifier env && typeParameters = None
then
let id =
Parse.identifier ~restricted_error:Error.StrictParamName env in
let param = fst id, Pattern.Identifier id in
[param], [], None, None
[param], [], None, None, None
else
let params, defaults, rest = Declaration.function_params env in
params, defaults, rest, Type.annotation_opt env in

let predicate = Parse.predicate env in
let returnType, predicate = Type.annotation_and_predicate_opt env in
params, defaults, rest, returnType, predicate in

(* It's hard to tell if an invalid expression was intended to be an
* arrow function before we see the =>. If there are no params, that
Expand Down Expand Up @@ -1791,7 +1810,6 @@ end = struct
body;
async;
generator = false; (* arrow functions cannot be generators *)
(* TODO: add syntax support for arrow predicates *)
predicate;
expression;
returnType;
Expand Down Expand Up @@ -2996,6 +3014,7 @@ end = struct
Expect.token env T_COLON;
let returnType = Type._type env in
let end_loc = fst returnType in
let predicate = Type.predicate_opt env in
let loc = Loc.btwn start_sig_loc end_loc in
let value = loc, Ast.Type.(Function {Function.
params;
Expand All @@ -3011,7 +3030,6 @@ end = struct
let end_loc = match Peek.semicolon_loc env with
| None -> end_loc
| Some end_loc -> end_loc in
let predicate = Parse.predicate env in
Eat.semicolon env;
let loc = Loc.btwn start_loc end_loc in
loc, Statement.DeclareFunction.({ id; predicate })
Expand Down Expand Up @@ -4342,6 +4360,7 @@ end = struct
| _ ->
expr

and conditional = Expression.conditional
and assignment = Expression.assignment
and object_initializer = Object._initializer
and object_key = Object.key
Expand Down Expand Up @@ -4428,23 +4447,6 @@ end = struct
and pattern = Pattern.pattern
and pattern_from_expr = Pattern.from_expr

and predicate env =
let checks_loc = Peek.loc env in
if Peek.token env = T_IDENTIFIER && Peek.value env = "checks" then (
Expect.token env T_IDENTIFIER;
if Expect.maybe env T_LPAREN then (
let exp = Parse.expression env in
let rparen_loc = Peek.loc env in
Expect.token env T_RPAREN;
let loc = Loc.btwn checks_loc rparen_loc in
Some (loc, Predicate.Declared exp)
)
else
Some (checks_loc, Predicate.Inferred)
)
else
None

end

(*****************************************************************************)
Expand Down
19 changes: 10 additions & 9 deletions src/parser/spider_monkey_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ and Type : sig
params: Type.t list;
}
end

module Predicate : sig
type t = Loc.t * t'
and t' =
| Declared of Expression.t
| Inferred
end

end = Type

and Statement : sig
Expand Down Expand Up @@ -358,7 +366,7 @@ and Statement : sig
module DeclareFunction : sig
type t = {
id: Identifier.t;
predicate: Predicate.t option;
predicate: Type.Predicate.t option;
}
end
module DeclareModule : sig
Expand Down Expand Up @@ -990,18 +998,11 @@ and Function : sig
body: body;
async: bool;
generator: bool;
predicate: Predicate.t option;
predicate: Type.Predicate.t option;
expression: bool;
returnType: Type.annotation option;
typeParameters: Type.ParameterDeclaration.t option;
}
end = Function

and Predicate : sig
type t = Loc.t * t'
and t' =
| Declared of Expression.t
| Inferred
end = Predicate

type program = Loc.t * Statement.t list * Comment.t list
14 changes: 7 additions & 7 deletions src/parser/test/hardcoded_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -4110,7 +4110,7 @@ module.exports = {
}
},
'Function Predicates': {
'declare function f(x: mixed): boolean checks (x !== null);': {
'declare function f(x: mixed): boolean %checks(x !== null);': {
'body.0': {
'type': 'DeclareFunction',
'id': {
Expand Down Expand Up @@ -4146,7 +4146,7 @@ module.exports = {
},
},
},
'function foo(x: mixed) checks { return x !== null }': {
'function foo(x: mixed): %checks { return x !== null }': {
'body.0': {
'type':'FunctionDeclaration',
'id':{
Expand Down Expand Up @@ -4208,7 +4208,7 @@ module.exports = {
'typeParameters':null
}
},
'var a1 = (x: mixed) checks => x !== null;': {
'var a1 = (x: mixed): %checks => x !== null;': {
'body.0': {
'type':'VariableDeclaration',
'declarations':[
Expand Down Expand Up @@ -4274,7 +4274,7 @@ module.exports = {
'kind':'var'
}
},
'(x) checks => x !== null;': {
'(x): %checks => x !== null;': {
'body.0': {
'type':'ExpressionStatement',
'expression':{
Expand Down Expand Up @@ -4317,10 +4317,10 @@ module.exports = {
}

},
'var a3: (x: mixed) => boolean checks (x !== null);': {
'errors.0.message': 'Unexpected identifier'
'var a3: (x: mixed) => boolean %checks(x !== null);': {
'errors.0.message': 'Unexpected token %'
},
'declare function f(x: mixed): boolean checks (var x = 1; typeof x == "string");': {
'declare function f(x: mixed): boolean %checks(var x = 1; typeof x == "string");': {
'errors.0.message': 'Unexpected token var'
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/typing/func_sig.ml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ let return_loc =
| {F.body = BodyBlock (loc, _); _} -> Loc.char_before loc

let function_kind {Ast.Function.async; generator; predicate; _ } =
Ast.Predicate.(match async, generator, predicate with
Ast.Type.Predicate.(match async, generator, predicate with
| true, true, None -> Utils_js.assert_false "async && generator"
| true, false, None -> Async
| false, true, None -> Generator
Expand All @@ -50,7 +50,7 @@ let mk cx tparams_map ~expr reason func =
let reason = mk_reason "return" (return_loc func) in
Anno.mk_type_annotation cx tparams_map reason returnType
in
let return_t = Ast.Predicate.(match predicate with
let return_t = Ast.Type.Predicate.(match predicate with
| None ->
return_t
| Some (_, Inferred) ->
Expand Down
Loading

0 comments on commit 1ae591f

Please sign in to comment.