Introduce __ReturnsVoidToRx
[hiphop-php.git] / hphp / hack / src / typing / typing_mutability.ml
blob46d922f33f21ca0c2a2c10f8af26b4dce1ebbc90
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 Typing_mutability_env
11 module T = Tast
12 module Env = Typing_env
13 module LMap = Local_id.Map
14 open Typing_defs
15 open Hh_core
17 let fun_returns_mutable (env : Typing_env.env) (id : Nast.sid) =
18 match Env.get_fun env (snd id) with
19 | None -> false
20 | Some fty ->
21 fty.ft_returns_mutable
23 (* Returns true if the expression returns a new owned mutable *)
24 let rec expr_returns_owned_mutable
25 (env : Typing_env.env) (e : T.expr)
26 : bool =
27 match snd e with
28 | T.New _ -> true
29 (* Function call *)
30 | T.Call (_, (_, T.Id id), _, _, _)
31 | T.Call (_, (_, T.Fun_id id), _, _, _) ->
32 fun_returns_mutable env id
33 | T.Call (_, ((_, (_, Tfun fty)), T.Obj_get _), _, _, _)
34 | T.Call (_, ((_, (_, Tfun fty)), T.Class_const _), _, _, _)->
35 fty.ft_returns_mutable
36 (* conditional operator returns owned mutable if both consequence and alternative
37 return owned mutable *)
38 | T.Eif (_, e1_opt, e2) ->
39 Option.value_map e1_opt ~default:true ~f:(expr_returns_owned_mutable env) &&
40 expr_returns_owned_mutable env e2
41 (* ?? operator returns owned mutable if both left hand side and right hand side
42 return owned mutable *)
43 | T.NullCoalesce (l, r) ->
44 expr_returns_owned_mutable env l && expr_returns_owned_mutable env r
45 (* cast returns owned mutable if its expression part is owned mutable *)
46 | T.Cast (_, e) ->
47 expr_returns_owned_mutable env e
48 (* XHP expression is considered owned mutable *)
49 | T.Xml _ -> true
50 (* l |> r returns owned mutable if r yields owned mutable *)
51 | T.Pipe (_, _, r) ->
52 expr_returns_owned_mutable env r
53 | _ -> false
55 let check_function_return_value
56 ~function_returns_mutable
57 ~function_returns_void_for_rx
58 (env: Typing_env.env)
59 fun_pos
60 (e: T.expr) =
61 let error_mutable e mut_opt =
62 let kind =
63 match mut_opt with
64 | None -> "non-mutable"
65 | Some Const -> "const"
66 | Some Borrowed -> "borrowed"
67 | Some Mutable -> assert false in
68 Errors.invalid_mutable_return_result (T.get_position e) fun_pos kind in
69 let error_borrowed_as_immutable e =
70 (* attempt to return borrowed value as immutable *)
71 Errors.cannot_return_borrowed_value_as_immutable
72 fun_pos
73 (T.get_position e) in
74 let rec aux e =
75 match snd e with
76 | T.Lvar (_, id) ->
77 let mut_env = Env.get_env_mutability env in
78 begin match LMap.get id mut_env with
79 | Some (_, Mutable) ->
80 (* it is ok to return mutably owned values *)
82 | Some (_, mut) when function_returns_mutable ->
83 error_mutable e (Some mut)
84 | Some (_, Borrowed) when not function_returns_mutable ->
85 (* attempt to return borrowed value as immutable
86 unless function is marked with __ReturnsVoidToRx in which case caller
87 will not be able to alias the value *)
88 if not function_returns_void_for_rx
89 then error_borrowed_as_immutable e
90 | _ ->
91 if function_returns_mutable then error_mutable e None
92 end
93 | T.This when not function_returns_mutable && Env.function_is_mutable env ->
94 (* mutable this is treated as borrowed and this cannot be returned as immutable
95 unless function is marked with __ReturnsVoidToRx in which case caller
96 will not be able to alias the value *)
97 if not function_returns_void_for_rx
98 then error_borrowed_as_immutable e
99 | T.Eif (_, e1_opt, e2) ->
100 Option.iter e1_opt ~f:aux;
101 aux e2
102 (* ?? operator returns owned mutable if both left hand side and right hand side
103 return owned mutable *)
104 | T.NullCoalesce (l, r) ->
105 aux l;
106 aux r;
107 (* cast returns owned mutable if its expression part is owned mutable *)
108 | T.Cast (_, e) ->
109 aux e
110 (* XHP expression is considered owned mutable *)
111 | T.Xml _ -> ()
112 (* l |> r returns owned mutable if r yields owned mutable *)
113 | T.Pipe (_, _, r) ->
114 aux r
115 (* NOTE: we only consider mutable objects as legal return values so
116 literals, arrays/varrays/darrays/collections, unary/binary expressions
117 that does not yield objects, ints/floats are not considered valid
119 CONSIDER: We might consider to report special error message for scenarios when
120 return value is known to be literal\primitive\value with immutable semantics.
122 | _ ->
123 if function_returns_mutable && not (expr_returns_owned_mutable env e)
124 then error_mutable e None in
125 aux e
127 (* Returns true if we can modify properties of the expression *)
128 let expr_is_mutable
129 (env : Typing_env.env) (e : T.expr) : bool =
130 match snd e with
131 | T.Lvar id ->
132 Env.is_mutable env (snd id)
133 | T.This when Env.function_is_mutable env -> true
134 | T.Call(_, (_, T.Id (_, id)), _, _, _) when id = SN.Rx.mutable_ -> true
135 | _ -> false
137 let check_rx_mutable_arguments
138 (p : Pos.t) (env : Typing_env.env) (tel : T.expr list) =
139 match tel with
140 | [e] when expr_returns_owned_mutable env e -> ()
141 | _ ->
142 (* HH\Rx\mutable function expects single fresh mutably owned value *)
143 Errors.invalid_argument_of_rx_mutable_function p
145 let freeze_local (p : Pos.t) (env : Typing_env.env) (tel : T.expr list)
146 : Typing_env.env =
147 match tel with
148 | [(_, T.Lvar (id_pos, id));] ->
149 let mut_env = Env.get_env_mutability env in
150 let mut_env =
151 match LMap.get id mut_env with
152 | Some (_, Mutable) ->
153 LMap.remove id mut_env
154 | Some x ->
155 Errors.invalid_freeze_target p id_pos (to_string x);
156 mut_env
157 | None ->
158 Errors.invalid_freeze_target p id_pos "immutable";
159 mut_env in
160 Env.env_with_mut env mut_env
161 | [((id_pos, _), T.This);] ->
162 Errors.invalid_freeze_target p id_pos "the this type, which is mutably borrowed";
164 | _ ->
165 (* Error, freeze takes a single local as an argument *)
166 Errors.invalid_freeze_use p;
169 (* Checks that each parameter that is marked mutable is mutable *)
170 (* There's no List.iter2_shortest so I'm stuck with this *)
171 (* Return the remaining expressions to check against the variadic argument *)
172 let rec check_param_mutability (env : Typing_env.env)
173 (params : 'a fun_params ) (el : T.expr list) : T.expr list =
174 match params, el with
175 | [], _
176 | _, [] -> el
177 | param::ps, e::es ->
178 if param.fp_mutable then
179 if not (expr_is_mutable env e) then
180 Env.error_if_reactive_context env @@ begin fun () ->
181 Errors.mutable_argument_mismatch (param.fp_pos) (T.get_position e)
182 end;
184 (* Check the rest *)
185 check_param_mutability env ps es
187 let check_mutability_fun_params env fty el =
188 let params = fty.ft_params in
189 let remaining_exprs = check_param_mutability env params el in
190 begin match fty.ft_arity with
191 | Fvariadic (_, param) when param.fp_mutable ->
192 begin match List.find remaining_exprs
193 ~f:(fun e -> not (expr_is_mutable env e)) with
194 | Some expr ->
195 Env.error_if_reactive_context env @@ begin fun () ->
196 Errors.mutable_argument_mismatch (param.fp_pos) (T.get_position expr)
198 | None -> ()
200 | _ -> () end
203 let enforce_mutable_call (env : Typing_env.env) (te : T.expr) =
204 match snd te with
205 | T.Call (_, (_, T.Id id), _, el, _)
206 | T.Call (_, (_, T.Fun_id id), _, el, _) ->
207 begin match Env.get_fun env (snd id) with
208 | Some fty ->
209 check_mutability_fun_params env fty el
210 | None -> ()
212 (* $x->method() where method is mutable *)
213 | T.Call (_, ((pos, (r, Tfun fty)), T.Obj_get (expr, _, _)), _, el, _) ->
214 (if fty.ft_mutable && not (expr_is_mutable env expr) then
215 Env.error_if_reactive_context env @@ begin fun () ->
216 let fpos = Reason.to_pos r in
217 Errors.mutable_call_on_immutable fpos pos
218 end);
219 check_mutability_fun_params env fty el
220 (* TAny, T.Calls that don't have types, etc *)
221 | _ -> ()
223 let rec is_byval_collection_type env ty =
224 let check t =
225 match t with
226 | (_, Tclass ((_, x), _)) ->
227 x = SN.Collections.cVec ||
228 x = SN.Collections.cDict ||
229 x = SN.Collections.cKeyset
230 | _, (Tarraykind _ | Ttuple _ | Tshape _)
231 -> true
232 | _, Tunresolved tl -> Core_list.for_all tl ~f:(is_byval_collection_type env)
233 | _ -> false in
234 let _, tl = Typing_utils.get_all_supertypes env ty in
235 Core_list.for_all tl ~f:check
237 let rec is_byval_collection_value env v =
238 match v with
239 | (_, ty), T.Lvar _ ->
240 is_byval_collection_type env ty
241 | (_, ty), T.Array_get (e, _) ->
242 is_byval_collection_type env ty &&
243 is_byval_collection_value env e
244 | _ -> false
246 (* Checks for assignment errors as a pass on the TAST *)
247 let handle_assignment_mutability
248 (env : Typing_env.env) (te1 : T.expr) (te2 : T.expr)
249 : Typing_env.env =
250 (* If e2 is a mutable expression, then e1 is added to the mutability env *)
251 let mut_env = Env.get_env_mutability env in
252 (* Check for modifying immutable objects *)
253 (match snd te1 with
254 (* Setting mutable locals is okay *)
255 | T.Obj_get (e1, _, _) when expr_is_mutable env e1 -> ()
256 | T.Array_get (e1, _)
257 when expr_is_mutable env e1 || is_byval_collection_value env e1 -> ()
258 | T.Class_get _
259 | T.Obj_get _
260 | T.Array_get _ ->
261 Env.error_if_reactive_context env @@ begin fun () ->
262 let pos = T.get_position te1 in
263 Errors.obj_set_reactive pos
265 | _ -> ());
266 let mut_env = match snd te1, snd te2 with
267 | _, T.Lvar(p, id2) when LMap.mem id2 mut_env ->
268 Env.error_if_reactive_context env @@ begin fun () ->
269 (* Reassigning mutables is not allowed; error *)
270 Errors.reassign_mutable_var p
271 end;
272 mut_env
273 (* var = mutable(v) - add the var to the env since it points to a owned mutable value *)
274 | T.Lvar (_, id), T.Call(_, (_, T.Id (_, n)), _, _, _) when n = SN.Rx.mutable_ ->
275 LMap.add id (T.get_position te1, Mutable) mut_env
276 (* If the Lvar gets reassigned and shadowed to something that
277 isn't a mutable, it is now a regular immutable variable.
279 | T.Lvar (_, id), _ ->
280 LMap.remove id mut_env
281 | _ ->
282 mut_env in
283 Env.env_with_mut env mut_env