Rewrite script declarations in sections
[hiphop-php.git] / hphp / hack / src / parser / coroutine / coroutine_lowerer.ml
blob77e60473cfef2a00194a38ca430a76da65517acd
1 (**
2 * Copyright (c) 2017, Facebook, Inc.
3 * All rights reserved.
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.
8 *)
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)
19 open Syntax
20 open CoroutineSyntax
21 open Coroutine_type_lowerer
23 (**
24 * Rewrites coroutine annotations.
26 * The following:
28 * public function returnVoidVoidCoroutineLambda(
29 * ): (coroutine function(): void) { ... }
31 * Will be rewritten into:
33 * public function returnVoidVoidCoroutineLambda(
34 * ): (function(
35 * CoroutineContinuation<CoroutineUnit>
36 * ): CoroutineResult<CoroutineUnit>) { ... }
38 * The following:
40 * public function returnIntIntCoroutineLambda(
41 * ): (coroutine function(int): int) { ... }
43 * Will be rewritten into:
45 * public function returnIntIntCoroutineLambda(
46 * ): (function(
47 * CoroutineContinuation<int>,
48 * int,
49 * ): CoroutineResult<int>) { ... }
51 let rewrite_coroutine_annotation
53 closure_parameter_list;
54 closure_return_type;
56 } as original_type) =
57 let new_return_type =
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
65 make_syntax (
66 ClosureTypeSpecifier {
67 original_type with
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
76 context
77 anon_node =
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
84 context
85 anonymous_body
86 anonymous_type
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
94 context
95 ({ lambda_parameters; _; } as lambda_signature)
96 lambda_body
97 lambda_node =
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
103 context
104 lambda_body
105 lambda_type
106 lambda_parameters in
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
114 context
115 ({function_parameter_list; _;} as original_header_node)
116 original_body =
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
121 context
122 original_body
123 function_type
124 function_parameter_list in
125 (new_header_node, new_body, closure_syntax)
127 let lower_coroutine_function
128 context
129 original_header
130 original_body =
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
135 context
136 new_header_node
137 new_body in
138 (closure_syntax, new_function_syntax)
140 let lower_coroutine_functions_and_types
141 parents
142 current_node
143 ((closures, lambda_count) as current_acc) =
144 match syntax current_node with
145 | FunctionDeclaration {
146 function_declaration_header = {
147 syntax = FunctionDeclarationHeader ({
148 function_coroutine; _;
149 } as header_node); _;
151 function_body; _;
152 } when not @@ is_missing function_coroutine ->
153 let context = Coroutine_context.make_from_context
154 current_node parents None in
155 let (closure_syntax, new_function_syntax) = lower_coroutine_function
156 context header_node function_body in
157 (((Option.to_list closure_syntax) @ closures, lambda_count),
158 Rewriter.Result.Replace new_function_syntax)
159 | LambdaExpression {
160 lambda_coroutine;
161 lambda_signature = { syntax = LambdaSignature lambda_signature; _; };
162 lambda_body;
164 } when not @@ is_missing lambda_coroutine ->
165 let context = Coroutine_context.make_from_context
166 current_node parents (Some lambda_count) in
167 let lambda_body = CoroutineSuspendRewriter.fix_up_lambda_body lambda_body in
168 let (lambda, closure_syntax) =
169 lower_coroutine_lambda
170 context
171 lambda_signature
172 lambda_body
173 current_node in
174 (((Option.to_list closure_syntax) @ closures, (lambda_count + 1)),
175 Rewriter.Result.Replace lambda)
176 | AnonymousFunction {
177 anonymous_coroutine_keyword;
179 } when not @@ is_missing anonymous_coroutine_keyword ->
180 let context = Coroutine_context.make_from_context
181 current_node parents (Some lambda_count) in
182 let (anon, closure_syntax) = lower_coroutine_anon context current_node in
183 (((Option.to_list closure_syntax) @ closures, (lambda_count + 1)),
184 Rewriter.Result.Replace anon)
185 | MethodishDeclaration {
186 methodish_function_decl_header = {
187 syntax = FunctionDeclarationHeader ({
188 function_coroutine; _;
189 } as header_node); _;
191 methodish_function_body; _;
192 } when not @@ is_missing function_coroutine ->
193 let context = Coroutine_context.make_from_context
194 current_node parents None in
195 let (new_header_node, new_body, closure_syntax) =
196 rewrite_method_or_function
197 context
198 header_node
199 methodish_function_body in
200 let new_method_syntax =
201 CoroutineMethodLowerer.rewrite_methodish_declaration
202 context
203 new_header_node
204 new_body in
205 (((Option.to_list closure_syntax) @ closures, lambda_count),
206 Rewriter.Result.Replace new_method_syntax)
207 | ClosureTypeSpecifier ({ closure_coroutine; _; } as type_node)
208 when not @@ is_missing closure_coroutine ->
209 let new_type_node = rewrite_coroutine_annotation type_node in
210 (current_acc, Rewriter.Result.Replace new_type_node)
211 | _ ->
212 (current_acc, Rewriter.Result.Keep)
215 * Appends the rewritten declaration onto the list of closures that were
216 * generated when rewritting that declaration
218 let combine_declaration closures declaration =
219 if is_classish_declaration declaration
220 then declaration :: closures
221 else closures @ [ declaration; ]
224 * Namespace declarations are a little harder to rewrite because we need to
225 * ensure that any closures that are generated from code within the namespace
226 * remain in the namespace. Additionally, since use statements can be used
227 * within a namespace body, it is necessary to partition the declarations in
228 * the namespace body
230 let rec rewrite_namespace_declaration node lambda_count =
231 match syntax node with
232 | NamespaceDeclaration ({
233 namespace_body = ({
234 syntax = NamespaceBody ({
235 namespace_declarations;
237 } as namespace_body_s);
239 } as namespace_body);
241 } as namespace_declaration_s) ->
242 let namespace_declaration_list =
243 syntax_node_to_list namespace_declarations in
244 let (lambda_count, namespace_declarations) =
245 rewrite_declaration_acc lambda_count namespace_declaration_list in
246 let namespace_declarations = make_list namespace_declarations in
247 let namespace_body =
248 Syntax.synthesize_from
249 namespace_body
250 (NamespaceBody { namespace_body_s with namespace_declarations; }) in
251 let new_declaration =
252 Syntax.synthesize_from
253 node
254 (NamespaceDeclaration
255 { namespace_declaration_s with namespace_body; }) in
256 (([], lambda_count), new_declaration)
257 | _ -> (([], lambda_count), node)
260 * Rewrites a top level declaration, appends the closures generated
261 * and then appends the result onto the accumulating list of rewritten
262 * declarations
264 and rewrite_declaration node (lambda_count, previous_declarations) =
265 let (closures, lambda_count), rewritten_node =
266 if is_namespace_declaration node
267 then
268 rewrite_namespace_declaration node lambda_count
269 else
270 Rewriter.parented_aggregating_rewrite_post
271 lower_coroutine_functions_and_types
272 node
273 ([], lambda_count) in
274 let closures = List.rev closures in
275 let rewritten_declaration = combine_declaration closures rewritten_node in
276 (lambda_count, rewritten_declaration @ previous_declarations)
279 * Rewrites a list of declarations, taking in a lambda count accumulator
281 and rewrite_declaration_acc lambda_count declaration =
282 List.fold_right
283 ~f:rewrite_declaration
284 ~init:(lambda_count, [])
285 declaration
287 let rewrite_all_declarations declaration_list =
288 let _, rewritten_declarations =
289 rewrite_declaration_acc 0 declaration_list in
290 rewritten_declarations
293 Lowers all coroutines found in a script
295 We are working around a significant shortcoming of HHVM here. We are supposed
296 to have an invariant that the order in which type declarations appear in a
297 Hack file is irrelevant, but this is not the case:
299 interface I {}
300 class B implements I {}
301 new D(); // Crashes here at runtime
302 class D extends B {}
304 The crash is due to a peculiarity in how HHVM handles interfaces.
306 The closure classes extend the closure base, which implements an interface.
307 We can therefore very easily get into this situation when generating closure
308 classes at the end of a file.
310 What we do then is gather up *all* the classes in a file, sort them to the
311 top of the file, follow them with the closure classes, and then the rest
312 of the code in the file.
314 This unfortunate code can be removed when the bug is fixed in HHVM, and
315 we can simply append the closure classes to the end of the list of
316 declarations.
318 let lower_coroutines root =
319 match syntax root with
320 | Script { script_declarations; } ->
321 let declarations = syntax_node_to_list script_declarations in
322 begin match declarations with
323 | hh_decl :: declarations ->
324 let rewritten_declarations = rewrite_all_declarations declarations in
325 let rewritten_declarations = hh_decl :: rewritten_declarations in
326 make_script (make_list rewritten_declarations)
327 | _ -> failwith "How did we get a script with no header element?" end
328 | _ -> failwith "How did we get a root that is not a script?"