syntax: remove VariableContex::stype=
authorJean Privat <jean@pryen.org>
Fri, 15 Jan 2010 19:43:53 +0000 (14:43 -0500)
committerJean Privat <jean@pryen.org>
Fri, 15 Jan 2010 19:43:53 +0000 (14:43 -0500)
Make VariableContext less mutable thus simplify merging code.

Fix also a type_evolution test that were wrong.

Signed-off-by: Jean Privat <jean@pryen.org>

src/syntax/control_flow.nit
src/syntax/typing.nit
tests/sav/base_var_type_evolution_null4_alt2.sav

index fc86679..79c8c01 100644 (file)
@@ -60,18 +60,7 @@ abstract class VariableContext
        # 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
-
-       # 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
+               return v.stype
        end
 
        # Variables by name (in the current context only)
@@ -80,21 +69,50 @@ abstract class VariableContext
        # All variables in all contextes
        var _all_variables: Set[Variable]
 
-       # Updated static type of variables
-       var _stypes: Map[Variable, nullable MMType] = new HashMap[Variable, nullable MMType]
-
        # Build a new VariableContext
-       fun sub(node: ANode): SubVariableContext
+       fun sub(node: ANode): VariableContext
        do
                return new SubVariableContext.with_prev(self, node)
        end
 
        # Build a nested VariableContext with new variable information
-       fun sub_with(node: ANode, v: Variable, t: MMType): SubVariableContext
+       fun sub_with(node: ANode, v: Variable, t: MMType): VariableContext
+       do
+               return new CastVariableContext(self, node, v, t)
+       end
+
+       # Merge various alternative contexts
+       # Note that self can belong to alternatives
+       fun merge(node: ANode, alternatives: Array[VariableContext], base: VariableContext): VariableContext
        do
-               var ctx = sub(node)
-               ctx.stype(v) = t
-               return ctx
+               return new MergeVariableContext(self, node, alternatives, base)
+       end
+
+       # Merge only context that are reachable
+       # Used for if/then/else merges
+       # Base is self
+       fun merge_reash(node: ANode, alt1, alt2: VariableContext, base: VariableContext): VariableContext
+       do
+               if alt1.unreash then
+                       if alt2.unreash then
+                               unreash = true
+                               return self
+                       else
+                               var t = alt2
+                               alt2 = alt1
+                               alt1 = t
+                       end
+               end
+
+               if alt2.unreash or alt1 == alt2 then
+                       if alt1 == self then
+                               return self
+                       else
+                               return merge(node, [alt1], base)
+                       end
+               else
+                       return merge(node, [alt1, alt2], base)
+               end
        end
 
        # The visitor of the context (used to display error)
@@ -126,148 +144,6 @@ abstract class VariableContext
                return _set_variables.has(v)
        end
 
-       # Merge back one flow context information
-       fun merge(ctx: VariableContext)
-       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
-       end
-
-       # Merge back two alternative flow context informations
-       fun merge2(ctx1, ctx2, basectx: VariableContext)
-       do
-               if ctx1.unreash then
-                       merge(ctx2)
-                       return
-               else if ctx2.unreash then
-                       merge(ctx1)
-                       return
-               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 == null or s2 == null then
-                               stype(v) = null
-                       else
-                               var sm = merge_types(s1, s2)
-                               if sm == null then
-                                       stype(v) = basectx.stype(v)
-                               else
-                                       stype(v) = sm
-                               end
-                       end
-               end
-       end
-
-       # Combine informations of ctx to the current flow context informations as an alternative
-       # Require that all contexts are reachable at the end
-       fun combine_merge(ctxs: Array[VariableContext], basectx: VariableContext)
-       do
-               for v in _all_variables do
-                       do
-                               if not is_set(v) then
-                                       for ctx in ctxs do
-                                               if not ctx.is_set(v) then break label set
-                                       end
-                                       mark_is_set(v)
-                               end
-                       end label set
-
-                       var candidate: nullable MMType = null
-                       var is_nullable = false
-                       var same_candidate: nullable MMType = ctxs.first.stype(v)
-                       for ctx in ctxs do
-                               var t = ctx.stype(v)
-                               if t == null then
-                                       stype(v) = null
-                                       continue label each_variable
-                               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
-                               stype(v) = same_candidate
-                       end
-                       if is_nullable then
-                               if candidate == null then
-                                       candidate = _visitor.type_none
-                               else
-                                       candidate = candidate.as_nullable
-                               end
-                       end
-                       if candidate == null then
-                               stype(v) = basectx.stype(v)
-                       else
-                               for ctx in ctxs do
-                                       var t = ctx.stype(v)
-                                       if not t < candidate then
-                                               stype(v) = basectx.stype(v)
-                                               continue label each_variable
-                                       end
-                               end
-                       end
-                       stype(v) = candidate
-               end label each_variable
-       end
-
-       # Combine and get the most specific comon supertype
-       # return null if no comon supertype is found
-       private fun merge_types(t1, t2: MMType): nullable MMType
-       do
-               if t1 == t2 then return t1
-               if t1 isa MMTypeNone then return t2.as_nullable
-               if t2 isa MMTypeNone then return t1.as_nullable
-               var is_nullable = false
-               if t1.is_nullable then
-                       is_nullable = true
-                       t1 = t1.as_notnull
-               end
-               if t2.is_nullable then
-                       is_nullable = true
-                       t2 = t2.as_notnull
-               end
-               var t: MMType
-               if t1 < t2 then
-                       t = t2
-               else if t2 < t1 then
-                       t = t1
-               else
-                       return null
-               end
-               if is_nullable then t = t.as_nullable
-               return t
-       end
-
        redef fun to_s
        do
                var s = new Buffer
@@ -279,8 +155,11 @@ abstract class VariableContext
                end
                return s.to_s
        end
+
+       private fun is_in(ctx: VariableContext): Bool = self == ctx
 end
 
+# Root of a variable context hierarchy
 class RootVariableContext
 special VariableContext
        init(visitor: AbsSyntaxVisitor, node: ANode)
@@ -290,10 +169,17 @@ special VariableContext
        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 SubVariableContext
 special VariableContext
        readable var _prev: VariableContext
 
+       redef fun is_in(ctx)
+       do
+               return ctx == self or _prev.is_in(ctx)
+       end
+
        redef fun [](s)
        do
                if _dico.has_key(s) then
@@ -303,13 +189,14 @@ special VariableContext
                end
        end
 
+       redef fun is_set(v)
+       do
+               return _set_variables.has(v) or _prev.is_set(v)
+       end
+
        redef fun stype(v)
        do
-               if _stypes.has_key(v) then
-                       return _stypes[v]
-               else
-                       return prev.stype(v)
-               end
+               return prev.stype(v)
        end
 
        init with_prev(p: VariableContext, node: ANode)
@@ -318,13 +205,130 @@ special VariableContext
                _prev = p
                _all_variables = p._all_variables
        end
+end
+
+# A variable context where a variable got a type evolution
+class CastVariableContext
+special SubVariableContext
+       # The casted variable
+       var _variable: Variable
+
+       # The new static type of the variable
+       var _stype: nullable MMType
+
+       redef fun stype(v)
+       do
+               if v == _variable then
+                       return _stype
+               else
+                       return prev.stype(v)
+               end
+       end
+
+       init(p: VariableContext, node: ANode, v: Variable, s: nullable MMType)
+       do
+               with_prev(p, node)
+               _variable = v
+               _stype = s
+       end
+end
+
+# Context that follows a previous context but where
+# Variable current static type and variable is_set depends on the combinaison of other contexts
+class MergeVariableContext
+special SubVariableContext
+       var _base: VariableContext
+       var _alts: Array[VariableContext]
+
+       # Updated static type of variables
+       var _stypes: Map[Variable, nullable MMType] = new HashMap[Variable, nullable MMType]
+
+       init(prev: VariableContext, node: ANode, alts: Array[VariableContext], base: VariableContext)
+       do
+               assert prev.is_in(base) else print "{node.location}: Error: prev {prev.node.location} is not in base {base.node.location}"
+               for a in alts do assert a.is_in(prev) else print "{node.location}: Error: alternative {a.node.location} is not in prev {prev.node.location}"
+               with_prev(prev, node)
+               _alts = alts
+               _base = base
+       end
+
+       redef fun stype(v)
+       do
+               if _stypes.has_key(v) then
+                       return _stypes[v]
+               else
+                       var s = merge_stype(v)
+                       _stypes[v] = s
+                       return s
+               end
+       end
+
+       private fun merge_stype(v: Variable): nullable MMType
+       do
+               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 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
+                                       print "{node.location}: is_set({v}) ? false : because not set in {ctx.node.location}"
+                                       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 ?
        fun must_be_set: Bool do return false
index d4d664c..618ace9 100644 (file)
@@ -293,7 +293,6 @@ redef class AClosureDecl
                        v.check_conform_expr(x, t)
                end
 
-               old_var_ctx.merge(v.variable_ctx)
                v.variable_ctx = old_var_ctx
                v.base_variable_ctx = old_base_var_ctx
                v.escapable_ctx.pop
@@ -393,8 +392,7 @@ redef class ABlockExpr
                        end
                end
 
-               old_var_ctx.merge(v.variable_ctx)
-               v.variable_ctx = old_var_ctx
+               v.variable_ctx = old_var_ctx.merge_reash(self, v.variable_ctx, v.variable_ctx, v.base_variable_ctx)
                _is_typed = true
        end
 end
@@ -518,8 +516,7 @@ redef class AIfExpr
                end
 
                # Merge 'then' and 'else' contexts
-               old_var_ctx.merge2(then_var_ctx, v.variable_ctx, v.base_variable_ctx)
-               v.variable_ctx = old_var_ctx
+               v.variable_ctx = old_var_ctx.merge_reash(self, then_var_ctx, v.variable_ctx, v.base_variable_ctx)
                _is_typed = true
        end
 end
@@ -536,7 +533,6 @@ redef class AWhileExpr
                var old_var_ctx = v.variable_ctx
                var old_base_var_ctx = v.base_variable_ctx
                v.base_variable_ctx = v.variable_ctx
-               v.variable_ctx = v.variable_ctx.sub(self)
 
                # Process condition
                v.enter_visit(n_expr)
@@ -555,12 +551,11 @@ redef class AWhileExpr
                        v.enter_visit(n_block)
                end
 
-               v.variable_ctx = old_var_ctx
-
                # Compute outside context (assert !cond + all breaks)
+               v.variable_ctx = old_var_ctx
                v.use_if_false_variable_ctx(n_expr)
                escapable.break_variable_contexts.add(v.variable_ctx)
-               old_var_ctx.combine_merge(escapable.break_variable_contexts, v.base_variable_ctx)
+               v.variable_ctx = old_var_ctx.merge(self, escapable.break_variable_contexts, v.base_variable_ctx)
 
                v.base_variable_ctx = old_base_var_ctx
                v.escapable_ctx.pop
@@ -580,7 +575,6 @@ redef class ALoopExpr
                var old_var_ctx = v.variable_ctx
                var old_base_var_ctx = v.base_variable_ctx
                v.base_variable_ctx = v.variable_ctx
-               v.variable_ctx = v.variable_ctx.sub(self)
 
                # Process inside
                if n_block != null then
@@ -591,11 +585,11 @@ redef class ALoopExpr
                # Compute outside context (assert all breaks)
                if escapable.break_variable_contexts.is_empty then
                        old_var_ctx.unreash = true
+                       v.variable_ctx = old_var_ctx
                else
-                       old_var_ctx.combine_merge(escapable.break_variable_contexts, v.base_variable_ctx)
+                       v.variable_ctx = old_var_ctx.merge(self, escapable.break_variable_contexts, v.base_variable_ctx)
                end
 
-               v.variable_ctx = old_var_ctx
                v.base_variable_ctx = old_base_var_ctx
                v.escapable_ctx.pop
                _is_typed = true
@@ -626,7 +620,12 @@ redef class AForExpr
                # Process collection
                v.enter_visit(n_expr)
 
-               if not v.check_conform_expr(n_expr, v.type_collection) then return
+               if not v.check_conform_expr(n_expr, v.type_collection) then
+                       v.variable_ctx = old_var_ctx
+                       v.base_variable_ctx = old_base_var_ctx
+                       v.escapable_ctx.pop
+                       return
+               end
                var expr_type = n_expr.stype
 
                # Get iterator
@@ -696,7 +695,7 @@ redef class AVarAssignExpr
                if btype != null and not v.check_conform_expr(n_value, btype) then return
 
                # Always cast
-               v.variable_ctx.stype(variable) = n_value.stype
+               v.variable_ctx = v.variable_ctx.sub_with(self, variable, n_value.stype)
 
                _is_typed = true
        end
@@ -748,7 +747,7 @@ redef class AVarReassignExpr
                if btype != null and not v.check_conform(n_value, t2, btype) then return
 
                # Always cast
-               v.variable_ctx.stype(variable) = t2
+               v.variable_ctx = v.variable_ctx.sub_with(self, variable, t2)
 
                _is_typed = true
        end
@@ -812,8 +811,7 @@ redef class AIfexprExpr
                v.enter_visit(n_else)
 
                # Merge 'then' and 'else' contexts
-               old_var_ctx.merge2(then_var_ctx, v.variable_ctx, v.base_variable_ctx)
-               v.variable_ctx = old_var_ctx
+               v.variable_ctx = old_var_ctx.merge_reash(self, then_var_ctx, v.variable_ctx, v.base_variable_ctx)
 
                var stype = v.check_conform_multiexpr(null, [n_then, n_else])
                if stype == null then return
index 88ccea7..bb74a9c 100644 (file)
@@ -1,8 +1 @@
-0
-0
-2
-
-1
-2
-1
-2
+alt/base_var_type_evolution_null4_alt2.nit:38,7: Type error: expected A, got nullable A