scope: refuse `&x` where x is a local variable
[nit.git] / src / semantize / scope.nit
index 83c5fe4..2f8a1bf 100644 (file)
 # 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
+import modelbuilder
 
 redef class ToolContext
+       # Run `APropdef::do_scope` on each propdef.
        var scope_phase: Phase = new ScopePhase(self, null)
 end
 
@@ -32,10 +34,16 @@ end
 # A local variable (including parameters, automatic variables and self)
 class Variable
        # The name of the variable (as used in the program)
-       var name: String
+       var name: String is writable
 
        # Alias of `name`
        redef fun to_s do return self.name
+
+       # The declaration of the variable, if any
+       var location: nullable Location = null is writable
+
+       # 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 +52,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 +71,43 @@ private class ScopeVisitor
        # The tool context used to display errors
        var toolcontext: ToolContext
 
-       var selfvariable: Variable = new Variable("self")
+       # The analysed property
+       var propdef: APropdef
 
-       init(toolcontext: ToolContext)
+       var selfvariable = new Variable("self")
+
+       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 +131,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 +156,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 +164,38 @@ 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.")
+                                       node.is_broken = true
                                        return null
                                end
                                return res
@@ -181,7 +203,8 @@ 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}`.")
+                               node.is_broken = true
                                return null
                        end
                        return res
@@ -192,20 +215,21 @@ 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)
+               node.is_broken = true
        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
 
@@ -227,11 +251,15 @@ redef class ANode
 end
 
 redef class APropdef
+       # The break escape mark associated with the return
+       var return_mark: nullable EscapeMark
+
        # Entry point of the scope analysis
        fun do_scope(toolcontext: ToolContext)
        do
-               var v = new ScopeVisitor(toolcontext)
+               var v = new ScopeVisitor(toolcontext, self)
                v.enter_visit(self)
+               v.shift_scope
        end
 end
 
@@ -257,12 +285,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 +300,63 @@ 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 AReturnExpr
+       redef fun accept_scope_visitor(v)
+       do
+               super
+
+               var escapemark = v.propdef.return_mark
+               if escapemark == null then
+                       escapemark = new EscapeMark
+                       v.propdef.return_mark = escapemark
+               end
+
+               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)
+               v.enter_visit_block(n_catch)
        end
 end
 
@@ -321,74 +370,120 @@ 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
 
 redef class AForExpr
-       # The automatic variables in order
-       var variables: nullable Array[Variable]
+       # The break escape mark associated with the 'for'
+       var break_mark: nullable EscapeMark
 
-       # The escape mark associated with the 'for'
-       var escapemark: nullable EscapeMark
+       # The continue escape mark associated with the 'for'
+       var continue_mark: nullable EscapeMark
 
        redef fun accept_scope_visitor(v)
        do
-               v.enter_visit(n_expr)
+               for g in n_groups do
+                       v.enter_visit(g.n_expr)
+               end
 
                # Protect automatic variables
                v.scopes.unshift(new Scope)
 
-               # Create the automatic variables
-               var variables = new Array[Variable]
-               self.variables = variables
-               for nid in n_ids do
-                       var va = new Variable(nid.text)
-                       v.register_variable(nid, va)
-                       variables.add(va)
+               for g in n_groups do
+                       # Create the automatic variables
+                       var variables = new Array[Variable]
+                       g.variables = variables
+                       for nid in g.n_ids do
+                               var va = new Variable(nid.text)
+                               v.register_variable(nid, va)
+                               variables.add(va)
+                       end
                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.scopes.shift
+               v.shift_scope
+       end
+end
+
+redef class AForGroup
+       # The automatic variables in order
+       var variables: nullable Array[Variable]
+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.shift_scope
+       end
+end
+
+redef class AAssertExpr
+       redef fun accept_scope_visitor(v)
+       do
+               v.enter_visit(n_expr)
+               v.enter_visit_block(n_else, null)
        end
 end
 
 redef class AVarFormExpr
        # The associated variable
-       var variable: nullable Variable
+       var variable: nullable Variable is writable
 end
 
 redef class ACallFormExpr
        redef fun accept_scope_visitor(v)
        do
                if n_expr isa AImplicitSelfExpr then
-                       var name = n_id.text
+                       var name = n_qid.n_id.text
                        var variable = v.search_variable(name)
                        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.")
+                               if not n_args.n_exprs.is_empty or n_args isa AParExprs or self isa ACallrefExpr then
+                                       v.error(self, "Error: `{name}` is a variable, not a method.")
                                        return
                                end
                                n = variable_create(variable)
@@ -402,27 +497,29 @@ 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
-               return new AVarExpr.init_avarexpr(n_id)
+               variable.warn_unread = false
+               return new AVarExpr.init_avarexpr(n_qid.n_id)
        end
 end
 
 redef class ACallAssignExpr
        redef fun variable_create(variable)
        do
-               return new AVarAssignExpr.init_avarassignexpr(n_id, n_assign, n_value)
+               return new AVarAssignExpr.init_avarassignexpr(n_qid.n_id, n_assign, n_value)
        end
 end
 
 redef class ACallReassignExpr
        redef fun variable_create(variable)
        do
-               return new AVarReassignExpr.init_avarreassignexpr(n_id, n_assign_op, n_value)
+               variable.warn_unread = false
+               return new AVarReassignExpr.init_avarreassignexpr(n_qid.n_id, n_assign_op, n_value)
        end
 end