diff options
author | Martin Odersky <odersky@gmail.com> | 2015-11-05 14:13:14 +0100 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2015-11-05 14:13:40 +0100 |
commit | 1e280d03886e3b20fa28661b0d25b6b11dec56f3 (patch) | |
tree | 1b0dcad7124c0e7cb9881f0c629fb01195fb5a48 | |
parent | 4b76eeaa13176aede421af3fe86c392c438b0e5d (diff) | |
download | dotty-1e280d03886e3b20fa28661b0d25b6b11dec56f3.tar.gz dotty-1e280d03886e3b20fa28661b0d25b6b11dec56f3.tar.bz2 dotty-1e280d03886e3b20fa28661b0d25b6b11dec56f3.zip |
Handle variance unsoundness in scalac
The included test
pos-special/variances-constr.scala
demonstrates an unsoundness in the variance checking of scalac.
Scalac excludes symbols owned by constructors from the
checking. This is unsound, as can be demonstrated by compiling the test
and observing output of the program run:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at Test$.main(variances-constr.scala:17)
at Test.main(variances-constr.scala)
Dotty allows this code only under -language:Scala2 and issues a migration warning.
-rw-r--r-- | src/dotty/tools/dotc/typer/VarianceChecker.scala | 12 | ||||
-rw-r--r-- | test/dotc/tests.scala | 2 | ||||
-rw-r--r-- | tests/neg/variances-constr.scala | 26 | ||||
-rw-r--r-- | tests/pos-special/variances-constr.scala | 21 |
4 files changed, 57 insertions, 4 deletions
diff --git a/src/dotty/tools/dotc/typer/VarianceChecker.scala b/src/dotty/tools/dotc/typer/VarianceChecker.scala index 1d3ceaa57..5d3bd4f20 100644 --- a/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -110,16 +110,20 @@ class VarianceChecker()(implicit ctx: Context) { private object Traverser extends TreeTraverser { def checkVariance(sym: Symbol) = Validator.validateDefinition(sym) match { case Some(VarianceError(tvar, required)) => - ctx.error( - i"${varianceString(tvar.flags)} $tvar occurs in ${varianceString(required)} position in type ${sym.info} of $sym", - sym.pos) + def msg = i"${varianceString(tvar.flags)} $tvar occurs in ${varianceString(required)} position in type ${sym.info} of $sym" + if (ctx.scala2Mode && sym.owner.isConstructor) + ctx.migrationWarning(s"According to new variance rules, this is no longer accepted; need to annotate with @uncheckedVariance:\n$msg", sym.pos) + else ctx.error(msg, sym.pos) case None => } override def traverse(tree: Tree)(implicit ctx: Context) = { def sym = tree.symbol // No variance check for private/protected[this] methods/values. - def skip = !sym.exists || sym.is(Local) + def skip = + !sym.exists || + sym.is(Local) || // !!! watch out for protected local! + sym.is(TypeParam) && sym.owner.isClass // already taken care of in primary constructor of class tree match { case defn: MemberDef if skip => ctx.debuglog(s"Skipping variance check of ${sym.showDcl}") diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 596c4a27e..e37a53084 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -102,6 +102,7 @@ class tests extends CompilerTest { @Test def pos_t2613 = compileFile(posSpecialDir, "t2613")(allowDeepSubtypes) @Test def pos_i871 = compileFile(posSpecialDir, "i871", scala2mode) + @Test def pos_variancesConstr = compileFile(posSpecialDir, "variances-constr", scala2mode) @Test def new_all = compileFiles(newDir, twice) @@ -137,6 +138,7 @@ class tests extends CompilerTest { @Test def neg_t2994 = compileFile(negDir, "t2994", xerrors = 2) @Test def neg_subtyping = compileFile(negDir, "subtyping", xerrors = 5) @Test def neg_variances = compileFile(negDir, "variances", xerrors = 2) + @Test def neg_variancesConstr = compileFile(negDir, "variances-constr", xerrors = 2) @Test def neg_i871_missingReturnType = compileFile(negDir, "i871", xerrors = 2) @Test def neg_badAuxConstr = compileFile(negDir, "badAuxConstr", xerrors = 2) @Test def neg_typetest = compileFile(negDir, "typetest", xerrors = 1) diff --git a/tests/neg/variances-constr.scala b/tests/neg/variances-constr.scala new file mode 100644 index 000000000..a5259c4da --- /dev/null +++ b/tests/neg/variances-constr.scala @@ -0,0 +1,26 @@ +class C[+A] { + + private[this] var y: A = _ + def getY: A = y + + class Inner(x: A) { // error A appears contravariantly + y = x + } + class Inner2[B <: A](x: B) { // error A appears contravariantly + y = x + } +} + +object Test { + + def main(args: Array[String]) = { + val x = new C[String] + val y: C[Any] = x + val i = new y.Inner(1) + val s: String = x.getY + val i2 = new y.Inner2(1) + val s2: String = x.getY + println(s) + } +} + diff --git a/tests/pos-special/variances-constr.scala b/tests/pos-special/variances-constr.scala new file mode 100644 index 000000000..b210b0440 --- /dev/null +++ b/tests/pos-special/variances-constr.scala @@ -0,0 +1,21 @@ +class C[+A] { + + private[this] var y: A = _ + def getY: A = y + + class Inner(x: A) { + y = x + } +} + +object Test { + + def main(args: Array[String]) = { + val x = new C[String] + val y: C[Any] = x + val i = new y.Inner(1) + val s: String = x.getY + println(s) + } +} + |