syntax: var assignment can bypass casted type
[nit.git] / src / syntax / typing.nit
index ee2eb0a..679b88e 100644 (file)
@@ -19,6 +19,7 @@ package typing
 
 import syntax_base
 import escape
+import control_flow
 
 redef class MMSrcModule
        # Walk trough the module and type statments and expressions
@@ -124,204 +125,6 @@ special AbsSyntaxVisitor
        end
 end
 
-# 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
-       meth [](s: Symbol): Variable
-       do
-               if _dico.has_key(s) then
-                       return _dico[s]
-               else
-                       return null
-               end
-       end
-
-       # Register a new variable with its name
-       meth add(v: Variable)
-       do
-               _dico[v.name] = v
-               _all_variables.add(v)
-       end
-
-       meth mark_is_set(v: Variable)
-       do
-               _set_variables.add(v)
-       end
-
-       meth check_is_set(n: PNode, 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
-       do
-               return v.stype
-       end
-
-       # Variables by name (in the current context only)
-       attr _dico: Map[Symbol, Variable]
-
-       # All variables in all contextes
-       attr _all_variables: Set[Variable]
-
-       # Build a new VariableContext
-       meth sub(node: PNode): SubVariableContext
-       do
-               return new SubVariableContext.with_prev(self, node)
-       end
-
-       # Build a nested VariableContext with new variable information
-       meth sub_with(node: PNode, v: Variable, t: MMType): SubVariableContext
-       do
-               return new CastVariableContext.with_prev(self, node, v, t)
-       end
-
-       # The visitor of the context (used to display error)
-       attr _visitor: AbsSyntaxVisitor
-
-       # The syntax node that introduced the context
-       readable attr _node: PNode
-
-       init(visitor: AbsSyntaxVisitor, node: PNode)
-       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
-
-       # Is a control flow already broken?
-       # Used to avoid repeating the same error message
-       readable writable attr _already_unreash: Bool = false
-
-       # Set of variable that are set (assigned)
-       readable attr _set_variables: HashSet[Variable] = new HashSet[Variable]
-
-       # Is a variable set?
-       meth is_set(v: Variable): Bool
-       do
-               return _set_variables.has(v)
-       end
-
-       # Merge back one flow context information
-       meth 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
-               end
-       end
-
-       # Merge back two alternative flow context informations
-       meth merge2(ctx1, ctx2: 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
-               end
-       end
-end
-
-class RootVariableContext
-special VariableContext
-       init(visitor: AbsSyntaxVisitor, node: PNode)
-       do
-               super(visitor, node)
-               _all_variables = new HashSet[Variable]
-       end
-end
-
-class SubVariableContext
-special VariableContext
-       readable attr _prev: VariableContext
-
-       redef meth [](s)
-       do
-               if _dico.has_key(s) then
-                       return _dico[s]
-               else
-                       return prev[s]
-               end
-       end
-
-       redef meth stype(v)
-       do
-               return prev.stype(v)
-       end
-
-       init with_prev(p: VariableContext, node: PNode)
-       do
-               init(p._visitor, node)
-               _prev = p
-               _all_variables = p._all_variables
-       end
-
-       redef meth is_set(v)
-       do
-               return _set_variables.has(v) or _prev.is_set(v)
-       end
-end
-
-class CastVariableContext
-special SubVariableContext
-       attr _variable: Variable
-       attr _var_type: MMType
-
-       redef meth stype(v)
-       do
-               if _variable == v then
-                       return _var_type
-               end
-               return prev.stype(v)
-       end
-
-       init with_prev(p: VariableContext, node: PNode, v: Variable, t: MMType)
-       do
-               super(p, node)
-               _variable = v
-               _var_type =t
-       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
-
 
 ###############################################################################
 
@@ -741,30 +544,37 @@ redef class AVarAssignExpr
        do
                v.variable_ctx.mark_is_set(variable)
                var t = v.variable_ctx.stype(variable)
-               v.check_conform_expr(n_value, t)
+               if v.check_conform_expr(n_value, variable.stype) then
+                       # Fall back to base type if current type does not match
+                       if not n_value.stype < t then
+                               v.variable_ctx.stype(variable) = variable.stype
+                       end
+               end
                _is_typed = true
        end
 end
 
 redef class AReassignFormExpr
-       # Compute and check method used through the reassigment operator 
-       private meth do_lvalue_typing(v: TypingVisitor, type_lvalue: MMType)
+       # Compute and check method used through the reassigment operator
+       # On success return the static type of the result of the reassigment operator
+       # Else display an error and return null
+       private meth do_rvalue_typing(v: TypingVisitor, type_lvalue: MMType): MMType
        do
                if type_lvalue == null then
-                       return
+                       return null
                end
                var name = n_assign_op.method_name
                var lc = type_lvalue.local_class
                if not lc.has_global_property_by_name(name) then
                        v.error(self, "Error: Method '{name}' doesn't exists in {type_lvalue}.")
-                       return
+                       return null
                end
                var prop = lc.select_method(name)
                prop.global.check_visibility(v, self, v.module, false)
                var psig = prop.signature_for(type_lvalue)
                _assign_method = prop
-               if not v.check_conform_expr(n_value, psig[0].not_for_self) then return
-               if not v.check_conform(self, psig.return_type.not_for_self, n_value.stype) then return
+               if not v.check_conform_expr(n_value, psig[0].not_for_self) then return null
+               return psig.return_type.not_for_self
        end
 
        # Method used through the reassigment operator (once computed)
@@ -777,7 +587,14 @@ redef class AVarReassignExpr
                v.variable_ctx.check_is_set(self, variable)
                v.variable_ctx.mark_is_set(variable)
                var t = v.variable_ctx.stype(variable)
-               do_lvalue_typing(v, t)
+               var t2 = do_rvalue_typing(v, t)
+               if t2 == null then return
+               if v.check_conform(self, t2, variable.stype) then
+                       # Fall back to base type if current type does not match
+                       if not t2 < t then
+                               v.variable_ctx.stype(variable) = variable.stype
+                       end
+               end
                _is_typed = true
        end
 end
@@ -1115,7 +932,9 @@ redef class AAttrReassignExpr
        do
                do_typing(v)
                if prop == null then return
-               do_lvalue_typing(v, attr_type)
+               var t = do_rvalue_typing(v, attr_type)
+               if t == null then return
+               v.check_conform(self, t, n_value.stype)
                _is_typed = true
        end
 end
@@ -1396,7 +1215,9 @@ special AReassignFormExpr
                var t = prop.signature_for(n_expr.stype).return_type
                if not n_expr.is_self then t = t.not_for_self
 
-               do_lvalue_typing(v, t)
+               var t2 = do_rvalue_typing(v, t)
+               if t2 == null then return
+               v.check_conform(self, t2, n_value.stype)
 
                _read_prop = prop
                var old_args = arguments