syntax: split the VariableContext into a Scope and a Flow context
[nit.git] / src / syntax / control_flow.nit
index 4ac5723..7661039 100644 (file)
@@ -19,12 +19,12 @@ package control_flow
 
 import syntax_base
 
-# Associate symbols to variable and variables to type
+# Associate symbols to variable
 # Can be nested
-abstract class VariableContext
+abstract class ScopeContext
        # Look for the variable from its name
        # Return null if nothing found
-       meth [](s: Symbol): Variable
+       fun [](s: Symbol): nullable Variable
        do
                if _dico.has_key(s) then
                        return _dico[s]
@@ -34,214 +34,344 @@ abstract class VariableContext
        end
 
        # Register a new variable with its name
-       meth add(v: Variable)
+       fun add(v: Variable)
        do
+               var old_var = self[v.name]
+               if old_var != null then
+                       _visitor.error(v.decl, "Error: '{v}' already defined at {old_var.decl.location.relative_to(v.decl.location)}.")
+               end
                _dico[v.name] = v
                _all_variables.add(v)
        end
 
-       meth mark_is_set(v: Variable)
+       # Build a new ScopeContext
+       fun sub(node: ANode): ScopeContext
+       do
+               return new SubScopeContext.with_prev(self, node)
+       end
+
+       # Variables by name (in the current context only)
+       var _dico: Map[Symbol, Variable] = new HashMap[Symbol, Variable]
+
+       # All variables in all contextes
+       var _all_variables: Set[Variable]
+
+       # The visitor of the context (used to display error)
+       var _visitor: AbsSyntaxVisitor
+
+       # The syntax node that introduced the context
+       readable var _node: ANode
+
+       init(visitor: AbsSyntaxVisitor, node: ANode)
+       do
+               _visitor = visitor
+               _node = node
+       end
+end
+
+# Root of a variable scope context hierarchy
+class RootScopeContext
+special ScopeContext
+       init(visitor: AbsSyntaxVisitor, node: ANode)
+       do
+               super(visitor, node)
+               _all_variables = new HashSet[Variable]
+       end
+end
+
+# Contexts that can see local variables of a prevous context
+# Local variables added to this context are not shared with the previous context
+class SubScopeContext
+special ScopeContext
+       readable var _prev: ScopeContext
+
+       redef fun [](s)
+       do
+               if _dico.has_key(s) then
+                       return _dico[s]
+               else
+                       return prev[s]
+               end
+       end
+
+       init with_prev(p: ScopeContext, node: ANode)
        do
-               _set_variables.add(v)
+               init(p._visitor, node)
+               _prev = p
+               _all_variables = p._all_variables
        end
+end
 
-       meth check_is_set(n: PNode, v: Variable)
+#################################################################
+
+# All-in-one context for flow control.
+# It features:
+# * reachability
+# * set/unset variable
+# * adaptive type of variable
+# FlowContextes are imutables, new contexts are created:
+# * as an empty root context
+# * as the adaptation of a existing context (see methods sub_*)
+# * as the merge of existing contexts
+abstract class FlowContext
+       # Display an error localised on `n' if the variable `v' is not set
+       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
-                               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
 
        # The effective static type of a given variable
        # May be different from the declaration static type
-       meth stype(v: Variable): MMType
+       fun stype(v: Variable): nullable MMType
        do
-               if _stypes.has_key(v) then
-                       return _stypes[v]
-               else
-                       return v.stype
-               end
+               return v.stype
        end
 
-       # Set effective static type of a given variable
-       # May be different from the declaration static type
-       meth stype=(v: Variable, t: MMType)
+       # Return a context where the variable is marked as set
+       fun sub_setvariable(v: Variable): FlowContext
        do
-               _stypes[v] = t
+               var ctx = new SubFlowContext.with_prev(self, node)
+               ctx._set_variables.add(v)
+               return ctx
        end
 
-       # Variables by name (in the current context only)
-       attr _dico: Map[Symbol, Variable]
-
-       # All variables in all contextes
-       attr _all_variables: Set[Variable]
+       # Return a context where unreash == true
+       fun sub_unreash(node: ANode): FlowContext
+       do
+               var ctx = new SubFlowContext.with_prev(self, node)
+               ctx._unreash = true
+               return ctx
+       end
 
-       # Updated static type of variables
-       attr _stypes: Map[Variable, MMType] = new HashMap[Variable, MMType]
+       # Return a context where v is casted as t
+       fun sub_with(node: ANode, v: Variable, t: MMType): FlowContext
+       do
+               return new CastFlowContext(self, node, v, t)
+       end
 
-       # Build a new VariableContext
-       meth sub(node: PNode): SubVariableContext
+       # Merge various alternative contexts (all must be reashable)
+       # Note that self can belong to alternatives
+       # Base is self
+       fun merge(node: ANode, alternatives: Array[FlowContext]): FlowContext
        do
-               return new SubVariableContext.with_prev(self, node)
+               for a in alternatives do assert not a.unreash
+               if alternatives.length == 1 then return alternatives.first
+               return new MergeFlowContext(self, node, alternatives)
        end
 
-       # Build a nested VariableContext with new variable information
-       meth sub_with(node: PNode, v: Variable, t: MMType): SubVariableContext
+       # Merge only context that are reachable
+       # Used for if/then/else merges
+       # Base is self
+       fun merge_reash(node: ANode, alt1, alt2: FlowContext): FlowContext
        do
-               var ctx = sub(node)
-               ctx.stype(v) = t
-               return ctx
+               if alt1.unreash then
+                       if alt2.unreash then
+                               return self.sub_unreash(node)
+                       else
+                               var t = alt2
+                               alt2 = alt1
+                               alt1 = t
+                       end
+               end
+
+               if alt2.unreash or alt1 == alt2 then
+                       return alt1
+                       #if alt1 == self then
+                       #       return self
+                       #else
+                       #       return merge(node, [alt1])
+                       #end
+               else
+                       return merge(node, [alt1, alt2])
+               end
        end
 
        # The visitor of the context (used to display error)
-       attr _visitor: AbsSyntaxVisitor
+       var _visitor: AbsSyntaxVisitor
 
        # The syntax node that introduced the context
-       readable attr _node: PNode
+       readable var _node: ANode
 
-       init(visitor: AbsSyntaxVisitor, node: PNode)
+       init(visitor: AbsSyntaxVisitor, node: ANode)
        do
                _visitor = visitor
                _node = node
-               _dico = new HashMap[Symbol, Variable]
        end
 
        # Is a control flow break met? (return, break, continue)
-       readable writable attr _unreash: Bool = false
+       readable var _unreash: Bool = false
 
        # Is a control flow already broken?
        # Used to avoid repeating the same error message
-       readable writable attr _already_unreash: Bool = false
+       readable writable var _already_unreash: Bool = false
 
        # Set of variable that are set (assigned)
-       readable attr _set_variables: HashSet[Variable] = new HashSet[Variable]
+       readable var _set_variables: HashSet[Variable] = new HashSet[Variable]
 
        # Is a variable set?
-       meth is_set(v: Variable): Bool
+       fun is_set(v: Variable): Bool
        do
                return _set_variables.has(v)
        end
+end
 
-       # Merge back one flow context information
-       meth merge(ctx: VariableContext)
+# Root of a variable context hierarchy
+class RootFlowContext
+special FlowContext
+       init(visitor: AbsSyntaxVisitor, node: ANode)
        do
-               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
+               super(visitor, node)
        end
+end
 
-       # Merge back two alternative flow context informations
-       meth merge2(ctx1, ctx2, basectx: VariableContext)
-       do
-               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
+# Contexts that are an evolution of a single previous context
+class SubFlowContext
+special FlowContext
+       readable var _prev: FlowContext
 
-                       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 s1 < s2 then
-                               stype(v) = s2
-                       else if s2 < s1 then
-                               stype(v) = s1
-                       else
-                               stype(v) = basectx.stype(v)
-                       end
-               end
+       redef fun is_set(v)
+       do
+               return _set_variables.has(v) or _prev.is_set(v)
        end
 
-       redef meth to_s
+       redef fun stype(v)
        do
-               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
-               return s.to_s
+               return prev.stype(v)
        end
-end
 
-class RootVariableContext
-special VariableContext
-       init(visitor: AbsSyntaxVisitor, node: PNode)
+       init with_prev(p: FlowContext, node: ANode)
        do
-               super(visitor, node)
-               _all_variables = new HashSet[Variable]
+               init(p._visitor, node)
+               _prev = p
        end
 end
 
-class SubVariableContext
-special VariableContext
-       readable attr _prev: VariableContext
+# A variable context where a variable got a type adptation
+class CastFlowContext
+special SubFlowContext
+       # The casted variable
+       var _variable: Variable
 
-       redef meth [](s)
+       # The new static type of the variable
+       var _stype: nullable MMType
+
+       redef fun stype(v)
        do
-               if _dico.has_key(s) then
-                       return _dico[s]
+               if v == _variable then
+                       return _stype
                else
-                       return prev[s]
+                       return prev.stype(v)
                end
        end
 
-       redef meth stype(v)
+       init(p: FlowContext, node: ANode, v: Variable, s: nullable MMType)
+       do
+               with_prev(p, node)
+               _variable = v
+               _stype = s
+       end
+end
+
+# Context that resulting from the combinaisons of other contexts.
+# Most of the merge computation are done lasily.
+class MergeFlowContext
+special FlowContext
+       var _base: FlowContext
+       var _alts: Array[FlowContext]
+
+       # Updated static type of variables
+       var _stypes: Map[Variable, nullable MMType] = new HashMap[Variable, nullable MMType]
+
+       init(base: FlowContext, node: ANode, alts: Array[FlowContext])
+       do
+               super(base._visitor, node)
+               _alts = alts
+               _base = base
+       end
+
+       redef fun stype(v)
        do
                if _stypes.has_key(v) then
                        return _stypes[v]
                else
-                       return prev.stype(v)
+                       var s = merge_stype(v)
+                       _stypes[v] = s
+                       return s
                end
        end
 
-       init with_prev(p: VariableContext, node: PNode)
+       private fun merge_stype(v: Variable): nullable MMType
        do
-               init(p._visitor, node)
-               _prev = p
-               _all_variables = p._all_variables
+               var candidate: nullable MMType = null
+               var is_nullable = false
+               var same_candidate: nullable MMType = _alts.first.stype(v)
+               for ctx in _alts do
+                       var t = ctx.stype(v)
+                       if t == null then
+                               return null
+                       end
+                       if t != same_candidate then
+                               same_candidate = null
+                       end
+                       if t isa MMTypeNone then
+                               is_nullable = true
+                               continue
+                       end
+                       if t isa MMNullableType then
+                               is_nullable = true
+                               t = t.as_notnull
+                       end
+                       if candidate == null or candidate < t then
+                               candidate = t
+                       end
+               end
+               if same_candidate != null then
+                       return same_candidate
+               end
+               if is_nullable then
+                       if candidate == null then
+                               return _visitor.type_none
+                       else
+                               candidate = candidate.as_nullable
+                       end
+               end
+               if candidate == null then
+                       return _base.stype(v)
+               else
+                       for ctx in _alts do
+                               var t = ctx.stype(v)
+                               if not t < candidate then
+                                       return _base.stype(v)
+                               end
+                       end
+               end
+               return candidate
        end
 
-       redef meth is_set(v)
+       redef fun is_set(v)
        do
-               return _set_variables.has(v) or _prev.is_set(v)
+               if _set_variables.has(v) then
+                       return true
+               else
+                       for ctx in _alts do
+                               if not ctx.is_set(v) then
+                                       return false
+                               end
+                       end
+                       _set_variables.add(v)
+                       return true
+               end
        end
 end
 
+
 redef class Variable
        # Is the variable must be set before being used ?
-       meth must_be_set: Bool do return false
+       fun must_be_set: Bool do return false
 end
 
 redef class VarVariable
-       redef meth must_be_set do return true
+       redef fun must_be_set do return true
 end