From 9e712d201ea7ac5c9e85f9a8e0b847210976ef29 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 7 Oct 2018 19:02:55 +0200 Subject: [PATCH 1/5] Fix #5202: Refine argForParam argForParam was overly naive for the cases where the prefix was an & or | type. --- .../src/dotty/tools/dotc/core/Types.scala | 46 ++++++++++++------- tests/neg/i5202.scala | 6 +++ 2 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 tests/neg/i5202.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 6af06094cf42..a869bbf6ce5a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1988,9 +1988,10 @@ object Types { } /** The argument corresponding to class type parameter `tparam` as seen from - * prefix `pre`. + * prefix `pre`. Can produce a TypeBounds type in case prefix is an & or | type + * and parameter is non-variant. */ - def argForParam(pre: Type)(implicit ctx: Context): Type = { + def argForParam(pre: Type, variance: Int)(implicit ctx: Context): Type = { val tparam = symbol val cls = tparam.owner val base = pre.baseType(cls) @@ -2010,10 +2011,17 @@ object Types { idx += 1 } NoType - case OrType(base1, base2) => argForParam(base1) | argForParam(base2) - case AndType(base1, base2) => argForParam(base1) & argForParam(base2) + case base: AndOrType => + var tp1 = argForParam(base.tp1, variance) + var tp2 = argForParam(base.tp2, variance) + if (tp1.isInstanceOf[TypeBounds] || tp2.isInstanceOf[TypeBounds] || variance == 0) { + // compute argument as a type bounds instead of a point type + tp1 = tp1.bounds + tp2 = tp2.bounds + } + if (base.isAnd == variance >= 0) tp1 & tp2 else tp1 | tp2 case _ => - if (pre.termSymbol is Package) argForParam(pre.select(nme.PACKAGE)) + if (pre.termSymbol is Package) argForParam(pre.select(nme.PACKAGE), variance) else if (pre.isBottomType) pre else NoType } @@ -2037,7 +2045,7 @@ object Types { else { if (isType) { val res = - if (currentSymbol.is(ClassTypeParam)) argForParam(prefix) + if (currentSymbol.is(ClassTypeParam)) argForParam(prefix, currentSymbol.paramVariance) else prefix.lookupRefined(name) if (res.exists) return res if (Config.splitProjections) @@ -4462,14 +4470,17 @@ object Types { * If the expansion is a wildcard parameter reference, convert its * underlying bounds to a range, otherwise return the expansion. */ - def expandParam(tp: NamedType, pre: Type): Type = tp.argForParam(pre) match { - case arg @ TypeRef(pre, _) if pre.isArgPrefixOf(arg.symbol) => - arg.info match { - case TypeBounds(lo, hi) => range(atVariance(-variance)(reapply(lo)), reapply(hi)) - case arg => reapply(arg) - } - case arg => reapply(arg) - } + def expandParam(tp: NamedType, pre: Type, variance: Int): Type = + tp.argForParam(pre, variance) match { + case arg @ TypeRef(pre, _) if pre.isArgPrefixOf(arg.symbol) => + arg.info match { + case TypeBounds(lo, hi) => range(atVariance(-variance)(reapply(lo)), reapply(hi)) + case arg => reapply(arg) + } + case TypeBounds(lo, hi) => + range(lo, hi) + case arg => reapply(arg) + } /** Derived selection. * @pre the (upper bound of) prefix `pre` has a member named `tp.name`. @@ -4479,12 +4490,15 @@ object Types { else pre match { case Range(preLo, preHi) => val forwarded = - if (tp.symbol.is(ClassTypeParam)) expandParam(tp, preHi) + if (tp.symbol.is(ClassTypeParam)) expandParam(tp, preHi, tp.symbol.paramVariance) else tryWiden(tp, preHi) forwarded.orElse( range(super.derivedSelect(tp, preLo), super.derivedSelect(tp, preHi))) case _ => - super.derivedSelect(tp, pre) + super.derivedSelect(tp, pre) match { + case TypeBounds(lo, hi) => range(lo, hi) + case tp => tp + } } override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type): Type = diff --git a/tests/neg/i5202.scala b/tests/neg/i5202.scala new file mode 100644 index 000000000000..02044707a10d --- /dev/null +++ b/tests/neg/i5202.scala @@ -0,0 +1,6 @@ +object Test { + val f: (Int => Int) | (String => Int) = (a: Int) => a + 3 + + f.apply(5) // error - found: Int expected: Int & String + f("c") // error - found: String expected: Int & String +} \ No newline at end of file From 00465f210301f14d44fbd2c6b2b549e3f3134884 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 7 Oct 2018 19:43:42 +0200 Subject: [PATCH 2/5] Drop single point ranges --- compiler/src/dotty/tools/dotc/core/Types.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index a869bbf6ce5a..773fc336a388 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4422,6 +4422,7 @@ object Types { protected def range(lo: Type, hi: Type): Type = if (variance > 0) hi else if (variance < 0) lo + else if (lo `eq` hi) lo else Range(lower(lo), upper(hi)) protected def isRange(tp: Type): Boolean = tp.isInstanceOf[Range] From fc9f114590bb9c38b3b22dde1e45bcd1262666f7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Oct 2018 13:15:21 +0200 Subject: [PATCH 3/5] Address some review comments --- .../src/dotty/tools/dotc/core/Types.scala | 18 ++++++++------- tests/neg/i5202.scala | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 773fc336a388..d408c85d08ef 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2045,7 +2045,7 @@ object Types { else { if (isType) { val res = - if (currentSymbol.is(ClassTypeParam)) argForParam(prefix, currentSymbol.paramVariance) + if (currentSymbol.is(ClassTypeParam)) argForParam(prefix, symbol.paramVariance) else prefix.lookupRefined(name) if (res.exists) return res if (Config.splitProjections) @@ -4471,17 +4471,19 @@ object Types { * If the expansion is a wildcard parameter reference, convert its * underlying bounds to a range, otherwise return the expansion. */ - def expandParam(tp: NamedType, pre: Type, variance: Int): Type = - tp.argForParam(pre, variance) match { + def expandParam(tp: NamedType, pre: Type): Type = { + def expandBounds(tp: TypeBounds) = + range(atVariance(-variance)(reapply(tp.lo)), reapply(tp.hi)) + tp.argForParam(pre, tp.symbol.paramVariance) match { case arg @ TypeRef(pre, _) if pre.isArgPrefixOf(arg.symbol) => arg.info match { - case TypeBounds(lo, hi) => range(atVariance(-variance)(reapply(lo)), reapply(hi)) - case arg => reapply(arg) + case argInfo: TypeBounds => expandBounds(argInfo) + case argInfo => reapply(arg) } - case TypeBounds(lo, hi) => - range(lo, hi) + case arg: TypeBounds => expandBounds(arg) case arg => reapply(arg) } + } /** Derived selection. * @pre the (upper bound of) prefix `pre` has a member named `tp.name`. @@ -4491,7 +4493,7 @@ object Types { else pre match { case Range(preLo, preHi) => val forwarded = - if (tp.symbol.is(ClassTypeParam)) expandParam(tp, preHi, tp.symbol.paramVariance) + if (tp.symbol.is(ClassTypeParam)) expandParam(tp, preHi) else tryWiden(tp, preHi) forwarded.orElse( range(super.derivedSelect(tp, preLo), super.derivedSelect(tp, preHi))) diff --git a/tests/neg/i5202.scala b/tests/neg/i5202.scala index 02044707a10d..71bb12e3cab9 100644 --- a/tests/neg/i5202.scala +++ b/tests/neg/i5202.scala @@ -3,4 +3,27 @@ object Test { f.apply(5) // error - found: Int expected: Int & String f("c") // error - found: String expected: Int & String +} + +class Foo[A] { + def foo(a: A): Unit = {} +} +class Co[+A] { + def foo(a: A): Unit = {} // error: contravariant occurs in covariant position + def bar: A = ??? +} +class Contra[-A] { + def foo(a: A): Unit = {} +} + +object Test2 { + def main(args: Array[String]): Unit = { + val x: Foo[Int] | Foo[String] = new Foo[Int] + x.foo("") // error, found: String, required: Int & String + val y: Contra[Int] | Contra[String] = new Contra[Int] + y.foo("") // error, found: String, required: Int & String + val z: Co[Int] | Co[String] = new Co[Int] + z.foo("") // OK + val s: String = z.bar // error: found Int | String, required: String + } } \ No newline at end of file From 76ee73475597a08a787d0c37cd5362d2887bfd5d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Oct 2018 13:26:19 +0200 Subject: [PATCH 4/5] Make argForParam not take a variance parameter --- compiler/src/dotty/tools/dotc/core/Types.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d408c85d08ef..ab5bfef2d183 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1991,7 +1991,7 @@ object Types { * prefix `pre`. Can produce a TypeBounds type in case prefix is an & or | type * and parameter is non-variant. */ - def argForParam(pre: Type, variance: Int)(implicit ctx: Context): Type = { + def argForParam(pre: Type)(implicit ctx: Context): Type = { val tparam = symbol val cls = tparam.owner val base = pre.baseType(cls) @@ -2012,8 +2012,9 @@ object Types { } NoType case base: AndOrType => - var tp1 = argForParam(base.tp1, variance) - var tp2 = argForParam(base.tp2, variance) + var tp1 = argForParam(base.tp1) + var tp2 = argForParam(base.tp2) + val variance = tparam.paramVariance if (tp1.isInstanceOf[TypeBounds] || tp2.isInstanceOf[TypeBounds] || variance == 0) { // compute argument as a type bounds instead of a point type tp1 = tp1.bounds @@ -2021,7 +2022,7 @@ object Types { } if (base.isAnd == variance >= 0) tp1 & tp2 else tp1 | tp2 case _ => - if (pre.termSymbol is Package) argForParam(pre.select(nme.PACKAGE), variance) + if (pre.termSymbol is Package) argForParam(pre.select(nme.PACKAGE)) else if (pre.isBottomType) pre else NoType } @@ -2045,7 +2046,7 @@ object Types { else { if (isType) { val res = - if (currentSymbol.is(ClassTypeParam)) argForParam(prefix, symbol.paramVariance) + if (currentSymbol.is(ClassTypeParam)) argForParam(prefix) else prefix.lookupRefined(name) if (res.exists) return res if (Config.splitProjections) @@ -4474,7 +4475,7 @@ object Types { def expandParam(tp: NamedType, pre: Type): Type = { def expandBounds(tp: TypeBounds) = range(atVariance(-variance)(reapply(tp.lo)), reapply(tp.hi)) - tp.argForParam(pre, tp.symbol.paramVariance) match { + tp.argForParam(pre) match { case arg @ TypeRef(pre, _) if pre.isArgPrefixOf(arg.symbol) => arg.info match { case argInfo: TypeBounds => expandBounds(argInfo) From 948c08501f78a97eeef7dd2a8e39d8d3ab397a11 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Oct 2018 18:26:40 +0200 Subject: [PATCH 5/5] Address remaining reviewer comment --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ab5bfef2d183..ee92d2ba34fb 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4497,7 +4497,7 @@ object Types { if (tp.symbol.is(ClassTypeParam)) expandParam(tp, preHi) else tryWiden(tp, preHi) forwarded.orElse( - range(super.derivedSelect(tp, preLo), super.derivedSelect(tp, preHi))) + range(super.derivedSelect(tp, preLo).loBound, super.derivedSelect(tp, preHi).hiBound)) case _ => super.derivedSelect(tp, pre) match { case TypeBounds(lo, hi) => range(lo, hi)