# 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
-# 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
+ fun mark_is_set(v: Variable)
+ do
+ _set_variables.add(v)
+ end
- # Current knowledge about variables types
- readable writable attr _control_flow_ctx: ControlFlowContext
+ fun check_is_set(n: ANode, v: Variable)
+ do
+ 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
+ var loc = x.node.location
+ print " {if loc != null then loc.to_s else "????"}: {x._set_variables.join(", ")} ; {x._dico.join(", ")}"
+ var x0 = x
+ if x0 isa SubVariableContext then
+ x = x0.prev
+ else
+ break
+ end
+ end
+ end
+ end
- init(tc, m) do super
-end
+ # The effective static type of a given variable
+ # May be different from the declaration static type
+ fun stype(v: Variable): nullable MMType
+ do
+ if _stypes.has_key(v) then
+ return _stypes[v]
+ else
+ return v.stype
+ end
+ end
-private class ControlFlowContext
- # Previous control flow context if any
- readable attr _prev: ControlFlowContext
+ # 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 return met?
- readable writable attr _has_return: Bool
+ # Variables by name (in the current context only)
+ var _dico: Map[Symbol, Variable]
- # Is a control flow break met? (return, break, continue)
- readable writable attr _unreash: Bool
+ # All variables in all contextes
+ var _all_variables: Set[Variable]
- # Is a control flow already broken?
- # Used to avoid repeating the same error message
- readable writable attr _already_unreash: Bool
+ # Updated static type of variables
+ var _stypes: Map[Variable, nullable MMType] = new HashMap[Variable, nullable MMType]
- # Current controlable block (for or while)
- readable writable attr _base_block: AControlableBlock
-
- meth sub: ControlFlowContext
+ # Build a new VariableContext
+ fun sub(node: ANode): SubVariableContext
do
- return new ControlFlowContext.with(self)
- end
-
- init
- do
+ return new SubVariableContext.with_prev(self, node)
end
- init with(p: ControlFlowContext)
+ # Build a nested VariableContext with new variable information
+ fun sub_with(node: ANode, v: Variable, t: MMType): SubVariableContext
do
- _prev = p
- _has_return = p.has_return
- _unreash = p.unreash
- _already_unreash = p.already_unreash
- _base_block = p.base_block
+ var ctx = sub(node)
+ ctx.stype(v) = t
+ return ctx
end
-end
-###############################################################################
+ # The visitor of the context (used to display error)
+ var _visitor: AbsSyntaxVisitor
-redef class PNode
- private meth accept_control_flow(v: ControlFlowVisitor)
+ # The syntax node that introduced the context
+ readable var _node: ANode
+
+ init(visitor: AbsSyntaxVisitor, node: ANode)
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)
+ # Is a control flow break met? (return, break, continue)
+ readable writable var _unreash: Bool = false
+
+ # 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]
+
+ # Is a variable set?
+ fun is_set(v: Variable): Bool
do
- v.control_flow_ctx = new ControlFlowContext
- super
+ return _set_variables.has(v)
end
-end
-redef class AConcreteMethPropdef
- redef meth accept_control_flow(v)
+ # Merge back one flow context information
+ fun merge(ctx: VariableContext)
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.")
+ 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
+ var s = stype(v)
+ var s1 = ctx.stype(v)
+ if s1 != s then stype(v) = s1
end
end
-end
-redef class ABlockExpr
- redef meth accept_control_flow(v)
+ # Merge back two alternative flow context informations
+ fun merge2(ctx1, ctx2, basectx: 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 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
+
+ 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
- v.visit(e)
end
end
-end
-redef class AReturnExpr
- redef meth accept_control_flow(v)
+ redef fun to_s
do
- super
- v.control_flow_ctx.has_return = true
- v.control_flow_ctx.unreash = true
+ var s = new Buffer
+ s.append(node.location.to_s)
+ for v in _all_variables do
+ var t = stype(v)
+ if t == null then continue
+ s.append(" {v}:{t}")
+ end
+ return s.to_s
end
end
-class ABlockControler
-special PExpr
- readable attr _block: AControlableBlock
-end
-
-redef class ABreakExpr
-special ABlockControler
- redef meth accept_control_flow(v)
+class RootVariableContext
+special VariableContext
+ init(visitor: AbsSyntaxVisitor, node: ANode)
do
- super
- var block = v.control_flow_ctx.base_block
- if block == null then
- v.error(self, "Syntax Error: 'break' statment outside block.")
- return
- end
- _block = block
- v.control_flow_ctx.unreash = true
- end
-end
-redef class AContinueExpr
-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: 'continue' outside block.")
- return
- end
- _block = block
- v.control_flow_ctx.unreash = true
+ super(visitor, node)
+ _all_variables = new HashSet[Variable]
end
end
-redef class AAbortExpr
- redef meth accept_control_flow(v)
+class SubVariableContext
+special VariableContext
+ readable var _prev: VariableContext
+
+ redef fun [](s)
do
- super
- v.control_flow_ctx.has_return = true
- v.control_flow_ctx.unreash = true
+ if _dico.has_key(s) then
+ return _dico[s]
+ else
+ return prev[s]
+ end
end
-end
-redef class AIfExpr
- redef meth accept_control_flow(v)
+ redef fun stype(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)
-
- if n_else == null then
- # Restore control flow ctx
- v.control_flow_ctx = old_control_flow_ctx
+ if _stypes.has_key(v) then
+ return _stypes[v]
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
-
- v.visit(n_else)
-
- # Restore and conclude
- v.control_flow_ctx = old_control_flow_ctx
- v.control_flow_ctx.has_return = v.control_flow_ctx.has_return and then_control_flow_ctx.has_return
- v.control_flow_ctx.unreash = v.control_flow_ctx.unreash and then_control_flow_ctx.unreash
+ return prev.stype(v)
end
end
-end
-class AControlableBlock
-special PExpr
- redef meth accept_control_flow(v)
+ init with_prev(p: VariableContext, node: ANode)
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
- v.control_flow_ctx = old_control_flow_ctx
+ init(p._visitor, node)
+ _prev = p
+ _all_variables = p._all_variables
end
-end
-redef class AWhileExpr
-special AControlableBlock
+ redef fun is_set(v)
+ do
+ return _set_variables.has(v) or _prev.is_set(v)
+ end
end
-redef class AForExpr
-special AControlableBlock
+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
-