Skip to content

Commit

Permalink
Ill-scoped reference checking in TreeCheckers
Browse files Browse the repository at this point in the history
Find trees which have an info referring to an out-of-scope
type parameter or local symbol, as could happen in the test
for SI-6981, in which tree transplanting did not substitute
symbols in symbol infos.

The enclosed, pending test for that bug that will now
fail under -Ycheck:extmethods -Xfatal-warnings.

    [Now checking: extmethods]
    [check: extmethods] The symbol, tpe or info of tree `(@scala.annotation.tailrec def loop(x: A): Unit = loop(x)) : (x: A)Unit` refers to a out-of-scope symbol, type A in class Foo. tree.symbol.ownerChain: method loop, method bippy$extension, object Foo, object O, package <empty>, package <root>
    [check: extmethods] The symbol, tpe or info of tree `(val x: A = _) : A` refers to a out-of-scope symbol, type A in class Foo. tree.symbol.ownerChain: value x, method loop, method bippy$extension, object Foo, object O, package <empty>, package <root>
    [check: extmethods] The symbol, tpe or info of tree `(loop(x)) : (x: A)Unit` refers to a out-of-scope symbol, type A in class Foo. tree.symbol.ownerChain: method loop, method bippy$extension, object Foo, object O, package <empty>, package <root>
    [check: extmethods] The symbol, tpe or info of tree `(loop) : (x: A)Unit` refers to a out-of-scope symbol, type A in class Foo. tree.symbol.ownerChain: method loop, method bippy$extension, object Foo, object O, package <empty>, package <root>
    [check: extmethods] The symbol, tpe or info of tree `(x) : A` refers to a out-of-scope symbol, type A in class Foo. tree.symbol.ownerChain: value x, method loop, method bippy$extension, object Foo, object O, package <empty>, package <root>
    [check: extmethods] The symbol, tpe or info of tree `(<synthetic> val x2: O.Foo[A] = (x1.asInstanceOf[O.Foo[A]]: O.Foo[A])) : O.Foo[A]` refers to a out-of-scope symbol, type A in class Foo. tree.symbol.ownerChain: value x2, method equals$extension, object Foo, object O, package <empty>, package <root>
    [check: extmethods] The symbol, tpe or info of tree `(<synthetic> val Foo$1: O.Foo[A] = x$1.asInstanceOf[O.Foo[A]]) : O.Foo[A]` refers to a out-of-scope symbol, type A in class Foo. tree.symbol.ownerChain: value Foo$1, method equals$extension, object Foo, object O, package <empty>, package <root>
    [check: extmethods] The symbol, tpe or info of tree `(Foo$1) : O.Foo[A]` refers to a out-of-scope symbol, type A in class Foo. tree.symbol.ownerChain: value Foo$1, method equals$extension, object Foo, object O, package <empty>, package <root>
    error: TreeCheckers detected non-compliant trees in t6891.scala
    one error found
  • Loading branch information
retronym committed Jan 26, 2013
1 parent 05ad682 commit cff0934
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 2 deletions.
45 changes: 43 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala
Expand Up @@ -117,7 +117,8 @@ abstract class TreeCheckers extends Analyzer {
try p.source.path + ":" + p.line
catch { case _: UnsupportedOperationException => p.toString }

def errorFn(msg: Any): Unit = println("[check: %s] %s".format(phase.prev, msg))
private var hasError: Boolean = false
def errorFn(msg: Any): Unit = {hasError = true; println("[check: %s] %s".format(phase.prev, msg))}
def errorFn(pos: Position, msg: Any): Unit = errorFn(posstr(pos) + ": " + msg)
def informFn(msg: Any) {
if (settings.verbose.value || settings.debug.value)
Expand Down Expand Up @@ -151,6 +152,7 @@ abstract class TreeCheckers extends Analyzer {
result
}
def runWithUnit[T](unit: CompilationUnit)(body: => Unit): Unit = {
hasError = false
val unit0 = currentUnit
currentRun.currentUnit = unit
body
Expand All @@ -169,6 +171,7 @@ abstract class TreeCheckers extends Analyzer {
checker.precheck.traverse(unit.body)
checker.typed(unit.body)
checker.postcheck.traverse(unit.body)
if (hasError) unit.warning(NoPosition, "TreeCheckers detected non-compliant trees in " + unit)
}
}

Expand Down Expand Up @@ -217,8 +220,11 @@ abstract class TreeCheckers extends Analyzer {
case _ => ()
}

object precheck extends Traverser {
object precheck extends TreeStackTraverser {
override def traverse(tree: Tree) {
checkSymbolRefsRespectScope(tree)
checkReturnReferencesDirectlyEnclosingDef(tree)

val sym = tree.symbol
def accessed = sym.accessed
def fail(msg: String) = errorFn(tree.pos, msg + classstr(tree) + " / " + tree)
Expand Down Expand Up @@ -289,6 +295,41 @@ abstract class TreeCheckers extends Analyzer {
}
super.traverse(tree)
}

private def checkSymbolRefsRespectScope(tree: Tree) {
def symbolOf(t: Tree): Symbol = Option(tree.symbol).getOrElse(NoSymbol)
def definedSymbolOf(t: Tree): Symbol = if (t.isDef) symbolOf(t) else NoSymbol
val info = Option(symbolOf(tree).info).getOrElse(NoType)
val referencedSymbols: List[Symbol] = {
val directRef = tree match {
case _: RefTree => symbolOf(tree).toOption
case _ => None
}
def referencedSyms(tp: Type) = (tp collect {
case TypeRef(_, sym, _) => sym
}).toList
val indirectRefs = referencedSyms(info)
(indirectRefs ++ directRef).distinct
}
for {
sym <- referencedSymbols
if (sym.isTypeParameter || sym.isLocal) && !(tree.symbol hasTransOwner sym.owner)
} errorFn(s"The symbol, tpe or info of tree `(${tree}) : ${info}` refers to a out-of-scope symbol, ${sym.fullLocationString}. tree.symbol.ownerChain: ${tree.symbol.ownerChain.mkString(", ")}")
}

private def checkReturnReferencesDirectlyEnclosingDef(tree: Tree) {
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 _ =>
}
}
}

object postcheck extends Traverser {
Expand Down
2 changes: 2 additions & 0 deletions src/reflect/scala/reflect/internal/Symbols.scala
Expand Up @@ -1651,6 +1651,8 @@ trait Symbols extends api.Symbols { self: SymbolTable =>

@inline final def map(f: Symbol => Symbol): Symbol = if (this eq NoSymbol) this else f(this)

final def toOption: Option[Symbol] = if (exists) Some(this) else None

// ------ cloneing -------------------------------------------------------------------

/** A clone of this symbol. */
Expand Down
9 changes: 9 additions & 0 deletions src/reflect/scala/reflect/internal/Trees.scala
Expand Up @@ -1488,6 +1488,15 @@ trait Trees extends api.Trees { self: SymbolTable =>
}
}

trait TreeStackTraverser extends Traverser {
import collection.mutable
val path: mutable.Stack[Tree] = mutable.Stack()
abstract override def traverse(t: Tree) = {
path push t
try super.traverse(t) finally path.pop()
}
}

private lazy val duplicator = new Transformer {
override val treeCopy = newStrictTreeCopier
override def transform(t: Tree) = {
Expand Down
1 change: 1 addition & 0 deletions test/pending/pos/t6891.flags
@@ -0,0 +1 @@
-Ycheck:extmethods -Xfatal-warnings
19 changes: 19 additions & 0 deletions test/pending/pos/t6891.scala
@@ -0,0 +1,19 @@
object O {
implicit class Foo[A](val value: String) extends AnyVal {
def bippy() = {
@annotation.tailrec def loop(x: A): Unit = loop(x)
()
}

def boppy() = {
@annotation.tailrec def loop(x: value.type): Unit = loop(x)
()
}
}
// uncaught exception during compilation: Types$TypeError("type mismatch;
// found : A(in method bippy$extension)
// required: A(in class Foo)") @ scala.tools.nsc.typechecker.Contexts$Context.issueCommon(Contexts.scala:396)
// error: scala.reflect.internal.Types$TypeError: type mismatch;
// found : A(in method bippy$extension)
// required: A(in class Foo)
}

0 comments on commit cff0934

Please sign in to comment.