Type and check 'continue' return value.
[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 # Current controlable block (for or while)
92 readable writable attr _base_block: AControlableBlock
93
94 # Set of variable that are set (assigned)
95 readable attr _set_variables: HashSet[Variable] = new HashSet[Variable]
96
97 # Is a variable set?
98 meth is_set(v: Variable): Bool
99 do
100 return _set_variables.has(v) or (_prev != null and _prev.is_set(v))
101 end
102
103 meth sub: ControlFlowContext
104 do
105 return new ControlFlowContext.with_prev(self)
106 end
107
108 init
109 do
110 end
111
112 init with_prev(p: ControlFlowContext)
113 do
114 _prev = p
115 _unreash = p.unreash
116 _already_unreash = p.already_unreash
117 _base_block = p.base_block
118 end
119 end
120
121 ###############################################################################
122
123 redef class PNode
124 private meth accept_control_flow(v: ControlFlowVisitor)
125 do
126 accept_abs_syntax_visitor(v)
127 end
128 end
129
130 redef class AMethPropdef
131 redef meth accept_control_flow(v)
132 do
133 v.control_flow_ctx = new ControlFlowContext
134 super
135 end
136 end
137
138 redef class AConcreteMethPropdef
139 redef meth accept_control_flow(v)
140 do
141 super
142 if v.control_flow_ctx.unreash == false and method.signature.return_type != null then
143 v.error(self, "Control error: Reached end of function (a 'return' with a value was expected).")
144 end
145 end
146 end
147
148 redef class AVardeclExpr
149 redef meth accept_control_flow(v)
150 do
151 super
152 if n_expr != null then v.mark_is_set(variable)
153 end
154 end
155
156 redef class ABlockExpr
157 redef meth accept_control_flow(v)
158 do
159 for e in n_expr do
160 if v.control_flow_ctx.unreash and not v.control_flow_ctx.already_unreash then
161 v.control_flow_ctx.already_unreash = true
162 v.warning(e, "Warning: unreachable statement.")
163 end
164 v.visit(e)
165 end
166 end
167 end
168
169 redef class AReturnExpr
170 redef meth accept_control_flow(v)
171 do
172 super
173 v.control_flow_ctx.unreash = true
174 end
175 end
176
177 class ABlockControler
178 special PExpr
179 readable attr _block: AControlableBlock
180 end
181
182 redef class ABreakExpr
183 special ABlockControler
184 redef meth accept_control_flow(v)
185 do
186 super
187 var block = v.control_flow_ctx.base_block
188 if block == null then
189 v.error(self, "Syntax Error: 'break' statment outside block.")
190 return
191 end
192 _block = block
193 v.control_flow_ctx.unreash = true
194 end
195 end
196 redef class AContinueExpr
197 special ABlockControler
198 redef meth accept_control_flow(v)
199 do
200 super
201 var block = v.control_flow_ctx.base_block
202 if block == null then
203 v.error(self, "Syntax Error: 'continue' outside block.")
204 return
205 end
206 _block = block
207 v.control_flow_ctx.unreash = true
208 end
209 end
210
211 redef class AAbortExpr
212 redef meth accept_control_flow(v)
213 do
214 super
215 v.control_flow_ctx.unreash = true
216 end
217 end
218
219 redef class AIfExpr
220 redef meth accept_control_flow(v)
221 do
222 v.visit(n_expr)
223
224 var old_control_flow_ctx = v.control_flow_ctx
225 v.control_flow_ctx = v.control_flow_ctx.sub
226
227 v.visit(n_then)
228
229 if n_else == null then
230 # Restore control flow ctx since the 'then" block is optional
231 v.control_flow_ctx = old_control_flow_ctx
232 else
233 # Remember what appens in the 'then'
234 var then_control_flow_ctx = v.control_flow_ctx
235 # Reset to execute the 'else'
236 v.control_flow_ctx = old_control_flow_ctx.sub
237
238 v.visit(n_else)
239
240 # Merge then and else in the old control_flow
241 old_control_flow_ctx.unreash = v.control_flow_ctx.unreash and then_control_flow_ctx.unreash
242
243 if v.control_flow_ctx.unreash then v.control_flow_ctx = then_control_flow_ctx
244 if then_control_flow_ctx.unreash then then_control_flow_ctx = v.control_flow_ctx
245 for variable in v.control_flow_ctx.set_variables do
246 if then_control_flow_ctx.is_set(variable) then
247 old_control_flow_ctx.set_variables.add(variable)
248 end
249 end
250 v.control_flow_ctx = old_control_flow_ctx
251 end
252 end
253 end
254
255 class AControlableBlock
256 special PExpr
257 redef meth accept_control_flow(v)
258 do
259 # Store old control flow values
260 var old_control_flow_ctx = v.control_flow_ctx
261 v.control_flow_ctx = v.control_flow_ctx.sub
262
263 # Register the block
264 v.control_flow_ctx.base_block = self
265
266 super
267
268 # Check control flow if any
269 check_control_flow(v)
270
271 # Restore control flow value since all controlable blocks are optionnal
272 v.control_flow_ctx = old_control_flow_ctx
273 end
274
275 private meth check_control_flow(v: ControlFlowVisitor) do end
276 end
277
278 redef class AWhileExpr
279 special AControlableBlock
280 end
281
282 redef class AForExpr
283 special AControlableBlock
284 end
285
286 redef class AVarExpr
287 redef meth accept_control_flow(v)
288 do
289 super
290 v.check_is_set(self, variable)
291 end
292 end
293
294 redef class AVarAssignExpr
295 redef meth accept_control_flow(v)
296 do
297 super
298 v.mark_is_set(variable)
299 end
300 end
301
302 redef class AVarReassignExpr
303 redef meth accept_control_flow(v)
304 do
305 super
306 v.check_is_set(self, variable)
307 v.mark_is_set(variable)
308 end
309 end
310
311 redef class AClosureDef
312 special AControlableBlock
313 redef meth accept_control_flow(v)
314 do
315 for va in variables do v.mark_is_set(va)
316 super
317 end
318
319 redef meth check_control_flow(v)
320 do
321 if v.control_flow_ctx.unreash == false and signature.return_type != null then
322 v.error(self, "Control error: Reached end of bloc (a 'continue' with a value was expected).")
323 end
324 end
325 end
326
327 redef class AOnceExpr
328 redef meth accept_control_flow(v)
329 do
330 if v.once_count > 0 then
331 v.warning(self, "Useless once in a once expression.")
332 end
333 v.once_count = v.once_count + 1
334
335 super
336
337 v.once_count = v.once_count - 1
338 end
339 end
340