X-Git-Url: http://nitlanguage.org diff --git a/src/semantize/scope.nit b/src/semantize/scope.nit index 83c5fe4..4ea5f27 100644 --- a/src/semantize/scope.nit +++ b/src/semantize/scope.nit @@ -14,12 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Identification and scping of local variables and labels. +# Identification and scoping of local variables and labels. module scope import phase redef class ToolContext + # Run `APropdef::do_scope` on each propdef. var scope_phase: Phase = new ScopePhase(self, null) end @@ -36,6 +37,12 @@ class Variable # Alias of `name` redef fun to_s do return self.name + + # The declaration of the variable, if any + var location: nullable Location = null + + # Is the local variable not read and need a warning? + var warn_unread = false is writable end # Mark where break and continue will branch. @@ -44,15 +51,12 @@ class EscapeMark # The name of the label (unless the mark is an anonymous loop mark) var name: nullable String - # Is the mark atached to a loop (loop, while, for) - # Such a mark is a candidate to a labelless 'continue' or 'break' - var for_loop: Bool - - # Each 'continue' attached to the mark - var continues: Array[AContinueExpr] = new Array[AContinueExpr] + # The associated `continue` mark, if any. + # If the mark attached to a loop (loop, while, for), a distinct mark is used. + private var continue_mark: nullable EscapeMark = null - # Each 'break' attached to the mark - var breaks: Array[ABreakExpr] = new Array[ABreakExpr] + # Each break/continue attached to the mark + var escapes = new Array[AEscapeExpr] end # Visit a npropdef and: @@ -66,28 +70,40 @@ private class ScopeVisitor # The tool context used to display errors var toolcontext: ToolContext - var selfvariable: Variable = new Variable("self") + var selfvariable = new Variable("self") - init(toolcontext: ToolContext) + init do - self.toolcontext = toolcontext scopes.add(new Scope) end # All stacked scope. `scopes.first` is the current scope - private var scopes: List[Scope] = new List[Scope] + var scopes = new List[Scope] + + # Shift and check the last scope + fun shift_scope + do + assert not scopes.is_empty + var scope = scopes.shift + for v in scope.variables.values do + if v.warn_unread then + toolcontext.advice(v.location, "unread-variable", "Warning: local variable {v.name} is never read.") + end + end + end - # Regiter a local variable. + # Register a local variable. # Display an error on toolcontext if a variable with the same name is masked. fun register_variable(node: ANode, variable: Variable): Bool do var name = variable.name var found = search_variable(name) if found != null then - self.error(node, "Error: A variable named `{name}' already exists") + self.error(node, "Error: a variable named `{name}` already exists.") return false end scopes.first.variables[name] = variable + variable.location = node.location return true end @@ -111,19 +127,19 @@ private class ScopeVisitor # Enter in a statement block `node` as inside a new scope. # The block can be optionally attached to an `escapemark`. - private fun enter_visit_block(node: nullable AExpr, escapemark: nullable EscapeMark) + fun enter_visit_block(node: nullable AExpr, escapemark: nullable EscapeMark) do if node == null then return var scope = new Scope scope.escapemark = escapemark scopes.unshift(scope) enter_visit(node) - scopes.shift + shift_scope end # Look for a label `name`. - # Return nulll if no such a label is found. - private fun search_label(name: String): nullable EscapeMark + # Return null if no such a label is found. + fun search_label(name: String): nullable EscapeMark do for scope in scopes do var res = scope.escapemark @@ -136,7 +152,7 @@ private class ScopeVisitor # Create a new escape mark (possibly with a label) # Display an error on toolcontext if a label with the same name is masked. - private fun make_escape_mark(nlabel: nullable ALabel, for_loop: Bool): EscapeMark + fun make_escape_mark(nlabel: nullable ALabel, for_loop: Bool): EscapeMark do var name: nullable String if nlabel != null then @@ -144,36 +160,37 @@ private class ScopeVisitor if nid == null then var res = search_label("") if res != null then - self.error(nlabel, "Syntax error: anonymous label already defined.") + self.error(nlabel, "Syntax Error: anonymous label already defined.") end name = "" else name = nid.text var found = self.search_label(name) if found != null then - self.error(nlabel, "Syntax error: label {name} already defined.") + self.error(nlabel, "Syntax Error: label `{name}` already defined.") end end else name = null end - var res = new EscapeMark(name, for_loop) + var res = new EscapeMark(name) + if for_loop then res.continue_mark = new EscapeMark(name) return res end # Look for an escape mark optionally associated with a label. - # If a label is given, the the escapemark of this label is returned. + # If a label is given, the escapemark of this label is returned. # If there is no label, the nearest escapemark that is `for loop` is returned. # If there is no valid escapemark, then an error is displayed ans null is returned. - # Return nulll if no such a label is found. - private fun get_escapemark(node: ANode, nlabel: nullable ALabel): nullable EscapeMark + # Return null if no such a label is found. + fun get_escapemark(node: ANode, nlabel: nullable ALabel): nullable EscapeMark do if nlabel != null then var nid = nlabel.n_id if nid == null then var res = search_label("") if res == null then - self.error(nlabel, "Syntax error: invalid anonymous label.") + self.error(nlabel, "Syntax Error: invalid anonymous label.") return null end return res @@ -181,7 +198,7 @@ private class ScopeVisitor var name = nid.text var res = search_label(name) if res == null then - self.error(nlabel, "Syntax error: invalid label {name}.") + self.error(nlabel, "Syntax Error: invalid label `{name}`.") return null end return res @@ -192,20 +209,20 @@ private class ScopeVisitor return res end end - self.error(node, "Syntax Error: 'break' statment outside block.") + self.error(node, "Syntax Error: `break` statement outside block.") return null end end # Display an error - private fun error(node: ANode, message: String) + fun error(node: ANode, message: String) do self.toolcontext.error(node.hot_location, message) end end private class Scope - var variables: HashMap[String, Variable] = new HashMap[String, Variable] + var variables = new HashMap[String, Variable] var escapemark: nullable EscapeMark = null @@ -232,6 +249,7 @@ redef class APropdef do var v = new ScopeVisitor(toolcontext) v.enter_visit(self) + v.shift_scope end end @@ -257,12 +275,13 @@ redef class AVardeclExpr var nid = self.n_id var variable = new Variable(nid.text) v.register_variable(nid, variable) + variable.warn_unread = true # wait for some read mark. self.variable = variable end end redef class ASelfExpr - # The variable associated with the self reciever + # The variable associated with the self receiver var variable: nullable Variable redef fun accept_scope_visitor(v) do @@ -271,43 +290,47 @@ redef class ASelfExpr end end -redef class AContinueExpr - # The escape mark associated with the continue +redef class AEscapeExpr + # The escape mark associated with the break/continue var escapemark: nullable EscapeMark +end + +redef class AContinueExpr redef fun accept_scope_visitor(v) do super var escapemark = v.get_escapemark(self, self.n_label) if escapemark == null then return # Skip error - if not escapemark.for_loop then + escapemark = escapemark.continue_mark + if escapemark == null then v.error(self, "Error: cannot 'continue', only 'break'.") + return end - escapemark.continues.add(self) + escapemark.escapes.add(self) self.escapemark = escapemark end end redef class ABreakExpr - # The escape mark associated with the break - var escapemark: nullable EscapeMark redef fun accept_scope_visitor(v) do super var escapemark = v.get_escapemark(self, self.n_label) if escapemark == null then return # Skip error - escapemark.breaks.add(self) + escapemark.escapes.add(self) self.escapemark = escapemark end end redef class ADoExpr - # The escape mark associated with the 'do' block - var escapemark: nullable EscapeMark + # The break escape mark associated with the 'do' block + var break_mark: nullable EscapeMark + redef fun accept_scope_visitor(v) do - self.escapemark = v.make_escape_mark(n_label, false) - v.enter_visit_block(n_block, self.escapemark) + self.break_mark = v.make_escape_mark(n_label, false) + v.enter_visit_block(n_block, self.break_mark) end end @@ -321,24 +344,34 @@ redef class AIfExpr end redef class AWhileExpr - # The escape mark associated with the 'while' - var escapemark: nullable EscapeMark + # The break escape mark associated with the 'while' + var break_mark: nullable EscapeMark + + # The continue escape mark associated with the 'while' + var continue_mark: nullable EscapeMark + redef fun accept_scope_visitor(v) do var escapemark = v.make_escape_mark(n_label, true) - self.escapemark = escapemark + self.break_mark = escapemark + self.continue_mark = escapemark.continue_mark v.enter_visit(n_expr) v.enter_visit_block(n_block, escapemark) end end redef class ALoopExpr - # The escape mark associated with the 'loop' - var escapemark: nullable EscapeMark + # The break escape mark associated with the 'loop' + var break_mark: nullable EscapeMark + + # The continue escape mark associated with the 'loop' + var continue_mark: nullable EscapeMark + redef fun accept_scope_visitor(v) do var escapemark = v.make_escape_mark(n_label, true) - self.escapemark = escapemark + self.break_mark = escapemark + self.continue_mark = escapemark.continue_mark v.enter_visit_block(n_block, escapemark) end end @@ -347,8 +380,11 @@ redef class AForExpr # The automatic variables in order var variables: nullable Array[Variable] - # The escape mark associated with the 'for' - var escapemark: nullable EscapeMark + # The break escape mark associated with the 'for' + var break_mark: nullable EscapeMark + + # The continue escape mark associated with the 'for' + var continue_mark: nullable EscapeMark redef fun accept_scope_visitor(v) do @@ -367,10 +403,29 @@ redef class AForExpr end var escapemark = v.make_escape_mark(n_label, true) - self.escapemark = escapemark + self.break_mark = escapemark + self.continue_mark = escapemark.continue_mark + v.enter_visit_block(n_block, escapemark) + + v.shift_scope + end +end + +redef class AWithExpr + # The break escape mark associated with the 'with' + var break_mark: nullable EscapeMark + + redef fun accept_scope_visitor(v) + do + v.scopes.unshift(new Scope) + + var escapemark = v.make_escape_mark(n_label, true) + self.break_mark = escapemark + + v.enter_visit(n_expr) v.enter_visit_block(n_block, escapemark) - v.scopes.shift + v.shift_scope end end @@ -388,7 +443,7 @@ redef class ACallFormExpr if variable != null then var n: AExpr if not n_args.n_exprs.is_empty or n_args isa AParExprs then - v.error(self, "Error: {name} is variable, not a function.") + v.error(self, "Error: `{name}` is a variable, not a method.") return end n = variable_create(variable) @@ -402,13 +457,14 @@ redef class ACallFormExpr super end - # Create a variable acces corresponding to the call form + # Create a variable access corresponding to the call form private fun variable_create(variable: Variable): AVarFormExpr is abstract end redef class ACallExpr redef fun variable_create(variable) do + variable.warn_unread = false return new AVarExpr.init_avarexpr(n_id) end end @@ -423,6 +479,7 @@ end redef class ACallReassignExpr redef fun variable_create(variable) do + variable.warn_unread = false return new AVarReassignExpr.init_avarreassignexpr(n_id, n_assign_op, n_value) end end