2 * Copyright (c) 2017, Facebook, Inc.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the "hack" directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
10 module CoroutineMethodLowerer
= Coroutine_method_lowerer
11 module CoroutineStateMachineGenerator
= Coroutine_state_machine_generator
12 module CoroutineSyntax
= Coroutine_syntax
13 module CoroutineTypeLowerer
= Coroutine_type_lowerer
14 module CoroutineSuspendRewriter
= Coroutine_suspend_rewriter
15 module Syntax
= Full_fidelity_editable_positioned_syntax
16 module List
= Core_list
17 module Rewriter
= Full_fidelity_rewriter.WithSyntax
(Syntax
)
21 open Coroutine_type_lowerer
24 * Rewrites coroutine annotations.
28 * public function returnVoidVoidCoroutineLambda(
29 * ): (coroutine function(): void) { ... }
31 * Will be rewritten into:
33 * public function returnVoidVoidCoroutineLambda(
35 * CoroutineContinuation<CoroutineUnit>
36 * ): CoroutineResult<CoroutineUnit>) { ... }
40 * public function returnIntIntCoroutineLambda(
41 * ): (coroutine function(int): int) { ... }
43 * Will be rewritten into:
45 * public function returnIntIntCoroutineLambda(
47 * CoroutineContinuation<int>,
49 * ): CoroutineResult<int>) { ... }
51 let rewrite_coroutine_annotation
53 closure_parameter_list
;
58 CoroutineTypeLowerer.rewrite_return_type closure_return_type
in
59 let continuation_parameter =
60 make_continuation_closure_parameter_syntax
new_return_type in
61 let new_parameter_list = prepend_to_comma_delimited_syntax_list
62 continuation_parameter closure_parameter_list
in
63 let coroutine_return_type =
64 make_coroutine_result_type_syntax
new_return_type in
66 ClosureTypeSpecifier
{
68 closure_coroutine
= make_missing
();
69 closure_parameter_list
= new_parameter_list;
70 closure_return_type
= coroutine_return_type;
74 (* TODO: Rename anonymous_parameters / function_parameter_list to match. *)
75 let lower_coroutine_anon
78 let ({ anonymous_body
; anonymous_parameters
; _
; } as anon
) =
79 get_anonymous_function anon_node
in
80 let ({anonymous_type
; _
;} as anon
) =
81 rewrite_anon_function_return_type anon
in
82 let anonymous_body, closure_syntax
=
83 CoroutineStateMachineGenerator.generate_coroutine_state_machine
87 anonymous_parameters
in
88 let anon = { anon with anonymous_body } in
89 let anon = Syntax.synthesize_from anon_node
(AnonymousFunction
anon) in
90 let anon = CoroutineMethodLowerer.rewrite_anon context
anon in
91 (anon, closure_syntax
)
93 let lower_coroutine_lambda
95 ({ lambda_parameters
; _
; } as lambda_signature
)
98 let lambda = get_lambda_expression lambda_node
in
99 let ({lambda_type
; _
;} as lambda_signature
) =
100 rewrite_lambda_return_type lambda_signature
in
101 let lambda_body, closure_syntax
=
102 CoroutineStateMachineGenerator.generate_coroutine_state_machine
107 let lambda = { lambda with lambda_body } in
108 let lambda = Syntax.synthesize_from lambda_node
(LambdaExpression
lambda) in
109 let lambda = CoroutineMethodLowerer.rewrite_lambda
110 context lambda_signature
lambda in
111 (lambda, closure_syntax
)
113 let rewrite_method_or_function
115 ({function_parameter_list
; _
;} as original_header_node
)
117 let ({function_type
; _
;} as new_header_node
) =
118 rewrite_function_header_return_type original_header_node
in
119 let new_body, closure_syntax
=
120 CoroutineStateMachineGenerator.generate_coroutine_state_machine
124 function_parameter_list
in
125 (new_header_node
, new_body, closure_syntax
)
127 let lower_coroutine_function
131 let (new_header_node
, new_body, closure_syntax
) = rewrite_method_or_function
132 context original_header original_body
in
133 let new_function_syntax =
134 CoroutineMethodLowerer.rewrite_function_declaration
138 (closure_syntax
, new_function_syntax)
140 let has_coroutine_modifier n
=
141 Core_list.exists
(syntax_node_to_list n
) ~f
:is_coroutine
143 let lower_coroutine_functions_and_types
146 ((closures
, lambda_count
) as current_acc
) =
147 match syntax current_node
with
148 | FunctionDeclaration
{
149 function_declaration_header
= {
150 syntax
= FunctionDeclarationHeader
({
151 function_modifiers
= m
; _
;
152 } as header_node
); _
;
155 } when has_coroutine_modifier m
->
156 let context = Coroutine_context.make_from_context
157 current_node parents None
in
158 let (closure_syntax
, new_function_syntax) = lower_coroutine_function
159 context header_node function_body
in
160 (((Option.to_list closure_syntax
) @ closures
, lambda_count
),
161 Rewriter.Result.Replace
new_function_syntax)
164 lambda_signature
= { syntax
= LambdaSignature lambda_signature
; _
; };
167 } when not
@@ is_missing lambda_coroutine
->
168 let context = Coroutine_context.make_from_context
169 current_node parents
(Some lambda_count
) in
170 let lambda_body = CoroutineSuspendRewriter.fix_up_lambda_body
lambda_body in
171 let (lambda, closure_syntax
) =
172 lower_coroutine_lambda
177 (((Option.to_list closure_syntax
) @ closures
, (lambda_count
+ 1)),
178 Rewriter.Result.Replace
lambda)
179 | AnonymousFunction
{
180 anonymous_coroutine_keyword
;
182 } when not
@@ is_missing anonymous_coroutine_keyword
->
183 let context = Coroutine_context.make_from_context
184 current_node parents
(Some lambda_count
) in
185 let (anon, closure_syntax
) = lower_coroutine_anon context current_node
in
186 (((Option.to_list closure_syntax
) @ closures
, (lambda_count
+ 1)),
187 Rewriter.Result.Replace
anon)
188 | MethodishDeclaration
{
189 methodish_function_decl_header
= {
190 syntax
= FunctionDeclarationHeader
({
191 function_modifiers
= m
; _
;
192 } as header_node
); _
;
194 methodish_function_body
; _
;
195 } when has_coroutine_modifier m
->
196 let context = Coroutine_context.make_from_context
197 current_node parents None
in
198 let (new_header_node
, new_body, closure_syntax
) =
199 rewrite_method_or_function
202 methodish_function_body
in
203 let new_method_syntax =
204 CoroutineMethodLowerer.rewrite_methodish_declaration
208 (((Option.to_list closure_syntax
) @ closures
, lambda_count
),
209 Rewriter.Result.Replace
new_method_syntax)
210 | ClosureTypeSpecifier
({ closure_coroutine
; _
; } as type_node
)
211 when not
@@ is_missing closure_coroutine
->
212 let new_type_node = rewrite_coroutine_annotation type_node
in
213 (current_acc
, Rewriter.Result.Replace
new_type_node)
215 (current_acc
, Rewriter.Result.Keep
)
218 * Appends the rewritten declaration onto the list of closures that were
219 * generated when rewritting that declaration
221 let combine_declaration closures declaration
=
222 if is_classish_declaration declaration
223 then declaration
:: closures
224 else closures
@ [ declaration
; ]
227 * Namespace declarations are a little harder to rewrite because we need to
228 * ensure that any closures that are generated from code within the namespace
229 * remain in the namespace. Additionally, since use statements can be used
230 * within a namespace body, it is necessary to partition the declarations in
233 let rec rewrite_namespace_declaration node lambda_count
=
234 match syntax node
with
235 | NamespaceDeclaration
({
237 syntax
= NamespaceBody
({
238 namespace_declarations
;
240 } as namespace_body_s
);
242 } as namespace_body
);
244 } as namespace_declaration_s
) ->
245 let namespace_declaration_list =
246 syntax_node_to_list namespace_declarations
in
247 let (lambda_count
, namespace_declarations
) =
248 rewrite_declaration_acc lambda_count
namespace_declaration_list in
249 let namespace_declarations = make_list
namespace_declarations in
251 Syntax.synthesize_from
253 (NamespaceBody
{ namespace_body_s
with namespace_declarations; }) in
254 let new_declaration =
255 Syntax.synthesize_from
257 (NamespaceDeclaration
258 { namespace_declaration_s
with namespace_body; }) in
259 (([], lambda_count
), new_declaration)
260 | _
-> (([], lambda_count
), node
)
263 * Rewrites a top level declaration, appends the closures generated
264 * and then appends the result onto the accumulating list of rewritten
267 and rewrite_declaration node
(lambda_count
, previous_declarations
) =
268 let (closures
, lambda_count
), rewritten_node
=
269 if is_namespace_declaration node
271 rewrite_namespace_declaration node lambda_count
273 Rewriter.parented_aggregating_rewrite_post
274 lower_coroutine_functions_and_types
276 ([], lambda_count
) in
277 let closures = List.rev
closures in
278 let rewritten_declaration = combine_declaration closures rewritten_node
in
279 (lambda_count
, rewritten_declaration @ previous_declarations
)
282 * Rewrites a list of declarations, taking in a lambda count accumulator
284 and rewrite_declaration_acc lambda_count declaration
=
286 ~f
:rewrite_declaration
287 ~init
:(lambda_count
, [])
290 let rewrite_all_declarations declaration_list
=
291 let _, rewritten_declarations
=
292 rewrite_declaration_acc
0 declaration_list
in
293 rewritten_declarations
296 Lowers all coroutines found in a script
298 We are working around a significant shortcoming of HHVM here. We are supposed
299 to have an invariant that the order in which type declarations appear in a
300 Hack file is irrelevant, but this is not the case:
303 class B implements I {}
304 new D(); // Crashes here at runtime
307 The crash is due to a peculiarity in how HHVM handles interfaces.
309 The closure classes extend the closure base, which implements an interface.
310 We can therefore very easily get into this situation when generating closure
311 classes at the end of a file.
313 What we do then is gather up *all* the classes in a file, sort them to the
314 top of the file, follow them with the closure classes, and then the rest
315 of the code in the file.
317 This unfortunate code can be removed when the bug is fixed in HHVM, and
318 we can simply append the closure classes to the end of the list of
321 let lower_coroutines root
=
322 match syntax root
with
323 | Script
{ script_declarations
; } ->
324 let declarations = syntax_node_to_list script_declarations
in
325 begin match declarations with
326 | hh_decl
:: declarations ->
327 let rewritten_declarations = rewrite_all_declarations declarations in
328 let rewritten_declarations = hh_decl
:: rewritten_declarations in
329 make_script
(make_list
rewritten_declarations)
330 | _ -> failwith
"How did we get a script with no header element?" end
331 | _ -> failwith
"How did we get a root that is not a script?"