Scope & Typing: visits the catch part of a do ... catch ... end
[nit.git] / src / semantize / scope.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2012 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 # Identification and scoping of local variables and labels.
18 module scope
19
20 import phase
21 import modelbuilder
22
23 redef class ToolContext
24 # Run `APropdef::do_scope` on each propdef.
25 var scope_phase: Phase = new ScopePhase(self, null)
26 end
27
28 private class ScopePhase
29 super Phase
30 redef fun process_npropdef(npropdef) do npropdef.do_scope(toolcontext)
31 end
32
33
34 # A local variable (including parameters, automatic variables and self)
35 class Variable
36 # The name of the variable (as used in the program)
37 var name: String is writable
38
39 # Alias of `name`
40 redef fun to_s do return self.name
41
42 # The declaration of the variable, if any
43 var location: nullable Location = null is writable
44
45 # Is the local variable not read and need a warning?
46 var warn_unread = false is writable
47 end
48
49 # Mark where break and continue will branch.
50 # Marks are either associated with a label of with a for_loop structure
51 class EscapeMark
52 # The name of the label (unless the mark is an anonymous loop mark)
53 var name: nullable String
54
55 # The associated `continue` mark, if any.
56 # If the mark attached to a loop (loop, while, for), a distinct mark is used.
57 private var continue_mark: nullable EscapeMark = null
58
59 # Each break/continue attached to the mark
60 var escapes = new Array[AEscapeExpr]
61 end
62
63 # Visit a npropdef and:
64 # * Identify variables and labels
65 # * Associate each break and continue to its escapemark
66 # * Transform `ACallFormExpr` that access a variable into `AVarFormExpr`
67 # FIXME: Should the class be private?
68 private class ScopeVisitor
69 super Visitor
70
71 # The tool context used to display errors
72 var toolcontext: ToolContext
73
74 var selfvariable = new Variable("self")
75
76 init
77 do
78 scopes.add(new Scope)
79 end
80
81 # All stacked scope. `scopes.first` is the current scope
82 var scopes = new List[Scope]
83
84 # Shift and check the last scope
85 fun shift_scope
86 do
87 assert not scopes.is_empty
88 var scope = scopes.shift
89 for v in scope.variables.values do
90 if v.warn_unread then
91 toolcontext.advice(v.location, "unread-variable", "Warning: local variable {v.name} is never read.")
92 end
93 end
94 end
95
96 # Register a local variable.
97 # Display an error on toolcontext if a variable with the same name is masked.
98 fun register_variable(node: ANode, variable: Variable): Bool
99 do
100 var name = variable.name
101 var found = search_variable(name)
102 if found != null then
103 self.error(node, "Error: a variable named `{name}` already exists.")
104 return false
105 end
106 scopes.first.variables[name] = variable
107 variable.location = node.location
108 return true
109 end
110
111 # Look for a variable named `name`.
112 # Return null if no such a variable is found.
113 fun search_variable(name: String): nullable Variable
114 do
115 for scope in scopes do
116 var res = scope.get_variable(name)
117 if res != null then
118 return res
119 end
120 end
121 return null
122 end
123
124 redef fun visit(n)
125 do
126 n.accept_scope_visitor(self)
127 end
128
129 # Enter in a statement block `node` as inside a new scope.
130 # The block can be optionally attached to an `escapemark`.
131 fun enter_visit_block(node: nullable AExpr, escapemark: nullable EscapeMark)
132 do
133 if node == null then return
134 var scope = new Scope
135 scope.escapemark = escapemark
136 scopes.unshift(scope)
137 enter_visit(node)
138 shift_scope
139 end
140
141 # Look for a label `name`.
142 # Return null if no such a label is found.
143 fun search_label(name: String): nullable EscapeMark
144 do
145 for scope in scopes do
146 var res = scope.escapemark
147 if res != null and res.name == name then
148 return res
149 end
150 end
151 return null
152 end
153
154 # Create a new escape mark (possibly with a label)
155 # Display an error on toolcontext if a label with the same name is masked.
156 fun make_escape_mark(nlabel: nullable ALabel, for_loop: Bool): EscapeMark
157 do
158 var name: nullable String
159 if nlabel != null then
160 var nid = nlabel.n_id
161 if nid == null then
162 var res = search_label("")
163 if res != null then
164 self.error(nlabel, "Syntax Error: anonymous label already defined.")
165 end
166 name = ""
167 else
168 name = nid.text
169 var found = self.search_label(name)
170 if found != null then
171 self.error(nlabel, "Syntax Error: label `{name}` already defined.")
172 end
173 end
174 else
175 name = null
176 end
177 var res = new EscapeMark(name)
178 if for_loop then res.continue_mark = new EscapeMark(name)
179 return res
180 end
181
182 # Look for an escape mark optionally associated with a label.
183 # If a label is given, the escapemark of this label is returned.
184 # If there is no label, the nearest escapemark that is `for loop` is returned.
185 # If there is no valid escapemark, then an error is displayed ans null is returned.
186 # Return null if no such a label is found.
187 fun get_escapemark(node: ANode, nlabel: nullable ALabel): nullable EscapeMark
188 do
189 if nlabel != null then
190 var nid = nlabel.n_id
191 if nid == null then
192 var res = search_label("")
193 if res == null then
194 self.error(nlabel, "Syntax Error: invalid anonymous label.")
195 node.is_broken = true
196 return null
197 end
198 return res
199 end
200 var name = nid.text
201 var res = search_label(name)
202 if res == null then
203 self.error(nlabel, "Syntax Error: invalid label `{name}`.")
204 node.is_broken = true
205 return null
206 end
207 return res
208 else
209 for scope in scopes do
210 var res = scope.escapemark
211 if res != null then
212 return res
213 end
214 end
215 self.error(node, "Syntax Error: `break` statement outside block.")
216 return null
217 end
218 end
219
220 # Display an error
221 fun error(node: ANode, message: String)
222 do
223 self.toolcontext.error(node.hot_location, message)
224 node.is_broken = true
225 end
226 end
227
228 private class Scope
229 var variables = new HashMap[String, Variable]
230
231 var escapemark: nullable EscapeMark = null
232
233 fun get_variable(name: String): nullable Variable
234 do
235 if self.variables.has_key(name) then
236 return self.variables[name]
237 else
238 return null
239 end
240 end
241 end
242
243 redef class ANode
244 private fun accept_scope_visitor(v: ScopeVisitor)
245 do
246 visit_all(v)
247 end
248 end
249
250 redef class APropdef
251 # Entry point of the scope analysis
252 fun do_scope(toolcontext: ToolContext)
253 do
254 var v = new ScopeVisitor(toolcontext)
255 v.enter_visit(self)
256 v.shift_scope
257 end
258 end
259
260 redef class AParam
261 # The variable associated with the parameter
262 var variable: nullable Variable
263 redef fun accept_scope_visitor(v)
264 do
265 super
266 var nid = self.n_id
267 var variable = new Variable(nid.text)
268 v.register_variable(nid, variable)
269 self.variable = variable
270 end
271 end
272
273 redef class AVardeclExpr
274 # The variable associated with the variable declaration
275 var variable: nullable Variable
276 redef fun accept_scope_visitor(v)
277 do
278 super
279 var nid = self.n_id
280 var variable = new Variable(nid.text)
281 v.register_variable(nid, variable)
282 variable.warn_unread = true # wait for some read mark.
283 self.variable = variable
284 end
285 end
286
287 redef class ASelfExpr
288 # The variable associated with the self receiver
289 var variable: nullable Variable
290 redef fun accept_scope_visitor(v)
291 do
292 super
293 self.variable = v.selfvariable
294 end
295 end
296
297 redef class AEscapeExpr
298 # The escape mark associated with the break/continue
299 var escapemark: nullable EscapeMark
300 end
301
302 redef class AContinueExpr
303 redef fun accept_scope_visitor(v)
304 do
305 super
306 var escapemark = v.get_escapemark(self, self.n_label)
307 if escapemark == null then return # Skip error
308 escapemark = escapemark.continue_mark
309 if escapemark == null then
310 v.error(self, "Error: cannot 'continue', only 'break'.")
311 return
312 end
313 escapemark.escapes.add(self)
314 self.escapemark = escapemark
315 end
316 end
317
318 redef class ABreakExpr
319 redef fun accept_scope_visitor(v)
320 do
321 super
322 var escapemark = v.get_escapemark(self, self.n_label)
323 if escapemark == null then return # Skip error
324 escapemark.escapes.add(self)
325 self.escapemark = escapemark
326 end
327 end
328
329
330 redef class ADoExpr
331 # The break escape mark associated with the 'do' block
332 var break_mark: nullable EscapeMark
333
334 redef fun accept_scope_visitor(v)
335 do
336 self.break_mark = v.make_escape_mark(n_label, false)
337 v.enter_visit_block(n_block, self.break_mark)
338 v.enter_visit_block(n_catch)
339 end
340 end
341
342 redef class AIfExpr
343 redef fun accept_scope_visitor(v)
344 do
345 v.enter_visit(n_expr)
346 v.enter_visit_block(n_then, null)
347 v.enter_visit_block(n_else, null)
348 end
349 end
350
351 redef class AWhileExpr
352 # The break escape mark associated with the 'while'
353 var break_mark: nullable EscapeMark
354
355 # The continue escape mark associated with the 'while'
356 var continue_mark: nullable EscapeMark
357
358 redef fun accept_scope_visitor(v)
359 do
360 var escapemark = v.make_escape_mark(n_label, true)
361 self.break_mark = escapemark
362 self.continue_mark = escapemark.continue_mark
363 v.enter_visit(n_expr)
364 v.enter_visit_block(n_block, escapemark)
365 end
366 end
367
368 redef class ALoopExpr
369 # The break escape mark associated with the 'loop'
370 var break_mark: nullable EscapeMark
371
372 # The continue escape mark associated with the 'loop'
373 var continue_mark: nullable EscapeMark
374
375 redef fun accept_scope_visitor(v)
376 do
377 var escapemark = v.make_escape_mark(n_label, true)
378 self.break_mark = escapemark
379 self.continue_mark = escapemark.continue_mark
380 v.enter_visit_block(n_block, escapemark)
381 end
382 end
383
384 redef class AForExpr
385 # The break escape mark associated with the 'for'
386 var break_mark: nullable EscapeMark
387
388 # The continue escape mark associated with the 'for'
389 var continue_mark: nullable EscapeMark
390
391 redef fun accept_scope_visitor(v)
392 do
393 for g in n_groups do
394 v.enter_visit(g.n_expr)
395 end
396
397 # Protect automatic variables
398 v.scopes.unshift(new Scope)
399
400 for g in n_groups do
401 # Create the automatic variables
402 var variables = new Array[Variable]
403 g.variables = variables
404 for nid in g.n_ids do
405 var va = new Variable(nid.text)
406 v.register_variable(nid, va)
407 variables.add(va)
408 end
409 end
410
411 var escapemark = v.make_escape_mark(n_label, true)
412 self.break_mark = escapemark
413 self.continue_mark = escapemark.continue_mark
414 v.enter_visit_block(n_block, escapemark)
415
416 v.shift_scope
417 end
418 end
419
420 redef class AForGroup
421 # The automatic variables in order
422 var variables: nullable Array[Variable]
423 end
424
425 redef class AWithExpr
426 # The break escape mark associated with the 'with'
427 var break_mark: nullable EscapeMark
428
429 redef fun accept_scope_visitor(v)
430 do
431 v.scopes.unshift(new Scope)
432
433 var escapemark = v.make_escape_mark(n_label, true)
434 self.break_mark = escapemark
435
436 v.enter_visit(n_expr)
437 v.enter_visit_block(n_block, escapemark)
438
439 v.shift_scope
440 end
441 end
442
443 redef class AAssertExpr
444 redef fun accept_scope_visitor(v)
445 do
446 v.enter_visit(n_expr)
447 v.enter_visit_block(n_else, null)
448 end
449 end
450
451 redef class AVarFormExpr
452 # The associated variable
453 var variable: nullable Variable is writable
454 end
455
456 redef class ACallFormExpr
457 redef fun accept_scope_visitor(v)
458 do
459 if n_expr isa AImplicitSelfExpr then
460 var name = n_qid.n_id.text
461 var variable = v.search_variable(name)
462 if variable != null then
463 var n: AExpr
464 if not n_args.n_exprs.is_empty or n_args isa AParExprs then
465 v.error(self, "Error: `{name}` is a variable, not a method.")
466 return
467 end
468 n = variable_create(variable)
469 n.variable = variable
470 replace_with(n)
471 n.accept_scope_visitor(v)
472 return
473 end
474 end
475
476 super
477 end
478
479 # Create a variable access corresponding to the call form
480 private fun variable_create(variable: Variable): AVarFormExpr is abstract
481 end
482
483 redef class ACallExpr
484 redef fun variable_create(variable)
485 do
486 variable.warn_unread = false
487 return new AVarExpr.init_avarexpr(n_qid.n_id)
488 end
489 end
490
491 redef class ACallAssignExpr
492 redef fun variable_create(variable)
493 do
494 return new AVarAssignExpr.init_avarassignexpr(n_qid.n_id, n_assign, n_value)
495 end
496 end
497
498 redef class ACallReassignExpr
499 redef fun variable_create(variable)
500 do
501 variable.warn_unread = false
502 return new AVarReassignExpr.init_avarreassignexpr(n_qid.n_id, n_assign_op, n_value)
503 end
504 end