/* NSC -- new Scala compiler * Copyright 2005-2013 LAMP/EPFL * @author Martin Odersky */ package scala.tools.nsc package typechecker import scala.collection.mutable import scala.reflect.internal.util.shortClassOfInstance import scala.reflect.internal.util.StringOps._ abstract class TreeCheckers extends Analyzer { import global._ override protected def onTreeCheckerError(pos: Position, msg: String) { if (settings.fatalWarnings) reporter.warning(pos, "\n** Error during internal checking:\n" + msg) } case class DiffResult[T](lost: List[T], gained: List[T]) { def isEmpty = lost.isEmpty && gained.isEmpty def lost_s = if (lost.isEmpty) "" else lost.mkString("lost: ", ", ", "") def gained_s = if (gained.isEmpty) "" else gained.mkString("gained: ", ", ", "") override def toString = ojoin(lost_s, gained_s) } def diffList[T](xs: List[T], ys: List[T]): DiffResult[T] = DiffResult(xs filterNot ys.contains, ys filterNot xs.contains) def diffTrees(t1: Tree, t2: Tree): DiffResult[Tree] = diffList(t1 filter (_ ne t1), t2 filter (_ ne t2)) def diffTemplates(t1: Template, t2: Template): String = { val parents = diffList(t1.parents, t2.parents).toString match { case "" => "" case s => "parents " + s } val stats = diffList(t1.body, t2.body).toString match { case "" => "" case s => "stats " + s } oempty(parents, stats) mkString ", " } def diff(t1: Tree, t2: Tree): String = (t1, t2) match { case (_: Literal, _: Literal) => "" case (t1: ImplDef, t2: ImplDef) => diff(t1.impl, t2.impl) case (t1: Template, t2: Template) => diffTemplates(t1, t2) case _ => diffTrees(t1, t2).toString // "" } private def clean_s(s: String) = s.replaceAllLiterally("scala.collection.", "s.c.") private def typestr(x: Type) = " (tpe = " + x + ")" private def treestr(t: Tree) = t + " [" + classString(t) + "]" + typestr(t.tpe) private def ownerstr(s: Symbol) = "'" + s + "'" + s.locationString private def wholetreestr(t: Tree) = nodeToString(t) + "\n" private def truncate(str: String, len: Int): String = ( if (str.length <= len) str else (str takeWhile (_ != '\n') take len - 3) + "..." ) private def signature(sym: Symbol) = clean_s(sym match { case null => "null" case _: ClassSymbol => sym.name + ": " + sym.tpe_* case _ => sym.defString }) private def classString(x: Any) = x match { case null => "" case t: Tree => t.shortClass case s: Symbol => s.shortSymbolClass case x: AnyRef => shortClassOfInstance(x) } private def nonPackageOwners(s: Symbol) = s.ownerChain drop 1 takeWhile (!_.hasPackageFlag) private def nonPackageOwnersPlusOne(s: Symbol) = nonPackageOwners(s) ::: (s.ownerChain dropWhile (!_.hasPackageFlag) take 1) private def ownersString(s: Symbol) = nonPackageOwnersPlusOne(s) match { case Nil => "NoSymbol" case xs => xs mkString " -> " } private def beststr(t: Tree) = "<" + { if (t.symbol != null && t.symbol != NoSymbol) "sym=" + ownerstr(t.symbol) else if (t.tpe.isComplete) "tpe=" + typestr(t.tpe) else t match { case x: DefTree => "name=" + x.name case x: RefTree => "reference=" + x.name case _ => "clazz=" + classString(t) } } + ">" /** This is a work in progress, don't take it too seriously. */ object SymbolTracker extends Traverser { type PhaseMap = mutable.Map[Symbol, List[Tree]] def symbolTreeMap[T <: Tree]() = mutable.Map[Symbol, List[T]]() withDefaultValue Nil var maps: List[(Phase, PhaseMap)] = ((NoPhase, null)) :: Nil def prev = maps.tail.head._2 def latest = maps.head._2 val defSyms = symbolTreeMap[DefTree]() val newSyms = mutable.HashSet[Symbol]() val movedMsgs = mutable.ListBuffer[String]() def sortedNewSyms = newSyms.toList.distinct sortBy (_.name.toString) def record(tree: Tree) { val sym = tree.symbol if ((sym eq null) || (sym eq NoSymbol)) return val prevMap = maps.tail.head._2 val prevTrees = if (prevMap eq null) Nil else prevMap(sym) tree match { case t: DefTree => defSyms(sym) ::= t case _ => } if (prevTrees.isEmpty) newSyms += sym else if (prevTrees exists (t => (t eq tree) || (t.symbol == sym))) () else { val s1 = (prevTrees map wholetreestr).distinct.sorted val s2 = wholetreestr(tree) if (s1 contains s2) () else movedMsgs += ("\n** %s moved:\n** Previously:\n%s\n** Currently:\n%s".format(ownerstr(sym), s1 mkString ", ", s2)) } } def reportChanges(): Unit = { // new symbols if (newSyms.nonEmpty) { informFn(newSyms.size + " new symbols.") val toPrint = if (settings.debug) sortedNewSyms mkString " " else "" newSyms.clear() if (toPrint != "") informFn(toPrint) } // moved symbols movedMsgs foreach errorFn movedMsgs.clear() // duplicate defs for ((sym, defs) <- defSyms ; if defs.size > 1) { errorFn("%s DefTrees with symbol '%s': %s".format(defs.size, ownerstr(sym), defs map beststr mkString ", ")) } defSyms.clear() } def check(ph: Phase, unit: CompilationUnit): Unit = { maps match { case ((`ph`, _)) :: _ => case _ => maps ::= ((ph, symbolTreeMap[Tree]())) } traverse(unit.body) reportChanges() } override def traverse(tree: Tree) { record(tree) super.traverse(tree) } } lazy val tpeOfTree = mutable.HashMap[Tree, Type]() private lazy val reportedAlready = mutable.HashSet[(Tree, Symbol)]() def posstr(p: Position): String = ( if (p eq null) "" else { try p.source.path + ":" + p.line catch { case _: UnsupportedOperationException => p.toString } } ) def errorFn(pos: Position, msg: Any): Unit = reporter.warning(pos, "[check: %s] %s".format(phase.prev, msg)) def errorFn(msg: Any): Unit = errorFn(NoPosition, msg) def informFn(msg: Any) { if (settings.verbose || settings.debug) println("[check: %s] %s".format(phase.prev, msg)) } def assertFn(cond: Boolean, msg: => Any) = if (!cond) errorFn(msg) private def wrap[T](msg: => Any)(body: => T): T = { try body catch { case x: Throwable => Console.println("Caught " + x) Console.println(msg) x.printStackTrace null.asInstanceOf[T] } } def checkTrees() { if (settings.verbose) Console.println("[consistency check at the beginning of phase " + phase + "]") currentRun.units foreach (x => wrap(x)(check(x))) } def runWithUnit[T](unit: CompilationUnit)(body: => Unit): Unit = { val unit0 = currentUnit currentRun.currentUnit = unit body currentRun.advanceUnit() assertFn(currentUnit == unit, "currentUnit is " + currentUnit + ", but unit is " + unit) currentRun.currentUnit = unit0 } def check(unit: CompilationUnit) { informProgress("checking "+unit) val context = rootContext(unit, checking = true) tpeOfTree.clear() SymbolTracker.check(phase, unit) val checker = new TreeChecker(context) runWithUnit(unit) { checker.precheck.traverse(unit.body) checker.typed(unit.body) checker.postcheck.traverse(unit.body) } } override def newTyper(context: Context): Typer = new TreeChecker(context) class TreeChecker(context0: Context) extends Typer(context0) { // If we don't intercept this all the synthetics get added at every phase, // with predictably unfortunate results. override protected def finishMethodSynthesis(templ: Template, clazz: Symbol, context: Context): Template = templ // XXX check for tree.original on TypeTrees. private def treesDiffer(t1: Tree, t2: Tree): Unit = { def len1 = t1.toString.length def len2 = t2.toString.length def name = t1 match { case t: NameTree => t.name case _ => t1.summaryString } def summary = s"${t1.shortClass} $name differs, bytes $len1 -> $len2, " errorFn(t1.pos, summary + diff(t1, t2)) } private def typesDiffer(tree: Tree, tp1: Type, tp2: Type) = errorFn(tree.pos, "types differ\n old: " + tp1 + "\n new: " + tp2 + "\n tree: " + tree) /** XXX Disabled reporting of position errors until there is less noise. */ private def noPos(t: Tree) = () // errorFn("no pos: " + treestr(t)) private def noType(t: Tree) = errorFn(t.pos, "no type: " + treestr(t)) private def checkSym(t: Tree) = if (t.symbol == NoSymbol) errorFn(t.pos, "no symbol: " + treestr(t)) private def passThrough(tree: Tree) = tree match { case EmptyTree | TypeTree() => true case _ => tree.tpe eq null } override def typed(tree: Tree, mode: Mode, pt: Type): Tree = ( if (passThrough(tree)) super.typed(tree, mode, pt) else checkedTyped(tree, mode, pt) ) private def checkedTyped(tree: Tree, mode: Mode, pt: Type): Tree = { val typed = wrap(tree)(super.typed(tree.clearType(), mode, pt)) // Vlad: super.typed returns null for package defs, why is that? if (typed eq null) return tree if (typed.tpe ne null) assert(!typed.tpe.isErroneous, "Tree has erroneous type: " + typed) if (tree ne typed) treesDiffer(tree, typed) tree } object precheck extends TreeStackTraverser { private var enclosingMemberDefs: List[MemberDef] = Nil private def pushMemberDef[T](md: MemberDef)(body: => T): T = { enclosingMemberDefs ::= md try body finally enclosingMemberDefs = enclosingMemberDefs.tail } override def traverse(tree: Tree): Unit = tree match { case md: MemberDef => pushMemberDef(md)(traverseInternal(tree)) case _ => traverseInternal(tree) } private def traverseInternal(tree: Tree) { if (!tree.canHaveAttrs) return checkSymbolRefsRespectScope(enclosingMemberDefs takeWhile (md => !md.symbol.hasPackageFlag), tree) checkReturnReferencesDirectlyEnclosingDef(tree) val sym = tree.symbol def accessed = sym.accessed def fail(msg: String) = errorFn(tree.pos, msg + tree.shortClass + " / " + tree) tree match { case DefDef(_, _, _, _, _, _) => if (sym.hasAccessorFlag && !sym.isDeferred) { sym.tpe.resultType match { case _: ConstantType => () case _ => checkSym(tree) /* XXX: lots of syms show up here with accessed == NoSymbol. */ if (accessed != NoSymbol) { val agetter = accessed.getterIn(sym.owner) val asetter = accessed.setterIn(sym.owner) assertFn(agetter == sym || asetter == sym, sym + " is getter or setter, but accessed sym " + accessed + " shows " + agetter + " and " + asetter ) } } } case ValDef(_, _, _, _) => if (sym.hasGetter && !sym.isOuterField && !sym.isOuterAccessor) { assertFn(sym.getterIn(sym.owner) != NoSymbol, ownerstr(sym) + " has getter but cannot be found. " + sym.ownerChain) } case Apply(fn, args) => if (args exists (_ == EmptyTree)) errorFn(tree.pos, "Apply arguments to " + fn + " contains an empty tree: " + args) case Select(qual, name) => checkSym(tree) case This(_) => checkSym(tree) if (sym.isStatic && sym.hasModuleFlag) () else if (currentOwner.ownerChain takeWhile (_ != sym) exists (_ == NoSymbol)) return fail("tree symbol "+sym+" does not point to enclosing class; tree = ") /* XXX: temporary while Import nodes are arriving untyped. */ case Import(_, _) => return case _ => } if (tree.pos == NoPosition) noPos(tree) else if (tree.tpe == null && isPastTyper) noType(tree) else if (tree.isDef) { checkSym(tree) tree match { case x: PackageDef => if ((sym.ownerChain contains currentOwner) || currentOwner.isEmptyPackageClass) () else fail(sym + " owner chain does not contain currentOwner " + currentOwner + sym.ownerChain) case _ => def cond(s: Symbol) = !s.isTerm || s.isMethod || s == sym.owner if (sym.owner != currentOwner) { val expected = currentOwner.ownerChain find (x => cond(x)) getOrElse { fail("DefTree can't find owner: ") ; NoSymbol } if (sym.owner != expected) fail(sm"""| | currentOwner chain: ${currentOwner.ownerChain take 3 mkString " -> "} | symbol chain: ${sym.ownerChain mkString " -> "}""" ) } } } super.traverse(tree) } private def checkSymbolRefsRespectScope(enclosingMemberDefs: List[MemberDef], tree: Tree) { def symbolOf(t: Tree): Symbol = if (t.symbol eq null) NoSymbol else t.symbol def typeOf(t: Tree): Type = if (t.tpe eq null) NoType else t.tpe def infoOf(t: Tree): Type = symbolOf(t).info def referencesInType(tp: Type) = tp collect { case TypeRef(_, sym, _) => sym } // Accessors are known to steal the type of the underlying field without cloning existential symbols at the new owner. // This happens in Namer#accessorTypeCompleter. We just look the other way here. if (symbolOf(tree).isAccessor) return val treeSym = symbolOf(tree) val treeInfo = infoOf(tree) val treeTpe = typeOf(tree) def isOk(sym: Symbol) = treeSym hasTransOwner sym.enclosingSuchThat(x => !x.isTypeParameterOrSkolem) // account for higher order type params def isEligible(sym: Symbol) = (sym ne NoSymbol) && ( sym.isTypeParameter || sym.isLocalToBlock ) val referencedSymbols = (treeSym :: referencesInType(treeInfo)).distinct filter (sym => isEligible(sym) && !isOk(sym)) def mk[T](what: String, x: T, str: T => String = (x: T) => "" + x): ((Any, String)) = x -> s"%10s %-20s %s".format(what, classString(x), truncate(str(x), 80).trim) def encls = enclosingMemberDefs.filterNot(_.symbol == treeSym).zipWithIndex map { case (md, i) => mk(s"encl(${i+1})", md.symbol, signature) } def mkErrorMsg(outOfScope: Symbol): String = { def front = List( mk[Tree]("tree", tree), mk[Position]("position", tree.pos, posstr), mk("with sym", treeSym, signature) ) def tpes = treeTpe match { case NoType => Nil case _ => mk[Type]("and tpe", treeTpe) :: Nil } def ref = mk[Symbol]("ref to", outOfScope, (s: Symbol) => s.nameString + " (" + s.debugFlagString + ")") val pairs = front ++ tpes ++ encls ++ (ref :: Nil) val width = pairs.map(_._2.length).max val fmt = "%-" + width + "s" val lines = pairs map { case (s: Symbol, msg) => fmt.format(msg) + " in " + ownersString(s) case (x, msg) => fmt.format(msg) } lines.mkString("Out of scope symbol reference {\n", "\n", "\n}") } referencedSymbols foreach (sym => if (!reportedAlready((tree, sym))) { errorFn("\n" + mkErrorMsg(sym)) reportedAlready += ((tree, sym)) } ) } private def checkReturnReferencesDirectlyEnclosingDef(tree: Tree): Unit = tree match { case _: Return => path collectFirst { case dd: DefDef => dd } match { case None => errorFn(s"Return node ($tree) must be enclosed in a DefDef") case Some(dd) if tree.symbol != dd.symbol => errorFn(s"Return symbol (${tree.symbol}} does not reference directly enclosing DefDef (${dd.symbol})") case _ => } case _ => } } object postcheck extends Traverser { override def traverse(tree: Tree): Unit = tree match { case EmptyTree | TypeTree() => () case _ => tpeOfTree get tree foreach { oldtpe => if (tree.tpe eq null) errorFn(s"tree.tpe=null for " + tree.shortClass + " (symbol: " + classString(tree.symbol) + " " + signature(tree.symbol) + "), last seen tpe was " + oldtpe) else if (oldtpe =:= tree.tpe) () else typesDiffer(tree, oldtpe, tree.tpe) super.traverse(tree setType oldtpe) } } } } }