syntax: while/loop exit type evolution
[nit.git] / src / syntax / typing.nit
index 70e7d5d..84a8483 100644 (file)
@@ -136,9 +136,6 @@ special AbsSyntaxVisitor
        end
 end
 
-redef class VarVariable
-       redef readable var _is_typed: Bool = false
-end
 
 ###############################################################################
 
@@ -373,9 +370,10 @@ redef class AVardeclExpr
                        end
                else if ne != null then
                        if not v.check_expr(ne) then return
-                       va.stype = n_expr.stype
+                       va.stype = ne.stype
+               else
+                       va.stype = v.type_object.as_nullable
                end
-               va._is_typed = true
                _is_typed = true
        end
 end
@@ -444,10 +442,13 @@ end
 redef class ABreakExpr
        redef fun after_typing(v)
        do
+               var unreash = v.variable_ctx.unreash
                v.variable_ctx.unreash = true
                var esc = compute_escapable_block(v.escapable_ctx)
                if esc == null then return
 
+               if not unreash then esc.break_variable_contexts.add(v.variable_ctx)
+
                var bl = esc.break_list
                if n_expr == null and bl != null then
                        v.error(self, "Error: break with a value required in this block.")
@@ -540,6 +541,10 @@ redef class AWhileExpr
                v.enter_visit(n_expr)
                v.check_conform_expr(n_expr, v.type_bool)
 
+               if n_expr isa ATrueExpr then
+                       v.warning(self, "Warning: use 'loop' instead of 'while true do'.")
+               end
+
                # Prepare inside context (assert cond)
                v.use_if_true_variable_ctx(n_expr)
 
@@ -550,6 +555,12 @@ redef class AWhileExpr
                end
 
                v.variable_ctx = old_var_ctx
+
+               # Compute outside context (assert !cond + all breaks)
+               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.base_variable_ctx = old_base_var_ctx
                v.escapable_ctx.pop
                _is_typed = true
@@ -576,6 +587,11 @@ redef class ALoopExpr
                        v.enter_visit(n_block)
                end
 
+               # Compute outside context (assert all breaks)
+               if not escapable.break_variable_contexts.is_empty then
+                       old_var_ctx.combine_merge(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
@@ -693,6 +709,10 @@ redef class AReassignFormExpr
                        return null
                end
                var name = n_assign_op.method_name
+               if type_lvalue isa MMTypeNone then
+                       v.error(self, "Error: Method '{name}' call on 'null'.")
+                       return null
+               end
                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}.")
@@ -774,15 +794,24 @@ redef class AIfexprExpr
                v.use_if_true_variable_ctx(n_expr)
 
                # Process 'then'
+               v.variable_ctx = v.variable_ctx.sub(n_then)
                v.enter_visit(n_then)
 
+               # Remember what appened in the 'then'
+               var then_var_ctx = v.variable_ctx
+
                # Prepare 'else' context
                v.variable_ctx = old_var_ctx
                v.use_if_false_variable_ctx(n_expr)
 
                # Process 'else'
+               v.variable_ctx = v.variable_ctx.sub(n_else)
                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
+
                var stype = v.check_conform_multiexpr(null, [n_then, n_else])
                if stype == null then return
 
@@ -1031,6 +1060,10 @@ redef class AAttrFormExpr
                if not v.check_expr(n_expr) then return
                var type_recv = n_expr.stype
                var name = n_id.to_symbol
+               if type_recv isa MMTypeNone then
+                       v.error(self, "Error: Attribute '{name}' access on 'null'.")
+                       return
+               end
                var lc = type_recv.local_class
                if not lc.has_global_property_by_name(name) then
                        v.error(self, "Error: Attribute {name} doesn't exists in {type_recv}.")
@@ -1223,16 +1256,20 @@ redef class AAbsSendExpr
 
        private fun get_property(v: TypingVisitor, type_recv: MMType, is_implicit_self: Bool, name: Symbol): nullable MMMethod
        do
+               if type_recv isa MMTypeNone then
+                       v.error(self, "Error: Method '{name}' call on 'null'.")
+                       return null
+               end
                var lc = type_recv.local_class
                var prop: nullable MMMethod = null
                if lc.has_global_property_by_name(name) then prop = lc.select_method(name)
                if prop == null and v.local_property.global.is_init then
-                       var props = type_recv.local_class.super_methods_named(name)
+                       var props = lc.super_methods_named(name)
                        if props.length > 1 then
                                v.error(self, "Error: Ambigous method name '{name}' for {props.join(", ")}. Use explicit designation.")
                                return null
                        else if props.length == 1 then 
-                               var p = type_recv.local_class[props.first.global]
+                               var p = lc[props.first.global]
                                assert p isa MMMethod
                                prop = p
                        end
@@ -1425,7 +1462,11 @@ redef class AEqExpr
                end
 
                if n_expr.stype isa MMTypeNone then
-                       try_to_isa(v, n_expr2)
+                       if n_expr2.stype isa MMTypeNone then
+                               v.warning(self, "Warning: comparaison between 2 null values.")
+                       else
+                               try_to_isa(v, n_expr2)
+                       end
                else if n_expr2.stype isa MMTypeNone then
                        try_to_isa(v, n_expr)
                end
@@ -1434,8 +1475,9 @@ redef class AEqExpr
        private fun try_to_isa(v: TypingVisitor, n: AExpr)
        do
                var variable = n.its_variable
-               if variable != null then
+               if variable != null and n.stype isa MMNullableType then
                        _if_false_variable_ctx = v.variable_ctx.sub_with(self, variable, n.stype.as_notnull)
+                       _if_true_variable_ctx = v.variable_ctx.sub_with(self, variable, v.type_none)
                end
        end
 end
@@ -1451,7 +1493,11 @@ redef class ANeExpr
                end
 
                if n_expr.stype isa MMTypeNone then
-                       try_to_isa(v, n_expr2)
+                       if n_expr2.stype isa MMTypeNone then
+                               v.warning(self, "Warning: comparaison between 2 null values.")
+                       else
+                               try_to_isa(v, n_expr2)
+                       end
                else if n_expr2.stype isa MMTypeNone then
                        try_to_isa(v, n_expr)
                end
@@ -1460,8 +1506,9 @@ redef class ANeExpr
        private fun try_to_isa(v: TypingVisitor, n: AExpr)
        do
                var variable = n.its_variable
-               if variable != null then
+               if variable != null and n.stype isa MMNullableType then
                        _if_true_variable_ctx = v.variable_ctx.sub_with(self, variable, n.stype.as_notnull)
+                       _if_false_variable_ctx = v.variable_ctx.sub_with(self, variable, v.type_none)
                end
        end
 end
@@ -1704,6 +1751,9 @@ special AExpr
                        v.warning(self, "Warning: Expression is already a {ttype}.")
                else if etype < ttype then
                        v.warning(self, "Warning: Expression is already a {ttype} since it is a {etype}.")
+               else if etype isa MMTypeNone then
+                       # ttype is not nullable because of prevous test
+                       v.warning(self, "Warning: Expression is null therefore cannot be a {ttype}.")
                else if etype.is_nullable and etype.as_notnull == ttype then
                        if ttype isa MMTypeFormal and ttype.bound.is_nullable then
                                # No warning in this case since with