Introduce __ReturnsVoidToRx
[hiphop-php.git] / hphp / hack / src / typing / nast_terminality.ml
blob33c077fba727cc992950c164a6e0ffa2f509c36f
1 (**
2 * Copyright (c) 2015, 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_core
11 open Nast
13 module Env = Typing_env
14 module SN = Naming_special_names
16 module FuncTerm = Typing_func_terminality
18 let static_meth_terminal env ci meth_id =
19 let class_name = match ci with
20 | CI (cls_id, _) -> Some (snd cls_id)
21 | CIself | CIstatic -> Some (Typing_env.get_self_id env)
22 | CIparent -> Some (Typing_env.get_parent_id env)
23 | CIexpr _ -> None (* we declared the types, but didn't check the bodies yet
24 so can't tell anything here *)
26 match class_name with
27 | Some class_name ->
28 FuncTerm.raise_exit_if_terminal
29 (FuncTerm.get_static_meth (Env.get_options env) class_name (snd meth_id))
30 | None -> ()
32 (* Module coded with an exception, if we find a terminal statement we
33 * throw the exception Exit.
35 module Terminal: sig
36 val case: Typing_env.env -> case -> bool
37 val block: Typing_env.env -> block -> bool
39 end = struct
41 let rec terminal env inside_case stl =
42 List.iter stl (terminal_ env inside_case)
44 and terminal_ env inside_case = function
45 | Break _ -> if inside_case then () else raise Exit
46 | Continue _
47 | Throw _
48 | Return _
49 | Goto _
50 | Expr (_, Yield_break)
51 | Expr (_, Assert (AE_assert (_, False)))
52 -> raise Exit
53 | Expr (_, Call (Cnormal, (_, Id (_, fun_name)), _, _, _)) ->
54 let tcopt = Env.get_options env in
55 FuncTerm.raise_exit_if_terminal (FuncTerm.get_fun tcopt fun_name)
56 | Expr (_, Call (Cnormal, (_, Class_const (((), ci), meth_id)), _, _, _)) ->
57 static_meth_terminal env ci meth_id
58 | If ((_, True), b1, _) -> terminal env inside_case b1
59 | If ((_, False), _, b2) -> terminal env inside_case b2
60 | If (_, b1, b2) ->
61 (try terminal env inside_case b1; () with Exit ->
62 terminal env inside_case b2)
63 | Switch (_, cl) ->
64 terminal_cl env cl
65 | Try (b, catch_list, _) ->
66 (* Note: return inside a finally block is allowed in PHP and
67 * overrides any return in try or catch. It is an error in <?hh,
68 * however. The only way that a finally block can thus be
69 * terminal is if it throws unconditionally -- however, there's
70 * no good case I (eletuchy) could think of for why one would
71 * write *always* throwing code inside a finally block.
73 (try terminal env inside_case b; () with Exit ->
74 terminal_catchl env inside_case catch_list)
75 | Using(_, _, b) ->
76 terminal env inside_case b
77 | While ((_, True), b)
78 | Do (b, (_, True))
79 | For ((_, Expr_list []), (_, Expr_list []), (_, Expr_list []), b) ->
80 if not (Nast.Visitor.HasBreak.block b) then raise Exit
81 | Do _
82 | While _
83 | For _
84 | Foreach _
85 | Noop
86 | Fallthrough
87 | GotoLabel _
88 | Expr _
89 | Static_var _
90 | Global_var _
91 | Let _
92 -> ()
94 and terminal_catchl env inside_case = function
95 | [] -> raise Exit
96 | (_, _, x) :: rl ->
97 (try
98 terminal env inside_case x
99 with Exit ->
100 terminal_catchl env inside_case rl
103 and terminal_cl env = function
104 (* Empty list case should only be when switch statement is malformed and has
105 no case or default blocks *)
106 | [] -> ()
107 | [Case (_, b)] | [Default b] -> terminal env true b
108 | Case (_, b) :: rl ->
109 (try
110 terminal env true b;
111 (* TODO check this *)
112 if List.exists b (function Break _ -> true | _ -> false)
113 then ()
114 else raise Exit
115 with Exit -> terminal_cl env rl)
116 | Default b :: rl ->
117 (try terminal env true b with Exit ->
118 terminal_cl env rl)
120 and terminal_case env = function
121 | Case (_, b) | Default b -> terminal env true b
123 let block env stl =
124 try terminal env false stl; false with Exit -> true
126 let case env c =
127 try terminal_case env c; false with Exit -> true
131 (* TODO jwatzman #3076304 convert this and Terminal to visitor pattern to
132 * remove copy-pasta *)
133 module SafeCase: sig
134 val check: Pos.t -> Typing_env.env -> case list -> unit
135 end = struct
137 let rec terminal env stl =
138 List.iter stl (terminal_ env)
140 and terminal_ env = function
141 | Fallthrough
142 | Break _
143 | Continue _
144 | Throw _
145 | Return _
146 | Goto _
147 | Expr (_, Yield_break)
148 | Expr (_, Assert (AE_assert (_, False))) -> raise Exit
149 | Expr (_, Call (Cnormal, (_, Id (_, fun_name)), _, _, _)) ->
150 let tcopt = Env.get_options env in
151 FuncTerm.raise_exit_if_terminal (FuncTerm.get_fun tcopt fun_name)
152 | Expr (_, Call (Cnormal, (_, Class_const (((), ci), meth_id)), _, _, _)) ->
153 static_meth_terminal env ci meth_id
154 | If ((_, True), b1, _) -> terminal env b1
155 | If ((_, False), _, b2) -> terminal env b2
156 | If (_, b1, b2) ->
157 (try terminal env b1; () with Exit -> terminal env b2)
158 | Switch (_, cl) ->
159 terminal_cl env cl
160 | Try (b, catches, _) ->
161 (* NOTE: contents of finally block are not executed in normal flow, so
162 * they cannot contribute to terminality *)
163 (try terminal env b; ()
164 with Exit -> terminal_catchl env catches)
165 | Do _
166 | While _
167 | Using _
168 | For _
169 | Foreach _
170 | Noop
171 | GotoLabel _
172 | Expr _
173 | Static_var _
174 | Global_var _
175 | Let _
176 -> ()
178 and terminal_catchl env = function
179 | [] -> raise Exit
180 | (_, _, x) :: rl ->
181 (try
182 terminal env x
183 with Exit ->
184 terminal_catchl env rl
187 and terminal_cl env = function
188 (* Empty list case should only be when switch statement is malformed and has
189 no case or default blocks *)
190 | [] -> ()
191 | [Case (_, b)] | [Default b] -> terminal env b
192 | Case (_, b) :: rl ->
193 (try
194 terminal env b;
195 (* TODO check this *)
196 if List.exists b (function Break _ -> true | _ -> false)
197 then ()
198 else raise Exit
199 with Exit -> terminal_cl env rl)
200 | Default b :: rl ->
201 (try terminal env b with Exit -> terminal_cl env rl)
203 let check p env = function
204 | [] -> () (* Skip empty cases so we can use tl below *)
205 | cl -> (* Skip the last case *)
206 List.iter (List.tl_exn (List.rev cl)) begin fun c ->
207 try match c with
208 (* Allow empty cases to fall through *)
209 | Case (_, [])
210 | Default [] -> ()
211 | Case (e, b) -> begin
212 terminal env b;
213 Errors.case_fallthrough p (fst e)
215 | Default b -> begin
216 terminal env b;
217 Errors.default_fallthrough p
219 with Exit -> ()