Permalink
Browse files

Introduce __ReturnsVoidToRx

Summary: This diff adds support for `__ReturnsVoidToRx` attribute which forces return type of the function to be `void` in reactive context. As a consequence methods annotated with this attribute are allowed to return mutably borrowed values (specifically `$this`) since rx caller will see return type as void and won't be able to use it.

Reviewed By: KendallHopkins

Differential Revision: D8038195

fbshipit-source-id: b1903d08c9f2d8b8e4e73239646d2e6b0851890f
  • Loading branch information...
vladima authored and hhvm-bot committed May 19, 2018
1 parent 8fb0181 commit d9e4630a4ddc2ed3934aa6ed0bd2839f9cf2e56f
Showing with 335 additions and 31 deletions.
  1. +7 −0 hphp/hack/src/decl/decl.ml
  2. +1 −0 hphp/hack/src/decl/decl_hint.ml
  3. +19 −0 hphp/hack/src/errors/errors.ml
  4. +2 −0 hphp/hack/src/errors/errors_sig.ml
  5. +2 −0 hphp/hack/src/naming/naming_special_names.ml
  6. +3 −2 hphp/hack/src/typing/tast_expand.ml
  7. +37 −21 hphp/hack/src/typing/typing.ml
  8. +1 −0 hphp/hack/src/typing/typing_defs.ml
  9. +2 −0 hphp/hack/src/typing/typing_env.ml
  10. +3 −0 hphp/hack/src/typing/typing_env_return_info.ml
  11. +1 −0 hphp/hack/src/typing/typing_extends.ml
  12. +4 −3 hphp/hack/src/typing/typing_log.ml
  13. +11 −4 hphp/hack/src/typing/typing_mutability.ml
  14. +6 −1 hphp/hack/src/typing/typing_mutability.mli
  15. +6 −0 hphp/hack/src/typing/typing_reactivity.ml
  16. +6 −0 hphp/hack/src/typing/typing_return.ml
  17. +22 −0 hphp/hack/src/typing/typing_subtype.ml
  18. +5 −0 hphp/hack/src/typing/typing_unify.ml
  19. +2 −0 hphp/hack/test/errors/error_map.ml
  20. +17 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx1.php
  21. +1 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx1.php.exp
  22. +22 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx2.php
  23. +4 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx2.php.exp
  24. +21 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx3.php
  25. +1 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx3.php.exp
  26. +25 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx4.php
  27. +11 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx4.php.exp
  28. +24 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx5.php
  29. +1 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx5.php.exp
  30. +19 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx6.php
  31. +1 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx6.php.exp
  32. +20 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx7.php
  33. +1 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx7.php.exp
  34. +26 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx8.php
  35. +1 −0 hphp/hack/test/typecheck/reactive/returns_void_to_rx8.php.exp
@@ -236,6 +236,9 @@ let has_return_disposable_attribute user_attributes =
let fun_returns_mutable user_attributes =
Attributes.mem SN.UserAttributes.uaMutableReturn user_attributes
let fun_returns_void_to_rx user_attributes =
Attributes.mem SN.UserAttributes.uaReturnsVoidToRx user_attributes
let has_mutable_attribute user_attributes =
Attributes.mem SN.UserAttributes.uaMutable user_attributes
@@ -313,6 +316,7 @@ and fun_decl_in_env env f =
check_params env f.f_params;
let reactivity = fun_reactivity env f.f_user_attributes in
let returns_mutable = fun_returns_mutable f.f_user_attributes in
let returns_void_to_rx = fun_returns_void_to_rx f.f_user_attributes in
let return_disposable = has_return_disposable_attribute f.f_user_attributes in
let arity_min = minimum_arity f.f_params in
let params = make_params env f.f_user_attributes reactivity f.f_params in
@@ -347,6 +351,7 @@ and fun_decl_in_env env f =
ft_returns_mutable = returns_mutable;
ft_return_disposable = return_disposable;
ft_decl_errors = None;
ft_returns_void_to_rx = returns_void_to_rx;
} in
ft
@@ -888,6 +893,7 @@ and method_decl env m =
let reactivity = fun_reactivity env m.m_user_attributes in
let mut = has_mutable_attribute m.m_user_attributes in
let returns_mutable = fun_returns_mutable m.m_user_attributes in
let returns_void_to_rx = fun_returns_void_to_rx m.m_user_attributes in
let return_disposable = has_return_disposable_attribute m.m_user_attributes in
let arity_min = minimum_arity m.m_params in
let params = make_params env m.m_user_attributes reactivity m.m_params in
@@ -922,6 +928,7 @@ and method_decl env m =
ft_returns_mutable = returns_mutable;
ft_return_disposable = return_disposable;
ft_decl_errors = None;
ft_returns_void_to_rx = returns_void_to_rx;
}
and method_check_override c m acc =
@@ -95,6 +95,7 @@ and hint_ p env = function
ft_mutable = false;
ft_returns_mutable = false;
ft_decl_errors = None;
ft_returns_void_to_rx = false;
}
| Happly ((p, "\\Tuple"), _)
| Happly ((p, "\\tuple"), _) ->
@@ -1082,6 +1082,8 @@ module Typing = struct
| InvalidArgumentOfRxMutableFunction
| LetVarImmutabilityViolation
| Unsealable
| ReturnVoidToRxMismatch
| ReturnsVoidToRxAsNonExpressionStatement
(* EXTEND HERE WITH NEW VALUES IF NEEDED *)
[@@ deriving enum, show { with_path = false } ]
let err_code = to_enum
@@ -2977,6 +2979,16 @@ let return_disposable_mismatch pos1_return_disposable pos1 pos2 =
pos2, if pos1_return_disposable then m2 else m1;
]
let return_void_to_rx_mismatch ~pos1_has_attribute pos1 pos2 =
let m1 = "This is marked <<__ReturnsVoidToRx>>." in
let m2 = "This is not marked <<__ReturnsVoidToRx>>." in
add_list
(Typing.err_code Typing.ReturnVoidToRxMismatch)
[
pos1, if pos1_has_attribute then m1 else m2;
pos2, if pos1_has_attribute then m2 else m1;
]
let this_as_lexical_variable pos =
add (Naming.err_code Naming.ThisAsLexicalVariable) pos "Cannot use $this as lexical variable"
@@ -3423,6 +3435,13 @@ let static_in_reactive_context pos name =
"Static " ^ name ^ " cannot be used in a reactive context."
)
let returns_void_to_rx_function_as_non_expression_statement pos fpos =
add_list (Typing.err_code Typing.ReturnsVoidToRxAsNonExpressionStatement) [
pos, "Cannot use result of function annotated with <<__ReturnsVoidToRx>> \
in reactive context";
fpos, "This is function declaration."
]
(*****************************************************************************)
(* Convert relative paths to absolute. *)
(*****************************************************************************)
@@ -534,4 +534,6 @@ module type S = sig
val missing_annotation_for_onlyrx_if_rxfunc_parameter: Pos.t -> unit
val mutable_in_nonreactive_context: Pos.t -> unit
val invalid_argument_of_rx_mutable_function: Pos.t -> unit
val return_void_to_rx_mismatch: pos1_has_attribute:bool -> Pos.t -> Pos.t -> unit
val returns_void_to_rx_function_as_non_expression_statement: Pos.t -> Pos.t -> unit
end
@@ -132,6 +132,7 @@ module UserAttributes = struct
let uaOnlyRxIfRxFunc = "__OnlyRxIfRxFunc"
let uaOnlyRxIfArgs = "__OnlyRxIfArgs"
let uaSealed = "__Sealed"
let uaReturnsVoidToRx = "__ReturnsVoidToRx"
let as_set = List.fold_right ~f:SSet.add ~init:SSet.empty
[
@@ -156,6 +157,7 @@ module UserAttributes = struct
uaOnlyRxIfRxFunc;
uaOnlyRxIfArgs;
uaSealed;
uaReturnsVoidToRx
]
end
@@ -56,14 +56,15 @@ let expand_ty env ty =
ft_where_constraints; ft_ret; ft_params;
ft_reactive; ft_return_disposable;
ft_mutable; ft_returns_mutable;
ft_is_coroutine; ft_ret_by_ref; ft_decl_errors } =
ft_is_coroutine; ft_ret_by_ref; ft_decl_errors;
ft_returns_void_to_rx } =
{ ft_pos; ft_deprecated; ft_arity; ft_abstract; ft_reactive; ft_is_coroutine;
ft_return_disposable; ft_ret_by_ref; ft_mutable; ft_returns_mutable;
ft_tparams = List.map exp_tparam ft_tparams;
ft_where_constraints = List.map exp_where_constraint ft_where_constraints;
ft_ret = exp_ty ft_ret;
ft_params = List.map exp_fun_param ft_params;
ft_decl_errors;
ft_decl_errors; ft_returns_void_to_rx;
}
and exp_fun_param { fp_pos; fp_name; fp_kind; fp_type; fp_mutable;
@@ -602,7 +602,7 @@ and stmt env = function
| Noop ->
env, T.Noop
| Expr e ->
let env, te, ty = expr env e in
let env, te, ty = expr ~is_expr_statement:true env e in
(* NB: this check does belong here and not in expr, even though it only
* applies to expressions -- we actually want to perform the check on
* statements that are expressions, e.g., "foo();" we want to check, but
@@ -658,8 +658,8 @@ and stmt env = function
let env = check_inout_return env in
let pos = fst e in
let Typing_env_return_info.{
return_type; return_disposable; return_mutable; return_explicit; return_by_ref } =
Env.get_return env in
return_type; return_disposable; return_mutable; return_explicit; return_by_ref;
return_void_to_rx } = Env.get_return env in
let expected =
if return_explicit
then Some (pos, Reason.URreturn,
@@ -671,6 +671,7 @@ and stmt env = function
then begin
Typing_mutability.check_function_return_value
~function_returns_mutable:return_mutable
~function_returns_void_for_rx: return_void_to_rx
env
env.Env.function_pos
te
@@ -1097,6 +1098,7 @@ and expr
?expected
?(accept_using_var = false)
?(is_using_clause = false)
?(is_expr_statement = false)
?is_func_arg
?forbid_uref
env e =
@@ -1106,12 +1108,14 @@ and expr
Typing_log.log_types 1 (fst e) env
[Typing_log.Log_sub ("Typing.expr " ^ Typing_reason.string_of_ureason r,
[Typing_log.Log_type ("expected_ty", ty)])] end;
raw_expr ~accept_using_var ~is_using_clause ?is_func_arg ?forbid_uref ?expected ~in_cond:false env e
raw_expr ~accept_using_var ~is_using_clause ~is_expr_statement
?is_func_arg ?forbid_uref ?expected ~in_cond:false env e
and raw_expr
~in_cond
?(accept_using_var = false)
?(is_using_clause = false)
?(is_expr_statement = false)
?expected
?lhs_of_null_coalesce
?is_func_arg
@@ -1120,7 +1124,7 @@ and raw_expr
env e =
debug_last_pos := fst e;
let env, te, ty =
expr_ ~in_cond ~accept_using_var ~is_using_clause ?expected
expr_ ~in_cond ~accept_using_var ~is_using_clause ~is_expr_statement ?expected
?lhs_of_null_coalesce ?is_func_arg ?forbid_uref
~valkind env e in
let () = match !expr_hook with
@@ -1225,6 +1229,7 @@ and expr_
~in_cond
?(accept_using_var = false)
?(is_using_clause = false)
?(is_expr_statement = false)
?lhs_of_null_coalesce
?(is_func_arg=false)
?(forbid_uref=false)
@@ -1278,9 +1283,9 @@ and expr_
let ty_arraykey = Reason.Ridx_dict key_pos, Tprim Tarraykey in
Type.sub_type p (Reason.index_class class_name) env key_ty ty_arraykey in
let check_call ~is_using_clause ~expected env p call_type e hl el uel ~in_suspend=
let check_call ~is_using_clause ~expected ~is_expr_statement env p call_type e hl el uel ~in_suspend=
let env, te, result =
dispatch_call ~is_using_clause ~expected p env call_type e hl el uel ~in_suspend in
dispatch_call ~is_using_clause ~expected ~is_expr_statement p env call_type e hl el uel ~in_suspend in
let env = Env.forget_members env p in
env, te, result in
@@ -1688,6 +1693,7 @@ and expr_
ft_returns_mutable = fty.ft_returns_mutable;
ft_return_disposable = fty.ft_return_disposable;
ft_decl_errors = None;
ft_returns_void_to_rx = fty.ft_returns_void_to_rx;
} in
make_result env (T.Method_caller(pos_cname, meth_name))
(reason, Tfun caller)
@@ -1846,7 +1852,8 @@ and expr_
tel,
[])) (Env.fresh_type())
| Call (call_type, e, hl, el, uel) ->
let env, te, ty = check_call ~is_using_clause ~expected env p call_type e hl el uel ~in_suspend:false in
let env, te, ty = check_call ~is_using_clause ~expected ~is_expr_statement
env p call_type e hl el uel ~in_suspend:false in
Typing_mutability.enforce_mutable_call env te;
env, te, ty
(* For example, e1 += e2. This is typed and translated as if
@@ -2089,14 +2096,15 @@ and expr_
make_result env (T.Yield taf) (Reason.Ryield_send p, Toption send)
| Await e ->
(* Await is permitted in a using clause e.g. using (await make_handle()) *)
let env, te, rty = expr ~is_using_clause env e in
let env, te, rty = expr ~is_using_clause ~is_expr_statement env e in
let env, ty = Async.overload_extract_from_awaitable env p rty in
make_result env (T.Await te) ty
| Suspend (e) ->
let env, te, ty =
match e with
| _, Call (call_type, e, hl, el, uel) ->
check_call ~is_using_clause ~expected env p call_type e hl el uel ~in_suspend:true
check_call ~is_using_clause ~expected ~is_expr_statement
env p call_type e hl el uel ~in_suspend:true
| (epos, _) ->
let env, te, (r, ty) = expr env e in
(* not a call - report an error *)
@@ -3343,7 +3351,7 @@ and is_abstract_ft fty = match fty with
* The typing of call is different.
*)
and dispatch_call ~expected ~is_using_clause p env call_type
and dispatch_call ~expected ~is_using_clause ~is_expr_statement p env call_type
(fpos, fun_expr as e) hl el uel ~in_suspend =
let make_call env te thl tel tuel ty =
env, T.make_typed_expr p ty (T.Call (call_type, te, thl, tel, tuel)), ty in
@@ -3642,6 +3650,7 @@ and is_abstract_ft fty = match fty with
ft_returns_mutable = fty.ft_returns_mutable;
ft_return_disposable = fty.ft_return_disposable;
ft_decl_errors = None;
ft_returns_void_to_rx = fty.ft_returns_void_to_rx;
}
) in
let containers = List.map vars (fun var ->
@@ -3875,7 +3884,8 @@ and is_abstract_ft fty = match fty with
class_get ~is_method:true ~is_const:false ~explicit_tparams:hl env ty1 m CIparent in
let fty = check_abstract_parent_meth (snd m) p fty in
check_coroutine_call env fty;
let env, tel, tuel, ty = call ~expected ~receiver_type:ty1 p env fty el uel in
let env, tel, tuel, ty =
call ~expected ~is_expr_statement ~receiver_type:ty1 p env fty el uel in
make_call env (T.make_typed_expr fpos fty
(T.Class_const (tcid, m))) hl tel tuel ty
end
@@ -3950,7 +3960,8 @@ and is_abstract_ft fty = match fty with
())
| _ -> () in
check_coroutine_call env fty;
let env, tel, tuel, ty = call ~expected ~receiver_type:ty1 p env fty el uel in
let env, tel, tuel, ty =
call ~expected ~receiver_type:ty1 ~is_expr_statement p env fty el uel in
make_call env (T.make_typed_expr fpos fty
(T.Class_const(te1, m))) hl tel tuel ty
@@ -3966,7 +3977,8 @@ and is_abstract_ft fty = match fty with
let tel = ref [] and tuel = ref [] and tftyl = ref [] in
let fn = (fun (env, fty, _) ->
check_coroutine_call env fty;
let env, tel_, tuel_, method_ = call ~expected ~receiver_type:ty1 p env fty el uel in
let env, tel_, tuel_, method_ =
call ~expected ~receiver_type:ty1 ~is_expr_statement p env fty el uel in
tel := tel_; tuel := tuel_;
tftyl := fty :: !tftyl;
env, method_, None) in
@@ -3985,13 +3997,15 @@ and is_abstract_ft fty = match fty with
Typing_hooks.dispatch_id_hook x env;
let env, fty = fun_type_of_id env x hl in
check_coroutine_call env fty;
let env, tel, tuel, ty = call ~expected p env fty el uel in
let env, tel, tuel, ty =
call ~expected ~is_expr_statement p env fty el uel in
make_call env (T.make_typed_expr fpos fty (T.Fun_id x)) hl tel tuel ty
| Id (_, id as x) ->
Typing_hooks.dispatch_id_hook x env;
let env, fty = fun_type_of_id env x hl in
check_coroutine_call env fty;
let env, tel, tuel, ty = call ~expected p env fty el uel in
let env, tel, tuel, ty =
call ~expected ~is_expr_statement p env fty el uel in
if id = SN.Rx.mutable_ then begin
Typing_mutability.check_rx_mutable_arguments p env tel;
if not (Env.env_local_reactive env) then
@@ -4001,7 +4015,8 @@ and is_abstract_ft fty = match fty with
| _ ->
let env, te, fty = expr env e in
check_coroutine_call env fty;
let env, tel, tuel, ty = call ~expected p env fty el uel in
let env, tel, tuel, ty =
call ~expected ~is_expr_statement p env fty el uel in
make_call env te hl tel tuel ty
and fun_type_of_id env x hl =
@@ -5040,8 +5055,8 @@ and inout_write_back env { fp_type; _ } (_, e) =
env
| _ -> env
and call ~expected ?receiver_type pos env fty el uel =
let env, tel, tuel, ty = call_ ~expected ~receiver_type pos env fty el uel in
and call ~expected ?(is_expr_statement=false) ?receiver_type pos env fty el uel =
let env, tel, tuel, ty = call_ ~expected ~is_expr_statement ~receiver_type pos env fty el uel in
(* We need to solve the constraints after every single function call.
* The type-checker is control-flow sensitive, the same value could
* have different type depending on the branch that we are in.
@@ -5050,7 +5065,7 @@ and call ~expected ?receiver_type pos env fty el uel =
let env = Env.check_todo env in
env, tel, tuel, ty
and call_ ~expected ~receiver_type pos env fty el uel =
and call_ ~expected ~receiver_type ~is_expr_statement pos env fty el uel =
let make_unpacked_traversable_ty pos ty =
let unpack_r = Reason.Runpack_param pos in
unpack_r, Tclass ((pos, SN.Collections.cTraversable), [ty])
@@ -5095,9 +5110,10 @@ and call_ ~expected ~receiver_type pos env fty el uel =
(* Typing of format string functions. It is dependent on the arguments (el)
* so it cannot be done earlier.
*)
Typing_reactivity.verify_void_return_to_rx ~is_expr_statement pos env ft;
let pos_def = Reason.to_pos r2 in
let env, ft = Typing_exts.retype_magic_func env ft el in
check_deprecated pos ft;
let pos_def = Reason.to_pos r2 in
let env, var_param = variadic_param env ft in
(* Force subtype with expected result *)
@@ -387,6 +387,7 @@ and 'phase fun_type = {
ft_mutable : bool ;
ft_returns_mutable : bool ;
ft_decl_errors : Errors.t option ;
ft_returns_void_to_rx: bool ;
}
(* Arity information for a fun_type; indicating the minimum number of
@@ -105,6 +105,7 @@ let make_ft p reactivity is_coroutine params ret_ty =
ft_returns_mutable = false;
ft_mutable = false;
ft_decl_errors = None;
ft_returns_void_to_rx = false;
}
let get_shape_field_name = function
@@ -317,6 +318,7 @@ let empty tcopt file ~droot = {
return_mutable = false;
return_explicit = false;
return_by_ref = false;
return_void_to_rx = false;
};
params = Local_id.Map.empty;
self_id = "";
@@ -27,4 +27,7 @@ type t = {
(* Is the function expected to return a reference? *)
return_by_ref: bool;
(* Is function return type in rx context treated as void? *)
return_void_to_rx: bool;
}
@@ -286,6 +286,7 @@ let default_constructor_ce class_ =
ft_returns_mutable = false;
ft_return_disposable = false;
ft_decl_errors = None;
ft_returns_void_to_rx = false;
}
in { ce_final = false;
ce_is_xhp_attr = false;
Oops, something went wrong.

0 comments on commit d9e4630

Please sign in to comment.