Skip to content

Commit e33a738

Browse files
committed
Enabled variance checking
Variance checking is now run as part of type-checking. Fixed tests that exhibited variance errors. Added tests where some classes of variance errors should be detected.
1 parent 38761d9 commit e33a738

File tree

10 files changed

+134
-40
lines changed

10 files changed

+134
-40
lines changed

src/dotty/tools/dotc/core/Types.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2203,6 +2203,7 @@ object Types {
22032203
override def variance = 1
22042204
override def toString = "Co" + super.toString
22052205
}
2206+
22062207
final class ContraTypeBounds(lo: Type, hi: Type, hc: Int) extends CachedTypeBounds(lo, hi, hc) {
22072208
override def variance = -1
22082209
override def toString = "Contra" + super.toString

src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
844844
checkNoDoubleDefs(cls)
845845
val impl1 = cpy.Template(impl, constr1, parents1, self1, body1)
846846
.withType(dummy.termRef)
847+
VarianceChecker.check(impl1)
847848
assignType(cpy.TypeDef(cdef, mods1, name, impl1), cls)
848849

849850
// todo later: check that

src/dotty/tools/dotc/typer/CheckVariances.scala renamed to src/dotty/tools/dotc/typer/VarianceChecker.scala

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,81 @@
11
package dotty.tools.dotc
2-
package transform
2+
package typer
33

44
import dotty.tools.dotc.ast.{ Trees, tpd }
55
import core._
6-
import Types._, Contexts._, Flags._, Symbols._, Annotations._, Trees._
6+
import Types._, Contexts._, Flags._, Symbols._, Annotations._, Trees._, NameOps._
77
import Decorators._
88
import Variances._
99

1010
object VarianceChecker {
11-
12-
case class VarianceError(tvar: Symbol, required: Variance)
11+
private case class VarianceError(tvar: Symbol, required: Variance)
12+
def check(tree: tpd.Tree)(implicit ctx: Context) =
13+
new VarianceChecker()(ctx).Traverser.traverse(tree)
1314
}
1415

1516
/** See comments at scala.reflect.internal.Variance.
1617
*/
17-
class VarianceChecker(implicit ctx: Context) {
18+
class VarianceChecker()(implicit ctx: Context) {
1819
import VarianceChecker._
1920
import tpd._
2021

2122
private object Validator extends TypeAccumulator[Option[VarianceError]] {
2223
private var base: Symbol = _
2324

25+
/** Is no variance checking needed within definition of `base`? */
26+
def ignoreVarianceIn(base: Symbol): Boolean = (
27+
base.isTerm
28+
|| base.is(Package)
29+
|| base.is(Local)
30+
)
31+
2432
/** The variance of a symbol occurrence of `tvar` seen at the level of the definition of `base`.
2533
* The search proceeds from `base` to the owner of `tvar`.
2634
* Initially the state is covariant, but it might change along the search.
2735
*/
28-
def relativeVariance(tvar: Symbol, base: Symbol, v: Variance = Covariant): Variance = {
29-
if (base.owner == tvar.owner) v
30-
else if ((base is Param) && base.owner.isTerm) relativeVariance(tvar, base.owner.owner, flip(v))
31-
else if (base.isTerm) Bivariant
36+
def relativeVariance(tvar: Symbol, base: Symbol, v: Variance = Covariant): Variance = /*ctx.traceIndented(i"relative variance of $tvar wrt $base, so far: $v")*/ {
37+
if (base == tvar.owner) v
38+
else if ((base is Param) && base.owner.isTerm)
39+
relativeVariance(tvar, paramOuter(base.owner), flip(v))
40+
else if (ignoreVarianceIn(base.owner)) Bivariant
3241
else if (base.isAliasType) relativeVariance(tvar, base.owner, Invariant)
3342
else relativeVariance(tvar, base.owner, v)
3443
}
3544

36-
def isUncheckedVariance(tp: Type): Boolean = tp match {
37-
case AnnotatedType(annot, tp1) =>
38-
annot.symbol == defn.UncheckedVarianceAnnot || isUncheckedVariance(tp1)
39-
case _ => false
40-
}
45+
/** The next level to take into account when determining the
46+
* relative variance with a method parameter as base. The method
47+
* is always skipped. If the method is a constructor, we also skip
48+
* its class owner, because constructors are not checked for variance
49+
* relative to the type parameters of their own class. On the other
50+
* hand constructors do count for checking the variance of type parameters
51+
* of enclosing classes. I believe the Scala 2 rules are too lenient in
52+
* that respect.
53+
*/
54+
private def paramOuter(meth: Symbol) =
55+
if (meth.isConstructor) meth.owner.owner else meth.owner
4156

57+
/** Check variance of abstract type `tvar` when referred from `base`. */
4258
private def checkVarianceOfSymbol(tvar: Symbol): Option[VarianceError] = {
4359
val relative = relativeVariance(tvar, base)
44-
val required = Variances.compose(relative, this.variance)
4560
if (relative == Bivariant) None
4661
else {
47-
def tvar_s = s"$tvar (${tvar.variance}${tvar.showLocated})"
62+
val required = compose(relative, this.variance)
63+
def tvar_s = s"$tvar (${varianceString(tvar.flags)} ${tvar.showLocated})"
4864
def base_s = s"$base in ${base.owner}" + (if (base.owner.isClass) "" else " in " + base.owner.enclosingClass)
49-
ctx.log(s"verifying $tvar_s is $required at $base_s")
50-
if (tvar.variance == required) None
65+
ctx.log(s"verifying $tvar_s is ${varianceString(required)} at $base_s")
66+
ctx.log(s"relative variance: ${varianceString(relative)}")
67+
ctx.log(s"current variance: ${this.variance}")
68+
ctx.log(s"owner chain: ${base.ownersIterator.toList}")
69+
if (tvar is required) None
5170
else Some(VarianceError(tvar, required))
5271
}
5372
}
5473

5574
/** For PolyTypes, type parameters are skipped because they are defined
5675
* explicitly (their TypeDefs will be passed here.) For MethodTypes, the
57-
* same is true of the parameters (ValDefs) unless we are inside a
58-
* refinement, in which case they are checked from here.
76+
* same is true of the parameters (ValDefs).
5977
*/
60-
def apply(status: Option[VarianceError], tp: Type): Option[VarianceError] =
78+
def apply(status: Option[VarianceError], tp: Type): Option[VarianceError] = ctx.traceIndented(s"variance checking $tp of $base at $variance") {
6179
if (status.isDefined) status
6280
else tp match {
6381
case tp: TypeRef =>
@@ -71,11 +89,12 @@ class VarianceChecker(implicit ctx: Context) {
7189
this(status, tp.resultType) // params will be checked in their ValDef nodes.
7290
case AnnotatedType(annot, _) if annot.symbol == defn.UncheckedVarianceAnnot =>
7391
status
74-
case tp: ClassInfo =>
75-
???
92+
//case tp: ClassInfo =>
93+
// ??? not clear what to do here yet. presumably, it's all checked at local typedefs
7694
case _ =>
7795
foldOver(status, tp)
7896
}
97+
}
7998

8099
def validateDefinition(base: Symbol): Option[VarianceError] = {
81100
val saved = this.base
@@ -85,12 +104,7 @@ class VarianceChecker(implicit ctx: Context) {
85104
}
86105
}
87106

88-
def varianceString(v: Variance) =
89-
if (v is Covariant) "covariant"
90-
else if (v is Contravariant) "contravariant"
91-
else "invariant"
92-
93-
object Traverser extends TreeTraverser {
107+
private object Traverser extends TreeTraverser {
94108
def checkVariance(sym: Symbol) = Validator.validateDefinition(sym) match {
95109
case Some(VarianceError(tvar, required)) =>
96110
ctx.error(
@@ -101,14 +115,8 @@ class VarianceChecker(implicit ctx: Context) {
101115

102116
override def traverse(tree: Tree) = {
103117
def sym = tree.symbol
104-
// No variance check for object-private/protected methods/values.
105-
// Or constructors, or case class factory or extractor.
106-
def skip = (
107-
sym == NoSymbol
108-
|| sym.is(Local)
109-
|| sym.owner.isConstructor
110-
//|| sym.owner.isCaseApplyOrUnapply // not clear why needed
111-
)
118+
// No variance check for private/protected[this] methods/values.
119+
def skip = !sym.exists || sym.is(Local)
112120
tree match {
113121
case defn: MemberDef if skip =>
114122
ctx.debuglog(s"Skipping variance check of ${sym.showDcl}")

src/dotty/tools/dotc/typer/Variances.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package dotty.tools.dotc
2-
package transform
2+
package typer
33

44
import dotty.tools.dotc.ast.{Trees, tpd}
55
import core._
@@ -90,4 +90,14 @@ object Variances {
9090
case _ =>
9191
Bivariant
9292
}
93+
94+
def varianceString(v: Variance) =
95+
if (v is Covariant) "covariant"
96+
else if (v is Contravariant) "contravariant"
97+
else "invariant"
98+
99+
def varianceString(v: Int) =
100+
if (v > 0) "+"
101+
else if (v < 0) "-"
102+
else ""
93103
}

test/dotc/tests.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ class tests extends CompilerTest {
8585
@Test def neg_tailcall2 = compileFile(negDir, "tailcall/tailrec-2", xerrors = 2)
8686
@Test def neg_tailcall3 = compileFile(negDir, "tailcall/tailrec-3", xerrors = 2)
8787
@Test def neg_t1843 = compileFile(negDir, "t1843", xerrors = 1)
88+
@Test def neg_t1843_variances = compileFile(negDir, "t1843-variances", xerrors = 1)
8889
@Test def neg_t2994 = compileFile(negDir, "t2994", xerrors = 2)
90+
@Test def neg_variances = compileFile(negDir, "variances", xerrors = 2)
8991

9092
@Test def dotc = compileDir(dotcDir + "tools/dotc", twice)
9193
@Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast", twice)

tests/neg/t1843-variances.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Scala Compiler Will Crash On this File
3+
* ... Or Will It?
4+
*
5+
*/
6+
7+
object Crash {
8+
trait UpdateType[A]
9+
case class StateUpdate[+A](updateType : UpdateType[A], value : A)
10+
case object IntegerUpdateType extends UpdateType[Integer]
11+
12+
//However this method will cause a crash
13+
def crash(updates: List[StateUpdate[_]]): Unit = {
14+
updates match {
15+
case Nil =>
16+
case u::us =>
17+
u match {
18+
//Line below seems to be the crashing line
19+
case StateUpdate(key, newValue) if (key == IntegerUpdateType) =>
20+
println("Requires a statement to induce the crash")
21+
case _ =>
22+
}
23+
}
24+
}
25+
}

tests/neg/variances.scala

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Tests variance checking on default methods
2+
import reflect.ClassTag
3+
4+
class Foo[+A: ClassTag](x: A) {
5+
6+
private[this] val elems: Array[A] = Array(x)
7+
8+
def f[B](x: Array[B] = elems): Array[B] = x // (1) should give a variance error here or ...
9+
10+
}
11+
12+
object Test extends App {
13+
14+
val foo: Foo[Object] = new Foo[String]("A")
15+
16+
val arr = foo.f[Object]()
17+
18+
arr(0) = new Integer(1) // (1) ... will give an ArrayStoreException here
19+
20+
}
21+
22+
class Outer[+A](x: A) {
23+
24+
private[this] var elem: A = x
25+
26+
def getElem: A = elem
27+
28+
class Inner(constrParam: A) { // (2) should give a variance error here or ...
29+
elem = constrParam
30+
}
31+
32+
}
33+
34+
object Test2 extends App {
35+
val o1: Outer[String] = new Outer[String]("A")
36+
val o2: Outer[Object] = o1
37+
new o2.Inner(new Integer(1))
38+
39+
val x: String = o1.getElem // (2) ... will give a classcast exeption here
40+
41+
}
42+
43+
44+

tests/pos/t1843.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
object Crash {
8-
trait UpdateType[A]
8+
trait UpdateType[+A]
99
case class StateUpdate[+A](updateType : UpdateType[A], value : A)
1010
case object IntegerUpdateType extends UpdateType[Integer]
1111

tests/pos/typers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ object typers {
3232
}
3333

3434
class List[+T] {
35-
def :: (x: T) = new :: (x, this)
35+
def :: [U >: T](x: U): List[U] = new :: (x, this)
3636

3737
def len: Int = this match {
3838
case x :: xs1 => 1 + xs1.len

tests/pos/variances.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait C[+T <: C[T, U], -U <: C[T, U]] {
2+
3+
}

0 commit comments

Comments
 (0)