syntax: do not nest variable context in ABlockExpr
[nit.git] / src / syntax / typing.nit
index c3a5ffc..adc5736 100644 (file)
@@ -136,9 +136,6 @@ special AbsSyntaxVisitor
        end
 end
 
-redef class VarVariable
-       redef readable var _is_typed: Bool = false
-end
 
 ###############################################################################
 
@@ -274,7 +271,10 @@ redef class AClosureDecl
                v.base_variable_ctx = v.variable_ctx
                v.variable_ctx = v.variable_ctx.sub(self)
 
-               var escapable = new EscapableClosure(self, variable.closure, null)
+               var blist: nullable Array[AExpr] = null
+               var t = v.local_property.signature.return_type
+               if t != null then blist = new Array[AExpr]
+               var escapable = new EscapableClosure(self, variable.closure, blist)
                _escapable = escapable
                v.escapable_ctx.push(escapable, null)
 
@@ -284,13 +284,15 @@ redef class AClosureDecl
                        if v.variable_ctx.unreash == false then
                                if variable.closure.signature.return_type != null then
                                        v.error(self, "Control error: Reached end of block (a 'continue' with a value was expected).")
-                               else if variable.closure.is_break then
-                                       v.error(self, "Control error: Reached end of break block (an 'abort' was expected).")
+                               else if variable.closure.is_break and escapable.break_list != null then
+                                       v.error(self, "Control error: Reached end of break block (a 'break' with a value was expected).")
                                end
                        end
                end
+               if blist != null then for x in blist do
+                       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
@@ -367,9 +369,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
@@ -377,19 +380,15 @@ end
 redef class ABlockExpr
        redef fun accept_typing(v)
        do
-               var old_var_ctx = v.variable_ctx
-               v.variable_ctx = v.variable_ctx.sub(self)
-
                for e in n_expr do
-                       if v.variable_ctx.unreash and not v.variable_ctx.already_unreash then
+                       if not v.variable_ctx.unreash then
+                               v.enter_visit(e)
+                       else if not v.variable_ctx.already_unreash then
                                v.variable_ctx.already_unreash = true
-                               v.warning(e, "Warning: unreachable statement.")
+                               v.error(e, "Error: unreachable statement.")
                        end
-                       v.enter_visit(e)
                end
 
-               old_var_ctx.merge(v.variable_ctx)
-               v.variable_ctx = old_var_ctx
                _is_typed = true
        end
 end
@@ -438,10 +437,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.")
@@ -472,9 +474,21 @@ redef class ADoExpr
                var escapable = new BreakOnlyEscapableBlock(self)
                _escapable = escapable
                v.escapable_ctx.push(escapable, n_label)
+               var old_var_ctx = v.variable_ctx
 
+               v.variable_ctx = old_var_ctx.sub(self)
                super
 
+               # Add the end of the block as an exit context
+               if not v.variable_ctx.unreash then
+                       escapable.break_variable_contexts.add(v.variable_ctx)
+               end
+
+               # Merge all exit contexts
+               if not escapable.break_variable_contexts.is_empty then
+                       v.variable_ctx = old_var_ctx.merge(self, escapable.break_variable_contexts, v.base_variable_ctx)
+               end
+
                v.escapable_ctx.pop
                _is_typed = true
        end
@@ -510,8 +524,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
@@ -528,12 +541,15 @@ 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)
                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)
 
@@ -543,7 +559,12 @@ redef class AWhileExpr
                        v.enter_visit(n_block)
                end
 
+               # 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)
+               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
                _is_typed = true
@@ -562,7 +583,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
@@ -570,7 +590,14 @@ redef class ALoopExpr
                        v.enter_visit(n_block)
                end
 
-               v.variable_ctx = old_var_ctx
+               # 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
+                       v.variable_ctx = old_var_ctx.merge(self, escapable.break_variable_contexts, v.base_variable_ctx)
+               end
+
                v.base_variable_ctx = old_base_var_ctx
                v.escapable_ctx.pop
                _is_typed = true
@@ -601,7 +628,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
@@ -671,7 +703,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
@@ -687,6 +719,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}.")
@@ -719,7 +755,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
@@ -768,15 +804,23 @@ 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
+               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
 
@@ -1025,6 +1069,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}.")
@@ -1217,16 +1265,25 @@ 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
+                       if name == (once "==".to_symbol) or name == (once "!=".to_symbol) then
+                               # Special case on != and == that are allowed for 'null'
+                               type_recv = v.type_object.as_nullable
+                       else
+                               v.error(self, "Error: Method '{name}' call on 'null'.")
+                               return null
+                       end
+               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
@@ -1419,7 +1476,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
@@ -1428,8 +1489,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
@@ -1445,7 +1507,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
@@ -1454,8 +1520,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
@@ -1677,8 +1744,8 @@ redef class AClosureDef
                if v.variable_ctx.unreash == false then
                        if closure.signature.return_type != null then
                                v.error(self, "Control error: Reached end of block (a 'continue' with a value was expected).")
-                       else if closure.is_break then
-                               v.error(self, "Control error: Reached end of break block (a 'break' was expected).")
+                       else if closure.is_break and esc.break_list != null then
+                               v.error(self, "Control error: Reached end of break block (a 'break' with a value was expected).")
                        end
                end
                v.variable_ctx = old_var_ctx
@@ -1698,6 +1765,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