Remove support for variable variables from parser, typechecker and frontend
[hiphop-php.git] / hphp / hack / src / typing / typing_get_locals.ml
blob2a7fb1ee7552fee1fa83fcb0c78d58d3e166dbfe
1 (**
2 * Copyright (c) 2015, 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 Ast
12 open Utils
14 module FuncTerm = Typing_func_terminality
15 module NS = Namespaces
17 (* Module calculating the locals for a statement
18 * This is useful when someone uses $x on both sides
19 * of an If statement, for example:
20 * if(true) {
21 * $x = 0;
22 * } else {
23 * $x = 1;
24 * }
27 (* TODO It really sucks that this and Nast_terminality.Terminal are very
28 * slightly different (notably, this version is somewhat buggier). Fixing that
29 * exposes a lot of errors in www unfortunately -- we should bite the bullet on
30 * fixing switch all the way when we do that, most likely though -- see tasks
31 * #3140431 and #2813555. *)
32 let rec terminal tcopt nsenv ~in_try stl =
33 List.iter stl (terminal_ tcopt nsenv ~in_try)
35 and terminal_ tcopt nsenv ~in_try (_, st_) =
36 match st_ with
37 | Throw _ when not in_try -> raise Exit
38 | Throw _ -> ()
39 | Continue _
40 | Expr (_, ( Call ((_, Id (_, "assert")), _, [_, False], [])
41 | Call ((_, Id (_, "invariant")), _, (_, False) :: _ :: _, [])))
42 | Return _ -> raise Exit
43 | Expr (_, Call ((_, Id fun_id), _, _, _)) ->
44 let _, fun_name = NS.elaborate_id nsenv NS.ElaborateFun fun_id in
45 FuncTerm.(raise_exit_if_terminal (get_fun tcopt fun_name))
46 | Expr (_, Call ((_, Class_const ((_, Id cls_id), (_, meth_name))), _, _, _)) ->
47 let _, cls_name = NS.elaborate_id nsenv NS.ElaborateClass cls_id in
48 FuncTerm.(raise_exit_if_terminal
49 (get_static_meth tcopt cls_name meth_name))
50 | If (_, b1, b2) ->
51 (try terminal tcopt nsenv ~in_try b1; () with Exit ->
52 terminal tcopt nsenv ~in_try b2)
53 | Switch (_, cl) ->
54 terminal_cl tcopt nsenv ~in_try cl
55 | Block b -> terminal tcopt nsenv ~in_try b
56 | Using u -> terminal tcopt nsenv ~in_try u.us_block
57 | Try (b, catch_l, _fb) ->
58 (* return is not allowed in finally, so we can ignore fb *)
59 (terminal tcopt nsenv ~in_try:true b;
60 List.iter catch_l (terminal_catch tcopt nsenv ~in_try))
61 | Markup _
62 | Let _
63 | Do _
64 | While _
65 | Declare _
66 | For _
67 | Foreach _
68 | Def_inline _
69 | Noop
70 | Expr _
71 | Unsafe
72 | Fallthrough
73 | Break _ (* TODO this is terminal sometimes too, except switch, see above. *)
74 | GotoLabel _
75 | Goto _
76 | Static_var _
77 | Global_var _
78 | Awaitall _
79 -> ()
81 and terminal_catch tcopt nsenv ~in_try (_, _, b) =
82 terminal tcopt nsenv ~in_try b
84 and terminal_cl tcopt nsenv ~in_try = function
85 | [] -> raise Exit
86 | Case (_, b) :: rl ->
87 (try
88 terminal tcopt nsenv ~in_try b;
89 if blockHasBreak b
90 then ()
91 else raise Exit
92 with Exit -> terminal_cl tcopt nsenv ~in_try rl)
93 | Default b :: rl ->
94 begin try terminal tcopt nsenv ~in_try b with
95 | Exit ->
96 terminal_cl tcopt nsenv ~in_try rl
97 end
99 and blockHasBreak = function
100 | [] -> false
101 | (_, Break _) :: _ -> true
102 | x :: xs ->
103 let x' =
104 match x with
105 | _, If (_, [], []) -> false
106 | _, If (_, b, []) | _, If (_, [], b) -> blockHasBreak b
107 | _, If (_, b1, b2) -> blockHasBreak b1 && blockHasBreak b2
108 | _ -> false
110 x' || blockHasBreak xs
112 let is_terminal tcopt nsenv stl =
113 try terminal tcopt nsenv ~in_try:false stl; false
114 with Exit -> true
116 let smap_union ((nsenv:Namespace_env.env), (m1:Pos.t SMap.t))
117 (m2:Pos.t SMap.t) =
118 let m_combined = SMap.fold SMap.add m1 m2 in
119 nsenv, m_combined
121 let rec lvalue tcopt (acc:(Namespace_env.env * Pos.t SMap.t)) = function
122 | (p, Lvar (_, x)) ->
123 let nsenv, m = acc in
124 nsenv, SMap.add x p m
125 | _, List lv -> List.fold_left lv ~init:acc ~f:(lvalue tcopt)
126 (* Ref forms a local inside a foreach *)
127 | (_, Unop (Uref, (p, Lvar (_, x)))) ->
128 let nsenv, m = acc in
129 nsenv, SMap.add x p m
130 | _ -> acc
132 let rec stmt tcopt (acc:(Namespace_env.env * Pos.t SMap.t)) (_, st_) =
133 let nsenv = fst acc in
134 match st_ with
135 | Expr e -> expr tcopt acc e
136 | Unsafe
137 | Fallthrough
138 | Markup _
139 | Break _ | Continue _ | Throw _ -> acc
140 | Do (b, e) ->
141 let acc = block tcopt acc b in
142 let acc = expr tcopt acc e in
144 | While (e, _b) -> expr tcopt acc e
145 | For (e1, e2, _e3, _b) ->
146 let acc = expr tcopt acc e1 in
147 let acc = expr tcopt acc e2 in
149 | Foreach (e, _await, as_e, _b) ->
150 let acc = expr tcopt acc e in
151 begin match as_e with
152 | As_v v -> expr tcopt acc v
153 | As_kv (k, v) ->
154 let acc = expr tcopt acc k in
155 let acc = expr tcopt acc v in
157 end (* match *)
158 | Declare _
159 | Return _ | GotoLabel _ | Goto _ | Static_var _
160 | Global_var _ | Def_inline _ | Noop -> acc
161 | Awaitall el ->
162 List.fold_left el ~init:acc ~f:(fun acc (_, e2) ->
163 expr tcopt acc e2
165 | Let (_x, _h, e) ->
166 (* We would like to exclude scoped locals here, but gather the locals in
167 * expression *)
168 expr tcopt acc e
169 | Using u -> block tcopt acc u.us_block
170 | Block b -> block tcopt acc b
171 | If (e, b1, b2) ->
172 let acc = expr tcopt acc e in
173 let term1 = is_terminal tcopt nsenv b1 in
174 let term2 = is_terminal tcopt nsenv b2 in
175 if term1 && term2
176 then acc
177 else if term1
178 then
179 let _, m2 = block tcopt (nsenv, SMap.empty) b2 in
180 smap_union acc m2
181 else if term2
182 then
183 let _, m1 = block tcopt (nsenv, SMap.empty) b1 in
184 smap_union acc m1
185 else begin
186 let _, m1 = block tcopt (nsenv, SMap.empty) b1 in
187 let _, m2 = block tcopt (nsenv, SMap.empty) b2 in
188 let (m:Pos.t SMap.t) = (smap_inter m1 m2) in
189 smap_union acc m
191 | Switch (e, cl) ->
192 let acc = expr tcopt acc e in
193 let cl = List.filter cl begin function
194 | Case (_, b)
195 | Default b -> not (is_terminal tcopt nsenv b)
196 end in
197 let cl = casel tcopt nsenv cl in
198 let c = smap_inter_list cl in
199 smap_union acc c
200 | Try (b, cl, _fb) ->
201 let _, c = block tcopt (nsenv, SMap.empty) b in
202 let cl = List.filter cl begin fun (_, _, b) ->
203 not (is_terminal tcopt nsenv b)
204 end in
205 let lcl = List.map cl (catch tcopt nsenv) in
206 let c = smap_inter_list (c :: lcl) in
207 smap_union acc c
209 and block tcopt acc l = List.fold_left l ~init:acc ~f:(stmt tcopt)
211 and casel tcopt nsenv = function
212 | [] -> []
213 | Case (_, []) :: rl -> casel tcopt nsenv rl
214 | Default b :: rl
215 | Case (_, b) :: rl ->
216 let _, b = block tcopt (nsenv, SMap.empty) b in
217 b :: casel tcopt nsenv rl
219 and catch tcopt nsenv (_, _, b) =
220 snd (block tcopt (nsenv, SMap.empty) b)
222 and expr tcopt acc (_, e) =
223 let expr_expr acc e1 e2 =
224 let acc = expr tcopt acc e1 in
225 let acc = expr tcopt acc e2 in
228 let field acc f =
229 match f with
230 | AFvalue e -> expr tcopt acc e
231 | AFkvalue (k, v) -> expr_expr acc k v
233 let exprs acc es =
234 List.fold_left es ~init:acc ~f:(expr tcopt)
236 match e with
237 | Binop (Eq None, lv, rv) ->
238 let acc = expr tcopt acc rv in
239 lvalue tcopt acc lv
240 | Array fields
241 | Collection (_, fields) ->
242 List.fold_left fields ~init:acc ~f:field
243 | Varray es
244 | List es
245 | Expr_list es
246 | Execution_operator es
247 | String2 es ->
248 exprs acc es
249 | PrefixedString (_, e) -> expr tcopt acc e
250 | Darray exprexprs ->
251 List.fold_left exprexprs ~init:acc ~f:(fun acc -> fun (e1, e2) -> expr_expr acc e1 e2)
252 | Shape fields ->
253 List.fold_left fields ~init:acc ~f:(fun acc -> fun (_, e) -> expr tcopt acc e)
254 | Clone e
255 | Await e
256 | Is (e, _)
257 | As (e, _, _)
258 | BracedExpr e
259 | ParenthesizedExpr e
260 | Cast (_, e)
261 | Unop (_, e)
262 | Class_const (e, _)
263 | Callconv (_, e)
264 | Import (_, e)
265 | Yield_from e
266 | Suspend e -> expr tcopt acc e
267 | Obj_get (e1, e2, _)
268 | Binop (_, e1, e2)
269 | Pipe (e1, e2)
270 | InstanceOf (e1, e2)
271 | Class_get (e1, e2) -> expr_expr acc e1 e2
272 | Array_get (e1, oe2) ->
273 let acc = expr tcopt acc e1 in
274 let acc = Option.value_map oe2 ~default:acc ~f:(expr tcopt acc) in
276 | New (e1, _, es2, es3)
277 | Call (e1, _, es2, es3) ->
278 let acc = expr tcopt acc e1 in
279 let acc = exprs acc es2 in
280 let acc = exprs acc es3 in
282 | Yield f ->
283 field acc f
284 | Eif (e1, oe2, e3) ->
285 let acc = expr tcopt acc e1 in
286 let _, acc2 = Option.value_map oe2 ~default:acc ~f:(expr tcopt acc) in
287 let _, acc3 = expr tcopt acc e3 in
288 smap_union acc (smap_inter acc2 acc3)
289 | NewAnonClass (es1, es2, _) ->
290 let acc = exprs acc es1 in
291 let acc = exprs acc es2 in
293 | Xml (_, attribs, es) ->
294 let attrib acc a =
295 match a with
296 | Xhp_simple (_, e)
297 | Xhp_spread e -> expr tcopt acc e
299 let acc = List.fold_left attribs ~init:acc ~f:attrib in
300 let acc = exprs acc es in
302 | Null
303 | True
304 | False
305 | Omitted
306 | Id _
307 | Yield_break
308 | Int _
309 | Float _
310 | String _
311 | Efun _
312 | Lfun _
313 | Lvar _
314 | Unsafeexpr _ -> acc