Allow to interleave visibility and function modifiers
[hiphop-php.git] / hphp / hack / src / parser / coroutine / coroutine_lowerer.ml
blob21f2645efecf0dd1ab9b3d6f7e690bd8f286a7cd
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 has_coroutine_modifier n =
141 Core_list.exists (syntax_node_to_list n) ~f:is_coroutine
143 let lower_coroutine_functions_and_types
144 parents
145 current_node
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); _;
154 function_body; _;
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)
162 | LambdaExpression {
163 lambda_coroutine;
164 lambda_signature = { syntax = LambdaSignature lambda_signature; _; };
165 lambda_body;
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
173 context
174 lambda_signature
175 lambda_body
176 current_node in
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
200 context
201 header_node
202 methodish_function_body in
203 let new_method_syntax =
204 CoroutineMethodLowerer.rewrite_methodish_declaration
205 context
206 new_header_node
207 new_body in
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)
214 | _ ->
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
231 * the namespace body
233 let rec rewrite_namespace_declaration node lambda_count =
234 match syntax node with
235 | NamespaceDeclaration ({
236 namespace_body = ({
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
250 let namespace_body =
251 Syntax.synthesize_from
252 namespace_body
253 (NamespaceBody { namespace_body_s with namespace_declarations; }) in
254 let new_declaration =
255 Syntax.synthesize_from
256 node
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
265 * declarations
267 and rewrite_declaration node (lambda_count, previous_declarations) =
268 let (closures, lambda_count), rewritten_node =
269 if is_namespace_declaration node
270 then
271 rewrite_namespace_declaration node lambda_count
272 else
273 Rewriter.parented_aggregating_rewrite_post
274 lower_coroutine_functions_and_types
275 node
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 =
285 List.fold_right
286 ~f:rewrite_declaration
287 ~init:(lambda_count, [])
288 declaration
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:
302 interface I {}
303 class B implements I {}
304 new D(); // Crashes here at runtime
305 class D extends B {}
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
319 declarations.
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?"