package dotty.tools.dotc
package typer
import dotty.tools.dotc.ast.{ Trees, tpd }
import core._
import Types._, Contexts._, Flags._, Symbols._, Annotations._, Trees._, NameOps._
import Decorators._
import Variances._
import util.Positions._
import rewrite.Rewrites.patch
import config.Printers.variances
/** Provides `check` method to check that all top-level definitions
* in tree are variance correct. Does not recurse inside methods.
* The method should be invoked once for each Template.
*/
object VarianceChecker {
case class VarianceError(tvar: Symbol, required: Variance)
def check(tree: tpd.Tree)(implicit ctx: Context) =
new VarianceChecker()(ctx).Traverser.traverse(tree)
}
class VarianceChecker()(implicit ctx: Context) {
import VarianceChecker._
import tpd._
private object Validator extends TypeAccumulator[Option[VarianceError]] {
private var base: Symbol = _
/** Is no variance checking needed within definition of `base`? */
def ignoreVarianceIn(base: Symbol): Boolean = (
base.isTerm
|| base.is(Package)
|| base.is(Local)
)
/** The variance of a symbol occurrence of `tvar` seen at the level of the definition of `base`.
* The search proceeds from `base` to the owner of `tvar`.
* Initially the state is covariant, but it might change along the search.
*/
def relativeVariance(tvar: Symbol, base: Symbol, v: Variance = Covariant): Variance = /*ctx.traceIndented(i"relative variance of $tvar wrt $base, so far: $v")*/ {
if (base == tvar.owner) v
else if ((base is Param) && base.owner.isTerm)
relativeVariance(tvar, paramOuter(base.owner), flip(v))
else if (ignoreVarianceIn(base.owner)) Bivariant
else if (base.isAliasType) relativeVariance(tvar, base.owner, Invariant)
else relativeVariance(tvar, base.owner, v)
}
/** The next level to take into account when determining the
* relative variance with a method parameter as base. The method
* is always skipped. If the method is a constructor, we also skip
* its class owner, because constructors are not checked for variance
* relative to the type parameters of their own class. On the other
* hand constructors do count for checking the variance of type parameters
* of enclosing classes. I believe the Scala 2 rules are too lenient in
* that respect.
*/
private def paramOuter(meth: Symbol) =
if (meth.isConstructor) meth.owner.owner else meth.owner
/** Check variance of abstract type `tvar` when referred from `base`. */
private def checkVarianceOfSymbol(tvar: Symbol): Option[VarianceError] = {
val relative = relativeVariance(tvar, base)
if (relative == Bivariant || tvar.is(BaseTypeArg)) None
else {
val required = compose(relative, this.variance)
def tvar_s = s"$tvar (${varianceString(tvar.flags)} ${tvar.showLocated})"
def base_s = s"$base in ${base.owner}" + (if (base.owner.isClass) "" else " in " + base.owner.enclosingClass)
ctx.log(s"verifying $tvar_s is ${varianceString(required)} at $base_s")
ctx.log(s"relative variance: ${varianceString(relative)}")
ctx.log(s"current variance: ${this.variance}")
ctx.log(s"owner chain: ${base.ownersIterator.toList}")
if (tvar is required) None
else Some(VarianceError(tvar, required))
}
}
/** For PolyTypes, type parameters are skipped because they are defined
* explicitly (their TypeDefs will be passed here.) For MethodTypes, the
* same is true of the parameters (ValDefs).
*/
def apply(status: Option[VarianceError], tp: Type): Option[VarianceError] = ctx.traceIndented(s"variance checking $tp of $base at $variance", variances) {
if (status.isDefined) status
else tp match {
case tp: TypeRef =>
val sym = tp.symbol
if (sym.variance != 0 && base.isContainedIn(sym.owner)) checkVarianceOfSymbol(sym)
else if (sym.isAliasType) this(status, sym.info.bounds.hi)
else foldOver(status, tp)
case tp: MethodOrPoly =>
this(status, tp.resultType) // params will be checked in their TypeDef or ValDef nodes.
case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot =>
status
//case tp: ClassInfo =>
// ??? not clear what to do here yet. presumably, it's all checked at local typedefs
case _ =>
foldOver(status, tp)
}
}
def validateDefinition(base: Symbol): Option[VarianceError] = {
val saved = this.base
this.base = base
try apply(None, base.info)
finally this.base = saved
}
}
private object Traverser extends TreeTraverser {
def checkVariance(sym: Symbol, pos: Position) = Validator.validateDefinition(sym) match {
case Some(VarianceError(tvar, required)) =>
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)
patch(Position(pos.end), " @scala.annotation.unchecked.uncheckedVariance") // TODO use an import or shorten if possible
}
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) || // !!! 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}")
case tree: TypeDef =>
checkVariance(sym, tree.pos)
case tree: ValDef =>
checkVariance(sym, tree.pos)
case DefDef(_, tparams, vparamss, _, _) =>
checkVariance(sym, tree.pos)
tparams foreach traverse
vparamss foreach (_ foreach traverse)
case Template(_, _, _, body) =>
traverseChildren(tree)
case _ =>
}
}
}
}