Check argument to 'unset' in reactive mode
[hiphop-php.git] / hphp / hack / src / hhbc / emit_param.ml
blob9b74aaa840cc2bce79ca340e9d64ac1ef9490cba
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 Hh_core
11 open Instruction_sequence
12 open Ast_class_expr
13 module A = Ast
15 let hack_arr_dv_arrs () =
16 Hhbc_options.hack_arr_dv_arrs !Hhbc_options.compiler_options
18 let from_variadic_param_hint_opt ho =
19 let p = Pos.none in
20 match ho with
21 | None -> Some (p, A.Happly ((p, "array"), []))
22 | Some h -> Some (p, A.Happly ((p, "array"), [h]))
24 let resolve_class_id ~scope cid =
25 let cexpr = expr_to_class_expr ~resolve_self:false scope cid in
26 match cexpr with
27 | Class_id ((p, _) as cid) -> p, A.Id cid
28 | Class_parent | Class_self | Class_static | Class_expr _
29 | Class_unnamed_local _ -> cid
31 let resolver_visitor =
32 object(_)
33 inherit [_] Ast.endo
35 method! on_Class_get scope _ cid id =
36 let cid = resolve_class_id ~scope cid in
37 A.Class_get (cid, id)
39 method! on_Class_const scope _ cid id =
40 let cid = resolve_class_id ~scope cid in
41 A.Class_const (cid, id)
43 end
45 let get_hint_display_name hint =
46 Option.map hint (fun h ->
47 match h with
48 | "HH\\bool" -> "bool"
49 | "array" -> "array"
50 | "HH\\varray" -> "HH\\varray"
51 | "HH\\darray" -> "HH\\darray"
52 | "HH\\varray_or_darray" -> "HH\\varray_or_darray"
53 | "HH\\vec_or_dict" -> "HH\\vec_or_dict"
54 | "HH\\arraylike" -> "HH\\arraylike"
55 | "HH\\int" -> "int"
56 | "HH\\num" -> "num"
57 | "HH\\arraykey" -> "arraykey"
58 | "HH\\float" -> "float"
59 | "HH\\string" -> "string"
60 | _ -> "class" )
63 (* By now only check default type for bool, array, int, float and string.
64 Return None when hint_type and default_value matches (in hh mode,
65 "class" type matches anything). If not, return default_value type string
66 for printing fatal parse error *)
67 let match_default_and_hint is_hh_file hint_type param_expr =
68 let accept_any = match hint_type with
69 | "class" -> is_hh_file
70 | _ -> false
72 if accept_any
73 then None
74 else match param_expr with
75 | Some (_, (A.True | A.False)) ->
76 begin match hint_type with
77 | "bool" -> None
78 | _ -> Some "Boolean"
79 end
80 | Some (_, A.Array _ ) ->
81 begin match hint_type with
82 | "array"
83 | "HH\\varray"
84 | "HH\\darray"
85 | "HH\\varray_or_darray" -> None
86 | _ -> Some "Array"
87 end
88 | Some (_, A.Int _ ) ->
89 begin match hint_type with
90 | "int"
91 | "num"
92 | "arraykey"
93 | "float" -> None
94 | _ -> Some "Int64"
95 end
96 | Some (_, A.Float _ ) ->
97 begin match hint_type with
98 | "float"
99 | "num" -> None
100 | _ -> Some "Double"
102 | Some (_, A.String _ ) ->
103 begin match hint_type with
104 | "string"
105 | "arraykey" -> None
106 | _ -> Some "String"
109 | _ -> None
111 (* Return None if it passes type check, otherwise return error msg *)
112 let default_type_check param_name param_type_info param_expr =
113 let hint =
114 Option.bind param_type_info Hhas_type_info.user_type
116 let hint_type = get_hint_display_name hint in
117 (* If matches, return None, otherwise return default_type *)
118 let default_type = Option.bind hint_type (function hint_type ->
119 match_default_and_hint
120 (Emit_env.is_hh_syntax_enabled ())
121 hint_type param_expr)
123 let param_true_name = Hhbc_string_utils.Locals.strip_dollar param_name
125 Option.bind default_type (function t ->
126 match hint_type with
127 | Some "class" -> Some ("Default value for parameter "
128 ^ param_true_name ^ " with a class type hint can only be NULL")
129 | Some h -> Some ("Default value for parameter "
130 ^ param_true_name ^ " with type " ^ t ^
131 " needs to have the same type as the type hint " ^ h)
132 | _ -> None )
134 let from_ast ~tparams ~namespace ~generate_defaults ~scope p =
135 let param_name = snd p.A.param_id in
136 let param_is_variadic = p.Ast.param_is_variadic in
137 let param_is_inout = p.Ast.param_callconv = Some A.Pinout in
138 let param_user_attributes =
139 Emit_attribute.from_asts namespace p.Ast.param_user_attributes in
140 let param_hint =
141 if param_is_variadic
142 then from_variadic_param_hint_opt p.Ast.param_hint
143 else p.Ast.param_hint
145 let tparams = if param_is_variadic then "array"::tparams else tparams in
146 let nullable =
147 match p.A.param_expr with
148 | Some (_, A.Null) -> true
149 | _ -> false in
150 let param_type_info = Option.map param_hint
151 Emit_type_hint.(hint_to_type_info
152 ~kind:Param ~skipawaitable:false ~nullable ~namespace ~tparams) in
153 (* Do the type check for default value type and hint type *)
154 let _ =
155 if not nullable then
156 match (default_type_check param_name param_type_info p.A.param_expr) with
157 | None -> ()
158 | Some s -> Emit_fatal.raise_fatal_parse (fst p.A.param_id) s
160 let param_expr =
161 Option.map p.Ast.param_expr ~f:(resolver_visitor#on_expr scope)
163 let param_default_value =
164 if generate_defaults
165 then Option.map param_expr ~f:(fun e -> Label.next_default_arg (), e)
166 else None
168 if param_is_variadic && param_name = "..." then None else
169 Some (Hhas_param.make param_name p.A.param_is_reference param_is_variadic
170 param_is_inout param_user_attributes param_type_info param_default_value)
172 let rename_params params =
173 let names = Hh_core.List.fold_left params
174 ~init:SSet.empty ~f:(fun n p -> SSet.add (Hhas_param.name p) n) in
175 let rec rename param_counts param =
176 let name = Hhas_param.name param in
177 match SMap.get name param_counts with
178 | None ->
179 (SMap.add name 0 param_counts, param)
180 | Some count ->
181 let param_counts = SMap.add name (count + 1) param_counts in
182 let newname = name ^ string_of_int count in
183 if SSet.mem newname names
184 then rename param_counts param
185 else param_counts, (Hhas_param.with_name newname param)
187 List.rev (snd (Hh_core.List.map_env SMap.empty (List.rev params) rename))
189 let from_asts ~namespace ~tparams ~generate_defaults ~scope ast_params =
190 let hhas_params = List.filter_map ast_params
191 (from_ast ~tparams ~namespace ~generate_defaults ~scope) in
192 rename_params hhas_params
194 let emit_param_default_value_setter ?(is_native = false) env pos params =
195 let setters = List.filter_map params (fun p ->
196 let param_name = Hhas_param.name p in
197 let dvo = Hhas_param.default_value p in
198 let nop_requirements e = is_native && snd e = A.Null && begin
199 let is_optional, is_mixed, is_callable =
200 match Hhas_param.type_info p with
201 | Some {Hhas_type_info.type_info_user_type = Some s; _} ->
202 let is_optional = String_utils.string_starts_with s "?" in
203 let s = String_utils.lstrip s "?" in
204 is_optional, s = "HH\\mixed", s = "callable"
205 | _ -> false, false, false in
206 is_mixed ||
207 Hhas_param.is_reference p ||
208 (is_optional && is_callable)
211 Option.map dvo (fun (l, e) ->
212 gather [
213 instr_label l;
214 if nop_requirements e then
215 instr_nop
216 else gather [
217 Emit_expression.emit_expr ~need_ref:false env e;
218 Emit_pos.emit_pos pos;
219 instr_setl (Local.Named param_name);
220 instr_popc;
222 ]) )
224 if List.is_empty setters
225 then empty, empty
226 else
227 let l = Label.next_regular () in
228 instr_label l, gather [gather setters; instr_jmpns l]