syntax: variable assignment can bypass cast
authorJean Privat <jean@pryen.org>
Mon, 22 Jun 2009 18:07:07 +0000 (14:07 -0400)
committerJean Privat <jean@pryen.org>
Wed, 24 Jun 2009 19:47:49 +0000 (15:47 -0400)
Bypassing is allowed if it is conform to the 'base' type of the variable.
The 'base' type of the variable is set before while, for and closures.

Bypassing also means that casted type must propagate trough flow control.

Some tests are updated to avoid unexpected cast flow.

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

15 files changed:
src/syntax/control_flow.nit
src/syntax/typing.nit
tests/base_classid.nit
tests/base_isa_cast.nit
tests/base_isa_cast2.nit
tests/base_isa_cast4.nit [new file with mode: 0644]
tests/base_isa_nil.nit
tests/sav/base_isa_cast2.sav
tests/sav/base_isa_cast2_alt8.sav
tests/sav/base_isa_cast4.sav [new file with mode: 0644]
tests/sav/base_isa_cast4_alt1.sav [new file with mode: 0644]
tests/sav/base_isa_cast4_alt2.sav [new file with mode: 0644]
tests/sav/base_isa_cast4_alt3.sav [new file with mode: 0644]
tests/sav/base_isa_cast4_alt4.sav [new file with mode: 0644]
tests/sav/base_isa_cast4_alt5.sav [new file with mode: 0644]

index f693eaa..4ac5723 100644 (file)
@@ -144,11 +144,14 @@ abstract class VariableContext
                        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
-       meth merge2(ctx1, ctx2: VariableContext)
+       meth merge2(ctx1, ctx2, basectx: VariableContext)
        do
                if ctx1.unreash then
                        merge(ctx2)
@@ -159,6 +162,21 @@ abstract class VariableContext
                        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 == s2 then
+                               stype(v) = s1
+                       else if s1 < s2 then
+                               stype(v) = s2
+                       else if s2 < s1 then
+                               stype(v) = s1
+                       else
+                               stype(v) = basectx.stype(v)
+                       end
                end
        end
 
index f9d5eba..e7aa1a5 100644 (file)
@@ -46,6 +46,9 @@ special AbsSyntaxVisitor
        # Current knowledge about variables names and types
        readable writable attr _variable_ctx: VariableContext
 
+       # Non-bypassable knowledge about variables names and types
+       readable writable attr _base_variable_ctx: VariableContext
+
        # Current knowledge about escapable blocks
        readable writable attr _escapable_ctx: EscapableContext = new EscapableContext(self)
 
@@ -166,6 +169,7 @@ redef class AMethPropdef
        redef meth accept_typing(v)
        do
                v.variable_ctx = new RootVariableContext(v, self)
+               v.base_variable_ctx = v.variable_ctx
                _self_var = v.self_var
                super
        end
@@ -249,6 +253,8 @@ redef class AClosureDecl
                v.variable_ctx.add(variable)
 
                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)
 
                _escapable = new EscapableClosure(self, variable.closure, null)
@@ -268,6 +274,7 @@ redef class AClosureDecl
 
                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
        end
 end
@@ -451,7 +458,7 @@ redef class AIfExpr
                end
 
                # Merge 'then' and 'else' contexts
-               old_var_ctx.merge2(then_var_ctx, v.variable_ctx)
+               old_var_ctx.merge2(then_var_ctx, v.variable_ctx, v.base_variable_ctx)
                v.variable_ctx = old_var_ctx
                _is_typed = true
        end
@@ -466,12 +473,15 @@ redef class AWhileExpr
                _escapable = new EscapableBlock(self)
                v.escapable_ctx.push(_escapable)
                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)
 
                super
 
                v.check_conform_expr(n_expr, v.type_bool)
                v.variable_ctx = old_var_ctx
+               v.base_variable_ctx = old_base_var_ctx
                v.escapable_ctx.pop
                _is_typed = true
        end
@@ -491,6 +501,8 @@ redef class AForExpr
                v.escapable_ctx.push(_escapable)
 
                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)
                var va = new AutoVariable(n_id.to_symbol, self)
                variable = va
@@ -529,6 +541,7 @@ redef class AForExpr
 
                # pop context
                v.variable_ctx = old_var_ctx
+               v.base_variable_ctx = old_base_var_ctx
                v.escapable_ctx.pop
                _is_typed = true
        end
@@ -559,12 +572,14 @@ redef class AVarAssignExpr
        do
                v.variable_ctx.mark_is_set(variable)
                var t = v.variable_ctx.stype(variable)
-               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
+
+               # Check the base type
+               var btype = v.base_variable_ctx.stype(variable)
+               if not v.check_conform_expr(n_value, btype) then return
+
+               # Bypasse cast if then current type does not match
+               if not n_value.stype < t then v.variable_ctx.stype(variable) = btype
+
                _is_typed = true
        end
 end
@@ -604,12 +619,14 @@ redef class AVarReassignExpr
                var t = v.variable_ctx.stype(variable)
                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
+
+               # Check the base type
+               var btype = v.base_variable_ctx.stype(variable)
+               if not v.check_conform(n_value, t2, btype) then return
+
+               # Bypasse cast if then current type does not match
+               if not t2 < t then v.variable_ctx.stype(variable) = btype
+
                _is_typed = true
        end
 end
@@ -1468,6 +1485,8 @@ redef class AClosureDef
                closure = esc.closure
 
                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)
                variables = new Array[AutoVariable]
                for i in [0..n_id.length[ do
@@ -1488,6 +1507,7 @@ redef class AClosureDef
                        end
                end
                v.variable_ctx = old_var_ctx
+               v.base_variable_ctx = old_base_var_ctx
        end
 end
 
index 30e14b3..5722f61 100644 (file)
@@ -36,12 +36,12 @@ special A
        init do end
 end
 
-var a: A
-var ab: A
-var b: B
-a = new A
-ab = new B
-b = new B
+var a: A = new A
+var ab: A = new B
+var b: B = new B
+
+
+
 (not(a == b)).output
 (a == a).output
 (a isa A).output
index f57511a..9a0adc5 100644 (file)
@@ -19,21 +19,21 @@ import kernel
 class A
        init do end
 end
-
 class B
 special A
        meth foo do 0.output
        init do end
 end
-
 class C
 special B
        meth bar do 1.output
        init do end
 end
 
+
+#alt5# var b: A = new B
 var a: A = new C
-#alt5# a = new B
+#alt5# a = b
 #alt6# a = new A
 if a isa B then
        a.foo
index 4bccc58..14de354 100644 (file)
@@ -25,7 +25,7 @@ special A
        meth foo(i: Int) do i.output
        init do end
 end
-
+var a0: A = new B
 var a: A = new B
 if a isa B then
        a.foo(1)
@@ -44,8 +44,8 @@ if not a isa B then
        assert a isa B
        a.foo(3)
 end
-#alt8#a.foo(4)
-
+a.foo(4)
+a = a0
 while a isa B do
        #alt8#a.foo(5)
        a = new A
diff --git a/tests/base_isa_cast4.nit b/tests/base_isa_cast4.nit
new file mode 100644 (file)
index 0000000..9104a90
--- /dev/null
@@ -0,0 +1,97 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2009 Jean Privat <jean@pryen.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 kernel
+
+class A
+       init do end
+end
+
+class B
+special A
+       meth foo(i: Int) do i.output
+       init do end
+end
+
+meth maybe: Bool do return true
+
+var a: A = new B
+
+assert a isa B
+a.foo(1)
+if maybe then
+       a = new A
+else
+       a = new A
+end
+#alt1#a.foo(1)
+
+a = new B
+assert a isa B
+a.foo(2)
+if maybe then
+else
+       a = new A
+end
+#alt2#a.foo(2)
+
+a = new B
+assert a isa B
+a.foo(3)
+if maybe then
+       a = new A
+else
+end
+#alt3#a.foo(3)
+
+a = new B
+assert a isa B
+a.foo(4)
+if maybe then
+else
+end
+a.foo(4)
+
+a = new B
+assert a isa B
+a.foo(5)
+if maybe then
+       a = new A
+end
+#alt4#a.foo(5)
+
+a = new B
+assert a isa B
+a.foo(6)
+if maybe then
+end
+a.foo(6)
+
+a = new B
+assert a isa B
+a.foo(7)
+while not maybe do
+       #alt5#a = new A
+end
+a.foo(7)
+
+a = new B
+assert a isa B
+a.foo(8)
+while not maybe do
+end
+a.foo(8)
+
index ec90a06..a862583 100644 (file)
@@ -26,13 +26,13 @@ class B
        init do end
 end
 
-var a: Object
-var a = new A
-var b = new B
+
+var a: Object = new A
+var b: Object = new B
 (a isa A).output
 (a isa Object).output
 (not a isa B).output
-a = null
-(a isa A).output
-(a isa Object).output
-(a isa B).output
+var c: Object = null
+(c isa A).output
+(c isa Object).output
+(c isa B).output
index c378c50..528a2bc 100644 (file)
@@ -1,3 +1,2 @@
-alt/base_isa_cast2_alt8.nit:47,1--7: Error: Method 'foo' doesn't exists in A.
 alt/base_isa_cast2_alt8.nit:50,2--8: Error: Method 'foo' doesn't exists in A.
 alt/base_isa_cast2_alt8.nit:62,1--7: Error: Method 'foo' doesn't exists in A.
diff --git a/tests/sav/base_isa_cast4.sav b/tests/sav/base_isa_cast4.sav
new file mode 100644 (file)
index 0000000..55d9015
--- /dev/null
@@ -0,0 +1,12 @@
+1
+2
+3
+4
+4
+5
+6
+6
+7
+7
+8
+8
diff --git a/tests/sav/base_isa_cast4_alt1.sav b/tests/sav/base_isa_cast4_alt1.sav
new file mode 100644 (file)
index 0000000..2ea0fd0
--- /dev/null
@@ -0,0 +1 @@
+alt/base_isa_cast4_alt1.nit:40,1--7: Error: Method 'foo' doesn't exists in A.
diff --git a/tests/sav/base_isa_cast4_alt2.sav b/tests/sav/base_isa_cast4_alt2.sav
new file mode 100644 (file)
index 0000000..5052496
--- /dev/null
@@ -0,0 +1 @@
+alt/base_isa_cast4_alt2.nit:49,1--7: Error: Method 'foo' doesn't exists in A.
diff --git a/tests/sav/base_isa_cast4_alt3.sav b/tests/sav/base_isa_cast4_alt3.sav
new file mode 100644 (file)
index 0000000..8c30f37
--- /dev/null
@@ -0,0 +1 @@
+alt/base_isa_cast4_alt3.nit:58,1--7: Error: Method 'foo' doesn't exists in A.
diff --git a/tests/sav/base_isa_cast4_alt4.sav b/tests/sav/base_isa_cast4_alt4.sav
new file mode 100644 (file)
index 0000000..631b853
--- /dev/null
@@ -0,0 +1 @@
+alt/base_isa_cast4_alt4.nit:74,1--7: Error: Method 'foo' doesn't exists in A.
diff --git a/tests/sav/base_isa_cast4_alt5.sav b/tests/sav/base_isa_cast4_alt5.sav
new file mode 100644 (file)
index 0000000..292de6b
--- /dev/null
@@ -0,0 +1 @@
+alt/base_isa_cast4_alt5.nit:87,6--10: Type error: expected B, got A