syntax: New escape block management
[nit.git] / src / syntax / control_flow.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2008 Jean Privat <jean@pryen.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Analysis control flow in property bodies, statements and expressions
18 package control_flow
19
20 import syntax_base
21
22 redef class MMSrcModule
23 # Walk trough the module and type statments and expressions
24 # Require than supermodules are processed
25 meth do_control_flow(tc: ToolContext)
26 do
27 var tv = new ControlFlowVisitor(tc, self)
28 tv.visit(node)
29 end
30 end
31
32 redef class Variable
33 # Is the variable must be set before being used ?
34 meth must_be_set: Bool do return false
35 end
36
37 redef class VarVariable
38 redef meth must_be_set do return true
39 end
40
41
42
43 # Control flow visitor
44 # * Check reachability in methods
45 # * Associate breaks and continues
46 # * Check some other warning
47 private class ControlFlowVisitor
48 special AbsSyntaxVisitor
49 redef meth visit(n)
50 do
51 if n != null then n.accept_control_flow(self)
52 end
53
54 # Number of nested once
55 readable writable attr _once_count: Int = 0
56
57 # Current knowledge about variables types
58 readable writable attr _control_flow_ctx: ControlFlowContext
59
60 meth check_is_set(n: PNode, v: Variable)
61 do
62 if v.must_be_set and not control_flow_ctx.is_set(v) then
63 error(n, "Error: variable '{v}' is possibly unset.")
64 var cfc = control_flow_ctx
65 while cfc != null do
66 print("cfc: " + cfc.set_variables.join(" "))
67 cfc = cfc.prev
68 end
69 end
70 end
71
72 meth mark_is_set(v: Variable)
73 do
74 control_flow_ctx.set_variables.add(v)
75 end
76
77 init(tc, m) do super
78 end
79
80 private class ControlFlowContext
81 # Previous control flow context if any
82 readable attr _prev: ControlFlowContext
83
84 # Is a control flow break met? (return, break, continue)
85 readable writable attr _unreash: Bool = false
86
87 # Is a control flow already broken?
88 # Used to avoid repeating the same error message
89 readable writable attr _already_unreash: Bool = false
90
91 # Set of variable that are set (assigned)
92 readable attr _set_variables: HashSet[Variable] = new HashSet[Variable]
93
94 # Is a variable set?
95 meth is_set(v: Variable): Bool
96 do
97 return _set_variables.has(v) or (_prev != null and _prev.is_set(v))
98 end
99
100 meth sub: ControlFlowContext
101 do
102 return new ControlFlowContext.with_prev(self)
103 end
104
105 init
106 do
107 end
108
109 init with_prev(p: ControlFlowContext)
110 do
111 _prev = p
112 _unreash = p.unreash
113 _already_unreash = p.already_unreash
114 end
115 end
116
117 ###############################################################################
118
119 redef class PNode
120 private meth accept_control_flow(v: ControlFlowVisitor)
121 do
122 accept_abs_syntax_visitor(v)
123 end
124 end
125
126 redef class AMethPropdef
127 redef meth accept_control_flow(v)
128 do
129 v.control_flow_ctx = new ControlFlowContext
130 super
131 end
132 end
133
134 redef class AConcreteMethPropdef
135 redef meth accept_control_flow(v)
136 do
137 super
138 if v.control_flow_ctx.unreash == false and method.signature.return_type != null then
139 v.error(self, "Control error: Reached end of function (a 'return' with a value was expected).")
140 end
141 end
142 end
143
144 redef class AVardeclExpr
145 redef meth accept_control_flow(v)
146 do
147 super
148 if n_expr != null then v.mark_is_set(variable)
149 end
150 end
151
152 redef class ABlockExpr
153 redef meth accept_control_flow(v)
154 do
155 for e in n_expr do
156 if v.control_flow_ctx.unreash and not v.control_flow_ctx.already_unreash then
157 v.control_flow_ctx.already_unreash = true
158 v.warning(e, "Warning: unreachable statement.")
159 end
160 v.visit(e)
161 end
162 end
163 end
164
165 redef class AReturnExpr
166 redef meth accept_control_flow(v)
167 do
168 super
169 v.control_flow_ctx.unreash = true
170 end
171 end
172
173 redef class ABreakExpr
174 redef meth accept_control_flow(v)
175 do
176 super
177 v.control_flow_ctx.unreash = true
178 end
179 end
180 redef class AContinueExpr
181 redef meth accept_control_flow(v)
182 do
183 super
184 v.control_flow_ctx.unreash = true
185 end
186 end
187
188 redef class AAbortExpr
189 redef meth accept_control_flow(v)
190 do
191 super
192 v.control_flow_ctx.unreash = true
193 end
194 end
195
196 redef class AClosureCallExpr
197 redef meth accept_control_flow(v)
198 do
199 super
200 if variable.closure.is_break then v.control_flow_ctx.unreash = true
201 end
202 end
203
204 redef class AIfExpr
205 redef meth accept_control_flow(v)
206 do
207 v.visit(n_expr)
208
209 var old_control_flow_ctx = v.control_flow_ctx
210 v.control_flow_ctx = v.control_flow_ctx.sub
211
212 v.visit(n_then)
213
214 if n_else == null then
215 # Restore control flow ctx since the 'then" block is optional
216 v.control_flow_ctx = old_control_flow_ctx
217 else
218 # Remember what appens in the 'then'
219 var then_control_flow_ctx = v.control_flow_ctx
220 # Reset to execute the 'else'
221 v.control_flow_ctx = old_control_flow_ctx.sub
222
223 v.visit(n_else)
224
225 # Merge then and else in the old control_flow
226 old_control_flow_ctx.unreash = v.control_flow_ctx.unreash and then_control_flow_ctx.unreash
227
228 if v.control_flow_ctx.unreash then v.control_flow_ctx = then_control_flow_ctx
229 if then_control_flow_ctx.unreash then then_control_flow_ctx = v.control_flow_ctx
230 for variable in v.control_flow_ctx.set_variables do
231 if then_control_flow_ctx.is_set(variable) then
232 old_control_flow_ctx.set_variables.add(variable)
233 end
234 end
235 v.control_flow_ctx = old_control_flow_ctx
236 end
237 end
238 end
239
240 class AControlableBlock
241 special PExpr
242 redef meth accept_control_flow(v)
243 do
244 # Store old control flow values
245 var old_control_flow_ctx = v.control_flow_ctx
246 v.control_flow_ctx = v.control_flow_ctx.sub
247
248 super
249
250 # Check control flow if any
251 check_control_flow(v)
252
253 # Restore control flow value since all controlable blocks are optionnal
254 v.control_flow_ctx = old_control_flow_ctx
255 end
256
257 private meth check_control_flow(v: ControlFlowVisitor) do end
258 end
259
260 redef class AWhileExpr
261 special AControlableBlock
262 end
263
264 redef class AForExpr
265 special AControlableBlock
266 end
267
268 redef class AVarExpr
269 redef meth accept_control_flow(v)
270 do
271 super
272 v.check_is_set(self, variable)
273 end
274 end
275
276 redef class AVarAssignExpr
277 redef meth accept_control_flow(v)
278 do
279 super
280 v.mark_is_set(variable)
281 end
282 end
283
284 redef class AVarReassignExpr
285 redef meth accept_control_flow(v)
286 do
287 super
288 v.check_is_set(self, variable)
289 v.mark_is_set(variable)
290 end
291 end
292
293 redef class AClosureDecl
294 redef meth accept_control_flow(v)
295 do
296 if n_expr != null then
297 var old_control_flow_ctx = v.control_flow_ctx
298 v.control_flow_ctx = v.control_flow_ctx.sub
299
300 super
301
302 if v.control_flow_ctx.unreash == false then
303 if variable.closure.signature.return_type != null then
304 v.error(self, "Control error: Reached end of bloc (a 'continue' with a value was expected).")
305 else if variable.closure.is_break then
306 v.error(self, "Control error: Reached end of break bloc (an 'abort' was expected).")
307 end
308 end
309
310 v.control_flow_ctx = old_control_flow_ctx
311 end
312 end
313 end
314
315 redef class AClosureDef
316 special AControlableBlock
317 redef meth accept_control_flow(v)
318 do
319 for va in variables do v.mark_is_set(va)
320 super
321 end
322
323 redef meth check_control_flow(v)
324 do
325 if v.control_flow_ctx.unreash == false then
326 if closure.signature.return_type != null then
327 v.error(self, "Control error: Reached end of bloc (a 'continue' with a value was expected).")
328 else if closure.is_break then
329 v.error(self, "Control error: Reached end of break bloc (a 'break' was expected).")
330 end
331 end
332 end
333 end
334
335 redef class AOnceExpr
336 redef meth accept_control_flow(v)
337 do
338 if v.once_count > 0 then
339 v.warning(self, "Useless once in a once expression.")
340 end
341 v.once_count = v.once_count + 1
342
343 super
344
345 v.once_count = v.once_count - 1
346 end
347 end
348