Merge: Fix type adaptation when there is loops
authorJean Privat <jean@pryen.org>
Tue, 14 Apr 2015 09:33:40 +0000 (16:33 +0700)
committerJean Privat <jean@pryen.org>
Tue, 14 Apr 2015 09:33:40 +0000 (16:33 +0700)
Basically one of the oldest bug of Nit.

The point of adaptive typing is that the static types of variables follows the static types of the assigned values, type tests and comparison to null.
This is a great feature of the language but was buggy because loops where not taken in account (old TODO), until the present PR.

The basic idea is just to track if a type adaptation occurs, if yes replay the whole typing phase.
The immediate drawback is that now typing require 2 passes most of the time, and sometime more.

This PR was a roller-coaster of emotions to develop since a lot a surprise issues were discovered.

* bug in flow that give me hours of despair.
* duplication of error messages; because multiple passes.
* premature warning/error messages; because some error conditions on types are detected at the first pass but a second pass find it is fine in fact... but too late the error message was printed.
* almost work at the first time (modulo the previous issues)
* only two bugs found in the whole code, quite impressive, and one of them is not a real bug but an autocast issue.

The issue about error duplication and premature errors is only workadounded (or fixme) in most places.
But proper solutions will be required in future PR.

close #647 and close #86, then reopen it again, then reclose it.

Pull-Request: #1257
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

41 files changed:
lib/a_star.nit
lib/ai/backtrack.nit
src/astvalidation.nit
src/compiler/abstract_compiler.nit
src/semantize/flow.nit
src/semantize/typing.nit
src/toolcontext.nit
tests/base_adaptive_loop.nit [new file with mode: 0644]
tests/base_adaptive_loop2.nit [new file with mode: 0644]
tests/base_adaptive_loop3.nit [new file with mode: 0644]
tests/base_adaptive_loop_array.nit [new file with mode: 0644]
tests/base_adaptive_loop_call.nit [new file with mode: 0644]
tests/base_adaptive_loop_null.nit [new file with mode: 0644]
tests/sav/base_adaptive_loop.res [new file with mode: 0644]
tests/sav/base_adaptive_loop2.res [new file with mode: 0644]
tests/sav/base_adaptive_loop2_alt1.res [new file with mode: 0644]
tests/sav/base_adaptive_loop2_alt2.res [new file with mode: 0644]
tests/sav/base_adaptive_loop2_alt3.res [new file with mode: 0644]
tests/sav/base_adaptive_loop3.res [new file with mode: 0644]
tests/sav/base_adaptive_loop3_alt1.res [new file with mode: 0644]
tests/sav/base_adaptive_loop3_alt2.res [new file with mode: 0644]
tests/sav/base_adaptive_loop3_alt3.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_alt1.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_array.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_array_1alt1.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_array_1alt1_alt1.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_array_1alt2.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_array_1alt2_alt1.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_array_alt1.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_call.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_call_alt1.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_call_alt2.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_null.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_null_alt1.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_null_alt2.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_null_alt3.res [new file with mode: 0644]
tests/sav/base_adaptive_loop_null_alt4.res [new file with mode: 0644]
tests/sav/base_no_object_alt1.res
tests/sav/base_notnull_1alt1_alt3.res
tests/sav/base_notnull_alt3.res
tests/sav/fixme/base_isa_cast4_alt5.res

index 2cbd7cf..9735d90 100644 (file)
@@ -147,7 +147,8 @@ class Node
 
                                while frontier_node != self do
                                        path.nodes.unshift(frontier_node)
-                                       frontier_node = frontier_node.best_source.as(not null)
+                                       frontier_node = frontier_node.best_source
+                                       assert frontier_node != null
                                end
 
                                return path
index 550df10..ddc68ef 100644 (file)
@@ -209,6 +209,7 @@ class BacktrackSolver[S: Object, A]
 
                                problem.backtrack(state, a)
                                node = node.parent
+                               assert node != null
                                continue
                        end
 
index b34d42f..2883197 100644 (file)
@@ -74,7 +74,7 @@ redef class AExpr
        do
                super
                if mtype == null and not is_typed then
-                       debug "TYPING: untyped expression"
+                       #debug "TYPING: untyped expression"
                end
        end
 end
index 3116924..8c9e4b7 100644 (file)
@@ -1628,6 +1628,12 @@ abstract class AbstractCompilerVisitor
        fun stmt(nexpr: nullable AExpr)
        do
                if nexpr == null then return
+               if nexpr.mtype == null and not nexpr.is_typed then
+                       # Untyped expression.
+                       # Might mean dead code
+                       # So just return
+                       return
+               end
 
                var narray = nexpr.comprehension
                if narray != null then
@@ -1647,6 +1653,13 @@ abstract class AbstractCompilerVisitor
        # `mtype` is the expected return type, pass null if no specific type is expected.
        fun expr(nexpr: AExpr, mtype: nullable MType): RuntimeVariable
        do
+               if nexpr.mtype == null then
+                       # Untyped expression.
+                       # Might mean dead code
+                       # so return a placebo result
+                       if mtype == null then mtype = compiler.mainmodule.object_type
+                       return new_var(mtype)
+               end
                var old = self.current_node
                self.current_node = nexpr
                var res = nexpr.expr(self).as(not null)
index 8dad3b7..b0e833a 100644 (file)
@@ -78,7 +78,7 @@ private class FlowVisitor
        fun printflow
        do
                var file = new FileWriter.open("flow.dot")
-               file.write("digraph \{\n")
+               file.write("digraph \{\nnode[shape=box];")
                for f in flows do
                        var s = ""
                        if f.node isa AExpr then
@@ -86,13 +86,14 @@ private class FlowVisitor
                        end
                        file.write "F{f.object_id} [label=\"{f.object_id}\\n{f.node.location}\\n{f.node.class_name}\\n{f.name}{s}\"];\n"
                        for p in f.previous do
-                               file.write "F{p.object_id} -> F{f.object_id};\n"
+                               s = ""
+                               if f.when_true == p then s = "[label=TRUE, style=dotted]"
+                               if f.when_false == p then s = "[label=FALSE, style=dotted]"
+                               if f.when_true == p and f.when_false == p then s = "[label=TRUE_FALSE, style=dotted]"
+                               file.write "F{p.object_id} -> F{f.object_id}{s};\n"
                        end
-                       if f.when_true != f then
-                               file.write "F{f.object_id} -> F{f.when_true.object_id}[label=TRUE, style=dotted];\n"
-                       end
-                       if f.when_false != f then
-                               file.write "F{f.object_id} -> F{f.when_false.object_id}[label=FALSE,style=dotted];\n"
+                       for p in f.loops do
+                               file.write "F{p.object_id} -> F{f.object_id}[label=LOOP, style=dashed, constraint=false];\n"
                        end
                end
                file.write("\}\n")
@@ -395,7 +396,7 @@ redef class AWhileExpr
                var after_block = v.current_flow_context
 
                before_loop.add_loop(after_block)
-               v.merge_continues_to(after_block, self.continue_mark)
+               v.merge_continues_to(before_loop, self.continue_mark)
 
                v.current_flow_context = after_expr.when_false
                v.merge_breaks(self.break_mark)
@@ -412,7 +413,7 @@ redef class ALoopExpr
                var after_block = v.current_flow_context
 
                before_loop.add_loop(after_block)
-               v.merge_continues_to(after_block, self.continue_mark)
+               v.merge_continues_to(before_loop, self.continue_mark)
 
                v.make_unreachable_flow
                v.merge_breaks(self.break_mark)
@@ -431,7 +432,7 @@ redef class AForExpr
                var after_block = v.current_flow_context
 
                before_loop.add_loop(after_block)
-               v.merge_continues_to(after_block, self.continue_mark)
+               v.merge_continues_to(before_loop, self.continue_mark)
 
                v.make_merge_flow(v.current_flow_context, before_loop)
                v.merge_breaks(self.break_mark)
index 2bbc78b..a46f632 100644 (file)
@@ -239,12 +239,16 @@ private class TypeVisitor
 
                if not mtype2 isa MNullType then return
 
-               if mtype isa MNullType then return
-
                # Check of useless null
                if not check_can_be_null(anode.n_expr, mtype) then return
 
-               mtype = mtype.as_notnull
+               if mtype isa MNullType then
+                       # Because of type adaptation, we cannot just stop here
+                       # so return use `null` as a bottom type that will be merged easily (cf) `merge_types`
+                       mtype = null
+               else
+                       mtype = mtype.as_notnull
+               end
 
                # Check for type adaptation
                var variable = anode.n_expr.its_variable
@@ -252,11 +256,11 @@ private class TypeVisitor
 
                # One is null (mtype2 see above) the other is not null
                if anode isa AEqExpr then
-                       anode.after_flow_context.when_true.set_var(variable, mtype2)
-                       anode.after_flow_context.when_false.set_var(variable, mtype)
+                       anode.after_flow_context.when_true.set_var(self, variable, mtype2)
+                       anode.after_flow_context.when_false.set_var(self, variable, mtype)
                else if anode isa ANeExpr then
-                       anode.after_flow_context.when_false.set_var(variable, mtype2)
-                       anode.after_flow_context.when_true.set_var(variable, mtype)
+                       anode.after_flow_context.when_false.set_var(self, variable, mtype2)
+                       anode.after_flow_context.when_true.set_var(self, variable, mtype)
                else
                        abort
                end
@@ -444,6 +448,8 @@ private class TypeVisitor
 
        fun get_variable(node: AExpr, variable: Variable): nullable MType
        do
+               if not variable.is_adapted then return variable.declared_type
+
                var flow = node.after_flow_context
                if flow == null then
                        self.error(node, "No context!")
@@ -456,7 +462,7 @@ private class TypeVisitor
                        #node.debug("*** START Collected for {variable}")
                        var mtypes = flow.collect_types(variable)
                        #node.debug("**** END Collected for {variable}")
-                       if mtypes == null or mtypes.length == 0 then
+                       if mtypes.length == 0 then
                                return variable.declared_type
                        else if mtypes.length == 1 then
                                return mtypes.first
@@ -468,12 +474,18 @@ private class TypeVisitor
                end
        end
 
+       # Some variables where type-adapted during the visit
+       var dirty = false
+
+       # Some loops had been visited during the visit
+       var has_loop = false
+
        fun set_variable(node: AExpr, variable: Variable, mtype: nullable MType)
        do
                var flow = node.after_flow_context
                assert flow != null
 
-               flow.set_var(variable, mtype)
+               flow.set_var(self, variable, mtype)
        end
 
        fun merge_types(node: ANode, col: Array[nullable MType]): nullable MType
@@ -540,49 +552,58 @@ end
 redef class Variable
        # The declared type of the variable
        var declared_type: nullable MType
+
+       # Was the variable type-adapted?
+       # This is used to speedup type retrieval while it remains `false`
+       private var is_adapted = false
 end
 
 redef class FlowContext
        # Store changes of types because of type evolution
        private var vars = new HashMap[Variable, nullable MType]
-       private var cache = new HashMap[Variable, nullable Array[nullable MType]]
 
        # Adapt the variable to a static type
        # Warning1: do not modify vars directly.
        # Warning2: sub-flow may have cached a unadapted variable
-       private fun set_var(variable: Variable, mtype: nullable MType)
+       private fun set_var(v: TypeVisitor, variable: Variable, mtype: nullable MType)
        do
+               if variable.declared_type == mtype and not variable.is_adapted then return
+               if vars.has_key(variable) and vars[variable] == mtype then return
                self.vars[variable] = mtype
-               self.cache.keys.remove(variable)
+               v.dirty = true
+               variable.is_adapted = true
+               #node.debug "set {variable} to {mtype or else "X"}"
        end
 
-       private fun collect_types(variable: Variable): nullable Array[nullable MType]
+       # Look in the flow and previous flow and collect all first reachable type adaptation of a local variable
+       private fun collect_types(variable: Variable): Array[nullable MType]
        do
-               if cache.has_key(variable) then
-                       return cache[variable]
-               end
-               var res: nullable Array[nullable MType] = null
-               if vars.has_key(variable) then
-                       var mtype = vars[variable]
-                       res = [mtype]
-               else if self.previous.is_empty then
-                       # Root flow
-                       res = [variable.declared_type]
-               else
-                       for flow in self.previous do
-                               if flow.is_unreachable then continue
-                               var r2 = flow.collect_types(variable)
-                               if r2 == null then continue
-                               if res == null then
-                                       res = r2.to_a
-                               else
-                                       for t in r2 do
-                                               if not res.has(t) then res.add(t)
-                                       end
+               #node.debug "flow for {variable}"
+               var res = new Array[nullable MType]
+
+               var todo = [self]
+               var seen = new HashSet[FlowContext]
+               while not todo.is_empty do
+                       var f = todo.pop
+                       if f.is_unreachable then continue
+                       if seen.has(f) then continue
+                       seen.add f
+
+                       if f.vars.has_key(variable) then
+                               # Found something. Collect it and do not process further on this path
+                               res.add f.vars[variable]
+                               #f.node.debug "process {variable}: got {f.vars[variable] or else "X"}"
+                       else
+                               todo.add_all f.previous
+                               todo.add_all f.loops
+                               if f.previous.is_empty then
+                                       # Root flowcontext mean a parameter or something related
+                                       res.add variable.declared_type
+                                       #f.node.debug "root process {variable}: got {variable.declared_type or else "X"}"
                                end
                        end
                end
-               cache[variable] = res
+               #self.node.debug "##### end flow for {variable}: {res.join(" ")}"
                return res
        end
 end
@@ -623,7 +644,12 @@ redef class AMethPropdef
                        assert variable != null
                        variable.declared_type = mtype
                end
-               v.visit_stmt(nblock)
+
+               loop
+                       v.dirty = false
+                       v.visit_stmt(nblock)
+                       if not v.has_loop or not v.dirty then break
+               end
 
                if not nblock.after_flow_context.is_unreachable and msignature.return_mtype != null then
                        # We reach the end of the function without having a return, it is bad
@@ -952,8 +978,8 @@ end
 redef class AWhileExpr
        redef fun accept_typing(v)
        do
+               v.has_loop = true
                v.visit_expr_bool(n_expr)
-
                v.visit_stmt(n_block)
                self.is_typed = true
        end
@@ -962,6 +988,7 @@ end
 redef class ALoopExpr
        redef fun accept_typing(v)
        do
+               v.has_loop = true
                v.visit_stmt(n_block)
                self.is_typed = true
        end
@@ -1097,12 +1124,14 @@ redef class AForExpr
 
        redef fun accept_typing(v)
        do
+               v.has_loop = true
                var mtype = v.visit_expr(n_expr)
                if mtype == null then return
 
                self.do_type_iterator(v, mtype)
 
                v.visit_stmt(n_block)
+
                self.mtype = n_block.mtype
                self.is_typed = true
        end
@@ -1317,6 +1346,8 @@ redef class AArrayExpr
                        end
                end
                if mtype == null then
+                       # Ensure monotony for type adaptation on loops
+                       if self.element_mtype != null then mtypes.add self.element_mtype
                        mtype = v.merge_types(self, mtypes)
                end
                if mtype == null or mtype isa MNullType then
@@ -1401,7 +1432,7 @@ redef class AIsaExpr
                        #var from = if orig != null then orig.to_s else "invalid"
                        #var to = if mtype != null then mtype.to_s else "invalid"
                        #debug("adapt {variable}: {from} -> {to}")
-                       self.after_flow_context.when_true.set_var(variable, mtype)
+                       self.after_flow_context.when_true.set_var(v, variable, mtype)
                end
 
                self.mtype = v.type_bool(self)
index 5a3e3cb..19a78c0 100644 (file)
@@ -54,6 +54,11 @@ class Message
                return location.as(not null) < other.location.as(not null)
        end
 
+       redef fun ==(other): Bool do
+               if not other isa Message then return false
+               return location == other.location and tag == other.tag and text == other.text
+       end
+
        redef fun to_s: String
        do
                var l = location
@@ -199,6 +204,7 @@ class ToolContext
        fun error(l: nullable Location, s: String): Message
        do
                var m = new Message(l,null,s)
+               if messages.has(m) then return m
                if l != null then l.add_message m
                messages.add m
                error_count = error_count + 1
@@ -233,6 +239,7 @@ class ToolContext
                if not opt_warning.value.has(tag) and opt_warn.value == 0 then return null
                if is_warning_blacklisted(l, tag) then return null
                var m = new Message(l, tag, text)
+               if messages.has(m) then return null
                if l != null then l.add_message m
                messages.add m
                warning_count = warning_count + 1
@@ -261,6 +268,7 @@ class ToolContext
                if not opt_warning.value.has(tag) and opt_warn.value <= 1 then return null
                if is_warning_blacklisted(l, tag) then return null
                var m = new Message(l, tag, text)
+               if messages.has(m) then return null
                if l != null then l.add_message m
                messages.add m
                warning_count = warning_count + 1
diff --git a/tests/base_adaptive_loop.nit b/tests/base_adaptive_loop.nit
new file mode 100644 (file)
index 0000000..d31598e
--- /dev/null
@@ -0,0 +1,33 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import standard::kernel
+
+fun foo(i: Int): nullable Int do return i
+fun bar(i: Int) do i.output
+
+var i = 0
+var c = 0
+while i < 10 do
+       i += 1
+
+       var b = foo(i)
+       if b == null then
+               continue#alt1#
+       end
+
+       bar(b)
+       c = b
+end
+bar(c)
diff --git a/tests/base_adaptive_loop2.nit b/tests/base_adaptive_loop2.nit
new file mode 100644 (file)
index 0000000..bade6da
--- /dev/null
@@ -0,0 +1,38 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import standard::kernel
+
+class A
+       fun foo do 1.output
+end
+
+class B
+       super A
+       fun bar do 2.output
+end
+
+var x: A #alt3# var x
+x = new B
+
+var i = 0
+while i < 3 do
+       i += 1
+       x.bar
+       x = new A
+       x.foo
+       break#alt1#
+end
+x.foo
+#alt2#x.bar
diff --git a/tests/base_adaptive_loop3.nit b/tests/base_adaptive_loop3.nit
new file mode 100644 (file)
index 0000000..33f4c8d
--- /dev/null
@@ -0,0 +1,30 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import standard::kernel
+
+redef class Int
+       fun next: nullable Int do if self < 20 then return self + 1 else return null
+end
+
+var t2: nullable Int = 10
+t2.output
+while t2 != null do
+       t2 = t2.next #alt1# t2 = null
+       while t2 != null do
+               t2.output
+               t2 = t2.next #alt2# t2 = null
+       end
+       #alt3#t2 = t2.next
+end
diff --git a/tests/base_adaptive_loop_array.nit b/tests/base_adaptive_loop_array.nit
new file mode 100644 (file)
index 0000000..868c0cc
--- /dev/null
@@ -0,0 +1,28 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import standard::collection::array
+
+var a #1alt1#var a: Array[Object]
+a = [1]#1alt2#
+#1alt2#a = 1
+var i = 0
+while i < 3 do
+       i += 1
+
+       a = [a]
+       a.length.output
+       #alt1#a.first.length.output
+end
+#alt1#a.length.output
diff --git a/tests/base_adaptive_loop_call.nit b/tests/base_adaptive_loop_call.nit
new file mode 100644 (file)
index 0000000..2a99ba7
--- /dev/null
@@ -0,0 +1,59 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import standard::kernel
+
+class O
+       fun foo: O do
+               0.output
+               return new B
+       end
+end
+
+class A
+       super O
+       redef fun foo: B do
+               1.output
+               return new B
+       end
+       fun bar: B do
+               10.output
+               return new B
+       end
+end
+
+class B
+       super O
+       redef fun foo: C do
+               2.output
+               return new C
+       end
+       fun bar: C do
+               20.output
+               return new C
+       end
+end
+
+class C
+       super O
+end
+
+var a: O #alt1# var a
+
+a = new A
+var i = 0
+while i < 4 do
+       a = a.foo #alt2# a = a.bar
+       i += 1
+end
diff --git a/tests/base_adaptive_loop_null.nit b/tests/base_adaptive_loop_null.nit
new file mode 100644 (file)
index 0000000..cfbb80f
--- /dev/null
@@ -0,0 +1,53 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import standard::kernel
+
+fun foo(i: Int): nullable Int do return i
+fun bar(i: Int) do i.output
+fun baz: nullable Int do return null
+
+bar(0)
+var i = null
+while i == null do
+       i = foo(1) #alt1# exit(1)
+end
+bar(i)
+
+i = foo(2)
+while i != null do
+       bar(i)
+       i = baz
+end
+#alt2#bar(i)
+
+i = null
+loop
+       i = foo(3)
+       if i == null then continue #alt3#
+       bar(i)
+       break
+end
+bar(i)
+
+i = 4
+loop
+       bar(i)
+       i = baz
+       if i != null then
+               bar(i)
+       else
+               break #alt4#
+       end
+end
diff --git a/tests/sav/base_adaptive_loop.res b/tests/sav/base_adaptive_loop.res
new file mode 100644 (file)
index 0000000..958699b
--- /dev/null
@@ -0,0 +1,11 @@
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+10
diff --git a/tests/sav/base_adaptive_loop2.res b/tests/sav/base_adaptive_loop2.res
new file mode 100644 (file)
index 0000000..afe234b
--- /dev/null
@@ -0,0 +1,3 @@
+2
+1
+1
diff --git a/tests/sav/base_adaptive_loop2_alt1.res b/tests/sav/base_adaptive_loop2_alt1.res
new file mode 100644 (file)
index 0000000..95f0863
--- /dev/null
@@ -0,0 +1 @@
+alt/base_adaptive_loop2_alt1.nit:32,2--6: Error: Method 'bar' doesn't exists in A.
diff --git a/tests/sav/base_adaptive_loop2_alt2.res b/tests/sav/base_adaptive_loop2_alt2.res
new file mode 100644 (file)
index 0000000..7fb541b
--- /dev/null
@@ -0,0 +1 @@
+alt/base_adaptive_loop2_alt2.nit:38,1--5: Error: Method 'bar' doesn't exists in A.
diff --git a/tests/sav/base_adaptive_loop2_alt3.res b/tests/sav/base_adaptive_loop2_alt3.res
new file mode 100644 (file)
index 0000000..afe234b
--- /dev/null
@@ -0,0 +1,3 @@
+2
+1
+1
diff --git a/tests/sav/base_adaptive_loop3.res b/tests/sav/base_adaptive_loop3.res
new file mode 100644 (file)
index 0000000..e33f5e9
--- /dev/null
@@ -0,0 +1,11 @@
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
diff --git a/tests/sav/base_adaptive_loop3_alt1.res b/tests/sav/base_adaptive_loop3_alt1.res
new file mode 100644 (file)
index 0000000..f599e28
--- /dev/null
@@ -0,0 +1 @@
+10
diff --git a/tests/sav/base_adaptive_loop3_alt2.res b/tests/sav/base_adaptive_loop3_alt2.res
new file mode 100644 (file)
index 0000000..b5fa014
--- /dev/null
@@ -0,0 +1,2 @@
+10
+11
diff --git a/tests/sav/base_adaptive_loop3_alt3.res b/tests/sav/base_adaptive_loop3_alt3.res
new file mode 100644 (file)
index 0000000..2b29529
--- /dev/null
@@ -0,0 +1 @@
+alt/base_adaptive_loop3_alt3.nit:29,7--13: Error: Method 'next' call on 'null'.
diff --git a/tests/sav/base_adaptive_loop_alt1.res b/tests/sav/base_adaptive_loop_alt1.res
new file mode 100644 (file)
index 0000000..8526116
--- /dev/null
@@ -0,0 +1,2 @@
+alt/base_adaptive_loop_alt1.nit:30,6: Type error: expected Int, got nullable Int
+alt/base_adaptive_loop_alt1.nit:31,6: Type error: expected Int, got nullable Int
diff --git a/tests/sav/base_adaptive_loop_array.res b/tests/sav/base_adaptive_loop_array.res
new file mode 100644 (file)
index 0000000..e8183f0
--- /dev/null
@@ -0,0 +1,3 @@
+1
+1
+1
diff --git a/tests/sav/base_adaptive_loop_array_1alt1.res b/tests/sav/base_adaptive_loop_array_1alt1.res
new file mode 100644 (file)
index 0000000..e8183f0
--- /dev/null
@@ -0,0 +1,3 @@
+1
+1
+1
diff --git a/tests/sav/base_adaptive_loop_array_1alt1_alt1.res b/tests/sav/base_adaptive_loop_array_1alt1_alt1.res
new file mode 100644 (file)
index 0000000..fcb49fa
--- /dev/null
@@ -0,0 +1,7 @@
+1
+1
+1
+1
+1
+1
+1
diff --git a/tests/sav/base_adaptive_loop_array_1alt2.res b/tests/sav/base_adaptive_loop_array_1alt2.res
new file mode 100644 (file)
index 0000000..e8183f0
--- /dev/null
@@ -0,0 +1,3 @@
+1
+1
+1
diff --git a/tests/sav/base_adaptive_loop_array_1alt2_alt1.res b/tests/sav/base_adaptive_loop_array_1alt2_alt1.res
new file mode 100644 (file)
index 0000000..aab8da7
--- /dev/null
@@ -0,0 +1,3 @@
+alt/base_adaptive_loop_array_1alt2_alt1.nit:26,2--15: Error: Method 'length' doesn't exists in Int.
+alt/base_adaptive_loop_array_1alt2_alt1.nit:26,2--15: Error: Method 'length' doesn't exists in nullable Object.
+alt/base_adaptive_loop_array_1alt2_alt1.nit:28,1--8: Error: Method 'length' doesn't exists in nullable Object.
diff --git a/tests/sav/base_adaptive_loop_array_alt1.res b/tests/sav/base_adaptive_loop_array_alt1.res
new file mode 100644 (file)
index 0000000..78d6853
--- /dev/null
@@ -0,0 +1,2 @@
+alt/base_adaptive_loop_array_alt1.nit:26,2--15: Error: Method 'length' doesn't exists in nullable Object.
+alt/base_adaptive_loop_array_alt1.nit:28,1--8: Error: Method 'length' doesn't exists in nullable Object.
diff --git a/tests/sav/base_adaptive_loop_call.res b/tests/sav/base_adaptive_loop_call.res
new file mode 100644 (file)
index 0000000..92c5e8d
--- /dev/null
@@ -0,0 +1,4 @@
+1
+2
+0
+2
diff --git a/tests/sav/base_adaptive_loop_call_alt1.res b/tests/sav/base_adaptive_loop_call_alt1.res
new file mode 100644 (file)
index 0000000..36d0245
--- /dev/null
@@ -0,0 +1 @@
+alt/base_adaptive_loop_call_alt1.nit:57,6--10: Error: Method 'foo' doesn't exists in nullable Object.
diff --git a/tests/sav/base_adaptive_loop_call_alt2.res b/tests/sav/base_adaptive_loop_call_alt2.res
new file mode 100644 (file)
index 0000000..f926a35
--- /dev/null
@@ -0,0 +1 @@
+alt/base_adaptive_loop_call_alt2.nit:57,6--10: Error: Method 'bar' doesn't exists in O.
diff --git a/tests/sav/base_adaptive_loop_null.res b/tests/sav/base_adaptive_loop_null.res
new file mode 100644 (file)
index 0000000..ccbe3cc
--- /dev/null
@@ -0,0 +1,6 @@
+0
+1
+2
+3
+3
+4
diff --git a/tests/sav/base_adaptive_loop_null_alt1.res b/tests/sav/base_adaptive_loop_null_alt1.res
new file mode 100644 (file)
index 0000000..573541a
--- /dev/null
@@ -0,0 +1 @@
+0
diff --git a/tests/sav/base_adaptive_loop_null_alt2.res b/tests/sav/base_adaptive_loop_null_alt2.res
new file mode 100644 (file)
index 0000000..17a24a7
--- /dev/null
@@ -0,0 +1 @@
+alt/base_adaptive_loop_null_alt2.nit:33,5: Type error: expected Int, got null
diff --git a/tests/sav/base_adaptive_loop_null_alt3.res b/tests/sav/base_adaptive_loop_null_alt3.res
new file mode 100644 (file)
index 0000000..0d93ee8
--- /dev/null
@@ -0,0 +1,2 @@
+alt/base_adaptive_loop_null_alt3.nit:39,6: Type error: expected Int, got nullable Int
+alt/base_adaptive_loop_null_alt3.nit:42,5: Type error: expected Int, got nullable Int
diff --git a/tests/sav/base_adaptive_loop_null_alt4.res b/tests/sav/base_adaptive_loop_null_alt4.res
new file mode 100644 (file)
index 0000000..bb3b854
--- /dev/null
@@ -0,0 +1 @@
+alt/base_adaptive_loop_null_alt4.nit:46,6: Type error: expected Int, got nullable Int
index e262c78..483e23d 100644 (file)
@@ -1,3 +1,2 @@
 alt/base_no_object_alt1.nit:11,9--13: Error: Method 'init' doesn't exists in A.
 alt/base_no_object_alt1.nit:13,4--12: Type Error: missing primitive class `Bool'.
-alt/base_no_object_alt1.nit:13,4--12: Type Error: missing primitive class `Bool'.
index d044957..8d31fe0 100644 (file)
@@ -1,8 +1,6 @@
 alt/base_notnull_1alt1_alt3.nit:23,7: Type error: expected Object, got null
 alt/base_notnull_1alt1_alt3.nit:24,7: Type error: expected Object, got null
-alt/base_notnull_1alt1_alt3.nit:26,8: Type error: expected Object, got null
 alt/base_notnull_1alt1_alt3.nit:27,12: Type error: expected Object, got null
-alt/base_notnull_1alt1_alt3.nit:29,8: Type error: expected Object, got null
 alt/base_notnull_1alt1_alt3.nit:30,12: Type error: expected Object, got null
 alt/base_notnull_1alt1_alt3.nit:31,7--20: Type error: as(not null) on null
 alt/base_notnull_1alt1_alt3.nit:32,7--20: Type error: as(not null) on null
index addd582..84ba687 100644 (file)
@@ -1,8 +1,6 @@
 alt/base_notnull_alt3.nit:23,7: Type error: expected Object, got null
 alt/base_notnull_alt3.nit:24,7: Type error: expected Object, got null
-alt/base_notnull_alt3.nit:26,8: Type error: expected Object, got null
 alt/base_notnull_alt3.nit:27,12: Type error: expected Object, got null
-alt/base_notnull_alt3.nit:29,8: Type error: expected Object, got null
 alt/base_notnull_alt3.nit:30,12: Type error: expected Object, got null
 alt/base_notnull_alt3.nit:31,7--20: Type error: as(not null) on null
 alt/base_notnull_alt3.nit:32,7--20: Type error: as(not null) on null
index 55d9015..930dc10 100644 (file)
@@ -1,12 +1 @@
-1
-2
-3
-4
-4
-5
-6
-6
-7
-7
-8
-8
+alt/base_isa_cast4_alt5.nit:89,1--8: Error: Method 'foo' doesn't exists in A.