Revert visibility of class/type constants.
[hiphop-php.git] / hphp / hack / src / hhbc / emit_class.ml
blob189cc8dd812c356f41e19da770f63d9107fed6d3
1 (*
2 * Copyright (c) 2017, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
10 open Core_kernel
11 open Instruction_sequence
12 module H = Hhbc_ast
13 module SU = Hhbc_string_utils
14 module SN = Naming_special_names
15 module TV = Typed_value
16 module A = Aast
18 let hack_arr_dv_arrs () =
19 Hhbc_options.hack_arr_dv_arrs !Hhbc_options.compiler_options
21 let add_symbol_refs class_base class_implements class_uses class_requirements =
22 let add_hhbc_id id =
23 Emit_symbol_refs.add_class (Hhbc_id.Class.to_raw_string id)
25 (match class_base with
26 | Some c -> add_hhbc_id c
27 | _ -> ());
28 List.iter class_implements add_hhbc_id;
29 List.iter class_uses (fun c ->
30 let c = Hhbc_string_utils.strip_global_ns c in
31 Emit_symbol_refs.add_class c);
32 List.iter class_requirements (function (_, c) ->
33 Emit_symbol_refs.add_class c)
35 let make_86method
36 ~name ~params ~is_static ~visibility ~is_abstract ~span instrs =
37 let method_attributes = [] in
38 (* TODO: move this. We just know that there are no iterators in 86methods *)
39 Iterator.reset_iterator ();
40 let method_is_final = false in
41 let method_visibility = visibility in
42 let method_return_type = None in
43 let method_decl_vars = [] in
44 let method_is_async = false in
45 let method_is_generator = false in
46 let method_is_pair_generator = false in
47 let method_is_closure_body = false in
48 let method_is_memoize_wrapper = false in
49 let method_is_memoize_wrapper_lsb = false in
50 let method_no_injection = true in
51 let method_inout_wrapper = false in
52 let method_doc_comment = None in
53 let method_is_interceptable = false in
54 let method_is_memoize_impl = false in
55 let method_env = None in
56 let method_body =
57 Emit_body.make_body
58 instrs
59 method_decl_vars
60 method_is_memoize_wrapper
61 method_is_memoize_wrapper_lsb
63 params
64 method_return_type
65 method_doc_comment
66 method_env
68 Hhas_method.make
69 method_attributes
70 method_visibility
71 is_static
72 method_is_final
73 is_abstract
74 method_no_injection
75 method_inout_wrapper
76 (Hhbc_id.Method.from_ast_name name)
77 method_body
78 span
79 method_is_async
80 method_is_generator
81 method_is_pair_generator
82 method_is_closure_body
83 method_is_interceptable
84 method_is_memoize_impl
85 Rx.NonRx
86 (* method_rx_disabled *)
87 false
89 let from_extends ~namespace ~is_enum _tparams extends =
90 if is_enum then
91 Some (Hhbc_id.Class.from_raw_string "HH\\BuiltinEnum")
92 else
93 match extends with
94 | [] -> None
95 | h :: _ -> Some (Emit_type_hint.hint_to_class ~namespace h)
97 let from_implements ~namespace implements =
98 List.map implements (Emit_type_hint.hint_to_class ~namespace)
100 let from_constant env (_, name) expr =
101 let (value, init_instrs) =
102 match expr with
103 | None -> (None, None)
104 | Some init ->
105 (match
106 Ast_constant_folder.expr_to_opt_typed_value
107 (Emit_env.get_namespace env)
108 init
109 with
110 | Some v -> (Some v, None)
111 | None ->
112 (Some Typed_value.Uninit, Some (Emit_expression.emit_expr env init)))
114 Hhas_constant.make name value init_instrs
116 let from_type_constant namespace tc =
117 let type_constant_name = snd tc.A.c_tconst_name in
118 match (tc.A.c_tconst_abstract, tc.A.c_tconst_type) with
119 | (A.TCAbstract None, _)
120 | ((A.TCPartiallyAbstract | A.TCConcrete), None) ->
121 Hhas_type_constant.make type_constant_name None
122 | (A.TCAbstract (Some init), _)
123 | ((A.TCPartiallyAbstract | A.TCConcrete), Some init) ->
124 (* TODO: Deal with the constraint *)
125 let type_constant_initializer =
126 (* Type constants do not take type vars hence tparams:[] *)
127 Some
128 (Emit_type_constant.hint_to_type_constant
129 ~tparams:[]
130 ~namespace
131 ~targ_map:SMap.empty
132 init)
134 Hhas_type_constant.make type_constant_name type_constant_initializer
136 let from_class_elt_classvars ast_class class_is_const tparams =
137 (* TODO: we need to emit doc comments for each property,
138 * not one per all properties on the same line *)
139 (* The doc comment is only for the first name in the list.
140 * Currently this is organized in the ast_to_nast module*)
141 let mapping_aux cv =
142 let hint =
143 if cv.A.cv_is_promoted_variadic then
144 None
145 else
146 cv.A.cv_type
148 Emit_property.from_ast
149 ast_class
150 cv.A.cv_user_attributes
151 cv.A.cv_abstract
152 cv.A.cv_is_static
153 cv.A.cv_visibility (* This used to be cv_kinds *)
154 class_is_const
155 hint
156 tparams
157 ast_class.A.c_namespace
158 cv.A.cv_doc_comment (* Doc comments are weird. T40098274 *)
159 ((), cv.A.cv_id, cv.A.cv_expr)
161 List.map ~f:mapping_aux ast_class.A.c_vars
163 let from_class_elt_constants env class_ =
164 let map_aux (c : Tast.class_const) =
165 from_constant env c.A.cc_id c.A.cc_expr
167 List.map ~f:map_aux class_.A.c_consts
169 let from_class_elt_requirements class_ =
170 let ns = class_.A.c_namespace in
171 List.map
172 ~f:(fun (h, is_extends) ->
173 let kind =
174 if is_extends then
175 Hhas_class.MustExtend
176 else
177 Hhas_class.MustImplement
179 (kind, Hhbc_id.Class.to_raw_string (Emit_type_hint.hint_to_class ns h)))
180 class_.A.c_reqs
182 let from_class_elt_typeconsts class_ =
183 List.map ~f:(from_type_constant class_.A.c_namespace) class_.A.c_typeconsts
185 let from_enum_type ~namespace opt =
186 match opt with
187 | Some e ->
188 let type_info_user_type =
189 Some
190 (Emit_type_hint.fmt_hint
191 ~namespace
192 ~tparams:[]
193 ~strip_tparams:true
194 e.A.e_base)
196 let type_info_type_constraint =
197 Hhas_type_constraint.make
198 None
199 [Hhas_type_constraint.HHType; Hhas_type_constraint.ExtendedHint]
201 Some (Hhas_type_info.make type_info_user_type type_info_type_constraint)
202 | _ -> None
204 let is_hh_namespace ns =
205 Option.value_map ns.Namespace_env.ns_name ~default:false ~f:(fun v ->
206 String.lowercase v = "hh")
208 let is_global_namespace ns = Option.is_none ns.Namespace_env.ns_name
210 let validate_class_name ns (p, class_name) =
211 (* per Parser::checkClassDeclName:
212 global names are always reserved in any namespace.
213 hh_reserved names are checked either if
214 - class is in global namespace
215 - class is in HH namespace *)
216 let is_special_class = String_utils.is_substring "$" class_name in
217 let check_hh_name = is_global_namespace ns || is_hh_namespace ns in
218 let name = SU.strip_ns class_name in
219 let is_reserved_global_name = SN.Typehints.is_reserved_global_name name in
220 let name_is_reserved =
221 (not is_special_class)
222 && ( is_reserved_global_name
223 || (check_hh_name && SN.Typehints.is_reserved_hh_name name) )
225 if name_is_reserved then
226 let message =
227 Printf.sprintf
228 "Cannot use '%s' as class name as it is reserved"
229 ( if is_reserved_global_name then
230 name
231 else
232 Utils.strip_ns class_name )
234 Emit_fatal.raise_fatal_parse p message
236 let emit_reified_extends_params env class_ =
237 let type_params =
238 match class_.A.c_extends with
239 | (_, Aast.Happly (_, l)) :: _ -> l
240 | _ -> []
242 if List.is_empty type_params then
243 let tv =
244 if hack_arr_dv_arrs () then
245 TV.Vec ([], None)
246 else
247 TV.VArray []
249 instr (H.ILitConst (H.TypedValue tv))
250 else
251 gather
253 Emit_expression.emit_reified_targs env class_.A.c_span type_params;
254 instr_record_reified_generic;
257 let emit_reified_init_body env num_reified class_ =
258 let check_length =
259 gather
261 instr_cgetl (Local.Named SU.Reified.reified_init_method_param_name);
262 instr_check_reified_generic_mismatch;
265 let set_prop =
266 if num_reified = 0 then
267 empty
268 else
269 (* $this->86reified_prop = $__typestructures *)
270 gather
272 check_length;
273 instr_checkthis;
274 instr_cgetl (Local.Named SU.Reified.reified_init_method_param_name);
275 instr_baseh;
276 instr_setm_pt
278 (Hhbc_id.Prop.from_raw_string SU.Reified.reified_prop_name);
279 instr_popc;
282 let return = gather [instr_null; instr_retc] in
283 if class_.A.c_extends = [] then
284 gather [set_prop; return]
285 else
286 let generic_arr = emit_reified_extends_params env class_ in
287 (* parent::86reifiedinit($generic_arr) *)
288 let call_parent =
289 gather
291 instr_nulluninit;
292 instr_nulluninit;
293 instr_nulluninit;
294 generic_arr;
295 instr_fcallclsmethodsd
296 (make_fcall_args 1)
297 Hhbc_ast.SpecialClsRef.Parent
298 (Hhbc_id.Method.from_raw_string SU.Reified.reified_init_method_name);
299 instr_popc;
302 gather [set_prop; call_parent; return]
304 let emit_reified_init_method env ast_class =
305 let num_reified =
306 List.count ast_class.A.c_tparams.A.c_tparam_list ~f:(fun t ->
307 not (t.A.tp_reified = A.Erased))
309 let maybe_has_reified_parents =
310 match ast_class.A.c_extends with
311 | (_, Aast.Happly (_, l)) :: _ -> not @@ List.is_empty l
312 | _ -> (* Hack classes can only extend a single parent *) false
314 if num_reified = 0 && not maybe_has_reified_parents then
316 else
317 let tc = Hhas_type_constraint.make (Some "HH\\varray") [] in
318 let params =
320 Hhas_param.make
321 SU.Reified.reified_init_method_param_name
322 false (* reference *)
323 false (* variadic *)
324 false (* inout *)
325 [] (* uattrs *)
326 (Some (Hhas_type_info.make (Some "HH\\varray") tc))
327 None;
328 (* default value *)
332 let instrs = emit_reified_init_body env num_reified ast_class in
334 make_86method
335 ~name:SU.Reified.reified_init_method_name
336 ~params
337 ~is_static:false
338 ~visibility:Aast.Protected
339 ~is_abstract:false
340 ~span:(Hhas_pos.pos_to_span ast_class.A.c_span)
341 instrs;
344 let emit_class (ast_class, hoisted) =
345 let namespace = ast_class.A.c_namespace in
346 validate_class_name namespace ast_class.A.c_name;
347 let env = Emit_env.make_class_env ast_class in
348 (* TODO: communicate this without looking at the name *)
349 let is_closure_class =
350 String.is_prefix ~prefix:"Closure$" (snd ast_class.A.c_name)
352 let class_attributes =
353 Emit_attribute.from_asts namespace ast_class.A.c_user_attributes
355 let class_attributes =
356 if is_closure_class then
357 class_attributes
358 else
359 Emit_attribute.add_reified_attribute
360 class_attributes
361 ast_class.A.c_tparams.A.c_tparam_list
363 let class_attributes =
364 if is_closure_class then
365 class_attributes
366 else
367 Emit_attribute.add_reified_parent_attribute
369 class_attributes
370 ast_class.A.c_extends
372 let class_is_const = Hhas_attribute.has_const class_attributes in
373 (* In the future, we intend to set class_no_dynamic_props independently from
374 * class_is_const, but for now class_is_const is the only thing that turns
375 * it on. *)
376 let class_no_dynamic_props = class_is_const in
377 let class_id = Hhbc_id.Class.elaborate_id namespace ast_class.A.c_name in
378 let class_is_trait = ast_class.A.c_kind = Ast_defs.Ctrait in
379 let class_is_interface = ast_class.A.c_kind = Ast_defs.Cinterface in
380 let class_uses =
381 List.filter_map ast_class.A.c_uses (fun (p, h) ->
382 match h with
383 | Aast.Happly ((_, name), _) ->
384 if class_is_interface then
385 Emit_fatal.raise_fatal_parse p "Interfaces cannot use traits"
386 else
387 Some name
388 | _ -> None)
390 let elaborate_namespace_id namespace id =
391 let id = Hhbc_id.Class.elaborate_id namespace id in
392 Hhbc_id.Class.to_raw_string id
394 let class_use_aliases =
395 List.map
396 ~f:(fun (ido1, id, ido2, vis) ->
397 let id1 = Option.map ido1 ~f:(elaborate_namespace_id namespace) in
398 let id2 = Option.map ido2 ~f:snd in
399 (id1, snd id, id2, vis))
400 ast_class.A.c_use_as_alias
402 let class_use_precedences =
403 List.map
404 ~f:(fun (id1, id2, ids) ->
405 let id1 = elaborate_namespace_id namespace id1 in
406 let id2 = snd id2 in
407 let ids = List.map ids ~f:(elaborate_namespace_id namespace) in
408 (id1, id2, ids))
409 ast_class.A.c_insteadof_alias
411 let class_method_trait_resolutions =
412 let string_of_trait trait =
413 match snd trait with
414 | Aast.Happly ((_, trait), _) -> trait
415 (* Happly converted from naming *)
416 | Aast.Hprim p -> Emit_type_hint.prim_to_string p
417 | Aast.Hany
418 | Aast.Herr ->
419 failwith "I'm convinced that this should be an error caught in naming"
420 | Aast.Hmixed -> SN.Typehints.mixed
421 | Aast.Hnonnull -> SN.Typehints.nonnull
422 | Aast.Habstr s -> s
423 | Aast.Harray _ -> SN.Typehints.array
424 | Aast.Hdarray _ -> SN.Typehints.darray
425 | Aast.Hvarray _ -> SN.Typehints.varray
426 | Aast.Hvarray_or_darray _ -> SN.Typehints.varray_or_darray
427 | Aast.Hthis -> SN.Typehints.this
428 | Aast.Hdynamic -> SN.Typehints.dynamic
429 | _ -> failwith "TODO Fail gracefully here"
431 List.map ast_class.A.c_method_redeclarations ~f:(fun mtr ->
432 (mtr, string_of_trait mtr.A.mt_trait))
434 let class_enum_type =
435 if ast_class.A.c_kind = Ast_defs.Cenum then
436 from_enum_type ~namespace:ast_class.A.c_namespace ast_class.A.c_enum
437 else
438 None
440 let class_xhp_attributes =
441 List.map
442 ~f:(fun (ho, cv, b, eo) -> Hhas_xhp_attribute.make ho cv b eo)
443 ast_class.A.c_xhp_attrs
445 let class_xhp_children =
446 match ast_class.A.c_xhp_children with
447 | (p, sl) :: _ -> Some (p, [sl])
448 | [] -> None
450 (* Find map instead of filter map. T40102763 *)
451 let class_xhp_categories =
452 match ast_class.A.c_xhp_category with
453 | Some (p, c) -> Some (p, List.map c ~f:snd)
454 | None -> None
456 let class_is_abstract = ast_class.A.c_kind = Ast_defs.Cabstract in
457 let class_is_final =
458 ast_class.A.c_final || class_is_trait || class_enum_type <> None
460 let class_is_sealed = Hhas_attribute.has_sealed class_attributes in
461 let tparams =
462 Emit_body.tparams_to_strings ast_class.A.c_tparams.A.c_tparam_list
464 let class_base =
465 if class_is_interface then
466 None
467 else
468 let base =
469 from_extends
470 ~namespace
471 ~is_enum:(class_enum_type <> None)
472 tparams
473 ast_class.A.c_extends
475 match base with
476 | Some cls
477 when String.lowercase (Hhbc_id.Class.to_raw_string cls) = "closure"
478 && not is_closure_class ->
479 Emit_fatal.raise_fatal_runtime
480 (fst ast_class.A.c_name)
481 "Class cannot extend Closure"
482 | _ -> base
484 let implements =
485 if class_is_interface then
486 ast_class.A.c_extends
487 else
488 ast_class.A.c_implements
490 let class_implements = from_implements ~namespace implements in
491 let class_span = Hhas_pos.pos_to_span ast_class.A.c_span in
492 (* TODO: communicate this without looking at the name *)
493 let additional_methods = [] in
494 let additional_methods =
495 match class_xhp_categories with
496 | None -> additional_methods
497 | Some cats ->
498 additional_methods @ Emit_xhp.from_category_declaration ast_class cats
500 let additional_methods =
501 match class_xhp_children with
502 | None -> additional_methods
503 | Some children ->
504 additional_methods
505 @ Emit_xhp.from_children_declaration ast_class children
507 let no_xhp_attributes =
508 class_xhp_attributes = [] && ast_class.A.c_xhp_attr_uses = []
510 let additional_methods =
511 if no_xhp_attributes then
512 additional_methods
513 else
514 additional_methods
515 @ Emit_xhp.from_attribute_declaration
516 ~ns:namespace
517 ast_class
518 class_xhp_attributes
519 ast_class.A.c_xhp_attr_uses
521 Label.reset_label ();
522 let class_properties =
523 from_class_elt_classvars ast_class class_is_const tparams
525 let env = Emit_env.make_class_env ast_class in
526 let class_constants = from_class_elt_constants env ast_class in
527 let class_requirements = from_class_elt_requirements ast_class in
528 let make_init_methods filter ~name =
530 List.exists class_properties (fun p ->
531 Option.is_some (Hhas_property.initializer_instrs p) && filter p)
532 then
533 let instrs =
534 gather
535 @@ List.filter_map class_properties (fun p ->
536 if filter p then
537 Hhas_property.initializer_instrs p
538 else
539 None)
541 let instrs = gather [instrs; instr_null; instr_retc] in
543 make_86method
544 ~name
545 ~params:[]
546 ~is_static:true
547 ~visibility:Aast.Private
548 ~is_abstract:false
549 ~span:class_span
550 instrs;
552 else
555 let property_has_lsb p =
556 Hhas_attribute.has_lsb (Hhas_property.attributes p)
558 let pinit_filter p = not (Hhas_property.is_static p) in
559 let sinit_filter p = Hhas_property.is_static p && not (property_has_lsb p) in
560 let linit_filter p = Hhas_property.is_static p && property_has_lsb p in
561 let pinit_methods = make_init_methods pinit_filter ~name:"86pinit" in
562 let sinit_methods = make_init_methods sinit_filter ~name:"86sinit" in
563 let linit_methods = make_init_methods linit_filter ~name:"86linit" in
564 let initialized_class_constants =
565 List.filter_map class_constants (fun p ->
566 match Hhas_constant.initializer_instrs p with
567 | None -> None
568 | Some instrs -> Some (Hhas_constant.name p, instrs))
570 let cinit_methods =
571 if List.is_empty initialized_class_constants then
573 else
574 let return_label = Label.next_regular () in
575 let rec make_cinit_instrs cs =
576 match cs with
577 | [] -> Emit_pos.emit_pos_then ast_class.A.c_span @@ instr_retc
578 | (name, instrs) :: cs ->
579 if List.is_empty cs then
580 gather [instrs; instr_label return_label; make_cinit_instrs cs]
581 else
582 let label = Label.next_regular () in
583 gather
585 instr_cgetl (Local.Named "$constName");
586 instr_string name;
587 instr_eq;
588 instr_jmpz label;
589 instrs;
590 Emit_pos.emit_pos ast_class.A.c_span;
591 instr_jmp return_label;
592 instr_label label;
593 make_cinit_instrs cs;
596 let instrs =
597 Emit_pos.emit_pos_then ast_class.A.c_span
598 @@ make_cinit_instrs initialized_class_constants
600 let params =
601 [Hhas_param.make "$constName" false false false [] None None]
604 make_86method
605 ~name:"86cinit"
606 ~params
607 ~is_static:true
608 ~visibility:Aast.Private
609 ~is_abstract:class_is_interface
610 ~span:class_span
611 instrs;
614 let should_emit_reified_init =
616 ( Emit_env.is_systemlib ()
617 || is_closure_class
618 || class_is_interface
619 || class_is_trait )
621 let reified_init_method =
622 if not should_emit_reified_init then
624 else
625 emit_reified_init_method env ast_class
627 let no_reifiedinit_needed =
628 (not (List.is_empty reified_init_method))
629 && List.is_empty ast_class.A.c_extends
631 let class_upper_bounds =
632 if Hhbc_options.enforce_generics_ub !Hhbc_options.compiler_options then
633 Emit_body.emit_generics_upper_bounds
634 ast_class.A.c_tparams.A.c_tparam_list
635 ~skipawaitable:false
636 ~namespace
637 else
640 let additional_methods =
641 additional_methods
642 @ reified_init_method
643 @ pinit_methods
644 @ sinit_methods
645 @ linit_methods
646 @ cinit_methods
648 let class_methods = Emit_method.from_asts ast_class ast_class.A.c_methods in
649 let class_methods = class_methods @ additional_methods in
650 let class_type_constants = from_class_elt_typeconsts ast_class in
651 let info =
652 Emit_memoize_method.make_info ast_class class_id ast_class.A.c_methods
654 let additional_properties =
655 if no_xhp_attributes then
657 else
658 Emit_xhp.properties_for_cache ~ns:namespace ast_class class_is_const
660 let additional_methods =
661 Emit_memoize_method.emit_wrapper_methods
663 info
664 ast_class
665 ast_class.A.c_methods
667 add_symbol_refs class_base class_implements class_uses class_requirements;
668 Hhas_class.make
669 class_attributes
670 class_base
671 class_implements
672 class_id
673 class_span
674 class_is_final
675 class_is_sealed
676 class_is_abstract
677 class_is_interface
678 class_is_trait
679 ast_class.A.c_is_xhp
680 hoisted
681 class_is_const
682 class_no_dynamic_props
683 no_reifiedinit_needed
684 class_uses
685 class_use_aliases (* Killing class_use_aliases T40098428 *)
686 class_use_precedences (* Killing class_use_precedences as well T40098428 *)
687 class_method_trait_resolutions
688 class_enum_type
689 (class_methods @ List.rev additional_methods)
690 (class_properties @ additional_properties)
691 class_constants
692 class_type_constants
693 class_requirements
694 class_upper_bounds
695 ast_class.A.c_doc_comment
697 let emit_classes_from_program
698 (ast : (Closure_convert.hoist_kind * Tast.def) list) =
699 let aux (is_top, d) =
700 match d with
701 | A.Class cd -> Some (emit_class (cd, is_top))
702 | _ -> None
704 List.filter_map ~f:aux ast