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.
10 open Typing_mutability_env
12 module Env
= Typing_env
13 module LMap
= Local_id.Map
17 let fun_returns_mutable (env
: Typing_env.env
) (id
: Nast.sid
) =
18 match Env.get_fun env
(snd id
) with
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
)
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 *)
47 expr_returns_owned_mutable env e
48 (* XHP expression is considered owned mutable *)
50 (* l |> r returns owned mutable if r yields owned mutable *)
52 expr_returns_owned_mutable env r
55 let check_function_return_value
56 ~function_returns_mutable
57 ~function_returns_void_for_rx
61 let error_mutable e mut_opt
=
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
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
91 if function_returns_mutable
then error_mutable e None
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;
102 (* ?? operator returns owned mutable if both left hand side and right hand side
103 return owned mutable *)
104 | T.NullCoalesce
(l
, r
) ->
107 (* cast returns owned mutable if its expression part is owned mutable *)
110 (* XHP expression is considered owned mutable *)
112 (* l |> r returns owned mutable if r yields owned mutable *)
113 | T.Pipe
(_
, _
, 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.
123 if function_returns_mutable
&& not
(expr_returns_owned_mutable env e
)
124 then error_mutable e None
in
127 (* Returns true if we can modify properties of the expression *)
129 (env
: Typing_env.env
) (e
: T.expr
) : bool =
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
137 let check_rx_mutable_arguments
138 (p
: Pos.t
) (env
: Typing_env.env
) (tel
: T.expr list
) =
140 | [e
] when expr_returns_owned_mutable env e
-> ()
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
)
148 | [(_
, T.Lvar
(id_pos
, id
));] ->
149 let mut_env = Env.get_env_mutability env
in
151 match LMap.get id
mut_env with
152 | Some
(_
, Mutable
) ->
153 LMap.remove id
mut_env
155 Errors.invalid_freeze_target p id_pos
(to_string x
);
158 Errors.invalid_freeze_target p id_pos
"immutable";
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";
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
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
)
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
195 Env.error_if_reactive_context env
@@ begin fun () ->
196 Errors.mutable_argument_mismatch
(param
.fp_pos
) (T.get_position expr
)
203 let enforce_mutable_call (env
: Typing_env.env
) (te
: T.expr
) =
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
209 check_mutability_fun_params env fty el
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
219 check_mutability_fun_params env fty el
220 (* TAny, T.Calls that don't have types, etc *)
223 let rec is_byval_collection_type env ty
=
226 | (_
, Tclass
((_
, x
), _
)) ->
227 x
= SN.Collections.cVec
||
228 x
= SN.Collections.cDict
||
229 x
= SN.Collections.cKeyset
230 | _
, (Tarraykind _
| Ttuple _
| Tshape _
)
232 | _
, Tunresolved tl
-> Core_list.for_all tl ~f
:(is_byval_collection_type env
)
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
=
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
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
)
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 *)
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
-> ()
261 Env.error_if_reactive_context env
@@ begin fun () ->
262 let pos = T.get_position te1
in
263 Errors.obj_set_reactive
pos
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
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
283 Env.env_with_mut env
mut_env