Skip to content

Commit

Permalink
Parsing support for explicit type arguments in new/call
Browse files Browse the repository at this point in the history
Summary:
This diff adds parsing support for explicit type arguments in new and call
expressions, e.g., `f<T>(x)`.

This syntax is overlaps with existing syntax in some cases. For example, the
program `f<T>(x)` already parses as nested binary expressions `((f<T)>x)`.

Flow will only attempt to parse this syntax as explicit type arguments if
`should_parse_types` is true -- i.e., if Flow is enabled for the file, either
through the `flow` pragma or the `--all` cmdline argument.

Due to the ambiguity, parsing this syntax uses backtracking via Try. When we
see `f<`, we try to parse the type argument list. If that fails, we rollback
and try parsing as a binary expression instead.

For optional chaining, the syntax `f?.<T>(x)` is parsed as an optional call
with type arguments.

Typing support for this syntax will be added in a follow-up. I have marked the
places where typing rules should consider type args with a TODO comment.

Reviewed By: gabelevi

Differential Revision: D7657631

fbshipit-source-id: 34390c43a10c30eeb18285f5282db502335f7419
  • Loading branch information
samwgoldman authored and facebook-github-bot committed May 2, 2018
1 parent c947de2 commit 40fbcdd
Show file tree
Hide file tree
Showing 48 changed files with 1,063 additions and 71 deletions.
4 changes: 4 additions & 0 deletions packages/flow-parser/test/esprima_test_runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ function handleSpecialObjectCompare(esprima, flow, env) {
delete param.typeAnnotation;
delete param.optional;
}
case 'CallExpression':
case 'NewExpression':
delete flow.typeArguments;
break;
}

switch (esprima.type) {
Expand Down
41 changes: 31 additions & 10 deletions src/common/reason.ml
Original file line number Diff line number Diff line change
Expand Up @@ -821,10 +821,17 @@ Ast.Expression.(match x with
do_wrap (left ^ " " ^ operator ^ " " ^ right)
| Binary { Binary.operator; left; right } ->
do_wrap (code_desc_of_operation left (`Binary operator) right)
| Call { Call.callee; arguments = [] } ->
(code_desc_of_expression ~wrap:true callee) ^ "()"
| Call { Call.callee; arguments = _ } ->
(code_desc_of_expression ~wrap:true callee) ^ "(...)"
| Call { Call.callee; targs; arguments } ->
let targs = match targs with
| None -> ""
| Some (_, []) -> "<>"
| Some (_, _::_) -> "<...>"
in
let args = match arguments with
| [] -> "()"
| _::_ -> "(...)"
in
(code_desc_of_expression ~wrap:true callee) ^ targs ^ args
| Class _ -> "class { ... }"
| Conditional { Conditional.test; consequent; alternate } ->
let wrap_test = match test with _, Conditional _ -> true | _ -> false in
Expand All @@ -849,20 +856,34 @@ Ast.Expression.(match x with
| PropertyExpression x -> "[" ^ code_desc_of_expression ~wrap:false x ^ "]"
))
| MetaProperty { MetaProperty.meta = (_, o); property = (_, p) } -> o ^ "." ^ p
| New { New.callee; arguments = [] } ->
"new " ^ (code_desc_of_expression ~wrap:true callee) ^ "()"
| New { New.callee; arguments = _ } ->
"new " ^ (code_desc_of_expression ~wrap:true callee) ^ "(...)"
| New { New.callee; targs; arguments } ->
let targs = match targs with
| None -> ""
| Some (_, []) -> "<>"
| Some (_, _::_) -> "<...>"
in
let args = match arguments with
| [] -> "()"
| _::_ -> "(...)"
in
"new " ^ (code_desc_of_expression ~wrap:true callee) ^ targs ^ args
| Object _ -> "{...}"
| OptionalCall { OptionalCall.
call = { Call.callee; arguments };
call = { Call.callee; targs; arguments };
optional;
} ->
let targ_string = match targs with
| None -> ""
| Some (_, []) -> "<>"
| Some (_, _::_) -> "<...>"
in
let arg_string = begin match arguments with
| [] -> "()"
| _ -> "(...)"
end in
code_desc_of_expression ~wrap:true callee ^ (if optional then "?." else "") ^ arg_string
code_desc_of_expression ~wrap:true callee ^
(if optional then "?." else "") ^
targ_string ^ arg_string
| OptionalMember { OptionalMember.
member = { Member._object; property; computed = _ };
optional;
Expand Down
2 changes: 2 additions & 0 deletions src/parser/ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -728,12 +728,14 @@ and Expression : sig
module New : sig
type 'M t = {
callee: 'M Expression.t;
targs: 'M Type.ParameterInstantiation.t option;
arguments: 'M expression_or_spread list;
}
end
module Call : sig
type 'M t = {
callee: 'M Expression.t;
targs: 'M Type.ParameterInstantiation.t option;
arguments: 'M expression_or_spread list;
}
end
Expand Down
2 changes: 2 additions & 0 deletions src/parser/estree_translator.ml
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ end with type t = Impl.t) = struct
| loc, New _new -> New.(
node "NewExpression" loc [
"callee", expression _new.callee;
"typeArguments", option type_parameter_instantiation _new.targs;
"arguments", array_of_list expression_or_spread _new.arguments;
]
)
Expand Down Expand Up @@ -1459,6 +1460,7 @@ end with type t = Impl.t) = struct

and call_node_properties call = Expression.Call.([
"callee", expression call.callee;
"typeArguments", option type_parameter_instantiation call.targs;
"arguments", array_of_list expression_or_spread call.arguments;
])

Expand Down
69 changes: 50 additions & 19 deletions src/parser/expression_parser.ml
Original file line number Diff line number Diff line change
Expand Up @@ -542,23 +542,38 @@ module Expression
and call_cover ?(allow_optional_chain=true) ?(in_optional_chain=false) env start_loc left =
let left = member_cover ~allow_optional_chain ~in_optional_chain env start_loc left in
let optional = last_token env = Some T_PLING_PERIOD in
match Peek.token env with
| T_LPAREN when not (no_call env) ->
let args_loc, arguments = arguments env in
let loc = Loc.btwn start_loc args_loc in
let call = { Expression.Call.
callee = as_expression env left;
arguments;
} in
let call = if optional || in_optional_chain
then Expression.(OptionalCall { OptionalCall.
call;
optional;
})
else Expression.Call call
in
call_cover ~allow_optional_chain ~in_optional_chain env start_loc
(Cover_expr (loc, call))
let arguments ?targs env =
let args_loc, arguments = arguments env in
let loc = Loc.btwn start_loc args_loc in
let call = { Expression.Call.
callee = as_expression env left;
targs;
arguments;
} in
let call = if optional || in_optional_chain
then Expression.(OptionalCall { OptionalCall.
call;
optional;
})
else Expression.Call call
in
call_cover ~allow_optional_chain ~in_optional_chain env start_loc
(Cover_expr (loc, call))
in
if no_call env then left
else match Peek.token env with
| T_LPAREN -> arguments env
| T_LESS_THAN when should_parse_types env ->
(* If we are parsing types, then f<T>(e) is a function call with a
type application. If we aren't, it's a nested binary expression. *)
let error_callback _ _ = raise Try.Rollback in
let env = env |> with_error_callback error_callback in
(* Parameterized call syntax is ambiguous, so we fall back to
standard parsing if it fails. *)
Try.or_else env ~fallback:left (fun env ->
let targs = Type.type_parameter_instantiation env in
arguments ?targs env
)
| _ -> left

and call ?(allow_optional_chain=true) env start_loc left =
Expand Down Expand Up @@ -597,12 +612,27 @@ module Expression
let callee = match Peek.token env with
| T_TEMPLATE_PART part -> tagged_template env callee_loc callee part
| _ -> callee in
let end_loc, arguments = match Peek.token env with
| T_LPAREN -> arguments env
let targs =
(* If we are parsing types, then new C<T>(e) is a constructor with a
type application. If we aren't, it's a nested binary expression. *)
if should_parse_types env
then
(* Parameterized call syntax is ambiguous, so we fall back to
standard parsing if it fails. *)
let error_callback _ _ = raise Try.Rollback in
let env = env |> with_error_callback error_callback in
Try.or_else env ~fallback:None Type.type_parameter_instantiation
else
None
in
let end_loc, arguments = match Peek.token env, targs with
| T_LPAREN, _ -> arguments env
| _, Some (targs_loc, _) -> targs_loc, []
| _ -> fst callee, [] in

Loc.btwn start_loc end_loc, Expression.(New New.({
callee;
targs;
arguments;
}))

Expand Down Expand Up @@ -711,6 +741,7 @@ module Expression
error env Parse_error.OptionalChainTemplate;
left
| T_LPAREN -> left
| T_LESS_THAN when should_parse_types env -> left
| _ ->
error_unexpected env;
left
Expand Down
5 changes: 5 additions & 0 deletions src/parser/parser_env.ml
Original file line number Diff line number Diff line change
Expand Up @@ -882,4 +882,9 @@ module Try = struct
let saved_state = save_state env in
try success env saved_state (parse env)
with Rollback -> rollback_state env saved_state

let or_else env ~fallback parse =
match to_parse env parse with
| ParsedSuccessfully result -> result
| FailedToParse -> fallback
end
1 change: 1 addition & 0 deletions src/parser/parser_env.mli
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,5 @@ module Try : sig
exception Rollback

val to_parse: env -> (env -> 'a) -> 'a parse_result
val or_else: env -> fallback:'a -> (env -> 'a) -> 'a
end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
f<T>(e);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"types": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"type":"Program",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":8}},
"range":[0,8],
"body":[
{
"type":"ExpressionStatement",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":8}},
"range":[0,8],
"expression":{
"type":"BinaryExpression",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":7}},
"range":[0,7],
"operator":">",
"left":{
"type":"BinaryExpression",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":3}},
"range":[0,3],
"operator":"<",
"left":{
"type":"Identifier",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":1}},
"range":[0,1],
"name":"f",
"typeAnnotation":null,
"optional":false
},
"right":{
"type":"Identifier",
"loc":{"source":null,"start":{"line":1,"column":2},"end":{"line":1,"column":3}},
"range":[2,3],
"name":"T",
"typeAnnotation":null,
"optional":false
}
},
"right":{
"type":"Identifier",
"loc":{"source":null,"start":{"line":1,"column":5},"end":{"line":1,"column":6}},
"range":[5,6],
"name":"e",
"typeAnnotation":null,
"optional":false
}
},
"directive":null
}
],
"comments":[]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
new C<T>(e);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"types": false
}
56 changes: 56 additions & 0 deletions src/parser/test/flow/typeapp_call/disabled_ambiguous_new.tree.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"type":"Program",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":12}},
"range":[0,12],
"body":[
{
"type":"ExpressionStatement",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":12}},
"range":[0,12],
"expression":{
"type":"BinaryExpression",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":11}},
"range":[0,11],
"operator":">",
"left":{
"type":"BinaryExpression",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":7}},
"range":[0,7],
"operator":"<",
"left":{
"type":"NewExpression",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":5}},
"range":[0,5],
"callee":{
"type":"Identifier",
"loc":{"source":null,"start":{"line":1,"column":4},"end":{"line":1,"column":5}},
"range":[4,5],
"name":"C",
"typeAnnotation":null,
"optional":false
},
"arguments":[]
},
"right":{
"type":"Identifier",
"loc":{"source":null,"start":{"line":1,"column":6},"end":{"line":1,"column":7}},
"range":[6,7],
"name":"T",
"typeAnnotation":null,
"optional":false
}
},
"right":{
"type":"Identifier",
"loc":{"source":null,"start":{"line":1,"column":9},"end":{"line":1,"column":10}},
"range":[9,10],
"name":"e",
"typeAnnotation":null,
"optional":false
}
},
"directive":null
}
],
"comments":[]
}
1 change: 1 addition & 0 deletions src/parser/test/flow/typeapp_call/function_call.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
f<T>();
49 changes: 49 additions & 0 deletions src/parser/test/flow/typeapp_call/function_call.tree.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"type":"Program",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":7}},
"range":[0,7],
"body":[
{
"type":"ExpressionStatement",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":7}},
"range":[0,7],
"expression":{
"type":"CallExpression",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":6}},
"range":[0,6],
"callee":{
"type":"Identifier",
"loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":1}},
"range":[0,1],
"name":"f",
"typeAnnotation":null,
"optional":false
},
"typeArguments":{
"type":"TypeParameterInstantiation",
"loc":{"source":null,"start":{"line":1,"column":1},"end":{"line":1,"column":4}},
"range":[1,4],
"params":[
{
"type":"GenericTypeAnnotation",
"loc":{"source":null,"start":{"line":1,"column":2},"end":{"line":1,"column":3}},
"range":[2,3],
"id":{
"type":"Identifier",
"loc":{"source":null,"start":{"line":1,"column":2},"end":{"line":1,"column":3}},
"range":[2,3],
"name":"T",
"typeAnnotation":null,
"optional":false
},
"typeParameters":null
}
]
},
"arguments":[]
},
"directive":null
}
],
"comments":[]
}
1 change: 1 addition & 0 deletions src/parser/test/flow/typeapp_call/function_call_chain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
f<T>(x)<U>(y);
Loading

0 comments on commit 40fbcdd

Please sign in to comment.