1
1
package dotty .tools .dotc
2
- package transform
2
+ package typer
3
3
4
4
import dotty .tools .dotc .ast .{ Trees , tpd }
5
5
import core ._
6
- import Types ._ , Contexts ._ , Flags ._ , Symbols ._ , Annotations ._ , Trees ._
6
+ import Types ._ , Contexts ._ , Flags ._ , Symbols ._ , Annotations ._ , Trees ._ , NameOps . _
7
7
import Decorators ._
8
8
import Variances ._
9
9
10
10
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)
13
14
}
14
15
15
16
/** See comments at scala.reflect.internal.Variance.
16
17
*/
17
- class VarianceChecker (implicit ctx : Context ) {
18
+ class VarianceChecker ()( implicit ctx : Context ) {
18
19
import VarianceChecker ._
19
20
import tpd ._
20
21
21
22
private object Validator extends TypeAccumulator [Option [VarianceError ]] {
22
23
private var base : Symbol = _
23
24
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
+
24
32
/** The variance of a symbol occurrence of `tvar` seen at the level of the definition of `base`.
25
33
* The search proceeds from `base` to the owner of `tvar`.
26
34
* Initially the state is covariant, but it might change along the search.
27
35
*/
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
32
41
else if (base.isAliasType) relativeVariance(tvar, base.owner, Invariant )
33
42
else relativeVariance(tvar, base.owner, v)
34
43
}
35
44
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
41
56
57
+ /** Check variance of abstract type `tvar` when referred from `base`. */
42
58
private def checkVarianceOfSymbol (tvar : Symbol ): Option [VarianceError ] = {
43
59
val relative = relativeVariance(tvar, base)
44
- val required = Variances .compose(relative, this .variance)
45
60
if (relative == Bivariant ) None
46
61
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}) "
48
64
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
51
70
else Some (VarianceError (tvar, required))
52
71
}
53
72
}
54
73
55
74
/** For PolyTypes, type parameters are skipped because they are defined
56
75
* 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).
59
77
*/
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 " ) {
61
79
if (status.isDefined) status
62
80
else tp match {
63
81
case tp : TypeRef =>
@@ -71,11 +89,12 @@ class VarianceChecker(implicit ctx: Context) {
71
89
this (status, tp.resultType) // params will be checked in their ValDef nodes.
72
90
case AnnotatedType (annot, _) if annot.symbol == defn.UncheckedVarianceAnnot =>
73
91
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
76
94
case _ =>
77
95
foldOver(status, tp)
78
96
}
97
+ }
79
98
80
99
def validateDefinition (base : Symbol ): Option [VarianceError ] = {
81
100
val saved = this .base
@@ -85,12 +104,7 @@ class VarianceChecker(implicit ctx: Context) {
85
104
}
86
105
}
87
106
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 {
94
108
def checkVariance (sym : Symbol ) = Validator .validateDefinition(sym) match {
95
109
case Some (VarianceError (tvar, required)) =>
96
110
ctx.error(
@@ -101,14 +115,8 @@ class VarianceChecker(implicit ctx: Context) {
101
115
102
116
override def traverse (tree : Tree ) = {
103
117
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 )
112
120
tree match {
113
121
case defn : MemberDef if skip =>
114
122
ctx.debuglog(s " Skipping variance check of ${sym.showDcl}" )
0 commit comments