From 426478abcd8ea04891a0fb4839a246ee0d13ea0f Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Mon, 22 Jun 2009 14:07:07 -0400 Subject: [PATCH] syntax: variable assignment can bypass cast 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 --- src/syntax/control_flow.nit | 20 +++++++- src/syntax/typing.nit | 46 +++++++++++++----- tests/base_classid.nit | 12 ++--- tests/base_isa_cast.nit | 6 +-- tests/base_isa_cast2.nit | 6 +-- tests/base_isa_cast4.nit | 97 +++++++++++++++++++++++++++++++++++++ tests/base_isa_nil.nit | 14 +++--- tests/sav/base_isa_cast2.sav | 1 + tests/sav/base_isa_cast2_alt8.sav | 1 - tests/sav/base_isa_cast4.sav | 12 +++++ tests/sav/base_isa_cast4_alt1.sav | 1 + tests/sav/base_isa_cast4_alt2.sav | 1 + tests/sav/base_isa_cast4_alt3.sav | 1 + tests/sav/base_isa_cast4_alt4.sav | 1 + tests/sav/base_isa_cast4_alt5.sav | 1 + 15 files changed, 186 insertions(+), 34 deletions(-) create mode 100644 tests/base_isa_cast4.nit create mode 100644 tests/sav/base_isa_cast4.sav create mode 100644 tests/sav/base_isa_cast4_alt1.sav create mode 100644 tests/sav/base_isa_cast4_alt2.sav create mode 100644 tests/sav/base_isa_cast4_alt3.sav create mode 100644 tests/sav/base_isa_cast4_alt4.sav create mode 100644 tests/sav/base_isa_cast4_alt5.sav diff --git a/src/syntax/control_flow.nit b/src/syntax/control_flow.nit index f693eaa..4ac5723 100644 --- a/src/syntax/control_flow.nit +++ b/src/syntax/control_flow.nit @@ -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 diff --git a/src/syntax/typing.nit b/src/syntax/typing.nit index f9d5eba..e7aa1a5 100644 --- a/src/syntax/typing.nit +++ b/src/syntax/typing.nit @@ -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 diff --git a/tests/base_classid.nit b/tests/base_classid.nit index 30e14b3..5722f61 100644 --- a/tests/base_classid.nit +++ b/tests/base_classid.nit @@ -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 diff --git a/tests/base_isa_cast.nit b/tests/base_isa_cast.nit index f57511a..9a0adc5 100644 --- a/tests/base_isa_cast.nit +++ b/tests/base_isa_cast.nit @@ -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 diff --git a/tests/base_isa_cast2.nit b/tests/base_isa_cast2.nit index 4bccc58..14de354 100644 --- a/tests/base_isa_cast2.nit +++ b/tests/base_isa_cast2.nit @@ -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 index 0000000..9104a90 --- /dev/null +++ b/tests/base_isa_cast4.nit @@ -0,0 +1,97 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2009 Jean Privat +# +# 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) + diff --git a/tests/base_isa_nil.nit b/tests/base_isa_nil.nit index ec90a06..a862583 100644 --- a/tests/base_isa_nil.nit +++ b/tests/base_isa_nil.nit @@ -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 diff --git a/tests/sav/base_isa_cast2.sav b/tests/sav/base_isa_cast2.sav index c65db77..85221a6 100644 --- a/tests/sav/base_isa_cast2.sav +++ b/tests/sav/base_isa_cast2.sav @@ -1,4 +1,5 @@ 1 2 3 +4 6 diff --git a/tests/sav/base_isa_cast2_alt8.sav b/tests/sav/base_isa_cast2_alt8.sav index c378c50..528a2bc 100644 --- a/tests/sav/base_isa_cast2_alt8.sav +++ b/tests/sav/base_isa_cast2_alt8.sav @@ -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 index 0000000..55d9015 --- /dev/null +++ b/tests/sav/base_isa_cast4.sav @@ -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 index 0000000..2ea0fd0 --- /dev/null +++ b/tests/sav/base_isa_cast4_alt1.sav @@ -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 index 0000000..5052496 --- /dev/null +++ b/tests/sav/base_isa_cast4_alt2.sav @@ -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 index 0000000..8c30f37 --- /dev/null +++ b/tests/sav/base_isa_cast4_alt3.sav @@ -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 index 0000000..631b853 --- /dev/null +++ b/tests/sav/base_isa_cast4_alt4.sav @@ -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 index 0000000..292de6b --- /dev/null +++ b/tests/sav/base_isa_cast4_alt5.sav @@ -0,0 +1 @@ +alt/base_isa_cast4_alt5.nit:87,6--10: Type error: expected B, got A -- 1.7.9.5