Merge: Contract implementation
[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 # The analysed property
75 var propdef: APropdef
76
77 var selfvariable = new Variable("self")
78
79 init
80 do
81 scopes.add(new Scope)
82 end
83
84 # All stacked scope. `scopes.first` is the current scope
85 var scopes = new List[Scope]
86
87 # Shift and check the last scope
88 fun shift_scope
89 do
90 assert not scopes.is_empty
91 var scope = scopes.shift
92 for v in scope.variables.values do
93 if v.warn_unread then
94 toolcontext.advice(v.location, "unread-variable", "Warning: local variable {v.name} is never read.")
95 end
96 end
97 end
98
99 # Register a local variable.
100 # Display an error on toolcontext if a variable with the same name is masked.
101 fun register_variable(node: ANode, variable: Variable): Bool
102 do
103 var name = variable.name
104 var found = search_variable(name)
105 if found != null then
106 self.error(node, "Error: a variable named `{name}` already exists.")
107 return false
108 end
109 scopes.first.variables[name] = variable
110 variable.location = node.location
111 return true
112 end
113
114 # Look for a variable named `name`.
115 # Return null if no such a variable is found.
116 fun search_variable(name: String): nullable Variable
117 do
118 for scope in scopes do
119 var res = scope.get_variable(name)
120 if res != null then
121 return res
122 end
123 end
124 return null
125 end
126
127 redef fun visit(n)
128 do
129 n.accept_scope_visitor(self)
130 end
131
132 # Enter in a statement block `node` as inside a new scope.
133 # The block can be optionally attached to an `escapemark`.
134 fun enter_visit_block(node: nullable AExpr, escapemark: nullable EscapeMark)
135 do
136 if node == null then return
137 var scope = new Scope
138 scope.escapemark = escapemark
139 scopes.unshift(scope)
140 enter_visit(node)
141 shift_scope
142 end
143
144 # Look for a label `name`.
145 # Return null if no such a label is found.
146 fun search_label(name: String): nullable EscapeMark
147 do
148 for scope in scopes do
149 var res = scope.escapemark
150 if res != null and res.name == name then
151 return res
152 end
153 end
154 return null
155 end
156
157 # Create a new escape mark (possibly with a label)
158 # Display an error on toolcontext if a label with the same name is masked.
159 fun make_escape_mark(nlabel: nullable ALabel, for_loop: Bool): EscapeMark
160 do
161 var name: nullable String
162 if nlabel != null then
163 var nid = nlabel.n_id
164 if nid == null then
165 var res = search_label("")
166 if res != null then
167 self.error(nlabel, "Syntax Error: anonymous label already defined.")
168 end
169 name = ""
170 else
171 name = nid.text
172 var found = self.search_label(name)
173 if found != null then
174 self.error(nlabel, "Syntax Error: label `{name}` already defined.")
175 end
176 end
177 else
178 name = null
179 end
180 var res = new EscapeMark(name)
181 if for_loop then res.continue_mark = new EscapeMark(name)
182 return res
183 end
184
185 # Look for an escape mark optionally associated with a label.
186 # If a label is given, the escapemark of this label is returned.
187 # If there is no label, the nearest escapemark that is `for loop` is returned.
188 # If there is no valid escapemark, then an error is displayed ans null is returned.
189 # Return null if no such a label is found.
190 fun get_escapemark(node: ANode, nlabel: nullable ALabel): nullable EscapeMark
191 do
192 if nlabel != null then
193 var nid = nlabel.n_id
194 if nid == null then
195 var res = search_label("")
196 if res == null then
197 self.error(nlabel, "Syntax Error: invalid anonymous label.")
198 node.is_broken = true
199 return null
200 end
201 return res
202 end
203 var name = nid.text
204 var res = search_label(name)
205 if res == null then
206 self.error(nlabel, "Syntax Error: invalid label `{name}`.")
207 node.is_broken = true
208 return null
209 end
210 return res
211 else
212 for scope in scopes do
213 var res = scope.escapemark
214 if res != null then
215 return res
216 end
217 end
218 self.error(node, "Syntax Error: `break` statement outside block.")
219 return null
220 end
221 end
222
223 # Display an error
224 fun error(node: ANode, message: String)
225 do
226 self.toolcontext.error(node.hot_location, message)
227 node.is_broken = true
228 end
229 end
230
231 private class Scope
232 var variables = new HashMap[String, Variable]
233
234 var escapemark: nullable EscapeMark = null
235
236 fun get_variable(name: String): nullable Variable
237 do
238 if self.variables.has_key(name) then
239 return self.variables[name]
240 else
241 return null
242 end
243 end
244 end
245
246 redef class ANode
247 private fun accept_scope_visitor(v: ScopeVisitor)
248 do
249 visit_all(v)
250 end
251 end
252
253 redef class APropdef
254 # The break escape mark associated with the return
255 var return_mark: nullable EscapeMark
256
257 # Entry point of the scope analysis
258 fun do_scope(toolcontext: ToolContext)
259 do
260 var v = new ScopeVisitor(toolcontext, self)
261 v.enter_visit(self)
262 v.shift_scope
263 end
264 end
265
266 redef class AParam
267 # The variable associated with the parameter
268 var variable: nullable Variable
269 redef fun accept_scope_visitor(v)
270 do
271 if variable != null then
272 v.register_variable(self.n_id, variable.as(not null))
273 return
274 end
275
276 super
277 var nid = self.n_id
278 var variable = new Variable(nid.text)
279 v.register_variable(nid, variable)
280 self.variable = variable
281 end
282 end
283
284 redef class AVardeclExpr
285 # The variable associated with the variable declaration
286 var variable: nullable Variable
287 redef fun accept_scope_visitor(v)
288 do
289 super
290 var nid = self.n_id
291 var variable = new Variable(nid.text)
292 v.register_variable(nid, variable)
293 variable.warn_unread = true # wait for some read mark.
294 self.variable = variable
295 end
296 end
297
298 redef class ASelfExpr
299 # The variable associated with the self receiver
300 var variable: nullable Variable
301 redef fun accept_scope_visitor(v)
302 do
303 super
304 self.variable = v.selfvariable
305 end
306 end
307
308 redef class AEscapeExpr
309 # The escape mark associated with the break/continue
310 var escapemark: nullable EscapeMark
311 end
312
313 redef class AContinueExpr
314 redef fun accept_scope_visitor(v)
315 do
316 super
317 var escapemark = v.get_escapemark(self, self.n_label)
318 if escapemark == null then return # Skip error
319 escapemark = escapemark.continue_mark
320 if escapemark == null then
321 v.error(self, "Error: cannot 'continue', only 'break'.")
322 return
323 end
324 escapemark.escapes.add(self)
325 self.escapemark = escapemark
326 end
327 end
328
329 redef class ABreakExpr
330 redef fun accept_scope_visitor(v)
331 do
332 super
333 var escapemark = v.get_escapemark(self, self.n_label)
334 if escapemark == null then return # Skip error
335 escapemark.escapes.add(self)
336 self.escapemark = escapemark
337 end
338 end
339
340 redef class AReturnExpr
341 redef fun accept_scope_visitor(v)
342 do
343 super
344
345 var escapemark = v.propdef.return_mark
346 if escapemark == null then
347 escapemark = new EscapeMark
348 v.propdef.return_mark = escapemark
349 end
350
351 escapemark.escapes.add(self)
352 self.escapemark = escapemark
353 end
354 end
355
356 redef class ADoExpr
357 # The break escape mark associated with the 'do' block
358 var break_mark: nullable EscapeMark
359
360 redef fun accept_scope_visitor(v)
361 do
362 self.break_mark = v.make_escape_mark(n_label, false)
363 v.enter_visit_block(n_block, self.break_mark)
364 v.enter_visit_block(n_catch)
365 end
366 end
367
368 redef class AIfExpr
369 redef fun accept_scope_visitor(v)
370 do
371 v.enter_visit(n_expr)
372 v.enter_visit_block(n_then, null)
373 v.enter_visit_block(n_else, null)
374 end
375 end
376
377 redef class AWhileExpr
378 # The break escape mark associated with the 'while'
379 var break_mark: nullable EscapeMark
380
381 # The continue escape mark associated with the 'while'
382 var continue_mark: nullable EscapeMark
383
384 redef fun accept_scope_visitor(v)
385 do
386 var escapemark = v.make_escape_mark(n_label, true)
387 self.break_mark = escapemark
388 self.continue_mark = escapemark.continue_mark
389 v.enter_visit(n_expr)
390 v.enter_visit_block(n_block, escapemark)
391 end
392 end
393
394 redef class ALoopExpr
395 # The break escape mark associated with the 'loop'
396 var break_mark: nullable EscapeMark
397
398 # The continue escape mark associated with the 'loop'
399 var continue_mark: nullable EscapeMark
400
401 redef fun accept_scope_visitor(v)
402 do
403 var escapemark = v.make_escape_mark(n_label, true)
404 self.break_mark = escapemark
405 self.continue_mark = escapemark.continue_mark
406 v.enter_visit_block(n_block, escapemark)
407 end
408 end
409
410 redef class AForExpr
411 # The break escape mark associated with the 'for'
412 var break_mark: nullable EscapeMark
413
414 # The continue escape mark associated with the 'for'
415 var continue_mark: nullable EscapeMark
416
417 redef fun accept_scope_visitor(v)
418 do
419 for g in n_groups do
420 v.enter_visit(g.n_expr)
421 end
422
423 # Protect automatic variables
424 v.scopes.unshift(new Scope)
425
426 for g in n_groups do
427 # Create the automatic variables
428 var variables = new Array[Variable]
429 g.variables = variables
430 for nid in g.n_ids do
431 var va = new Variable(nid.text)
432 v.register_variable(nid, va)
433 variables.add(va)
434 end
435 end
436
437 var escapemark = v.make_escape_mark(n_label, true)
438 self.break_mark = escapemark
439 self.continue_mark = escapemark.continue_mark
440 v.enter_visit_block(n_block, escapemark)
441
442 v.shift_scope
443 end
444 end
445
446 redef class AForGroup
447 # The automatic variables in order
448 var variables: nullable Array[Variable]
449 end
450
451 redef class AWithExpr
452 # The break escape mark associated with the 'with'
453 var break_mark: nullable EscapeMark
454
455 redef fun accept_scope_visitor(v)
456 do
457 v.scopes.unshift(new Scope)
458
459 var escapemark = v.make_escape_mark(n_label, true)
460 self.break_mark = escapemark
461
462 v.enter_visit(n_expr)
463 v.enter_visit_block(n_block, escapemark)
464
465 v.shift_scope
466 end
467 end
468
469 redef class AAssertExpr
470 redef fun accept_scope_visitor(v)
471 do
472 v.enter_visit(n_expr)
473 v.enter_visit_block(n_else, null)
474 end
475 end
476
477 redef class AVarFormExpr
478 # The associated variable
479 var variable: nullable Variable is writable
480 end
481
482 redef class ACallFormExpr
483 redef fun accept_scope_visitor(v)
484 do
485 if n_expr isa AImplicitSelfExpr then
486 var name = n_qid.n_id.text
487 var variable = v.search_variable(name)
488 if variable != null then
489 var n: AExpr
490 if not n_args.n_exprs.is_empty or n_args isa AParExprs or self isa ACallrefExpr then
491 v.error(self, "Error: `{name}` is a variable, not a method.")
492 return
493 end
494 n = variable_create(variable)
495 n.variable = variable
496 replace_with(n)
497 n.accept_scope_visitor(v)
498 return
499 end
500 end
501
502 super
503 end
504
505 # Create a variable access corresponding to the call form
506 private fun variable_create(variable: Variable): AVarFormExpr is abstract
507 end
508
509 redef class ACallExpr
510 redef fun variable_create(variable)
511 do
512 variable.warn_unread = false
513 return new AVarExpr.init_avarexpr(n_qid.n_id)
514 end
515 end
516
517 redef class ACallAssignExpr
518 redef fun variable_create(variable)
519 do
520 return new AVarAssignExpr.init_avarassignexpr(n_qid.n_id, n_assign, n_value)
521 end
522 end
523
524 redef class ACallReassignExpr
525 redef fun variable_create(variable)
526 do
527 variable.warn_unread = false
528 return new AVarReassignExpr.init_avarreassignexpr(n_qid.n_id, n_assign_op, n_value)
529 end
530 end
531
532 redef class ALambdaExpr
533 redef fun accept_scope_visitor(v)
534 do
535 # TODO
536 return
537 end
538 end