aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/src/dotty/tools/dotc/transform/TreeChecker.scala')
-rw-r--r--compiler/src/dotty/tools/dotc/transform/TreeChecker.scala452
1 files changed, 452 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
new file mode 100644
index 000000000..4a09d2fef
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
@@ -0,0 +1,452 @@
+package dotty.tools.dotc
+package transform
+
+import TreeTransforms._
+import core.Names.Name
+import core.DenotTransformers._
+import core.Denotations._
+import core.SymDenotations._
+import core.Contexts._
+import core.Symbols._
+import core.Types._
+import core.Flags._
+import core.Constants._
+import core.StdNames._
+import core.Decorators._
+import core.TypeErasure.isErasedType
+import core.Phases.Phase
+import core.Mode
+import typer._
+import typer.ErrorReporting._
+import reporting.ThrowingReporter
+import ast.Trees._
+import ast.{tpd, untpd}
+import util.SourcePosition
+import collection.mutable
+import ProtoTypes._
+import config.Printers
+import java.lang.AssertionError
+
+import dotty.tools.dotc.core.Names
+
+import scala.util.control.NonFatal
+
+/** Run by -Ycheck option after a given phase, this class retypes all syntax trees
+ * and verifies that the type of each tree node so obtained conforms to the type found in the tree node.
+ * It also performs the following checks:
+ *
+ * - The owner of each definition is the same as the owner of the current typing context.
+ * - Ident nodes do not refer to a denotation that would need a select to be accessible
+ * (see tpd.needsSelect).
+ * - After typer, identifiers and select nodes refer to terms only (all types should be
+ * represented as TypeTrees then).
+ */
+class TreeChecker extends Phase with SymTransformer {
+ import ast.tpd._
+
+
+ private val seenClasses = collection.mutable.HashMap[String, Symbol]()
+ private val seenModuleVals = collection.mutable.HashMap[String, Symbol]()
+
+ def isValidJVMName(name: Name) =
+ !name.exists(c => c == '.' || c == ';' || c =='[' || c == '/')
+
+ def isValidJVMMethodName(name: Name) =
+ !name.exists(c => c == '.' || c == ';' || c =='[' || c == '/' || c == '<' || c == '>')
+
+ def printError(str: String)(implicit ctx: Context) = {
+ ctx.echo(Console.RED + "[error] " + Console.WHITE + str)
+ }
+
+ val NoSuperClass = Trait | Package
+
+ def testDuplicate(sym: Symbol, registry: mutable.Map[String, Symbol], typ: String)(implicit ctx: Context) = {
+ val name = sym.fullName.toString
+ if (this.flatClasses && registry.contains(name))
+ printError(s"$typ defined twice $sym ${sym.id} ${registry(name).id}")
+ registry(name) = sym
+ }
+
+ def checkCompanion(symd: SymDenotation)(implicit ctx: Context): Unit = {
+ val cur = symd.linkedClass
+ val prev = ctx.atPhase(ctx.phase.prev) { implicit ctx =>
+ symd.symbol.linkedClass
+ }
+
+ if (prev.exists)
+ assert(cur.exists, i"companion disappeared from $symd")
+ }
+
+ def transformSym(symd: SymDenotation)(implicit ctx: Context): SymDenotation = {
+ val sym = symd.symbol
+
+ if (sym.isClass && !sym.isAbsent) {
+ val validSuperclass = sym.isPrimitiveValueClass || defn.syntheticCoreClasses.contains(sym) ||
+ (sym eq defn.ObjectClass) || (sym is NoSuperClass) || (sym.asClass.superClass.exists)
+ if (!validSuperclass)
+ printError(s"$sym has no superclass set")
+
+ testDuplicate(sym, seenClasses, "class")
+ }
+
+ if (sym.is(Method) && sym.is(Deferred) && sym.is(Private))
+ assert(false, s"$sym is both Deferred and Private")
+
+ checkCompanion(symd)
+
+ symd
+ }
+
+ def phaseName: String = "Ycheck"
+
+ def run(implicit ctx: Context): Unit = {
+ check(ctx.allPhases, ctx)
+ }
+
+ private def previousPhases(phases: List[Phase])(implicit ctx: Context): List[Phase] = phases match {
+ case (phase: TreeTransformer) :: phases1 =>
+ val subPhases = phase.miniPhases
+ val previousSubPhases = previousPhases(subPhases.toList)
+ if (previousSubPhases.length == subPhases.length) previousSubPhases ::: previousPhases(phases1)
+ else previousSubPhases
+ case phase :: phases1 if phase ne ctx.phase =>
+ phase :: previousPhases(phases1)
+ case _ =>
+ Nil
+ }
+
+ def check(phasesToRun: Seq[Phase], ctx: Context) = {
+ val prevPhase = ctx.phase.prev // can be a mini-phase
+ val squahsedPhase = ctx.squashed(prevPhase)
+ ctx.echo(s"checking ${ctx.compilationUnit} after phase ${squahsedPhase}")
+
+ val checkingCtx = ctx
+ .fresh
+ .setMode(Mode.ImplicitsEnabled)
+ .setReporter(new ThrowingReporter(ctx.reporter))
+
+ val checker = new Checker(previousPhases(phasesToRun.toList)(ctx))
+ try checker.typedExpr(ctx.compilationUnit.tpdTree)(checkingCtx)
+ catch {
+ case NonFatal(ex) => //TODO CHECK. Check that we are bootstrapped
+ implicit val ctx: Context = checkingCtx
+ println(i"*** error while checking ${ctx.compilationUnit} after phase ${checkingCtx.phase.prev} ***")
+ throw ex
+ }
+ }
+
+ class Checker(phasesToCheck: Seq[Phase]) extends ReTyper {
+
+ val nowDefinedSyms = new mutable.HashSet[Symbol]
+ val everDefinedSyms = new mutable.HashMap[Symbol, Tree]
+
+ def withDefinedSym[T](tree: untpd.Tree)(op: => T)(implicit ctx: Context): T = tree match {
+ case tree: DefTree =>
+ val sym = tree.symbol
+ assert(isValidJVMName(sym.name), s"${sym.fullName} name is invalid on jvm")
+ everDefinedSyms.get(sym) match {
+ case Some(t) =>
+ if (t ne tree)
+ ctx.warning(i"symbol ${sym.fullName} is defined at least twice in different parts of AST")
+ // should become an error
+ case None =>
+ everDefinedSyms(sym) = tree
+ }
+ assert(!nowDefinedSyms.contains(sym), i"doubly defined symbol: ${sym.fullName} in $tree")
+
+ if (ctx.settings.YcheckMods.value) {
+ tree match {
+ case t: MemberDef =>
+ if (t.name ne sym.name) ctx.warning(s"symbol ${sym.fullName} name doesn't correspond to AST: ${t}")
+ // todo: compare trees inside annotations
+ case _ =>
+ }
+ }
+
+ nowDefinedSyms += tree.symbol
+ //ctx.echo(i"defined: ${tree.symbol}")
+ val res = op
+ nowDefinedSyms -= tree.symbol
+ //ctx.echo(i"undefined: ${tree.symbol}")
+ res
+ case _ => op
+ }
+
+ def withDefinedSyms[T](trees: List[untpd.Tree])(op: => T)(implicit ctx: Context) =
+ trees.foldRightBN(op)(withDefinedSym(_)(_))
+
+ def withDefinedSymss[T](vparamss: List[List[untpd.ValDef]])(op: => T)(implicit ctx: Context): T =
+ vparamss.foldRightBN(op)(withDefinedSyms(_)(_))
+
+ def assertDefined(tree: untpd.Tree)(implicit ctx: Context) =
+ if (tree.symbol.maybeOwner.isTerm)
+ assert(nowDefinedSyms contains tree.symbol, i"undefined symbol ${tree.symbol}")
+
+ /** assert Java classes are not used as objects */
+ def assertIdentNotJavaClass(tree: Tree)(implicit ctx: Context): Unit = tree match {
+ case _ : untpd.Ident =>
+ assert(!tree.symbol.is(JavaModule), "Java class can't be used as value: " + tree)
+ case _ =>
+ }
+
+ /** check Java classes are not used as objects */
+ def checkIdentNotJavaClass(tree: Tree)(implicit ctx: Context): Unit = tree match {
+ // case tree: untpd.Ident =>
+ // case tree: untpd.Select =>
+ // case tree: untpd.Bind =>
+ case vd : ValDef =>
+ assertIdentNotJavaClass(vd.forceIfLazy)
+ case dd : DefDef =>
+ assertIdentNotJavaClass(dd.forceIfLazy)
+ // case tree: untpd.TypeDef =>
+ case Apply(fun, args) =>
+ assertIdentNotJavaClass(fun)
+ args.foreach(assertIdentNotJavaClass _)
+ // case tree: untpd.This =>
+ // case tree: untpd.Literal =>
+ // case tree: untpd.New =>
+ case Typed(expr, _) =>
+ assertIdentNotJavaClass(expr)
+ case NamedArg(_, arg) =>
+ assertIdentNotJavaClass(arg)
+ case Assign(_, rhs) =>
+ assertIdentNotJavaClass(rhs)
+ case Block(stats, expr) =>
+ stats.foreach(assertIdentNotJavaClass _)
+ assertIdentNotJavaClass(expr)
+ case If(_, thenp, elsep) =>
+ assertIdentNotJavaClass(thenp)
+ assertIdentNotJavaClass(elsep)
+ // case tree: untpd.Closure =>
+ case Match(selector, cases) =>
+ assertIdentNotJavaClass(selector)
+ cases.foreach(caseDef => assertIdentNotJavaClass(caseDef.body))
+ case Return(expr, _) =>
+ assertIdentNotJavaClass(expr)
+ case Try(expr, cases, finalizer) =>
+ assertIdentNotJavaClass(expr)
+ cases.foreach(caseDef => assertIdentNotJavaClass(caseDef.body))
+ assertIdentNotJavaClass(finalizer)
+ // case tree: TypeApply =>
+ // case tree: Super =>
+ case SeqLiteral(elems, _) =>
+ elems.foreach(assertIdentNotJavaClass)
+ // case tree: TypeTree =>
+ // case tree: SingletonTypeTree =>
+ // case tree: AndTypeTree =>
+ // case tree: OrTypeTree =>
+ // case tree: RefinedTypeTree =>
+ // case tree: AppliedTypeTree =>
+ // case tree: ByNameTypeTree =>
+ // case tree: TypeBoundsTree =>
+ // case tree: Alternative =>
+ // case tree: PackageDef =>
+ case Annotated(arg, _) =>
+ assertIdentNotJavaClass(arg)
+ case _ =>
+ }
+
+ override def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): tpd.Tree = {
+ val tpdTree = super.typed(tree, pt)
+ checkIdentNotJavaClass(tpdTree)
+ tpdTree
+ }
+
+ override def typedUnadapted(tree: untpd.Tree, pt: Type)(implicit ctx: Context): tpd.Tree = {
+ val res = tree match {
+ case _: untpd.UnApply =>
+ // can't recheck patterns
+ tree.asInstanceOf[tpd.Tree]
+ case _: untpd.TypedSplice | _: untpd.Thicket | _: EmptyValDef[_] =>
+ super.typedUnadapted(tree)
+ case _ if tree.isType =>
+ promote(tree)
+ case _ =>
+ val tree1 = super.typedUnadapted(tree, pt)
+ def isSubType(tp1: Type, tp2: Type) =
+ (tp1 eq tp2) || // accept NoType / NoType
+ (tp1 <:< tp2)
+ def divergenceMsg(tp1: Type, tp2: Type) =
+ s"""Types differ
+ |Original type : ${tree.typeOpt.show}
+ |After checking: ${tree1.tpe.show}
+ |Original tree : ${tree.show}
+ |After checking: ${tree1.show}
+ |Why different :
+ """.stripMargin + core.TypeComparer.explained((tp1 <:< tp2)(_))
+ if (tree.hasType) // it might not be typed because Typer sometimes constructs new untyped trees and resubmits them to typedUnadapted
+ assert(isSubType(tree1.tpe, tree.typeOpt), divergenceMsg(tree1.tpe, tree.typeOpt))
+ tree1
+ }
+ checkNoOrphans(res.tpe)
+ phasesToCheck.foreach(_.checkPostCondition(res))
+ res
+ }
+
+ /** Check that PolyParams and MethodParams refer to an enclosing type */
+ def checkNoOrphans(tp: Type)(implicit ctx: Context) = new TypeMap() {
+ val definedBinders = mutable.Set[Type]()
+ def apply(tp: Type): Type = {
+ tp match {
+ case tp: BindingType =>
+ definedBinders += tp
+ mapOver(tp)
+ definedBinders -= tp
+ case tp: ParamType =>
+ assert(definedBinders.contains(tp.binder), s"orphan param: $tp")
+ case tp: TypeVar =>
+ apply(tp.underlying)
+ case _ =>
+ mapOver(tp)
+ }
+ tp
+ }
+ }.apply(tp)
+
+ def checkNotRepeated(tree: Tree)(implicit ctx: Context): tree.type = {
+ def allowedRepeated = (tree.symbol.flags is Case) && tree.tpe.widen.isRepeatedParam
+
+ assert(!tree.tpe.widen.isRepeatedParam || allowedRepeated, i"repeated parameter type not allowed here: $tree")
+ tree
+ }
+
+ /** Check that all methods have MethodicType */
+ def isMethodType(pt: Type)(implicit ctx: Context): Boolean = pt match {
+ case at: AnnotatedType => isMethodType(at.tpe)
+ case _: MethodicType => true // MethodType, ExprType, PolyType
+ case _ => false
+ }
+
+ override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = {
+ assert(tree.isTerm || !ctx.isAfterTyper, tree.show + " at " + ctx.phase)
+ assert(tree.isType || !needsSelect(tree.tpe), i"bad type ${tree.tpe} for $tree # ${tree.uniqueId}")
+ assertDefined(tree)
+
+ checkNotRepeated(super.typedIdent(tree, pt))
+ }
+
+ /** Makes sure the symbol in the tree can be approximately reconstructed by
+ * calling `member` on the qualifier type.
+ * Approximately means: The two symbols might be different but one still overrides the other.
+ */
+ override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = {
+ assert(tree.isTerm || !ctx.isAfterTyper, tree.show + " at " + ctx.phase)
+ val tpe = tree.typeOpt
+ val sym = tree.symbol
+ if (!tpe.isInstanceOf[WithFixedSym] && sym.exists && !sym.is(Private)) {
+ val qualTpe = tree.qualifier.typeOpt
+ val member =
+ if (sym.is(Private)) qualTpe.member(tree.name)
+ else qualTpe.nonPrivateMember(tree.name)
+ val memberSyms = member.alternatives.map(_.symbol)
+ assert(memberSyms.exists(mbr =>
+ sym == mbr ||
+ sym.overriddenSymbol(mbr.owner.asClass) == mbr ||
+ mbr.overriddenSymbol(sym.owner.asClass) == sym),
+ ex"""symbols differ for $tree
+ |was : $sym
+ |alternatives by type: $memberSyms%, % of types ${memberSyms.map(_.info)}%, %
+ |qualifier type : ${tree.qualifier.typeOpt}
+ |tree type : ${tree.typeOpt} of class ${tree.typeOpt.getClass}""")
+ }
+ checkNotRepeated(super.typedSelect(tree, pt))
+ }
+
+ override def typedThis(tree: untpd.This)(implicit ctx: Context) = {
+ val res = super.typedThis(tree)
+ val cls = res.symbol
+ assert(cls.isStaticOwner || ctx.owner.isContainedIn(cls), i"error while typing $tree, ${ctx.owner} is not contained in $cls")
+ res
+ }
+
+ private def checkOwner(tree: untpd.Tree)(implicit ctx: Context): Unit = {
+ def ownerMatches(symOwner: Symbol, ctxOwner: Symbol): Boolean =
+ symOwner == ctxOwner ||
+ ctxOwner.isWeakOwner && ownerMatches(symOwner, ctxOwner.owner) ||
+ ctx.phase.labelsReordered && symOwner.isWeakOwner && ownerMatches(symOwner.owner, ctxOwner)
+ assert(ownerMatches(tree.symbol.owner, ctx.owner),
+ i"bad owner; ${tree.symbol} has owner ${tree.symbol.owner}, expected was ${ctx.owner}\n" +
+ i"owner chain = ${tree.symbol.ownersIterator.toList}%, %, ctxOwners = ${ctx.outersIterator.map(_.owner).toList}%, %")
+ }
+
+ override def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(implicit ctx: Context) = {
+ val TypeDef(_, impl @ Template(constr, _, _, _)) = cdef
+ assert(cdef.symbol == cls)
+ assert(impl.symbol.owner == cls)
+ assert(constr.symbol.owner == cls)
+ assert(cls.primaryConstructor == constr.symbol, i"mismatch, primary constructor ${cls.primaryConstructor}, in tree = ${constr.symbol}")
+ checkOwner(impl)
+ checkOwner(impl.constr)
+
+ def isNonMagicalMethod(x: Symbol) =
+ x.is(Method) &&
+ !x.isCompanionMethod &&
+ !x.isValueClassConvertMethod
+
+ val symbolsNotDefined = cls.classInfo.decls.toSet.filter(isNonMagicalMethod) -- impl.body.map(_.symbol) - constr.symbol
+
+ assert(symbolsNotDefined.isEmpty,
+ i" $cls tree does not define methods: ${symbolsNotDefined.toList}%, %\n" +
+ i"expected: ${cls.classInfo.decls.toSet.filter(isNonMagicalMethod).toList}%, %\n" +
+ i"defined: ${impl.body.map(_.symbol)}%, %")
+
+ super.typedClassDef(cdef, cls)
+ }
+
+ override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) =
+ withDefinedSyms(ddef.tparams) {
+ withDefinedSymss(ddef.vparamss) {
+ if (!sym.isClassConstructor && !(sym.name eq Names.STATIC_CONSTRUCTOR)) assert(isValidJVMMethodName(sym.name), s"${sym.fullName} name is invalid on jvm")
+ val tpdTree = super.typedDefDef(ddef, sym)
+ assert(isMethodType(sym.info), i"wrong type, expect a method type for ${sym.fullName}, but found: ${sym.info}")
+ tpdTree
+ }
+ }
+
+ override def typedCase(tree: untpd.CaseDef, pt: Type, selType: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = {
+ withDefinedSyms(tree.pat.asInstanceOf[tpd.Tree].filterSubTrees(_.isInstanceOf[ast.Trees.Bind[_]])) {
+ super.typedCase(tree, pt, selType, gadtSyms)
+ }
+ }
+
+ override def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context) =
+ withDefinedSyms(tree.stats) { super.typedBlock(tree, pt) }
+
+ override def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context) =
+ withDefinedSyms(tree.bindings) { super.typedInlined(tree, pt) }
+
+ /** Check that all defined symbols have legal owners.
+ * An owner is legal if it is either the same as the context's owner
+ * or there's an owner chain of valdefs starting at the context's owner and
+ * reaching up to the symbol's owner. The reason for this relaxed matching
+ * is that we should be able to pull out an expression as an initializer
+ * of a helper value without having to do a change owner traversal of the expression.
+ */
+ override def typedStats(trees: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = {
+ for (tree <- trees) tree match {
+ case tree: untpd.DefTree => checkOwner(tree)
+ case _: untpd.Thicket => assert(false, i"unexpanded thicket $tree in statement sequence $trees%\n%")
+ case _ =>
+ }
+ super.typedStats(trees, exprOwner)
+ }
+
+ override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol], forcedDefined: Boolean = false)(implicit ctx: Context): Tree =
+ tree
+
+ override def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context) = {
+ def isPrimaryConstructorReturn =
+ ctx.owner.isPrimaryConstructor && pt.isRef(ctx.owner.owner) && tree.tpe.isRef(defn.UnitClass)
+ if (ctx.mode.isExpr &&
+ !tree.isEmpty &&
+ !isPrimaryConstructorReturn &&
+ !pt.isInstanceOf[FunProto])
+ assert(tree.tpe <:< pt, {
+ val mismatch = err.typeMismatchMsg(tree.tpe, pt)
+ i"""|${mismatch.msg}
+ |tree = $tree""".stripMargin
+ })
+ tree
+ }
+ }
+}