Make hh_single_type_check --MRO output easier to read
[hiphop-php.git] / hphp / hack / src / typing / typing_mutability.ml
blob88bda0ae83aea863449feef0495d9e9b7adff8ea
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_prelude
11 open Typing_mutability_env
12 open Typing_defs
13 module Env = Typing_env
14 module T = Aast
15 module LMap = Local_id.Map
17 module type Env_S = sig
18 type env
20 val env_reactivity : env -> reactivity
22 val get_fun : env -> Decl_provider.fun_key -> Decl_provider.fun_decl option
24 val expand_type : env -> locl_ty -> env * locl_ty
25 end
27 module Shared (Env : Env_S) = struct
28 (* true if function has <<__ReturnMutable>> annotation, otherwise false *)
29 let is_fun_call_returning_mutable (env : Env.env) (e : Tast.expr) : bool =
30 let fun_ty_returns_mutable fun_ty =
31 match get_node fun_ty with
32 | Tfun fty -> get_ft_returns_mutable fty
33 | _ -> false
35 let fun_returns_mutable id =
36 match Env.get_fun env (snd id) with
37 | Some { fe_type = fun_ty; _ } -> fun_ty_returns_mutable fun_ty
38 | _ -> false
40 match snd e with
41 (* Function call *)
42 | T.Call ((_, T.Id id), _, _, _)
43 | T.Call ((_, T.Fun_id id), _, _, _) ->
44 fun_returns_mutable id
45 | T.Call (((_, fun_ty), T.Obj_get _), _, _, _)
46 | T.Call (((_, fun_ty), T.Class_const _), _, _, _)
47 | T.Call (((_, fun_ty), T.Lvar _), _, _, _) ->
48 let (_, efun_ty) = Env.expand_type env fun_ty in
49 fun_ty_returns_mutable efun_ty
50 | _ -> false
51 end
53 include Shared (struct
54 type env = Typing_env_types.env
56 let env_reactivity = Typing_env_types.env_reactivity
58 let get_fun = Typing_env.get_fun
60 let expand_type = Typing_env.expand_type
61 end)
63 let handle_value_in_return
64 ~function_returns_mutable
65 ~function_returns_void_for_rx
66 (env : Typing_env_types.env)
67 fun_pos
68 (e : Tast.expr) : Typing_env_types.env =
69 let error_mutable e mut_opt =
70 let kind =
71 match mut_opt with
72 | Immutable -> "(non-mutable)"
73 | MaybeMutable -> "(maybe-mutable)"
74 | Borrowed -> "(borrowed)"
75 | Mutable -> assert false
77 Errors.invalid_mutable_return_result (Tast.get_position e) fun_pos kind
79 let error_borrowed_as_immutable e =
80 (* attempt to return borrowed value as immutable *)
81 Errors.cannot_return_borrowed_value_as_immutable
82 fun_pos
83 (Tast.get_position e)
85 let rec aux env e =
86 match snd e with
87 (* ignore nulls - it is ok to return then from functions
88 that return nullable types and for non-nullable return types it will
89 be an error anyways *)
90 | T.Null -> env
91 (* allow bare new expressions
92 - implicit Rx\mutable in __MutableReturn functions *)
93 | T.New _
94 | T.Xml _ ->
95 env
96 | T.Call ((_, T.Id (_, id)), _, _, _) when String.equal id SN.Rx.mutable_ ->
97 (* ok to return result of Rx\mutable - implicit Rx\move *)
98 env
99 | T.Pipe (_, _, r) ->
100 (* ok for pipe if rhs returns mutable *)
101 aux env r
102 | T.Binop (Ast_defs.QuestionQuestion, l, r) ->
103 let env = aux env l in
104 aux env r
105 | T.Lvar (_, id) ->
106 let mut_env = Env.get_env_mutability env in
107 begin
108 match LMap.find_opt id mut_env with
109 | Some (p, Mutable) ->
110 (* it is ok to return mutably owned values *)
111 let env = Env.unset_local env id in
112 Env.env_with_mut env (LMap.add id (p, Mutable) mut_env)
113 | Some (_, Borrowed) when not function_returns_mutable ->
114 (* attempt to return borrowed value as immutable
115 unless function is marked with __ReturnsVoidToRx in which case caller
116 will not be able to alias the value *)
117 if not function_returns_void_for_rx then error_borrowed_as_immutable e;
119 | Some (_, mut) when function_returns_mutable ->
120 error_mutable e mut;
122 | _ -> env
124 | T.This
125 when (not function_returns_mutable)
126 && Option.is_some (Env.function_is_mutable env) ->
127 (* mutable this is treated as borrowed and this cannot be returned as immutable
128 unless function is marked with __ReturnsVoidToRx in which case caller
129 will not be able to alias the value *)
130 if not function_returns_void_for_rx then error_borrowed_as_immutable e;
132 | _ ->
133 (* for __MutableReturn functions allow delegating calls
134 to __MutableReturn functions *)
135 ( if function_returns_mutable && not (is_fun_call_returning_mutable env e)
136 then
137 let kind =
138 "not a valid return value for `__MutableReturn` functions."
140 Errors.invalid_mutable_return_result (Tast.get_position e) fun_pos kind
144 aux env e
146 let freeze_or_move_local
147 (p : Pos.t)
148 (env : Typing_env_types.env)
149 (tel : Tast.expr list)
150 (invalid_target : Pos.t -> Pos.t -> string -> unit)
151 (invalid_use : Pos.t -> unit) : Typing_env_types.env =
152 match tel with
153 | [(_, T.Any)] -> env
154 | [(_, T.Lvar (id_pos, id))] ->
155 let mut_env = Env.get_env_mutability env in
156 begin
157 match LMap.find_opt id mut_env with
158 | Some (p, Mutable) ->
159 let env = Env.unset_local env id in
160 Env.env_with_mut env (LMap.add id (p, Mutable) mut_env)
161 | Some x ->
162 invalid_target p id_pos (to_string x);
164 | None ->
165 invalid_target p id_pos "immutable";
168 | [((id_pos, _), T.This)] ->
169 invalid_target p id_pos "the `this` type, which is mutably borrowed";
171 | _ ->
172 (* Error, freeze/move takes a single local as an argument *)
173 invalid_use p;
176 let freeze_local (p : Pos.t) (env : Typing_env_types.env) (tel : Tast.expr list)
177 : Typing_env_types.env =
178 freeze_or_move_local
182 Errors.invalid_freeze_target
183 Errors.invalid_freeze_use
185 let move_local (p : Pos.t) (env : Typing_env_types.env) (tel : Tast.expr list) :
186 Typing_env_types.env =
187 freeze_or_move_local
191 Errors.invalid_move_target
192 Errors.invalid_move_use
194 let rec is_move_or_mutable_call ?(allow_move = true) te =
195 match te with
196 | T.Call ((_, T.Id (_, n)), _, _, _) ->
197 String.equal n SN.Rx.mutable_ || (allow_move && String.equal n SN.Rx.move)
198 | T.Pipe (_, _, (_, r)) -> is_move_or_mutable_call ~allow_move:false r
199 | _ -> false
201 (* Checks for assignment errors as a pass on the TAST *)
202 let handle_assignment_mutability
203 (env : Typing_env_types.env) (te1 : Tast.expr) (te2 : Tast.expr_ option) :
204 Typing_env_types.env =
205 (* If e2 is a mutable expression, then e1 is added to the mutability env *)
206 let mut_env = Env.get_env_mutability env in
207 let mut_env =
208 match (snd te1, te2) with
209 | (_, Some T.This)
210 when Option.equal
211 equal_param_mutability
212 (Env.function_is_mutable env)
213 (Some Param_borrowed_mutable) ->
214 (* aliasing $this - bad for __Mutable and __MaybeMutable functions *)
215 ( Env.error_if_reactive_context env @@ fun () ->
216 Errors.reassign_mutable_this
217 ~in_collection:false
218 ~is_maybe_mutable:false
219 (Tast.get_position te1) );
220 mut_env
221 | (_, Some T.This)
222 when Option.equal
223 equal_param_mutability
224 (Env.function_is_mutable env)
225 (Some Param_maybe_mutable) ->
226 (* aliasing $this - bad for __Mutable and __MaybeMutable functions *)
227 ( Env.error_if_reactive_context env @@ fun () ->
228 Errors.reassign_mutable_this
229 ~in_collection:false
230 ~is_maybe_mutable:true
231 (Tast.get_position te1) );
232 mut_env
233 (* var = mutable(v)/move(v) - add the var to the env since it points to a owned mutable value *)
234 | (T.Lvar (p, id), Some e) when is_move_or_mutable_call e ->
235 begin
236 match LMap.find_opt id mut_env with
237 | Some ((_, (Immutable | Borrowed | MaybeMutable)) as mut) ->
238 (* error when assigning owned mutable to another mutability flavor *)
239 Errors.invalid_mutability_flavor p (to_string mut) "mutable"
240 | _ -> ()
241 end;
242 LMap.add id (Tast.get_position te1, Mutable) mut_env
243 (* Reassigning mutables is not allowed; error *)
244 | (_, Some (T.Lvar (p, id2)))
245 when Option.value_map
246 (LMap.find_opt id2 mut_env)
247 ~default:false
248 ~f:(fun (_, m) -> not (equal_mut_type m Immutable)) ->
249 ( Env.error_if_reactive_context env @@ fun () ->
250 match LMap.find id2 mut_env with
251 | (_, MaybeMutable) ->
252 Errors.reassign_maybe_mutable_var ~in_collection:false p
253 | _ -> Errors.reassign_mutable_var ~in_collection:false p );
254 mut_env
255 (* If the Lvar gets reassigned and shadowed to something that
256 isn't a mutable, it is now a regular immutable variable.
258 | (T.Lvar (p, id), _) ->
259 begin
260 match LMap.find_opt id mut_env with
261 (* ok if local is immutable*)
262 | Some (_, Immutable) -> mut_env
263 (* error assigning immutable value to local known to be mutable *)
264 | Some mut ->
265 Errors.invalid_mutability_flavor p (to_string mut) "immutable";
266 mut_env
267 | None ->
268 (* ok - add new locals *)
269 LMap.add id (Tast.get_position te1, Immutable) mut_env
271 | _ -> mut_env
273 Env.env_with_mut env mut_env