syntax: 'meth' -> 'fun', 'attr' -> 'var'
[nit.git] / src / syntax / control_flow.nit
index 0891fe2..44d1270 100644 (file)
@@ -1,6 +1,6 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
-# Copyright 2008 Jean Privat <jean@pryen.org>
+# Copyright 2008-2009 Jean Privat <jean@pryen.org>
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Analysis control flow in property bodies, statements and expressions
+# Analysis control flow and variable visibility in property bodies, statements and expressions
 package control_flow
 
 import syntax_base
 
-redef class MMSrcModule
-       # Walk trough the module and type statments and expressions
-       # Require than supermodules are processed
-       meth do_control_flow(tc: ToolContext)
+# Associate symbols to variable and variables to type
+# Can be nested
+abstract class VariableContext
+       # Look for the variable from its name
+       # Return null if nothing found
+       fun [](s: Symbol): nullable Variable
        do
-               var tv = new ControlFlowVisitor(tc, self)
-               tv.visit(node)
+               if _dico.has_key(s) then
+                       return _dico[s]
+               else
+                       return null
+               end
        end
-end
-
-redef class Variable
-       # Is the variable must be set before being used ?
-       meth must_be_set: Bool do return false
-end
-
-redef class VarVariable
-       redef meth must_be_set do return true
-end
-
-
 
-# Control flow visitor
-# * Check reachability in methods
-# * Associate breaks and continues
-# * Check some other warning
-private class ControlFlowVisitor
-special AbsSyntaxVisitor
-       redef meth visit(n)
+       # Register a new variable with its name
+       fun add(v: Variable)
        do
-               if n != null then n.accept_control_flow(self)
+               _dico[v.name] = v
+               _all_variables.add(v)
        end
 
-       # Number of nested once
-       readable writable attr _once_count: Int
-
-       # Current knowledge about variables types
-       readable writable attr _control_flow_ctx: ControlFlowContext
+       fun mark_is_set(v: Variable)
+       do
+               _set_variables.add(v)
+       end
 
-       meth check_is_set(n: PNode, v: Variable)
+       fun check_is_set(n: PNode, v: Variable)
        do
-               if v.must_be_set and not control_flow_ctx.is_set(v) then
-                       error(n, "Error: variable '{v}' is possibly unset.")
-                       var cfc = control_flow_ctx
-                       while cfc != null do
-                               print("cfc: " + cfc.set_variables.join(" "))
-                               cfc = cfc.prev
+               if v.must_be_set and not is_set(v) then
+                       _visitor.error(n, "Error: variable '{v}' is possibly unset.")
+                       var x = self
+                       while true do
+                               print "  {x.node.locate}: {x._set_variables.join(", ")} ; {x._dico.join(", ")}"
+                               var x0 = x
+                               if x0 isa SubVariableContext then
+                                       x = x0.prev
+                               else
+                                       break
+                               end
                        end
                end
        end
 
-       meth mark_is_set(v: Variable)
+       # The effective static type of a given variable
+       # May be different from the declaration static type
+       fun stype(v: Variable): nullable MMType
        do
-               control_flow_ctx.set_variables.add(v)
+               if _stypes.has_key(v) then
+                       return _stypes[v]
+               else
+                       return v.stype
+               end
        end
 
-       init(tc, m) do super
-end
-
-private class ControlFlowContext
-       # Previous control flow context if any
-       readable attr _prev: ControlFlowContext
-
-       # Is a return met?
-       readable writable attr _has_return: Bool 
-
-       # Is a control flow break met? (return, break, continue)
-       readable writable attr _unreash: Bool
+       # Set effective static type of a given variable
+       # May be different from the declaration static type
+       fun stype=(v: Variable, t: nullable MMType)
+       do
+               _stypes[v] = t
+       end
 
-       # Is a control flow already broken?
-       # Used to avoid repeating the same error message
-       readable writable attr _already_unreash: Bool
+       # Variables by name (in the current context only)
+       var _dico: Map[Symbol, Variable]
 
-       # Current controlable block (for or while)
-       readable writable attr _base_block: AControlableBlock
+       # All variables in all contextes
+       var _all_variables: Set[Variable]
 
-       # Set of variable that are set (assigned)
-       readable attr _set_variables: HashSet[Variable] = new HashSet[Variable]
+       # Updated static type of variables
+       var _stypes: Map[Variable, nullable MMType] = new HashMap[Variable, nullable MMType]
 
-       # Is a variable set?
-       meth is_set(v: Variable): Bool
+       # Build a new VariableContext
+       fun sub(node: PNode): SubVariableContext
        do
-               return _set_variables.has(v) or (_prev != null and _prev.is_set(v))
+               return new SubVariableContext.with_prev(self, node)
        end
 
-       meth sub: ControlFlowContext
+       # Build a nested VariableContext with new variable information
+       fun sub_with(node: PNode, v: Variable, t: MMType): SubVariableContext
        do
-               return new ControlFlowContext.with_prev(self)
-       end
-
-       init
-       do 
+               var ctx = sub(node)
+               ctx.stype(v) = t
+               return ctx
        end
 
-       init with_prev(p: ControlFlowContext)
-       do
-               _prev = p
-               _has_return = p.has_return
-               _unreash = p.unreash
-               _already_unreash = p.already_unreash
-               _base_block = p.base_block
-       end
-end
+       # The visitor of the context (used to display error)
+       var _visitor: AbsSyntaxVisitor
 
-###############################################################################
+       # The syntax node that introduced the context
+       readable var _node: PNode
 
-redef class PNode
-       private meth accept_control_flow(v: ControlFlowVisitor) 
+       init(visitor: AbsSyntaxVisitor, node: PNode)
        do
-               accept_abs_syntax_visitor(v)
+               _visitor = visitor
+               _node = node
+               _dico = new HashMap[Symbol, Variable]
        end
-end
 
-redef class AMethPropdef
-       redef meth accept_control_flow(v)
-       do
-               v.control_flow_ctx = new ControlFlowContext
-               super
-       end
-end
+       # Is a control flow break met? (return, break, continue)
+       readable writable var _unreash: Bool = false
 
-redef class AConcreteMethPropdef
-       redef meth accept_control_flow(v)
-       do
-               super
-               if v.control_flow_ctx.has_return == false and method.signature.return_type != null then
-                       v.error(self, "Control error: Reached end of function.")
-               end
-       end
-end
+       # Is a control flow already broken?
+       # Used to avoid repeating the same error message
+       readable writable var _already_unreash: Bool = false
+
+       # Set of variable that are set (assigned)
+       readable var _set_variables: HashSet[Variable] = new HashSet[Variable]
 
-redef class AVardeclExpr
-       redef meth accept_control_flow(v)
+       # Is a variable set?
+       fun is_set(v: Variable): Bool
        do
-               super
-               if n_expr != null then v.mark_is_set(variable)
+               return _set_variables.has(v)
        end
-end
 
-redef class ABlockExpr
-       redef meth accept_control_flow(v)
+       # Merge back one flow context information
+       fun merge(ctx: VariableContext)
        do
-               for e in n_expr do
-                       if v.control_flow_ctx.unreash and not v.control_flow_ctx.already_unreash then
-                               v.control_flow_ctx.already_unreash = true
-                               v.warning(e, "Warning: unreachable statement.")
+               if ctx.unreash then
+                       unreash = true
+                       if ctx.already_unreash then already_unreash = true
+                       return
+               end
+               for v in _all_variables do
+                       if not is_set(v) and ctx.is_set(v) then
+                               mark_is_set(v)
                        end
-                       v.visit(e)
+                       var s = stype(v)
+                       var s1 = ctx.stype(v)
+                       if s1 != s then stype(v) = s1
                end
        end
-end
 
-redef class AReturnExpr
-       redef meth accept_control_flow(v)
+       # Merge back two alternative flow context informations
+       fun merge2(ctx1, ctx2, basectx: VariableContext)
        do
-               super
-               v.control_flow_ctx.has_return = true
-               v.control_flow_ctx.unreash = true
-       end
-end
-
-class ABlockControler
-special PExpr
-       readable attr _block: AControlableBlock
-end
+               if ctx1.unreash then
+                       merge(ctx2)
+               else if ctx2.unreash then
+                       merge(ctx1)
+               end
+               for v in _all_variables do
+                       if not is_set(v) and ctx1.is_set(v) and ctx2.is_set(v) then
+                               mark_is_set(v)
+                       end
 
-redef class ABreakExpr
-special ABlockControler
-       redef meth accept_control_flow(v)
-       do
-               super
-               var block = v.control_flow_ctx.base_block
-               if block == null then
-                       v.error(self, "Syntax Error: 'break' statment outside block.")
-                       return
+                       var s = stype(v)
+                       var s1 = ctx1.stype(v)
+                       var s2 = ctx2.stype(v)
+                       if s1 == s and s2 == s then
+                               # NOP
+                       else if s1 == s2 then
+                               stype(v) = s1
+                       else if s2 == null or s1 < s2 then
+                               stype(v) = s2
+                       else if s1 == null or s2 < s1 then
+                               stype(v) = s1
+                       else
+                               stype(v) = basectx.stype(v)
+                       end
                end
-               _block = block
-               v.control_flow_ctx.unreash = true
        end
-end
-redef class AContinueExpr
-special ABlockControler
-       redef meth accept_control_flow(v)
+
+       redef fun to_s
        do
-               super
-               var block = v.control_flow_ctx.base_block
-               if block == null then
-                       v.error(self, "Syntax Error: 'continue' outside block.")
-                       return
+               var s = new Buffer
+               s.append(node.locate)
+               for v in _all_variables do
+                       var t = stype(v)
+                       if t == null then continue
+                       s.append(" {v}:{t}")
                end
-               _block = block
-               v.control_flow_ctx.unreash = true
+               return s.to_s
        end
 end
 
-redef class AAbortExpr
-       redef meth accept_control_flow(v)
+class RootVariableContext
+special VariableContext
+       init(visitor: AbsSyntaxVisitor, node: PNode)
        do
-               super
-               v.control_flow_ctx.has_return = true
-               v.control_flow_ctx.unreash = true
+               super(visitor, node)
+               _all_variables = new HashSet[Variable]
        end
 end
 
-redef class AIfExpr
-       redef meth accept_control_flow(v)
-       do
-               v.visit(n_expr)
-
-               var old_control_flow_ctx = v.control_flow_ctx
-               v.control_flow_ctx = v.control_flow_ctx.sub
-
-               v.visit(n_then)
+class SubVariableContext
+special VariableContext
+       readable var _prev: VariableContext
 
-               if n_else == null then
-                       # Restore control flow ctx since the 'then" block is optional
-                       v.control_flow_ctx = old_control_flow_ctx
+       redef fun [](s)
+       do
+               if _dico.has_key(s) then
+                       return _dico[s]
                else
-                       # Remember what appens in the 'then'
-                       var then_control_flow_ctx = v.control_flow_ctx
-                       # Reset to execute the 'else'
-                       v.control_flow_ctx = old_control_flow_ctx.sub
-
-                       v.visit(n_else)
-
-                       # Merge then and else in the old control_flow
-                       old_control_flow_ctx.has_return = v.control_flow_ctx.has_return and then_control_flow_ctx.has_return
-                       old_control_flow_ctx.unreash = v.control_flow_ctx.unreash and then_control_flow_ctx.unreash
-
-                       if v.control_flow_ctx.unreash then v.control_flow_ctx = then_control_flow_ctx
-                       if then_control_flow_ctx.unreash then then_control_flow_ctx = v.control_flow_ctx
-                       for variable in v.control_flow_ctx.set_variables do
-                               if then_control_flow_ctx.is_set(variable) then
-                                       old_control_flow_ctx.set_variables.add(variable)
-                               end
-                       end
-                       v.control_flow_ctx = old_control_flow_ctx
+                       return prev[s]
                end
        end
-end
 
-class AControlableBlock
-special PExpr
-       redef meth accept_control_flow(v)
+       redef fun stype(v)
        do
-               # Store old control flow values
-               var old_control_flow_ctx = v.control_flow_ctx
-               v.control_flow_ctx = v.control_flow_ctx.sub
-
-               # Register the block
-               v.control_flow_ctx.base_block = self
-
-               super
-
-               # Restore control flow value since all controlable blocks are optionnal
-               v.control_flow_ctx = old_control_flow_ctx
+               if _stypes.has_key(v) then
+                       return _stypes[v]
+               else
+                       return prev.stype(v)
+               end
        end
-end
 
-redef class AWhileExpr
-special AControlableBlock
-end
-
-redef class AForExpr
-special AControlableBlock
-end
-
-redef class AVarExpr
-       redef meth accept_control_flow(v)
+       init with_prev(p: VariableContext, node: PNode)
        do
-               super
-               v.check_is_set(self, variable)
+               init(p._visitor, node)
+               _prev = p
+               _all_variables = p._all_variables
        end
-end
 
-redef class AVarAssignExpr
-       redef meth accept_control_flow(v)
+       redef fun is_set(v)
        do
-               super
-               v.mark_is_set(variable)
+               return _set_variables.has(v) or _prev.is_set(v)
        end
 end
 
-redef class AVarReassignExpr
-       redef meth accept_control_flow(v)
-       do
-               super
-               v.check_is_set(self, variable)
-               v.mark_is_set(variable)
-       end
+redef class Variable
+       # Is the variable must be set before being used ?
+       fun must_be_set: Bool do return false
 end
 
-
-redef class AOnceExpr
-       redef meth accept_control_flow(v)
-       do
-               if v.once_count > 0 then
-                       v.warning(self, "Useless once in a once expression.")
-               end
-               v.once_count = v.once_count + 1
-
-               super
-
-               v.once_count = v.once_count - 1
-       end
+redef class VarVariable
+       redef fun must_be_set do return true
 end
-