1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2012 Jean Privat <jean@pryen.org>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # Identification and scoping of local variables and labels.
23 redef class ToolContext
24 # Run `APropdef::do_scope` on each propdef.
25 var scope_phase
: Phase = new ScopePhase(self, null)
28 private class ScopePhase
30 redef fun process_npropdef
(npropdef
) do npropdef
.do_scope
(toolcontext
)
34 # A local variable (including parameters, automatic variables and self)
36 # The name of the variable (as used in the program)
37 var name
: String is writable
40 redef fun to_s
do return self.name
42 # The declaration of the variable, if any
43 var location
: nullable Location = null is writable
45 # Is the local variable not read and need a warning?
46 var warn_unread
= false is writable
49 # Mark where break and continue will branch.
50 # Marks are either associated with a label of with a for_loop structure
52 # The name of the label (unless the mark is an anonymous loop mark)
53 var name
: nullable String
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
59 # Each break/continue attached to the mark
60 var escapes
= new Array[AEscapeExpr]
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
71 # The tool context used to display errors
72 var toolcontext
: ToolContext
74 # The analysed property
77 var selfvariable
= new Variable("self")
84 # All stacked scope. `scopes.first` is the current scope
85 var scopes
= new List[Scope]
87 # Shift and check the last scope
90 assert not scopes
.is_empty
91 var scope
= scopes
.shift
92 for v
in scope
.variables
.values
do
94 toolcontext
.advice
(v
.location
, "unread-variable", "Warning: local variable {v.name} is never read.")
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
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.")
109 scopes
.first
.variables
[name
] = variable
110 variable
.location
= node
.location
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
118 for scope
in scopes
do
119 var res
= scope
.get_variable
(name
)
129 n
.accept_scope_visitor
(self)
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)
136 if node
== null then return
137 var scope
= new Scope
138 scope
.escapemark
= escapemark
139 scopes
.unshift
(scope
)
144 # Look for a label `name`.
145 # Return null if no such a label is found.
146 fun search_label
(name
: String): nullable EscapeMark
148 for scope
in scopes
do
149 var res
= scope
.escapemark
150 if res
!= null and res
.name
== name
then
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
161 var name
: nullable String
162 if nlabel
!= null then
163 var nid
= nlabel
.n_id
165 var res
= search_label
("")
167 self.error
(nlabel
, "Syntax Error: anonymous label already defined.")
172 var found
= self.search_label
(name
)
173 if found
!= null then
174 self.error
(nlabel
, "Syntax Error: label `{name}` already defined.")
180 var res
= new EscapeMark(name
)
181 if for_loop
then res
.continue_mark
= new EscapeMark(name
)
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
192 if nlabel
!= null then
193 var nid
= nlabel
.n_id
195 var res
= search_label
("")
197 self.error
(nlabel
, "Syntax Error: invalid anonymous label.")
198 node
.is_broken
= true
204 var res
= search_label
(name
)
206 self.error
(nlabel
, "Syntax Error: invalid label `{name}`.")
207 node
.is_broken
= true
212 for scope
in scopes
do
213 var res
= scope
.escapemark
218 self.error
(node
, "Syntax Error: `break` statement outside block.")
224 fun error
(node
: ANode, message
: String)
226 self.toolcontext
.error
(node
.hot_location
, message
)
227 node
.is_broken
= true
232 var variables
= new HashMap[String, Variable]
234 var escapemark
: nullable EscapeMark = null
236 fun get_variable
(name
: String): nullable Variable
238 if self.variables
.has_key
(name
) then
239 return self.variables
[name
]
247 private fun accept_scope_visitor
(v
: ScopeVisitor)
254 # The break escape mark associated with the return
255 var return_mark
: nullable EscapeMark
257 # Entry point of the scope analysis
258 fun do_scope
(toolcontext
: ToolContext)
260 var v
= new ScopeVisitor(toolcontext
, self)
267 # The variable associated with the parameter
268 var variable
: nullable Variable
269 redef fun accept_scope_visitor
(v
)
271 if variable
!= null then
272 v
.register_variable
(self.n_id
, variable
.as(not null))
278 var variable
= new Variable(nid
.text
)
279 v
.register_variable
(nid
, variable
)
280 self.variable
= variable
284 redef class AVardeclExpr
285 # The variable associated with the variable declaration
286 var variable
: nullable Variable
287 redef fun accept_scope_visitor
(v
)
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
298 redef class ASelfExpr
299 # The variable associated with the self receiver
300 var variable
: nullable Variable
301 redef fun accept_scope_visitor
(v
)
304 self.variable
= v
.selfvariable
308 redef class AEscapeExpr
309 # The escape mark associated with the break/continue
310 var escapemark
: nullable EscapeMark
313 redef class AContinueExpr
314 redef fun accept_scope_visitor
(v
)
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'.")
324 escapemark
.escapes
.add
(self)
325 self.escapemark
= escapemark
329 redef class ABreakExpr
330 redef fun accept_scope_visitor
(v
)
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
340 redef class AReturnExpr
341 redef fun accept_scope_visitor
(v
)
345 var escapemark
= v
.propdef
.return_mark
346 if escapemark
== null then
347 escapemark
= new EscapeMark
348 v
.propdef
.return_mark
= escapemark
351 escapemark
.escapes
.add
(self)
352 self.escapemark
= escapemark
357 # The break escape mark associated with the 'do' block
358 var break_mark
: nullable EscapeMark
360 redef fun accept_scope_visitor
(v
)
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
)
369 redef fun accept_scope_visitor
(v
)
371 v
.enter_visit
(n_expr
)
372 v
.enter_visit_block
(n_then
, null)
373 v
.enter_visit_block
(n_else
, null)
377 redef class AWhileExpr
378 # The break escape mark associated with the 'while'
379 var break_mark
: nullable EscapeMark
381 # The continue escape mark associated with the 'while'
382 var continue_mark
: nullable EscapeMark
384 redef fun accept_scope_visitor
(v
)
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
)
394 redef class ALoopExpr
395 # The break escape mark associated with the 'loop'
396 var break_mark
: nullable EscapeMark
398 # The continue escape mark associated with the 'loop'
399 var continue_mark
: nullable EscapeMark
401 redef fun accept_scope_visitor
(v
)
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
)
411 # The break escape mark associated with the 'for'
412 var break_mark
: nullable EscapeMark
414 # The continue escape mark associated with the 'for'
415 var continue_mark
: nullable EscapeMark
417 redef fun accept_scope_visitor
(v
)
420 v
.enter_visit
(g
.n_expr
)
423 # Protect automatic variables
424 v
.scopes
.unshift
(new Scope)
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
)
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
)
446 redef class AForGroup
447 # The automatic variables in order
448 var variables
: nullable Array[Variable]
451 redef class AWithExpr
452 # The break escape mark associated with the 'with'
453 var break_mark
: nullable EscapeMark
455 redef fun accept_scope_visitor
(v
)
457 v
.scopes
.unshift
(new Scope)
459 var escapemark
= v
.make_escape_mark
(n_label
, true)
460 self.break_mark
= escapemark
462 v
.enter_visit
(n_expr
)
463 v
.enter_visit_block
(n_block
, escapemark
)
469 redef class AAssertExpr
470 redef fun accept_scope_visitor
(v
)
472 v
.enter_visit
(n_expr
)
473 v
.enter_visit_block
(n_else
, null)
477 redef class AVarFormExpr
478 # The associated variable
479 var variable
: nullable Variable is writable
482 redef class ACallFormExpr
483 redef fun accept_scope_visitor
(v
)
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
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.")
494 n
= variable_create
(variable
)
495 n
.variable
= variable
497 n
.accept_scope_visitor
(v
)
505 # Create a variable access corresponding to the call form
506 private fun variable_create
(variable
: Variable): AVarFormExpr is abstract
509 redef class ACallExpr
510 redef fun variable_create
(variable
)
512 variable
.warn_unread
= false
513 return new AVarExpr.init_avarexpr
(n_qid
.n_id
)
517 redef class ACallAssignExpr
518 redef fun variable_create
(variable
)
520 return new AVarAssignExpr.init_avarassignexpr
(n_qid
.n_id
, n_assign
, n_value
)
524 redef class ACallReassignExpr
525 redef fun variable_create
(variable
)
527 variable
.warn_unread
= false
528 return new AVarReassignExpr.init_avarreassignexpr
(n_qid
.n_id
, n_assign_op
, n_value
)
532 redef class ALambdaExpr
533 redef fun accept_scope_visitor
(v
)