Permalink
Browse files

Support Rx\IS_ENABLED constant

Summary: This diff adds support for Rx\IS_ENABLED constant - environment can set this value to indicate that reactive capabilities are not enabled so code can use less safe implementation. If condition uses Rx\IS_ENABLED then false branch is treated as running in non-reactive context (initial context must be reactive)

Reviewed By: jamesjwu

Differential Revision: D6922598

fbshipit-source-id: 7753db90bf6d48c8218dd4ce30c1610b18216a52
  • Loading branch information...
vladima authored and hhvm-bot committed Feb 8, 2018
1 parent 0e4d8c0 commit a8bb68d3ec94bcc223b1f96666e6cb6a2cf34156
@@ -0,0 +1,14 @@
<?hh // decl
/**
* Copyright (c) 2018, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the "hack" directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
namespace HH\Rx {
const bool IS_ENABLED = false;
}
@@ -308,6 +308,7 @@ module HH = struct
let asio_va = "\\HH\\Asio\\va"
let lib_tuple_from_async = "\\HH\\Lib\\Tuple\\from_async"
let lib_tuple_gen = "\\HH\\Lib\\Tuple\\gen"
let rx_is_enabled = "\\HH\\Rx\\IS_ENABLED"
end
@@ -1525,6 +1525,13 @@ and expr_
| None ->
make_result env (T.Id id) (Reason.Rnone, Tany)
| Some ty ->
if cst_name = SN.HH.rx_is_enabled
then begin
if Env.is_checking_lambda ()
then Errors.rx_enabled_in_lambdas cst_pos
else if Env.env_reactivity env = Nonreactive
then Errors.rx_enabled_in_non_rx_context cst_pos
end;
let env, ty =
Phase.localize_with_self env ty in
make_result env (T.Id id) ty
@@ -5419,6 +5426,11 @@ and condition ?lhs_of_null_coalesce env tparamet =
| p, Binop ((Ast.Diff | Ast.Diff2 as op), e1, e2) ->
let op = if op = Ast.Diff then Ast.Eqeq else Ast.EQeqeq in
condition env (not tparamet) (p, Binop (op, e1, e2))
| _, Id (_, s) when s = SN.HH.rx_is_enabled ->
(* when Rx\IS_ENABLED is false - switch env to non-reactive *)
if not tparamet
then Env.set_env_reactive env Nonreactive
else env
| _, Binop (Ast.AMpamp, e1, e2) when tparamet ->
let env = condition env true e1 in
let env = condition env true e2 in
@@ -243,13 +243,14 @@ let empty_fake_members = {
valid = SSet.empty;
}
let empty_local tpenv = {
let empty_local tpenv local_reactive = {
tpenv = tpenv;
fake_members = empty_fake_members;
local_types = Typing_continuations.Map.empty;
local_using_vars = Local_id.Set.empty;
local_type_history = Local_id.Map.empty;
local_mutability = Local_id.Map.empty;
local_reactive = local_reactive;
}
let empty tcopt file ~droot = {
@@ -259,7 +260,7 @@ let empty tcopt file ~droot = {
outer_reason = Reason.URnone;
tenv = IMap.empty;
subst = IMap.empty;
lenv = empty_local SMap.empty;
lenv = empty_local SMap.empty Nonreactive;
todo = [];
in_loop = false;
inside_constructor = false;
@@ -282,7 +283,6 @@ let empty tcopt file ~droot = {
parent_id = "";
parent = Reason.none, Tany;
fun_kind = Ast.FSync;
fun_reactive = Nonreactive;
fun_mutable = false;
anons = IMap.empty;
file = file;
@@ -291,15 +291,15 @@ let empty tcopt file ~droot = {
}
let set_env_reactive env reactive =
{ env with genv = {env.genv with fun_reactive = reactive }}
{ env with lenv = {env.lenv with local_reactive = reactive }}
let set_env_function_pos env function_pos =
{ env with function_pos }
let lambda_reactive = ref None
let env_reactivity env =
Option.value !lambda_reactive ~default:env.genv.fun_reactive
Option.value !lambda_reactive ~default:env.lenv.local_reactive
(* Some form (strict/shallow/local) of reactivity *)
let env_local_reactive env =
@@ -328,6 +328,9 @@ let not_lambda_reactive () =
| Some _ -> Some Nonreactive
| None -> None)
let is_checking_lambda () =
Option.is_some !lambda_reactive
let error_if_reactive_context env f =
not_lambda_reactive ();
if env_local_reactive env then f ()
@@ -374,7 +377,7 @@ let get_env_mutability env =
let fresh_tenv env f =
f { env with
todo = [];
lenv = empty_local env.lenv.tpenv;
lenv = empty_local env.lenv.tpenv env.lenv.local_reactive;
tenv = IMap.empty;
in_loop = false
}
@@ -753,7 +756,7 @@ let unbind = unbind []
*)
let set_local env x new_type =
let {fake_members; local_types; local_type_history; local_using_vars;
tpenv; local_mutability;} = env.lenv in
tpenv; local_mutability; local_reactive} = env.lenv in
let env, new_type = unbind env new_type in
let next_cont = LEnvC.get_cont Cont.Next local_types in
let all_types, expr_id =
@@ -775,7 +778,7 @@ let set_local env x new_type =
let local_type_history = Local_id.Map.add x all_types local_type_history in
let env = { env with
lenv = {fake_members; local_types; local_type_history; local_using_vars;
tpenv; local_mutability;} }
tpenv; local_mutability; local_reactive; } }
in
env
@@ -787,13 +790,15 @@ let set_using_var env x =
env.lenv with local_using_vars = Local_id.Set.add x env.lenv.local_using_vars } }
let unset_local env local =
let {fake_members; local_types ; local_type_history; local_using_vars; tpenv; local_mutability;} = env.lenv in
let {fake_members; local_types ; local_type_history;
local_using_vars; tpenv; local_mutability;local_reactive; } = env.lenv in
let local_types = LEnvC.remove_from_cont Cont.Next local local_types in
let local_using_vars = Local_id.Set.remove local local_using_vars in
let local_type_history = Local_id.Map.remove local local_type_history in
let local_mutability = Local_id.Map.remove local local_mutability in
let env = { env with
lenv = {fake_members; local_types; local_type_history; local_using_vars; tpenv; local_mutability} }
lenv = {fake_members; local_types; local_type_history; local_using_vars;
tpenv; local_mutability; local_reactive} }
in
env
@@ -25,6 +25,7 @@ val expand_type : env -> locl ty -> env * locl ty
val make_ft : Pos.t -> reactivity -> bool -> decl fun_params -> decl ty -> decl fun_type
val get_shape_field_name : Nast.shape_field_name -> string
val empty_fake_members : fake_members
val empty_local : tpenv -> reactivity -> local_env
val empty : TypecheckerOptions.t -> Relative_path.t ->
droot: Typing_deps.Dep.variant option -> env
val is_typedef : Typedefs.key -> bool
@@ -111,6 +112,7 @@ val add_generic_parameters : env -> Nast.tparam list -> env
val get_generic_parameters : env -> string list
val check_lambda_reactive : (unit -> 'a) -> reactivity * 'a
val not_lambda_reactive : unit -> unit
val is_checking_lambda : unit -> bool
val error_if_reactive_context : env -> (unit -> unit) -> unit
val add_fresh_generic_parameter : env -> string -> env * string
val get_tpenv_size : env -> int
@@ -41,6 +41,8 @@ type local_env = {
fake_members : fake_members;
local_types : local_types;
local_mutability : Typing_mutability_env.mutability_env;
(* Whether current environment is reactive *)
local_reactive : reactivity;
local_type_history : local_history Local_id.Map.t;
(* Local variables that were assigned in a `using` clause *)
local_using_vars : Local_id.Set.t;
@@ -91,8 +93,6 @@ and genv = {
self : locl ty;
static : bool;
fun_kind : Ast.fun_kind;
(* Whether current function is reactive *)
fun_reactive : reactivity;
fun_mutable : bool;
anons : anon IMap.t;
file : Relative_path.t;
@@ -40,6 +40,7 @@
fake_members : fake_members;
local_types : local_types;
local_mutability : Typing_mutability_env.mutability_env;
local_reactive : reactivity;
local_type_history : local_history Local_id.Map.t;
(* Local variables that were assigned in a `using` clause *)
local_using_vars : Local_id.Set.t;
@@ -90,7 +91,6 @@ and genv = {
self : locl ty;
static : bool;
fun_kind : Ast.fun_kind;
fun_reactive : reactivity;
fun_mutable : bool;
anons : anon IMap.t;
file : Relative_path.t;
@@ -61,6 +61,7 @@ let intersect env parent_lenv lenv1 lenv2 =
let parent_locals_with_hist = Env.merge_locals_and_history parent_lenv in
let local_mutability = Typing_mutability_env.intersect_mutability
parent_lenv.local_mutability lenv1.local_mutability lenv2.local_mutability in
let local_reactive = parent_lenv.local_reactive in
let env, new_locals =
LMap.fold begin fun local_id (all_types1, ty1, eid1) (env, locals) ->
match LMap.get local_id lenv2_locals_with_hist with
@@ -92,6 +93,7 @@ let intersect env parent_lenv lenv1 lenv2 =
local_using_vars;
tpenv;
local_mutability;
local_reactive;
}
}
@@ -158,6 +160,8 @@ let integrate env parent_lenv child_lenv =
tpenv = env.lenv.tpenv;
(* The mutability of the entire block is always that of the child *)
local_mutability = child_lenv.local_mutability;
(* always grab reactive context from child *)
local_reactive = child_lenv.local_reactive;
}
}
@@ -236,6 +240,7 @@ let fully_integrate env parent_lenv =
local_using_vars;
tpenv = child_lenv.tpenv;
local_mutability = child_lenv.local_mutability;
local_reactive = child_lenv.local_reactive;
} }
let env_with_empty_fakes env =
@@ -1003,6 +1003,8 @@ module Typing = struct
let mutable_return_result_mismatch = 4217 (* DONT MODIFY!!!! *)
let nonreactive_call_from_shallow = 4218 (* DONT MODIFY!!!! *)
let enum_type_typedef_nonnull = 4219 (* DONT MODIFy!!!! *)
let rx_enabled_in_non_rx_context = 4220 (* DONT MODIFY!!!! *)
let rx_enabled_in_lambdas = 4221 (* DONT MODIFY!!!! *)
(* EXTEND HERE WITH NEW VALUES IF NEEDED *)
end
@@ -3079,6 +3081,16 @@ let nonreactive_call_from_shallow pos decl_pos =
decl_pos, "This function is not reactive."
]
let rx_enabled_in_non_rx_context pos =
add Typing.rx_enabled_in_non_rx_context pos (
"\\HH\\Rx\\IS_ENABLED can only be used in reactive functions."
)
let rx_enabled_in_lambdas pos =
add Typing.rx_enabled_in_lambdas pos (
"\\HH\\Rx\\IS_ENABLED cannot be used inside lambdas."
)
let nonreactive_append pos =
let msg = "Cannot append to a Hack Collection types in a reactive context" in
add Typing.nonreactive_append pos msg
@@ -478,4 +478,6 @@ module type S = sig
val inout_argument_bad_type : Pos.t -> (Pos.t * string) list -> unit
val nonreactive_call_from_shallow : Pos.t -> Pos.t -> unit
val illegal_destructor : Pos.t -> unit
val rx_enabled_in_non_rx_context : Pos.t -> unit
val rx_enabled_in_lambdas : Pos.t -> unit
end
@@ -0,0 +1,18 @@
<?hh // strict
function f(): int {
if (HH\Rx\IS_ENABLED) {
return rx();
} else {
return nonrx();
}
}
<<__Rx>>
function rx() {
return 1;
}
function nonrx() {
return 1;
}
@@ -0,0 +1,2 @@
File "test_rx_enabled1.php", line 4, characters 7-22:
\HH\Rx\IS_ENABLED can only be used in reactive functions. (Typing[4220])
@@ -0,0 +1,88 @@
<?hh // strict
<<__Rx>>
function f1(): int {
if (HH\Rx\IS_ENABLED) {
return rx();
} else {
return nonrx();
}
}
<<__Rx>>
function f2(): int {
return HH\Rx\IS_ENABLED ? rx() : nonrx();
}
// ======== RxShallow ========
<<__RxShallow>>
function f3(): int {
if (HH\Rx\IS_ENABLED) {
return rx() + rxshallow();
} else {
return nonrx();
}
}
<<__RxShallow>>
function f4(): int {
return HH\Rx\IS_ENABLED ? rx() + rxshallow() : nonrx();
}
// ======== RxLocal ========
<<__RxLocal>>
function f5(): int {
if (HH\Rx\IS_ENABLED) {
return rx();
} else {
return nonrx();
}
}
<<__RxLocal>>
function f6(): int {
return HH\Rx\IS_ENABLED ? rx() : nonrx();
}
<<__Rx>>
function f7(): int {
invariant(HH\Rx\IS_ENABLED, "Host with Rx support expected.");
return rx();
}
<<__Rx>>
function f8(): int {
invariant(!HH\Rx\IS_ENABLED, "Host with Rx support not expected.");
return nonrx();
}
<<__Rx>>
function f9(): int {
if (HH\Rx\IS_ENABLED) {
return 0;
}
return nonrx();
}
<<__Rx>>
function f10(): int {
if (!HH\Rx\IS_ENABLED) {
return nonrx();
}
return rx();
}
<<__Rx>>
function rx(): int {
return 1;
}
<<__RxShallow>>
function rxshallow(): int {
return 1;
}
function nonrx(): int {
return 1;
}
Oops, something went wrong.

0 comments on commit a8bb68d

Please sign in to comment.