From 26fd06ebce76c7ce43a9a30fb27fe09a1be0a824 Mon Sep 17 00:00:00 2001 From: Eugene Letuchy Date: Fri, 22 May 2015 18:01:18 -0700 Subject: [PATCH] typechecker: unify function terminality code for `noreturn` Summary: We currently need to know for whether a function is terminal both on `Nast` (during typing) and on the `Ast` (when getting the list of locals to determine which ones are "pending"). Ensure that the relevant code is shared. Reviewed By: @int3 Differential Revision: D2095062 --- hphp/hack/src/js/Makefile | 121 +++++++-------- hphp/hack/src/naming/naming.ml | 44 +++--- hphp/hack/src/naming/naming_ast_helpers.ml | 140 +++++++++++------- hphp/hack/src/naming/nastVisitor.ml | 2 +- hphp/hack/src/parsing/namespaces.ml | 2 +- hphp/hack/src/typing/nastCheck.ml | 6 +- hphp/hack/src/typing/nast_terminality.ml | 31 +--- hphp/hack/src/typing/typing_compare.ml | 6 +- hphp/hack/src/typing/typing_compare.mli | 2 +- hphp/hack/src/typing/typing_decl.ml | 6 +- hphp/hack/src/typing/typing_env.ml | 46 +----- hphp/hack/src/typing/typing_env.mli | 24 +-- hphp/hack/src/typing/typing_heap.ml | 84 +++++++++++ hphp/hack/src/typing/typing_heap.mli | 37 +++++ hphp/hack/src/typing/typing_print.ml | 4 +- hphp/hack/src/typing/typing_print.mli | 8 +- hphp/hack/src/typing/typing_subtype.ml | 3 +- hphp/hack/src/typing/typing_tdef.ml | 9 +- hphp/hack/src/typing/typing_variance.ml | 12 +- .../noreturn_typehint_terminality.php | 22 ++- 20 files changed, 361 insertions(+), 248 deletions(-) create mode 100644 hphp/hack/src/typing/typing_heap.ml create mode 100644 hphp/hack/src/typing/typing_heap.mli diff --git a/hphp/hack/src/js/Makefile b/hphp/hack/src/js/Makefile index 8fa435a40a5f9..73dcedbf7b3fe 100644 --- a/hphp/hack/src/js/Makefile +++ b/hphp/hack/src/js/Makefile @@ -28,6 +28,7 @@ SRC = \ typing_reason.ml\ nast.ml\ typing_defs.ml\ + typing_heap.ml\ typing_env.ml\ naming_heap.ml\ naming_ast_helpers.ml\ @@ -94,178 +95,181 @@ deploy: hh_ide.js #TODO: a bid sad to do that, can probably just reuse the lib.cma lexer_hack.ml: $(TOP)/parsing/lexer_hack.ml - cp $(TOP)/parsing/lexer_hack.ml . + cp $< . format_hack.ml: $(TOP)/parsing/format_hack.ml - cp $(TOP)/parsing/format_hack.ml . + cp $< . parser_hack.ml: $(TOP)/parsing/parser_hack.ml - cp $(TOP)/parsing/parser_hack.ml . + cp $< . pos.ml: $(TOP)/utils/pos.ml - cp $(TOP)/utils/pos.ml . + cp $< . ide.ml: $(TOP)/globals/ide.ml - cp $(TOP)/globals/ide.ml . + cp $< . prefix.ml: $(TOP)/heap/prefix.ml - cp $(TOP)/heap/prefix.ml . + cp $< . value.ml: $(TOP)/heap/value.ml - cp $(TOP)/heap/value.ml . + cp $< . socket.ml: $(TOP)/socket/socket.ml - cp $(TOP)/socket/socket.ml . + cp $< . namespace_env.ml: $(TOP)/parsing/namespace_env.ml - cp $(TOP)/parsing/namespace_env.ml . + cp $< . ast.ml: $(TOP)/parsing/ast.ml - cp $(TOP)/parsing/ast.ml . + cp $< . namespaces.ml: $(TOP)/parsing/namespaces.ml - cp $(TOP)/parsing/namespaces.ml . + cp $< . parser_heap.ml: $(TOP)/parsing/parser_heap.ml - cp $(TOP)/parsing/parser_heap.ml . + cp $< . parser.ml: $(TOP)/parsing/parser.ml - cp $(TOP)/parsing/parser.ml . + cp $< . lexer.ml: $(TOP)/parsing/lexer.ml - cp $(TOP)/parsing/lexer.ml . + cp $< . hackedLexer.ml: $(TOP)/parsing/hackedLexer.ml - cp $(TOP)/parsing/hackedLexer.ml . + cp $< . tokenType.ml: $(TOP)/parsing/tokenType.ml - cp $(TOP)/parsing/tokenType.ml . + cp $< . fileInfo.ml: $(TOP)/deps/fileInfo.ml - cp $(TOP)/deps/fileInfo.ml . + cp $< . lexing_modes.ml: $(TOP)/parsing/lexing_modes.ml - cp $(TOP)/parsing/lexing_modes.ml . + cp $< . typing_suggest.ml: $(TOP)/typing/typing_suggest.ml - cp $(TOP)/typing/typing_suggest.ml . + cp $< . typing_utils.ml: $(TOP)/typing/typing_utils.ml - cp $(TOP)/typing/typing_utils.ml . + cp $< . typeVisitor.ml: $(TOP)/typing/typeVisitor.ml - cp $(TOP)/typing/typeVisitor.ml . + cp $< . coverage_level.ml: $(TOP)/typing/coverage_level.ml - cp $(TOP)/typing/coverage_level.ml . + cp $< . typing_dynamic_yield.ml: $(TOP)/typing/typing_dynamic_yield.ml - cp $(TOP)/typing/typing_dynamic_yield.ml . + cp $< . typing_enum.ml: $(TOP)/typing/typing_enum.ml - cp $(TOP)/typing/typing_enum.ml . + cp $< . typing_print.ml: $(TOP)/typing/typing_print.ml - cp $(TOP)/typing/typing_print.ml . + cp $< . typing_exts.ml: $(TOP)/typing/typing_exts.ml - cp $(TOP)/typing/typing_exts.ml . + cp $< . typing_expand.ml: $(TOP)/typing/typing_expand.ml - cp $(TOP)/typing/typing_expand.ml . + cp $< . typing_async.ml: $(TOP)/typing/typing_async.ml - cp $(TOP)/typing/typing_async.ml . + cp $< . typing_hint.ml: $(TOP)/typing/typing_hint.ml - cp $(TOP)/typing/typing_hint.ml . + cp $< . typing_extends.ml: $(TOP)/typing/typing_extends.ml - cp $(TOP)/typing/typing_extends.ml . + cp $< . typing.ml: $(TOP)/typing/typing.ml - cp $(TOP)/typing/typing.ml . + cp $< . typing_variance.ml: $(TOP)/typing/typing_variance.ml - cp $(TOP)/typing/typing_variance.ml . + cp $< . typing_decl.ml: $(TOP)/typing/typing_decl.ml - cp $(TOP)/typing/typing_decl.ml . + cp $< . typing_inherit.ml: $(TOP)/typing/typing_inherit.ml - cp $(TOP)/typing/typing_inherit.ml . + cp $< . nast.ml: $(TOP)/naming/nast.ml - cp $(TOP)/naming/nast.ml . + cp $< . nastVisitor.ml: $(TOP)/naming/nastVisitor.ml - cp $(TOP)/naming/nastVisitor.ml . + cp $< . nastCheck.ml: $(TOP)/typing/nastCheck.ml - cp $(TOP)/typing/nastCheck.ml . + cp $< . nast_terminality.ml: $(TOP)/typing/nast_terminality.ml - cp $^ . + cp $< . nastInitCheck.ml: $(TOP)/typing/nastInitCheck.ml - cp $(TOP)/typing/nastInitCheck.ml . + cp $< . autocomplete.ml: $(TOP)/globals/autocomplete.ml - cp $(TOP)/globals/autocomplete.ml . + cp $< . find_refs.ml: $(TOP)/globals/find_refs.ml - cp $(TOP)/globals/find_refs.ml . + cp $< . naming_ast_helpers.ml: $(TOP)/naming/naming_ast_helpers.ml - cp $^ . + cp $< . naming_heap.ml: $(TOP)/naming/naming_heap.ml - cp $(TOP)/naming/naming_heap.ml . + cp $< . naming.ml: $(TOP)/naming/naming.ml - cp $(TOP)/naming/naming.ml . + cp $< . typing_reason.ml: $(TOP)/typing/typing_reason.ml - cp $(TOP)/typing/typing_reason.ml . + cp $< . typing_defs.ml: $(TOP)/typing/typing_defs.ml - cp $(TOP)/typing/typing_defs.ml . + cp $< . + +typing_heap.ml: $(TOP)/typing/typing_heap.ml + cp $< . typing_env.ml: $(TOP)/typing/typing_env.ml - cp $(TOP)/typing/typing_env.ml . + cp $< . typing_ops.ml: $(TOP)/typing/typing_ops.ml - cp $(TOP)/typing/typing_ops.ml . + cp $< . fix_file.ml: $(TOP)/parsing/fix_file.ml - cp $(TOP)/parsing/fix_file.ml . + cp $< . colorFile.ml: $(TOP)/client/colorFile.ml - cp $(TOP)/client/colorFile.ml . + cp $< . typing_instantiate.ml: $(TOP)/typing/typing_instantiate.ml - cp $(TOP)/typing/typing_instantiate.ml . + cp $< . typing_alias.ml: $(TOP)/typing/typing_alias.ml - cp $(TOP)/typing/typing_alias.ml . + cp $< . typing_generic.ml: $(TOP)/typing/typing_generic.ml - cp $(TOP)/typing/typing_generic.ml . + cp $< . typing_unify.ml: $(TOP)/typing/typing_unify.ml - cp $(TOP)/typing/typing_unify.ml . + cp $< . typing_tdef.ml: $(TOP)/typing/typing_tdef.ml - cp $(TOP)/typing/typing_tdef.ml . + cp $< . typing_subtype.ml: $(TOP)/typing/typing_subtype.ml - cp $(TOP)/typing/typing_subtype.ml . + cp $< . typing_lenv.ml: $(TOP)/typing/typing_lenv.ml - cp $(TOP)/typing/typing_lenv.ml . + cp $< . fileOutline.ml: $(TOP)/server/fileOutline.ml - cp $(TOP)/server/fileOutline.ml . + cp $< . clean:: @@ -299,6 +303,7 @@ clean:: rm -f naming.ml rm -f typing_reason.ml rm -f typing_defs.ml + rm -f typing_heap.ml rm -f typing_instantiate.ml rm -f typing_utils.ml rm -f typing_env.ml diff --git a/hphp/hack/src/naming/naming.ml b/hphp/hack/src/naming/naming.ml index c0e3430936977..bfa8ededff824 100644 --- a/hphp/hack/src/naming/naming.ml +++ b/hphp/hack/src/naming/naming.ml @@ -73,10 +73,10 @@ type genv = { (* Set of function names defined, and their positions *) funs: (pi_hash * canon_names_hash) ref; - (* Set of typedef names defined, and their position *) + (* Set of typedef names defined, and their positions *) typedefs: pi_hash ref; - (* Set of constant names defined, and their position *) + (* Set of constant names defined, and their positions *) gconsts: pi_hash ref; (* The current class, None if we are in a function *) @@ -1656,7 +1656,8 @@ and stmt env st = and if_stmt env st e b1 b2 = let e = expr env e in - let vars = Naming_ast_helpers.GetLocals.stmt SMap.empty st in + let nsenv = (fst env).namespace in + let _, vars = Naming_ast_helpers.GetLocals.stmt (nsenv, SMap.empty) st in SMap.iter (fun x p -> Env.new_pending_lvar env (p, x)) vars; let result = Env.scope env ( fun env -> @@ -1691,7 +1692,8 @@ and for_stmt env e1 e2 e3 b = and switch_stmt env st e cl = let e = expr env e in - let vars = Naming_ast_helpers.GetLocals.stmt SMap.empty st in + let nsenv = (fst env).namespace in + let _, vars = Naming_ast_helpers.GetLocals.stmt (nsenv, SMap.empty) st in SMap.iter (fun x p -> Env.new_pending_lvar env (p, x)) vars; let result = Env.scope env ( fun env -> @@ -1718,20 +1720,22 @@ and foreach_stmt env e aw ae b = and as_expr env aw = function | As_v ev -> - let vars = Naming_ast_helpers.GetLocals.lvalue SMap.empty ev in - SMap.iter (fun x p -> ignore (Env.new_lvar env (p, x))) vars; - let ev = expr env ev in - (match aw with - | None -> N.As_v ev - | Some p -> N.Await_as_v (p, ev)) + let nsenv = (fst env).namespace in + let _, vars = Naming_ast_helpers.GetLocals.lvalue (nsenv, SMap.empty) ev in + SMap.iter (fun x p -> ignore (Env.new_lvar env (p, x))) vars; + let ev = expr env ev in + (match aw with + | None -> N.As_v ev + | Some p -> N.Await_as_v (p, ev)) | As_kv ((p1, Lvar k), ev) -> - let k = p1, N.Lvar (Env.new_lvar env k) in - let vars = Naming_ast_helpers.GetLocals.lvalue SMap.empty ev in - SMap.iter (fun x p -> ignore (Env.new_lvar env (p, x))) vars; - let ev = expr env ev in - (match aw with - | None -> N.As_kv (k, ev) - | Some p -> N.Await_as_kv (p, k, ev)) + let k = p1, N.Lvar (Env.new_lvar env k) in + let nsenv = (fst env).namespace in + let _, vars = Naming_ast_helpers.GetLocals.lvalue (nsenv, SMap.empty) ev in + SMap.iter (fun x p -> ignore (Env.new_lvar env (p, x))) vars; + let ev = expr env ev in + (match aw with + | None -> N.As_kv (k, ev) + | Some p -> N.Await_as_kv (p, k, ev)) | As_kv ((p, _), _) -> Errors.expected_variable p; let x1 = p, N.Lvar (Env.new_lvar env (p, "__internal_placeholder")) in @@ -1741,7 +1745,8 @@ and as_expr env aw = function | Some p -> N.Await_as_kv (p, x1, x2)) and try_stmt env st b cl fb = - let vars = Naming_ast_helpers.GetLocals.stmt SMap.empty st in + let nsenv = (fst env).namespace in + let _, vars = Naming_ast_helpers.GetLocals.stmt (nsenv, SMap.empty) st in SMap.iter (fun x p -> Env.new_pending_lvar env (p, x)) vars; let result = Env.scope env ( fun env -> @@ -2046,7 +2051,8 @@ and expr_ env = function | Unop (uop, e) -> N.Unop (uop, expr env e) | Binop (Eq None as op, lv, e2) -> let e2 = expr env e2 in - let vars = Naming_ast_helpers.GetLocals.lvalue SMap.empty lv in + let nsenv = (fst env).namespace in + let _, vars = Naming_ast_helpers.GetLocals.lvalue (nsenv, SMap.empty) lv in SMap.iter (fun x p -> ignore (Env.new_lvar env (p, x))) vars; N.Binop (op, expr env lv, e2) | Binop (bop, e1, e2) -> diff --git a/hphp/hack/src/naming/naming_ast_helpers.ml b/hphp/hack/src/naming/naming_ast_helpers.ml index 776e373db6089..db3966f890006 100644 --- a/hphp/hack/src/naming/naming_ast_helpers.ml +++ b/hphp/hack/src/naming/naming_ast_helpers.ml @@ -11,32 +11,39 @@ open Utils open Ast +module FuncTerm = Typing_heap.FuncTerminality + (* helpers for GetLocals below *) (* TODO It really sucks that this and Nast_terminality.Terminal are very * slightly different (notably, this version is somewhat buggier). Fixing that * exposes a lot of errors in www unfortunately -- we should bite the bullet on * fixing switch all the way when we do that, most likely though -- see tasks * #3140431 and #2813555. *) -let rec terminal in_try stl = List.iter (terminal_ in_try) stl -and terminal_ in_try = function +let rec terminal nsenv ~in_try stl = List.iter (terminal_ nsenv ~in_try) stl +and terminal_ nsenv ~in_try = function | Throw _ when not in_try -> raise Exit | Throw _ -> () | Continue _ | Expr (_, (Call ((_, Id (_, "assert")), [_, False], []) - | Call ((_, Id (_, "invariant")), (_, False) :: _ :: _, []) - | Call ((_, Id (_, "invariant_violation")), _ :: _, []))) - | Return _ - | Expr (_, Call ((_, Id (_, ("exit" | "die"))), _, _)) -> raise Exit + | Call ((_, Id (_, "invariant")), (_, False) :: _ :: _, []))) + | Return _ -> raise Exit + | Expr (_, Call ((_, Id fun_id), _, _)) -> + let _, fun_name = Namespaces.elaborate_id nsenv fun_id in + FuncTerm.raise_exit_if_terminal (FuncTerm.get_fun fun_name) + | Expr (_, Call ((_, Class_const (cls_id, (_, meth_name))), _, _)) + when (snd cls_id).[0] <> '$' -> + let _, cls_name = Namespaces.elaborate_id nsenv cls_id in + FuncTerm.raise_exit_if_terminal (FuncTerm.get_static_meth cls_name meth_name) | If (_, b1, b2) -> - (try terminal in_try b1; () with Exit -> - terminal in_try b2) + (try terminal nsenv ~in_try b1; () with Exit -> + terminal nsenv ~in_try b2) | Switch (_, cl) -> - terminal_cl in_try cl - | Block b -> terminal in_try b + terminal_cl nsenv ~in_try cl + | Block b -> terminal nsenv ~in_try b | Try (b, catch_l, finb) -> (* return is not allowed in finally, so we can ignore fb *) - (terminal true b; - List.iter (terminal_catch in_try) catch_l) + (terminal nsenv ~in_try:true b; + List.iter (terminal_catch nsenv ~in_try) catch_l) | Do _ | While _ | For _ @@ -48,22 +55,24 @@ and terminal_ in_try = function | Break _ (* TODO this is terminal sometimes too, except switch, see above. *) | Static_var _ -> () -and terminal_catch in_try (_, _, b) = - terminal in_try b +and terminal_catch nsenv ~in_try (_, _, b) = + terminal nsenv ~in_try b -and terminal_cl in_try = function +and terminal_cl nsenv ~in_try = function | [] -> raise Exit | Case (_, b) :: rl -> (try - terminal in_try b; + terminal nsenv ~in_try b; if List.exists (function Break _ -> true | _ -> false) b then () else raise Exit - with Exit -> terminal_cl in_try rl) + with Exit -> terminal_cl nsenv ~in_try rl) | Default b :: rl -> - (try terminal in_try b with Exit -> terminal_cl in_try rl) + (try terminal nsenv ~in_try b with Exit -> terminal_cl nsenv ~in_try rl) -let is_terminal stl = try terminal false stl; false with Exit -> true +let is_terminal nsenv stl = + try terminal nsenv ~in_try:false stl; false + with Exit -> true (* Module calculating the locals for a statement * This is useful when someone uses $x on both sides @@ -76,19 +85,29 @@ let is_terminal stl = try terminal false stl; false with Exit -> true *) module GetLocals = struct - let rec lvalue acc = function - | (p, Lvar (_, x)) -> SMap.add x p acc + let smap_union ((nsenv:Namespace_env.env), (m1:Pos.t Utils.SMap.t)) + (m2:Pos.t Utils.SMap.t) = + let m_combined = SMap.fold SMap.add m1 m2 in + nsenv, m_combined + + let rec lvalue (acc:(Namespace_env.env * Pos.t Utils.SMap.t)) = function + | (p, Lvar (_, x)) -> + let nsenv, m = acc in + nsenv, SMap.add x p m | _, List lv -> List.fold_left lvalue acc lv (* Ref forms a local inside a foreach *) - | (_, Ref (p, Lvar (_, x))) -> SMap.add x p acc + | (_, Ref (p, Lvar (_, x))) -> + let nsenv, m = acc in + nsenv, SMap.add x p m | _ -> acc - let rec stmt acc st = + let rec stmt (acc:(Namespace_env.env * Pos.t Utils.SMap.t)) st = + let nsenv = fst acc in match st with | Expr (_, Binop (Eq None, lv, rv)) | Expr (_, Eif ((_, Binop (Eq None, lv, rv)), _, _)) -> - let acc = stmt acc (Expr rv) in - lvalue acc lv + let acc = stmt acc (Expr rv) in + lvalue acc lv | Unsafe | Fallthrough | Expr _ | Break _ | Continue _ | Throw _ @@ -96,47 +115,54 @@ module GetLocals = struct | Return _ | Static_var _ | Noop -> acc | Block b -> block acc b | If (_, b1, b2) -> - let term1 = is_terminal b1 in - let term2 = is_terminal b2 in - if term1 && term2 - then acc - else if term1 - then smap_union acc (block SMap.empty b2) - else if term2 - then smap_union acc (block SMap.empty b1) - else - let b1 = block SMap.empty b1 in - let b2 = block SMap.empty b2 in - smap_union acc (smap_inter b1 b2) + let term1 = is_terminal nsenv b1 in + let term2 = is_terminal nsenv b2 in + if term1 && term2 + then acc + else if term1 + then + let _, m2 = block (nsenv, SMap.empty) b2 in + smap_union acc m2 + else if term2 + then + let _, m1 = block (nsenv, SMap.empty) b1 in + smap_union acc m1 + else begin + let _, m1 = block (nsenv, SMap.empty) b1 in + let _, m2 = block (nsenv, SMap.empty) b2 in + let (m:Pos.t Utils.SMap.t) = (smap_inter m1 m2) in + smap_union acc m + end | Switch (e, cl) -> - let cl = List.filter begin function - | Case (_, b) - | Default b -> not (is_terminal b) - end cl in - let cl = casel cl in - let c = smap_inter_list cl in - smap_union acc c + let cl = List.filter begin function + | Case (_, b) + | Default b -> not (is_terminal nsenv b) + end cl in + let cl = casel nsenv cl in + let c = smap_inter_list cl in + smap_union acc c | Try (b, cl, f) -> - let c = block SMap.empty b in - let lcl = List.map catch cl in - let tcl = List.map (fun (_, _, b) -> is_terminal b) cl in - let cl = List.fold_right2 begin fun x y acc -> - if y then acc else x :: acc - end lcl tcl [] in - let c = smap_inter_list (c :: cl) in - smap_union acc c + let _, c = block (nsenv, SMap.empty) b in + let lcl = List.map (catch nsenv) cl in + let tcl = List.map (fun (_, _, b) -> is_terminal nsenv b) cl in + let cl = List.fold_right2 begin fun x y acc -> + if y then acc else x :: acc + end lcl tcl [] in + let c = smap_inter_list (c :: cl) in + smap_union acc c and block acc l = List.fold_left stmt acc l - and casel = function + and casel nsenv = function | [] -> [] - | Case (_, []) :: rl -> casel rl + | Case (_, []) :: rl -> casel nsenv rl | Default b :: rl | Case (_, b) :: rl -> - let b = block SMap.empty b in - b :: casel rl + let _, b = block (nsenv, SMap.empty) b in + b :: casel nsenv rl - and catch (_, _, b) = block SMap.empty b + and catch nsenv (_, _, b) = + snd (block (nsenv, SMap.empty) b) end diff --git a/hphp/hack/src/naming/nastVisitor.ml b/hphp/hack/src/naming/nastVisitor.ml index 10492aa47f70b..3befce6d1312d 100644 --- a/hphp/hack/src/naming/nastVisitor.ml +++ b/hphp/hack/src/naming/nastVisitor.ml @@ -210,7 +210,7 @@ class virtual ['a] nast_visitor: ['a] nast_visitor_type = object(this) | String s -> this#on_string acc s | This -> this#on_this acc | Id sid -> this#on_id acc sid - | Lplaceholder sid -> acc + | Lplaceholder _sid -> acc | Lvar id -> this#on_lvar acc id | Fun_id sid -> this#on_fun_id acc sid | Method_id (expr, pstr) -> this#on_method_id acc expr pstr diff --git a/hphp/hack/src/parsing/namespaces.ml b/hphp/hack/src/parsing/namespaces.ml index f46f79b32b924..bf92eac83ab24 100644 --- a/hphp/hack/src/parsing/namespaces.ml +++ b/hphp/hack/src/parsing/namespaces.ml @@ -138,7 +138,7 @@ let elaborate_id_no_autos = elaborate_id_impl ~autoimport:false * Fully-qualifies the things we need for Parsing_service.AddDeps -- the classes * we extend, traits we use, interfaces we implement; along with classes we * define. So that we can also use them to figure out fallback behavior, we also - * fully-qualifiy functions that we define, even though AddDeps doesn't need + * fully-qualify functions that we define, even though AddDeps doesn't need * them this early. * * Note that, since AddDeps doesn't need it, we don't recursively traverse diff --git a/hphp/hack/src/typing/nastCheck.ml b/hphp/hack/src/typing/nastCheck.ml index 65ec6debbfcab..ffd899a18d2c3 100644 --- a/hphp/hack/src/typing/nastCheck.ml +++ b/hphp/hack/src/typing/nastCheck.ml @@ -280,11 +280,11 @@ and hint_ env p = function hint env h; () | Happly ((_, x), hl) when Typing_env.is_typedef x -> - let tdef = Typing_env.Typedefs.find_unsafe x in + let tdef = Typing_heap.Typedefs.find_unsafe x in let params = match tdef with - | Typing_env.Typedef.Error -> [] - | Typing_env.Typedef.Ok (_, x, _, _, _) -> x + | Typing_heap.Typedef.Error -> [] + | Typing_heap.Typedef.Ok (_, x, _, _, _) -> x in check_params env p x params hl | Happly ((_, x), hl) -> diff --git a/hphp/hack/src/typing/nast_terminality.ml b/hphp/hack/src/typing/nast_terminality.ml index 713553482ed73..5856d04dcfcdc 100644 --- a/hphp/hack/src/typing/nast_terminality.ml +++ b/hphp/hack/src/typing/nast_terminality.ml @@ -12,26 +12,7 @@ open Nast module SN = Naming_special_names -(* Not adding a Typing_dep here because it will be added anyway when - * the Nast is fully processed (by the caller of this code) *) -let get_fun = Typing_env.Funs.get - -let get_static_meth (cls_name:string) (meth_name:string) = - match Typing_env.Classes.get cls_name with - | None -> None - | Some { Typing_defs.tc_smethods ; _ } -> - begin match Utils.SMap.get meth_name tc_smethods with - | None -> None - | Some { Typing_defs.ce_type = (_r, Typing_defs.Tfun fty) ; _} - -> Some fty - | Some _ -> None - end - -let terminal_func_call_ = function - | None -> () - | Some ({ Typing_defs.ft_ret = (_r, Typing_defs.Tprim Tnoreturn); _}) - -> raise Exit - | Some _ -> () +module FuncTerm = Typing_heap.FuncTerminality (* Module coded with an exception, if we find a terminal statement we * throw the exception Exit. @@ -54,9 +35,10 @@ end = struct | Expr (_, Assert (AE_assert (_, False))) -> raise Exit | Expr (_, Call (Cnormal, (_, Id (_, fun_name)), _, _)) -> - terminal_func_call_ (get_fun fun_name) + FuncTerm.raise_exit_if_terminal (FuncTerm.get_fun fun_name) | Expr (_, Call (Cnormal, (_, Class_const (CI cls_id, meth_id)), _, _)) -> - terminal_func_call_ (get_static_meth (snd cls_id) (snd meth_id)) + FuncTerm.raise_exit_if_terminal + (FuncTerm.get_static_meth (snd cls_id) (snd meth_id)) | If ((_, True), b1, _) -> terminal inside_case b1 | If ((_, False), _, b2) -> terminal inside_case b2 | If (_, b1, b2) -> @@ -141,9 +123,10 @@ end = struct | Expr (_, Yield_break) | Expr (_, Assert (AE_assert (_, False))) -> raise Exit | Expr (_, Call (Cnormal, (_, Id (_, fun_name)), _, _)) -> - terminal_func_call_ (get_fun fun_name) + FuncTerm.raise_exit_if_terminal (FuncTerm.get_fun fun_name) | Expr (_, Call (Cnormal, (_, Class_const (CI cls_id, meth_id)), _, _)) -> - terminal_func_call_ (get_static_meth (snd cls_id) (snd meth_id)) + FuncTerm.raise_exit_if_terminal + (FuncTerm.get_static_meth (snd cls_id) (snd meth_id)) | If ((_, True), b1, _) -> terminal b1 | If ((_, False), _, b2) -> terminal b2 | If (_, b1, b2) -> diff --git a/hphp/hack/src/typing/typing_compare.ml b/hphp/hack/src/typing/typing_compare.ml index d0a32a722c2b8..e135f138f466f 100644 --- a/hphp/hack/src/typing/typing_compare.ml +++ b/hphp/hack/src/typing/typing_compare.ml @@ -405,12 +405,12 @@ module TraversePos(ImplementPos: sig val pos: Pos.t -> Pos.t end) = struct } and typedef = function - | Typing_env.Typedef.Error as x -> x - | Typing_env.Typedef.Ok (is_abstract, tparams, tcstr, h, pos) -> + | Typing_heap.Typedef.Error as x -> x + | Typing_heap.Typedef.Ok (is_abstract, tparams, tcstr, h, pos) -> let tparams = List.map type_param tparams in let tcstr = ty_opt tcstr in let tdef = (is_abstract, tparams, tcstr, ty h, pos) in - Typing_env.Typedef.Ok tdef + Typing_heap.Typedef.Ok tdef end (*****************************************************************************) diff --git a/hphp/hack/src/typing/typing_compare.mli b/hphp/hack/src/typing/typing_compare.mli index cf849a0ef1213..7faef43419e83 100644 --- a/hphp/hack/src/typing/typing_compare.mli +++ b/hphp/hack/src/typing/typing_compare.mli @@ -10,7 +10,7 @@ open Utils open Typing_deps -open Typing_env +open Typing_heap val get_extend_deps : DepSet.elt -> DepSet.t -> DepSet.t diff --git a/hphp/hack/src/typing/typing_decl.ml b/hphp/hack/src/typing/typing_decl.ml index 338f37575e87b..96eeed8b555b4 100644 --- a/hphp/hack/src/typing/typing_decl.ml +++ b/hphp/hack/src/typing/typing_decl.ml @@ -862,9 +862,9 @@ and type_typedef_naming_and_decl nenv tdef = let env = sub_type env constraint_type concrete_type in env, Some constraint_type in - let visibility = - if is_abstract then Env.Typedef.Private else Env.Typedef.Public - in + let visibility = if is_abstract + then Typing_heap.Typedef.Private + else Typing_heap.Typedef.Public in let tdecl = visibility, params, tcstr, concrete_type, pos in Env.add_typedef tid tdecl; Naming_heap.TypedefHeap.add tid decl; diff --git a/hphp/hack/src/typing/typing_env.ml b/hphp/hack/src/typing/typing_env.ml index 6f7f3839af1ed..7180bff2d3514 100644 --- a/hphp/hack/src/typing/typing_env.ml +++ b/hphp/hack/src/typing/typing_env.ml @@ -24,44 +24,10 @@ module Dep = Typing_deps.Dep * very well isolated. *) -(* Module used to represent serialized classes *) -module Class = struct - type t = Typing_defs.class_type - let prefix = Prefix.make() -end - -(* a function type *) -module Fun = struct - type t = decl Typing_defs.fun_type - let prefix = Prefix.make() -end - -module Typedef = struct - - type visibility = - | Public - | Private - - type tdef = - visibility * Typing_defs.tparam list * decl ty option * decl ty * Pos.t - - type tdef_or_error = - | Error - | Ok of tdef - - type t = tdef_or_error - let prefix = Prefix.make() -end - -module GConst = struct - type t = decl ty - let prefix = Prefix.make() -end - -module Funs = SharedMem.WithCache (String) (Fun) -module Classes = SharedMem.WithCache (String) (Class) -module Typedefs = SharedMem.WithCache (String) (Typedef) -module GConsts = SharedMem.WithCache (String) (GConst) +module Funs = Typing_heap.Funs +module Classes = Typing_heap.Classes +module Typedefs = Typing_heap.Typedefs +module GConsts = Typing_heap.GConsts type fake_members = { last_call : Pos.t option; @@ -311,7 +277,7 @@ let add_class x y = Classes.add x y let add_typedef x y = - Typedefs.add x (Typedef.Ok y) + Typedefs.add x (Typing_heap.Typedef.Ok y) let is_typedef x = match Typedefs.get x with @@ -334,7 +300,7 @@ let get_enum_constraint x = | Some e -> e.te_constraint let add_typedef_error x = - Typedefs.add x Typedef.Error + Typedefs.add x Typing_heap.Typedef.Error (* Adds a new function (global) *) let add_fun x ft = diff --git a/hphp/hack/src/typing/typing_env.mli b/hphp/hack/src/typing/typing_env.mli index 24f3df2e74fe6..b0326d9448cfb 100644 --- a/hphp/hack/src/typing/typing_env.mli +++ b/hphp/hack/src/typing/typing_env.mli @@ -11,24 +11,10 @@ open Utils open Typing_defs -module Class : sig type t = class_type val prefix : Prefix.t end -module Fun : sig type t = decl fun_type val prefix : Prefix.t end -module Typedef : - sig - type visibility = Public | Private - type tdef = - visibility * tparam list * decl ty option * - decl ty * Pos.t - type tdef_or_error = Error | Ok of tdef - type t = tdef_or_error - val prefix : Prefix.t - end -module GConst : sig type t = decl ty val prefix : Prefix.t end - -module Funs : module type of SharedMem.WithCache (String) (Fun) -module Classes : module type of SharedMem.WithCache (String) (Class) -module Typedefs : module type of SharedMem.WithCache (String) (Typedef) -module GConsts : module type of SharedMem.WithCache (String) (GConst) +module Funs = Typing_heap.Funs +module Classes = Typing_heap.Classes +module Typedefs = Typing_heap.Typedefs +module GConsts = Typing_heap.GConsts type fake_members = { last_call : Pos.t option; @@ -68,7 +54,7 @@ val empty_fake_members : fake_members val empty_local : local_env val empty : TypecheckerOptions.t -> Relative_path.t -> env val add_class : Classes.key -> Classes.t -> unit -val add_typedef : Typedefs.key -> Typedef.tdef -> unit +val add_typedef : Typedefs.key -> Typing_heap.Typedef.tdef -> unit val is_typedef : Typedefs.key -> bool val get_enum : Classes.key -> Classes.t option val is_enum : Classes.key -> bool diff --git a/hphp/hack/src/typing/typing_heap.ml b/hphp/hack/src/typing/typing_heap.ml new file mode 100644 index 0000000000000..7ceac2299271d --- /dev/null +++ b/hphp/hack/src/typing/typing_heap.ml @@ -0,0 +1,84 @@ +(** + * Copyright (c) 2014, 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. + * + *) + +open Utils +open Typing_defs + +(* The following classes are used to make sure we make no typing + * mistake when interacting with the database. The database knows + * how to associate a string to a string. We need to deserialize + * the string and make sure the type is correct. By using these + * modules, the places where there could be a typing mistake are + * very well isolated. +*) + +(* Module used to represent serialized classes *) +module Class = struct + type t = Typing_defs.class_type + let prefix = Prefix.make() +end + +(* a function type *) +module Fun = struct + type t = decl Typing_defs.fun_type + let prefix = Prefix.make() +end + +module Typedef = struct + + type visibility = + | Public + | Private + + type tdef = + visibility * Typing_defs.tparam list * decl ty option * decl ty * Pos.t + + type tdef_or_error = + | Error + | Ok of tdef + + type t = tdef_or_error + let prefix = Prefix.make() +end + +module GConst = struct + type t = decl ty + let prefix = Prefix.make() +end + +module Funs = SharedMem.WithCache (String) (Fun) +module Classes = SharedMem.WithCache (String) (Class) +module Typedefs = SharedMem.WithCache (String) (Typedef) +module GConsts = SharedMem.WithCache (String) (GConst) + +module FuncTerminality = struct + + (* Not adding a Typing_dep here because it will be added when the + * Nast is fully processed (by the caller of this code) *) + let get_fun = Funs.get + + let get_static_meth (cls_name:string) (meth_name:string) = + match Classes.get cls_name with + | None -> None + | Some { Typing_defs.tc_smethods ; _ } -> + begin match Utils.SMap.get meth_name tc_smethods with + | None -> None + | Some { Typing_defs.ce_type = (_r, Typing_defs.Tfun fty) ; _} -> + Some fty + | Some _ -> None + end + + let raise_exit_if_terminal = function + | None -> () + | Some ({ Typing_defs.ft_ret = (_r, Typing_defs.Tprim Nast.Tnoreturn); _}) + -> raise Exit + | Some _ -> () + +end diff --git a/hphp/hack/src/typing/typing_heap.mli b/hphp/hack/src/typing/typing_heap.mli new file mode 100644 index 0000000000000..549785245c90c --- /dev/null +++ b/hphp/hack/src/typing/typing_heap.mli @@ -0,0 +1,37 @@ +(** + * Copyright (c) 2014, 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. + * + *) + +open Utils +open Typing_defs + +module Class : sig type t = class_type val prefix : Prefix.t end +module Fun : sig type t = decl fun_type val prefix : Prefix.t end +module Typedef : + sig + type visibility = Public | Private + type tdef = + visibility * tparam list * decl ty option * + decl ty * Pos.t + type tdef_or_error = Error | Ok of tdef + type t = tdef_or_error + val prefix : Prefix.t + end +module GConst : sig type t = decl ty val prefix : Prefix.t end + +module Funs : module type of SharedMem.WithCache (String) (Fun) +module Classes : module type of SharedMem.WithCache (String) (Class) +module Typedefs : module type of SharedMem.WithCache (String) (Typedef) +module GConsts : module type of SharedMem.WithCache (String) (GConst) + +module FuncTerminality : sig + val get_fun : string -> Fun.t option + val get_static_meth : string -> string -> Fun.t option + val raise_exit_if_terminal : Fun.t option -> unit +end diff --git a/hphp/hack/src/typing/typing_print.ml b/hphp/hack/src/typing/typing_print.ml index 8f56d6cf15fcc..fb090fd29119d 100644 --- a/hphp/hack/src/typing/typing_print.ml +++ b/hphp/hack/src/typing/typing_print.ml @@ -507,8 +507,8 @@ end module PrintTypedef = struct let typedef = function - | Typing_env.Typedef.Error -> "[Error]" - | Typing_env.Typedef.Ok (_vis, tparaml, constr_opt, ty, pos) -> + | Typing_heap.Typedef.Error -> "[Error]" + | Typing_heap.Typedef.Ok (_vis, tparaml, constr_opt, ty, pos) -> let tparaml_s = PrintClass.tparam_list tparaml in let constr_s = match constr_opt with | None -> "[None]" diff --git a/hphp/hack/src/typing/typing_print.mli b/hphp/hack/src/typing/typing_print.mli index b3ee80880b88b..86ddc050772f7 100644 --- a/hphp/hack/src/typing/typing_print.mli +++ b/hphp/hack/src/typing/typing_print.mli @@ -17,7 +17,7 @@ val error: 'a Typing_defs.ty_ -> string val suggest: 'a Typing_defs.ty -> string val full: Typing_env.env -> 'a Typing_defs.ty -> string val full_strip_ns: Typing_env.env -> 'a Typing_defs.ty -> string -val class_: Typing_env.Class.t -> string -val gconst: Typing_env.GConst.t -> string -val fun_: Typing_env.Fun.t -> string -val typedef: Typing_env.Typedef.t -> string +val class_: Typing_heap.Class.t -> string +val gconst: Typing_heap.GConst.t -> string +val fun_: Typing_heap.Fun.t -> string +val typedef: Typing_heap.Typedef.t -> string diff --git a/hphp/hack/src/typing/typing_subtype.ml b/hphp/hack/src/typing/typing_subtype.ml index 986cf79a4917b..e4c1c71a61b62 100644 --- a/hphp/hack/src/typing/typing_subtype.ml +++ b/hphp/hack/src/typing/typing_subtype.ml @@ -15,6 +15,7 @@ module Reason = Typing_reason module Inst = Typing_instantiate module Unify = Typing_unify module Env = Typing_env +module DefsDB = Typing_heap module TDef = Typing_tdef module TSubst = Typing_subst module TUtils = Typing_utils @@ -432,7 +433,7 @@ and sub_type_with_uenv env (uenv_super, ty_super) (uenv_sub, ty_sub) = when name_super = name_sub -> let td = Env.get_typedef env name_super in (match td with - | Some (Env.Typedef.Ok (_, tparams, _, _, _)) -> + | Some (DefsDB.Typedef.Ok (_, tparams, _, _, _)) -> let variancel = List.map (fun (variance, _, _) -> variance) tparams in diff --git a/hphp/hack/src/typing/typing_tdef.ml b/hphp/hack/src/typing/typing_tdef.ml index 0bc9d8a4c691c..0d82efc11d816 100644 --- a/hphp/hack/src/typing/typing_tdef.ml +++ b/hphp/hack/src/typing/typing_tdef.ml @@ -13,6 +13,7 @@ open Typing_defs module Reason = Typing_reason module Env = Typing_env +module DefsDB = Typing_heap module Inst = Typing_instantiate module TSubst = Typing_subst module TUtils = Typing_utils @@ -34,15 +35,15 @@ let expand_typedef_ ?force_expand:(force_expand=false) ety_env env r x argl = let tdef = Typing_env.get_typedef env x in let tdef = match tdef with None -> assert false | Some x -> x in match tdef with - | Env.Typedef.Error -> env, (ety_env, (r, Tany)) - | Env.Typedef.Ok tdef -> + | DefsDB.Typedef.Error -> env, (ety_env, (r, Tany)) + | DefsDB.Typedef.Ok tdef -> let visibility, tparaml, tcstr, expanded_ty, tdef_pos = tdef in let should_expand = force_expand || match visibility with - | Env.Typedef.Private -> + | DefsDB.Typedef.Private -> Pos.filename tdef_pos = Env.get_file env - | Env.Typedef.Public -> true + | DefsDB.Typedef.Public -> true in if List.length tparaml <> List.length argl then begin diff --git a/hphp/hack/src/typing/typing_variance.ml b/hphp/hack/src/typing/typing_variance.ml index 4347c06b0b10f..ff2ac5294871c 100644 --- a/hphp/hack/src/typing/typing_variance.ml +++ b/hphp/hack/src/typing/typing_variance.ml @@ -320,13 +320,13 @@ let get_class_variance root (pos, class_name) = let dep = Typing_deps.Dep.Class class_name in Typing_deps.add_idep (Some root) dep; let tparams = - if Typing_env.Typedefs.mem class_name + if Typing_heap.Typedefs.mem class_name then - match Typing_env.Typedefs.get class_name with - | Some (Typing_env.Typedef.Ok (_, tparams, _, _, _)) -> tparams + match Typing_heap.Typedefs.get class_name with + | Some (Typing_heap.Typedef.Ok (_, tparams, _, _, _)) -> tparams | _ -> [] else - match Typing_env.Classes.get class_name with + match Typing_heap.Classes.get class_name with | None -> [] | Some { tc_tparams; _ } -> tc_tparams in @@ -350,8 +350,8 @@ let rec class_ class_name class_type impl = (*****************************************************************************) and typedef type_name = - match Typing_env.Typedefs.get type_name with - | Some (Typing_env.Typedef.Ok (_, tparams, _cstr, ty, _)) -> + match Typing_heap.Typedefs.get type_name with + | Some (Typing_heap.Typedef.Ok (_, tparams, _cstr, ty, _)) -> let root = Typing_deps.Dep.Class type_name in let env = SMap.empty in let pos = Reason.to_pos (fst ty) in diff --git a/hphp/hack/test/typecheck/noreturn_typehint_terminality.php b/hphp/hack/test/typecheck/noreturn_typehint_terminality.php index b6a815fe35e02..bf11c9273bfeb 100644 --- a/hphp/hack/test/typecheck/noreturn_typehint_terminality.php +++ b/hphp/hack/test/typecheck/noreturn_typehint_terminality.php @@ -10,16 +10,34 @@ public static function invariant_violation(string $arg): noreturn { } } -function foo(?int $x): int { +function test_fun1(?int $x): int { if ($x === null) { my_invariant_violation("yup!"); } return $x; } -function bar(?string $x): string { +function test_fun2(?int $x): int { + if ($x === null) { + my_invariant_violation("yup!"); + } else { + $y = $x; + } + return $y; +} + +function test_meth1(?int $x): int { if ($x === null) { C::invariant_violation("yup!"); } return $x; } + +function test_meth2(?int $x): int { + if ($x === null) { + C::invariant_violation("yup!"); + } else { + $y = $x; + } + return $y; +}