2 * Copyright (c) 2017, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
11 open Instruction_sequence
13 module SU
= Hhbc_string_utils
14 module SN
= Naming_special_names
15 module TV
= Typed_value
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
=
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
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)
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
60 method_is_memoize_wrapper
61 method_is_memoize_wrapper_lsb
76 (Hhbc_id.Method.from_ast_name name
)
81 method_is_pair_generator
82 method_is_closure_body
83 method_is_interceptable
84 method_is_memoize_impl
86 (* method_rx_disabled *)
89 let from_extends ~namespace ~is_enum _tparams extends
=
91 Some
(Hhbc_id.Class.from_raw_string
"HH\\BuiltinEnum")
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
) =
103 | None
-> (None
, None
)
106 Ast_constant_folder.expr_to_opt_typed_value
107 (Emit_env.get_namespace env
)
110 | Some v
-> (Some v
, 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:[] *)
128 (Emit_type_constant.hint_to_type_constant
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*)
143 if cv
.A.cv_is_promoted_variadic
then
148 Emit_property.from_ast
150 cv
.A.cv_user_attributes
153 cv
.A.cv_visibility
(* This used to be cv_kinds *)
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
172 ~f
:(fun (h
, is_extends
) ->
175 Hhas_class.MustExtend
177 Hhas_class.MustImplement
179 (kind, Hhbc_id.Class.to_raw_string
(Emit_type_hint.hint_to_class
ns h
)))
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
=
188 let type_info_user_type =
190 (Emit_type_hint.fmt_hint
196 let type_info_type_constraint =
197 Hhas_type_constraint.make
199 [Hhas_type_constraint.HHType
; Hhas_type_constraint.ExtendedHint
]
201 Some
(Hhas_type_info.make
type_info_user_type type_info_type_constraint)
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
228 "Cannot use '%s' as class name as it is reserved"
229 ( if is_reserved_global_name then
232 Utils.strip_ns class_name
)
234 Emit_fatal.raise_fatal_parse p
message
236 let emit_reified_extends_params env class_
=
238 match class_
.A.c_extends
with
239 | (_
, Aast.Happly
(_
, l
)) :: _
-> l
242 if List.is_empty
type_params then
244 if hack_arr_dv_arrs () then
249 instr
(H.ILitConst
(H.TypedValue
tv))
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_
=
261 instr_cgetl
(Local.Named
SU.Reified.reified_init_method_param_name
);
262 instr_check_reified_generic_mismatch
;
266 if num_reified
= 0 then
269 (* $this->86reified_prop = $__typestructures *)
274 instr_cgetl
(Local.Named
SU.Reified.reified_init_method_param_name
);
278 (Hhbc_id.Prop.from_raw_string
SU.Reified.reified_prop_name
);
282 let return = gather
[instr_null
; instr_retc
] in
283 if class_
.A.c_extends
= [] then
284 gather
[set_prop; return]
286 let generic_arr = emit_reified_extends_params env class_
in
287 (* parent::86reifiedinit($generic_arr) *)
295 instr_fcallclsmethodsd
297 Hhbc_ast.SpecialClsRef.Parent
298 (Hhbc_id.Method.from_raw_string
SU.Reified.reified_init_method_name
);
302 gather
[set_prop; call_parent; return]
304 let emit_reified_init_method env ast_class
=
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
317 let tc = Hhas_type_constraint.make
(Some
"HH\\varray") [] in
321 SU.Reified.reified_init_method_param_name
322 false (* reference *)
326 (Some
(Hhas_type_info.make
(Some
"HH\\varray") tc))
332 let instrs = emit_reified_init_body env
num_reified ast_class
in
335 ~
name:SU.Reified.reified_init_method_name
338 ~visibility
:Aast.Protected
340 ~span
:(Hhas_pos.pos_to_span ast_class
.A.c_span
)
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
359 Emit_attribute.add_reified_attribute
361 ast_class
.A.c_tparams
.A.c_tparam_list
363 let class_attributes =
364 if is_closure_class then
367 Emit_attribute.add_reified_parent_attribute
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
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
381 List.filter_map ast_class
.A.c_uses
(fun (p
, h
) ->
383 | Aast.Happly
((_
, name), _
) ->
384 if class_is_interface then
385 Emit_fatal.raise_fatal_parse p
"Interfaces cannot use traits"
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 =
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 =
404 ~f
:(fun (id1, id2, ids
) ->
405 let id1 = elaborate_namespace_id namespace id1 in
407 let ids = List.map
ids ~f
:(elaborate_namespace_id namespace) in
409 ast_class
.A.c_insteadof_alias
411 let class_method_trait_resolutions =
412 let string_of_trait trait
=
414 | Aast.Happly
((_
, trait
), _
) -> trait
415 (* Happly converted from naming *)
416 | Aast.Hprim p
-> Emit_type_hint.prim_to_string p
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
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
440 let class_xhp_attributes =
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
])
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
)
456 let class_is_abstract = ast_class
.A.c_kind
= Ast_defs.Cabstract
in
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
462 Emit_body.tparams_to_strings ast_class
.A.c_tparams
.A.c_tparam_list
465 if class_is_interface then
471 ~is_enum
:(class_enum_type <> None
)
473 ast_class
.A.c_extends
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"
485 if class_is_interface then
486 ast_class
.A.c_extends
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
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
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
515 @ Emit_xhp.from_attribute_declaration
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
)
535 @@ List.filter_map
class_properties (fun p
->
537 Hhas_property.initializer_instrs p
541 let instrs = gather
[instrs; instr_null
; instr_retc
] in
547 ~visibility
:Aast.Private
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
568 | Some
instrs -> Some
(Hhas_constant.name p
, instrs))
571 if List.is_empty
initialized_class_constants then
574 let return_label = Label.next_regular
() in
575 let rec make_cinit_instrs cs
=
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
]
582 let label = Label.next_regular
() in
585 instr_cgetl
(Local.Named
"$constName");
590 Emit_pos.emit_pos ast_class
.A.c_span
;
591 instr_jmp
return_label;
593 make_cinit_instrs cs
;
597 Emit_pos.emit_pos_then ast_class
.A.c_span
598 @@ make_cinit_instrs initialized_class_constants
601 [Hhas_param.make
"$constName" false false false [] None None
]
608 ~visibility
:Aast.Private
609 ~is_abstract
:class_is_interface
614 let should_emit_reified_init =
616 ( Emit_env.is_systemlib
()
618 || class_is_interface
621 let reified_init_method =
622 if not
should_emit_reified_init then
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
640 let additional_methods =
642 @ reified_init_method
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
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
658 Emit_xhp.properties_for_cache ~
ns:namespace ast_class
class_is_const
660 let additional_methods =
661 Emit_memoize_method.emit_wrapper_methods
665 ast_class
.A.c_methods
667 add_symbol_refs class_base class_implements class_uses class_requirements;
682 class_no_dynamic_props
683 no_reifiedinit_needed
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
689 (class_methods @ List.rev
additional_methods)
690 (class_properties @ additional_properties)
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
) =
701 | A.Class cd
-> Some
(emit_class (cd
, is_top
))
704 List.filter_map ~f
:aux ast