aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala
diff options
context:
space:
mode:
authorFelix Mulder <felix.mulder@gmail.com>2016-11-02 11:08:28 +0100
committerGuillaume Martres <smarter@ubuntu.com>2016-11-22 01:35:07 +0100
commit8a61ff432543a29234193cd1f7c14abd3f3d31a0 (patch)
treea8147561d307af862c295cfc8100d271063bb0dd /compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala
parent6a455fe6da5ff9c741d91279a2dc6fe2fb1b472f (diff)
downloaddotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.gz
dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.bz2
dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.zip
Move compiler and compiler tests to compiler dir
Diffstat (limited to 'compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala')
-rw-r--r--compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala148
1 files changed, 148 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala
new file mode 100644
index 000000000..d5dd5a024
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala
@@ -0,0 +1,148 @@
+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: MethodType =>
+ this(status, tp.resultType) // params will be checked in their TypeDef nodes.
+ case tp: PolyType =>
+ this(status, tp.resultType) // params will be checked in their 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 _ =>
+ }
+ }
+ }
+}