diff options
author | Paul Phillips <paulp@improving.org> | 2010-09-27 19:51:27 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-09-27 19:51:27 +0000 |
commit | a56c33b6a470939a1396015972f09e19dc493245 (patch) | |
tree | 4c38946754440c8860180ccda2b1bb83eb938a3a /src | |
parent | b40f4ba3221f1091f4821423f18c157907e9a3c7 (diff) | |
download | scala-a56c33b6a470939a1396015972f09e19dc493245.tar.gz scala-a56c33b6a470939a1396015972f09e19dc493245.tar.bz2 scala-a56c33b6a470939a1396015972f09e19dc493245.zip |
Bringing the tree and icode checkers back to life.
build/pack/bin/scalac -d /tmp -Ycheck-debug -Ycheck:all \
src/compiler/scala/tools/nsc/Global.scala
That blows up in constructors as most files do, so also try it with
-Ycheck:icode to see the pretty icode output (for a little while anyway,
after which it will again blow up.)
Our work has only just begun! See test/checker-tests/fail*.scala for
11 examples of places where the checker cries foul. Many of them are
telling us about real issues and we should listen, but I will need help
to figure out which are legitimate and which should be eliminated by
altering the checkers.
This patch also hacks on some territory the checkers drew me into,
especially TypeKinds, where I figured anything which had been commented
out since 2005 was fair game.
(Optional) review by dragos. (The one place I know I could use a look is
in Checkers.scala, because I had to relax some checks and add at least
one newer opcode.)
Diffstat (limited to 'src')
22 files changed, 661 insertions, 665 deletions
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 75dd7aea63..6d9bc2976a 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -755,12 +755,11 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable if (settings.check contains globalPhase.prev.name) { if (globalPhase.prev.checkable) { phase = globalPhase + inform("[Now checking: " + phase.prev.name + "]") if (globalPhase.id >= icodePhase.id) icodeChecker.checkICodes else checker.checkTrees } - else if (!settings.check.doAllPhases) { - warning("It is not possible to check the result of the "+globalPhase.name+" phase") - } + else inform("[Not checkable: " + globalPhase.prev.name + "]") } if (settings.Ystatistics.value) statistics.print(phase) advancePhase diff --git a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala b/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala index 3ce61cf65e..32c0d936b2 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala @@ -405,13 +405,9 @@ trait BasicBlocks { out.println() } - def fullString: String = { - val buf = new StringBuilder() - buf.append("Block ").append(label.toString()) - buf.append("\nSuccessors: ").append(successors) - buf.append("\nPredecessors: ").append(predecessors) - buf.toString() - } + private def succString = if (successors.isEmpty) "[S: N/A]" else successors.distinct.mkString("[S: ", ", ", "]") + private def predString = if (predecessors.isEmpty) "[P: N/A]" else predecessors.distinct.mkString("[P: ", ", ", "]") + def fullString: String = List("Block", label, succString, predString) mkString " " override def toString(): String = "" + label diff --git a/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala b/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala index 2b4d66fc49..8f402876bb 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala @@ -3,7 +3,6 @@ * @author Martin Odersky */ - package scala.tools.nsc package backend package icode @@ -66,9 +65,15 @@ abstract class Checkers { val emptyStack = new TypeStack() val STRING = REFERENCE(definitions.StringClass) - val SCALA_ALL = REFERENCE(definitions.NothingClass) - val SCALA_ALL_REF = REFERENCE(definitions.NullClass) - val THROWABLE = REFERENCE(definitions.ThrowableClass) + val SCALA_NOTHING = REFERENCE(definitions.NothingClass) + val SCALA_NULL = REFERENCE(definitions.NullClass) + + /** A wrapper to route log messages to debug output also. + */ + def logChecker(msg: String) = { + log(msg) + checkerDebug(msg) + } def checkICodes: Unit = { if (settings.verbose.value) @@ -76,33 +81,30 @@ abstract class Checkers { classes.values foreach check } + /** Only called when m1 < m2, so already known that (m1 ne m2). + */ + private def isConfict(m1: IMember, m2: IMember, canOverload: Boolean) = ( + (m1.symbol.name == m2.symbol.name) && + (!canOverload || (m1.symbol.tpe =:= m2.symbol.tpe)) + ) + def check(cls: IClass) { - log("Checking class " + cls) + logChecker("\n** Checking class " + cls) clasz = cls - for (f1 <- cls.fields; f2 <- cls.fields if f1 ne f2) - if (f1.symbol.name == f2.symbol.name) - Checkers.this.global.error("Repetitive field name: " + - f1.symbol.fullName); - - for (m1 <- cls.methods; m2 <- cls.methods if m1 ne m2) - if (m1.symbol.name == m2.symbol.name && - m1.symbol.tpe =:= m2.symbol.tpe) - Checkers.this.global.error("Repetitive method: " + - m1.symbol.fullName); - clasz.methods.foreach(check) - } + for (f1 <- cls.fields ; f2 <- cls.fields ; if f1 < f2) + if (isConfict(f1, f2, false)) + Checkers.this.global.error("Repetitive field name: " + f1.symbol.fullName) - /** Apply the give function to each pair of the cartesian product of - * l1 x l2. - */ - def pairwise[a](l1: List[a], l2: List[a])(f: (a, a) => Unit) = - l1 foreach { x => - l2 foreach { y => f(x, y) } - } + for (m1 <- cls.methods ; m2 <- cls.methods ; if m1 < m2) + if (isConfict(m1, m2, true)) + Checkers.this.global.error("Repetitive method: " + m1.symbol.fullName) + + clasz.methods foreach check + } def check(m: IMethod) { - log("Checking method " + m) + logChecker("\n** Checking method " + m) method = m if (!m.isDeferred) check(m.code) @@ -116,7 +118,8 @@ abstract class Checkers { if (!(worklist contains bl)) worklist += bl - in.clear; out.clear; + in.clear; + out.clear; code = c; worklist += c.startBlock for (bl <- c.blocks) { @@ -124,15 +127,17 @@ abstract class Checkers { out += (bl -> emptyStack) } - while (worklist.length > 0) { - val block = worklist(0); worklist.trimStart(1); + while (worklist.nonEmpty) { + val block = worklist(0); + worklist.trimStart(1); val output = check(block, in(block)); - if (output != out(block) || - (out(block) eq emptyStack)) { - log("Output changed for block: " + block.fullString); - out(block) = output; - append(block.successors); - block.successors foreach meet; + if (output != out(block) || (out(block) eq emptyStack)) { + if (block.successors.nonEmpty || block.successors.nonEmpty) + logChecker("Output changed for " + block.fullString) + + out(block) = output + append(block.successors) + block.successors foreach meet } } } @@ -149,9 +154,19 @@ abstract class Checkers { if (s1 eq emptyStack) s2 else if (s2 eq emptyStack) s1 else { - if (s1.length != s2.length) + if (s1.isEmpty && s2.isEmpty) { + // PP: I do not know the significance of this condition, but it arises a lot + // so I'm taking the intuitive position that any two empty stacks are as good + // as another, rather than throwing an exception as it did. + // If the reference eq test is achieving something please document. + return emptyStack + } + else if (s1.length != s2.length) throw new CheckerException("Incompatible stacks: " + s1 + " and " + s2 + " in " + method + " at entry to block: " + bl); - new TypeStack((s1.types, s2.types).zipped map lub) + + val types = (s1.types, s2.types).zipped map lub + checkerDebug("Checker created new stack: (%s, %s) => %s".format(s1.types, s2.types, types)) + new TypeStack(types) } } @@ -170,29 +185,67 @@ abstract class Checkers { * produced type stack. */ def check(b: BasicBlock, initial: TypeStack): TypeStack = { - log("** Checking block:\n" + b.fullString + " with initial stack:\n" + initial) + logChecker({ + val prefix = "** Checking " + b.fullString + + if (initial.isEmpty) prefix + else prefix + " with initial stack " + initial.types.mkString("[", ", ", "]") + }) + var stack = new TypeStack(initial) + def checkStack(len: Int) { + if (stack.length < len) + ICodeChecker.this.error("Expected at least " + len + " elements on the stack", stack) + } + + def sizeString(push: Boolean) = { + val arrow = if (push) "-> " else "<- " + val sp = " " * stack.length + + sp + stack.length + arrow + } + def _popStack: TypeKind = { + if (stack.isEmpty) { + error("Popped empty stack in " + b.fullString + ", throwing a Unit") + return UNIT + } + + val res = stack.pop + checkerDebug(sizeString(false) + res) + res + } + def popStack = { checkStack(1) ; _popStack } + def popStack2 = { checkStack(2) ; (_popStack, _popStack) } + def popStack3 = { checkStack(3) ; (_popStack, _popStack, _popStack) } + def clearStack() = 1 to stack.length foreach (_ => popStack) + + def pushStack(xs: TypeKind*): Unit = { + xs foreach { x => + if (x == UNIT) + logChecker("Ignoring pushed UNIT") + else { + stack push x + checkerDebug(sizeString(true) + x) + } + } + } this.typeStack = stack this.basicBlock = b def typeError(k1: TypeKind, k2: TypeKind) { - error(" expected: " + k1 + " but " + k2 + " found") + error("\n expected: " + k1 + "\n found: " + k2) } - for (instr <- b) { + def subtypeTest(k1: TypeKind, k2: TypeKind): Unit = + if (k1 <:< k2) () + else typeError(k2, k1) - def checkStack(len: Int) { - if (stack.length < len) - ICodeChecker.this.error("Expected at least " + len + " elements on the stack", stack); - else - () - } + for (instr <- b) { - def checkLocal(local: Local) { - method.lookupLocal(local.sym.name) match { - case None => error(" " + local + " is not defined in method " + method); - case _ => () + def checkLocal(local: Local): Unit = { + (method lookupLocal local.sym.name) getOrElse { + error(" " + local + " is not defined in method " + method) } } @@ -208,17 +261,17 @@ abstract class Checkers { /** Checks that tpe is a subtype of one of the allowed types */ def checkType(tpe: TypeKind, allowed: TypeKind*) { - if (isOneOf(tpe, allowed: _*)) - () - else - error(tpe.toString() + " is not one of: " + allowed.toList.mkString("{", ", ", "}")); + if (isOneOf(tpe, allowed: _*)) () + else error(tpe + " is not one of: " + allowed.mkString("{", ", ", "}")) } + def checkNumeric(tpe: TypeKind) = + checkType(tpe, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE) /** Checks that the 2 topmost elements on stack are of the * kind TypeKind. */ def checkBinop(kind: TypeKind) { - val (a, b) = stack.pop2 + val (a, b) = popStack2 checkType(a, kind) checkType(b, kind) } @@ -227,7 +280,7 @@ abstract class Checkers { def checkMethodArgs(method: Symbol) { val params = method.info.paramTypes checkStack(params.length) - params.reverse.foreach( (tpe) => checkType(stack.pop, toTypeKind(tpe))) + params.reverse foreach (tpe => checkType(popStack, toTypeKind(tpe))) } /** Checks that the object passed as receiver has a method @@ -241,11 +294,11 @@ abstract class Checkers { case REFERENCE(sym) => checkBool(sym.info.member(method.name) != NoSymbol, "Method " + method + " does not exist in " + sym.fullName); - if (method hasFlag Flags.PRIVATE) + if (method.isPrivate) checkBool(method.owner == clasz.symbol, "Cannot call private method of " + method.owner.fullName + " from " + clasz.symbol.fullName); - else if (method hasFlag Flags.PROTECTED) + else if (method.isProtected) checkBool(clasz.symbol isSubClass method.owner, "Cannot call protected method of " + method.owner.fullName + " from " + clasz.symbol.fullName); @@ -259,7 +312,7 @@ abstract class Checkers { } def checkBool(cond: Boolean, msg: String) = - if (cond) () else error(msg) + if (!cond) error(msg) this.instruction = instr @@ -270,18 +323,16 @@ abstract class Checkers { } instr match { case THIS(clasz) => - stack push toTypeKind(clasz.tpe) + pushStack(toTypeKind(clasz.tpe)) case CONSTANT(const) => - stack push toTypeKind(const.tpe) + pushStack(toTypeKind(const.tpe)) case LOAD_ARRAY_ITEM(kind) => - checkStack(2) - (stack.pop2: @unchecked) match { + popStack2 match { case (INT, ARRAY(elem)) => - if (!(elem <:< kind)) - typeError(kind, elem); - stack.push(elem); + subtypeTest(elem, kind) + pushStack(elem) case (a, b) => error(" expected and INT and a array reference, but " + a + ", " + b + " found"); @@ -289,33 +340,32 @@ abstract class Checkers { case LOAD_LOCAL(local) => checkLocal(local) - stack.push(local.kind) + pushStack(local.kind) case LOAD_FIELD(field, isStatic) => - if (isStatic) { - // the symbol's owner should contain it's field, but - // this is already checked by the type checker, no need - // to redo that here - } else { - checkStack(1) - val obj = stack.pop - checkField(obj, field) - } - stack.push(toTypeKind(field.tpe)) + // the symbol's owner should contain it's field, but + // this is already checked by the type checker, no need + // to redo that here + if (isStatic) () + else checkField(popStack, field) + + pushStack(toTypeKind(field.tpe)) case LOAD_MODULE(module) => checkBool((module.isModule || module.isModuleClass), "Expected module: " + module + " flags: " + Flags.flagsToString(module.flags)); - stack.push(toTypeKind(module.tpe)); + pushStack(toTypeKind(module.tpe)); + + case STORE_THIS(kind) => + val actualType = popStack + if (actualType.isReferenceType) subtypeTest(actualType, kind) + else error("Expected this reference but found: " + actualType) case STORE_ARRAY_ITEM(kind) => - checkStack(3); - (stack.pop3: @unchecked) match { + popStack3 match { case (k, INT, ARRAY(elem)) => - if (!(k <:< kind)) - typeError(kind, k); - if (!(k <:< elem)) - typeError(elem, k); + subtypeTest(k, kind) + subtypeTest(k, elem) case (a, b, c) => error(" expected and array reference, and int and " + kind + " but " + a + ", " + b + ", " + c + " found"); @@ -323,166 +373,145 @@ abstract class Checkers { case STORE_LOCAL(local) => checkLocal(local) - checkStack(1) - - val actualType = stack.pop; - if (!(actualType <:< local.kind) && - //actualType != CASE_CLASS && - local.kind != SCALA_ALL_REF) - typeError(local.kind, actualType); - - case STORE_FIELD(field, isStatic) => - if (isStatic) { - checkStack(1); - val fieldType = toTypeKind(field.tpe); - val actualType = stack.pop; - if (!(actualType <:< fieldType)) - // && actualType != CASE_CLASS) - typeError(fieldType, actualType); - } else { - checkStack(2); - stack.pop2 match { - case (value, obj) => - checkField(obj, field); - val fieldType = toTypeKind(field.tpe); - if (fieldType != SCALA_ALL_REF && !(value <:< fieldType)) - //&& value != CASE_CLASS) - typeError(fieldType, value); - } - } + val actualType = popStack + // PP: ThrowableReference is temporary to deal with exceptions + // not yet appearing typed. + if (actualType == ThrowableReference || local.kind == SCALA_NULL) () + else subtypeTest(actualType, local.kind) + + case STORE_FIELD(field, true) => // static + val fieldType = toTypeKind(field.tpe) + val actualType = popStack + subtypeTest(actualType, fieldType) + + case STORE_FIELD(field, false) => // not static + val (value, obj) = popStack2 + checkField(obj, field) + val fieldType = toTypeKind(field.tpe) + if (fieldType == SCALA_NULL) () + else subtypeTest(value, fieldType) case CALL_PRIMITIVE(primitive) => - checkStack(instr.consumed); + checkStack(instr.consumed) primitive match { case Negation(kind) => - checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE); - checkType(stack.pop, kind); - stack push kind; + checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE) + checkType(popStack, kind) + pushStack(kind) case Test(op, kind, zero) => - if (zero) { - val actualType = stack.pop; - checkType(actualType, kind); - } else - checkBinop(kind); - stack push BOOL + if (zero) checkType(popStack, kind) + else checkBinop(kind) + + pushStack(BOOL) case Comparison(op, kind) => - checkType(kind, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE) + checkNumeric(kind) checkBinop(kind) - stack push INT + pushStack(INT) case Arithmetic(op, kind) => - checkType(kind, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE) + checkNumeric(kind) if (op == NOT) - checkType(stack.pop, kind) + checkType(popStack, kind) else checkBinop(kind) - stack push kind + pushStack(kind) case Logical(op, kind) => - checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG) + checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG) checkBinop(kind) - stack push kind + pushStack(kind) case Shift(op, kind) => checkType(kind, BYTE, CHAR, SHORT, INT, LONG) - val (a, b) = stack.pop2 + val (a, b) = popStack2 checkType(a, INT) checkType(b, kind) - stack push kind + pushStack(kind) case Conversion(src, dst) => - checkType(src, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE) - checkType(dst, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE) - checkType(stack.pop, src) - stack push dst + checkNumeric(src) + checkNumeric(dst) + checkType(popStack, src) + pushStack(dst) case ArrayLength(kind) => - val arr = stack.pop - arr match { - case ARRAY(elem) => - checkType(elem, kind); - case _ => - error(" array reference expected, but " + arr + " found"); + popStack match { + case ARRAY(elem) => checkType(elem, kind) + case arr => error(" array reference expected, but " + arr + " found") } - stack push INT + pushStack(INT) case StartConcat => - stack.push(ConcatClass) + pushStack(ConcatClass) case EndConcat => - checkType(stack.pop, ConcatClass) - stack.push(STRING) + checkType(popStack, ConcatClass) + pushStack(STRING) case StringConcat(el) => - checkType(stack.pop, el) - checkType(stack.pop, ConcatClass) - stack push ConcatClass + checkType(popStack, el) + checkType(popStack, ConcatClass) + pushStack(ConcatClass) } case CALL_METHOD(method, style) => + def paramCount = method.info.paramTypes.length + def pushReturnType = pushStack(toTypeKind(method.info.resultType)) + style match { case Dynamic | InvokeDynamic => - checkStack(1 + method.info.paramTypes.length) + checkStack(1 + paramCount) checkMethodArgs(method) - checkMethod(stack.pop, method) - stack.push(toTypeKind(method.info.resultType)) + checkMethod(popStack, method) + pushReturnType case Static(onInstance) => if (onInstance) { - checkStack(1 + method.info.paramTypes.length) - checkBool(method.hasFlag(Flags.PRIVATE) || method.isConstructor, + checkStack(1 + paramCount) + checkBool(method.isPrivate || method.isConstructor, "Static call to non-private method.") checkMethodArgs(method) - checkMethod(stack.pop, method) + checkMethod(popStack, method) if (!method.isConstructor) - stack.push(toTypeKind(method.info.resultType)); - } else { - checkStack(method.info.paramTypes.length); + pushReturnType + } + else { + checkStack(paramCount); checkMethodArgs(method); - stack.push(toTypeKind(method.info.resultType)); + pushReturnType } case SuperCall(mix) => - checkStack(1 + method.info.paramTypes.length) + checkStack(1 + paramCount) checkMethodArgs(method) - checkMethod(stack.pop, method) - stack.push(toTypeKind(method.info.resultType)) + checkMethod(popStack, method) + pushReturnType } case NEW(kind) => - kind match { - case REFERENCE(cls) => - stack.push(kind) - //bq: had to change from _ to null, because otherwise would be unreachable code - case null => - error("NEW call to non-reference type: " + kind) - } + pushStack(kind) case CREATE_ARRAY(elem, dims) => checkStack(dims) stack.pop(dims) foreach (checkType(_, INT)) - stack.push(ARRAY(elem)) + pushStack(ARRAY(elem)) case IS_INSTANCE(tpe) => - val ref = stack.pop - checkBool(ref.isReferenceType || ref.isArrayType, - "IS_INSTANCE on primitive type: " + ref) - checkBool(tpe.isReferenceType || tpe.isArrayType, - "IS_INSTANCE to primitive type: " + tpe) - stack.push(BOOL); + val ref = popStack + checkBool(!ref.isValueType, "IS_INSTANCE on primitive type: " + ref) + checkBool(!tpe.isValueType, "IS_INSTANCE on primitive type: " + tpe) + pushStack(BOOL) case CHECK_CAST(tpe) => - val ref = stack.pop - checkBool(ref.isReferenceType || ref.isArrayType, - "CHECK_CAST on primitive type: " + ref) - checkBool(tpe.isReferenceType || tpe.isArrayType, - "CHECK_CAST to primitive type: " + tpe) - stack.push(tpe); + val ref = popStack + checkBool(!ref.isValueType, "CHECK_CAST to primitive type: " + ref) + checkBool(!tpe.isValueType, "CHECK_CAST to primitive type: " + tpe) + pushStack(tpe) case SWITCH(tags, labels) => - checkType(stack.pop, INT) + checkType(popStack, INT) checkBool(tags.length == labels.length - 1, "The number of tags and labels does not coincide.") checkBool(labels forall (b => code.blocks contains b), @@ -504,60 +533,46 @@ abstract class Checkers { "Jump to non-existant block " + success) checkBool(code.blocks contains failure, "Jump to non-existant block " + failure) - checkType(stack.pop, kind) + checkType(popStack, kind) + case RETURN(UNIT) => () case RETURN(kind) => - kind match { - case UNIT => () - - case REFERENCE(_) | ARRAY(_) => - checkStack(1) - val top = stack.pop - checkBool(top.isReferenceType || top.isArrayType, - "" + kind + " is a reference type, but " + top + " is not"); - case _ => - checkStack(1) - val top = stack.pop - checkType(top, kind) - } + val top = popStack + if (kind.isValueType) checkType(top, kind) + else checkBool(!top.isValueType, "" + kind + " is a reference type, but " + top + " is not"); case THROW() => - val thrown = stack.pop + val thrown = popStack checkBool(thrown.toType <:< definitions.ThrowableClass.tpe, "Element on top of stack should implement 'Throwable': " + thrown); - stack.push(SCALA_ALL) + pushStack(SCALA_NOTHING) case DROP(kind) => - checkType(stack.pop, kind) + checkType(popStack, kind) case DUP(kind) => - val top = stack.pop + val top = popStack checkType(top, kind) - stack.push(top) - stack.push(top) + pushStack(top) + pushStack(top) case MONITOR_ENTER() => - checkStack(1) - checkBool(stack.pop.isReferenceType, - "MONITOR_ENTER on non-reference type") + checkBool(popStack.isReferenceType, "MONITOR_ENTER on non-reference type") case MONITOR_EXIT() => - checkStack(1) - checkBool(stack.pop.isReferenceType, - "MONITOR_EXIT on non-reference type") + checkBool(popStack.isReferenceType, "MONITOR_EXIT on non-reference type") case BOX(kind) => - checkStack(1) - checkType(stack.pop, kind) - stack.push(icodes.AnyRefReference) + checkType(popStack, kind) + pushStack(icodes.ObjectReference) case UNBOX(kind) => - checkStack(1) - stack.pop - stack.push(kind) + popStack + pushStack(kind) case LOAD_EXCEPTION() => - stack.push(THROWABLE) + clearStack() + pushStack(ThrowableReference) case SCOPE_ENTER(_) | SCOPE_EXIT(_) => () @@ -572,15 +587,16 @@ abstract class Checkers { //////////////// Error reporting ///////////////////////// def error(msg: String) { - Console.println(method.toString() + " in block: " + basicBlock.label) - printLastInstructions + Console.println("Error in " + method + ", block: " + basicBlock.label) + printLastInstructions(8) Checkers.this.global.error("ICode checker: " + method + ": " + msg) } - /** Prints the last 4 instructions. */ - def printLastInstructions { - val buf = basicBlock.reverse dropWhile (_ != instruction) take 4 reverse; + /** Prints the last n instructions. */ + def printLastInstructions(n: Int) { + val buf = basicBlock.reverse dropWhile (_ != instruction) take n reverse; + Console.println("Last " + buf.size + " instructions: ") buf foreach (Console println _) Console.println("at: " + buf.head.pos) } diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index ec8ffd1ddb..93eaa9123f 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -55,10 +55,9 @@ abstract class GenICode extends SubComponent { // this depends on the backend! should be changed. val ANY_REF_CLASS = REFERENCE(ObjectClass) - - val SCALA_ALL = REFERENCE(NothingClass) - val SCALA_ALLREF = REFERENCE(NullClass) - val THROWABLE = REFERENCE(ThrowableClass) + val SCALA_NOTHING = REFERENCE(NothingClass) + val SCALA_NULL = REFERENCE(NullClass) + val THROWABLE = REFERENCE(ThrowableClass) override def run { scalaPrimitives.init @@ -222,7 +221,7 @@ abstract class GenICode extends SubComponent { case rarg :: Nil => resKind = getMaxType(larg.tpe :: rarg.tpe :: Nil); if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code)) - assert(resKind.isIntType | resKind == BOOL, + assert(resKind.isIntegralType | resKind == BOOL, resKind.toString() + " incompatible with arithmetic modulo operation: " + ctx1); ctx1 = genLoad(larg, ctx1, resKind) @@ -704,7 +703,7 @@ abstract class GenICode extends SubComponent { val ctx1 = genLoad(expr, ctx, THROWABLE) ctx1.bb.emit(THROW(), tree.pos) ctx1.bb.enterIgnoreMode - generatedType = SCALA_ALL + generatedType = SCALA_NOTHING ctx1 case New(tpt) => @@ -1028,7 +1027,7 @@ abstract class GenICode extends SubComponent { generatedType = DOUBLE case (NullTag, _) => ctx.bb.emit(CONSTANT(value), tree.pos); - generatedType = SCALA_ALLREF + generatedType = SCALA_NULL case _ => ctx.bb.emit(CONSTANT(value), tree.pos); generatedType = toTypeKind(tree.tpe) @@ -1121,7 +1120,7 @@ abstract class GenICode extends SubComponent { } private def adapt(from: TypeKind, to: TypeKind, ctx: Context, pos: Position): Unit = { - if (!(from <:< to) && !(from == SCALA_ALLREF && to == SCALA_ALL)) { + if (!(from <:< to) && !(from == SCALA_NULL && to == SCALA_NOTHING)) { to match { case UNIT => ctx.bb.emit(DROP(from), pos) @@ -1134,13 +1133,18 @@ abstract class GenICode extends SubComponent { assert(!from.isReferenceType && !to.isReferenceType, "type error: can't convert from " + from + " to " + to +" in unit "+this.unit) ctx.bb.emit(CALL_PRIMITIVE(Conversion(from, to)), pos); } - } else if (from == SCALA_ALL) { + } else if (from == SCALA_NOTHING) { ctx.bb.emit(THROW()) ctx.bb.enterIgnoreMode - } else if (from == SCALA_ALLREF) { + } else if (from == SCALA_NULL) { ctx.bb.emit(DROP(from)) ctx.bb.emit(CONSTANT(Constant(null))) - } else (from, to) match { + } + else if (from == THROWABLE) { + log("Inserted check-cast on throwable to " + to + " at " + pos) + ctx.bb.emit(CHECK_CAST(to)) + } + else (from, to) match { case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, LONG))) case _ => () } @@ -1874,6 +1878,15 @@ abstract class GenICode extends SubComponent { buf.toString() } + /** PP: This instruction was only emitted when settings.Xdce.value = true, + * but I don't understand the condition. It seems like the exception handler + * stacks won't balance without it, so put it in under -Ycheck too. + */ + def maybeLoadException(ctx: Context) = { + if (settings.Xdce.value || !settings.check.isDefault) + ctx.bb.emit(LOAD_EXCEPTION()) + } + def this(other: Context) = { this() this.packg = other.packg @@ -2089,7 +2102,7 @@ abstract class GenICode extends SubComponent { this.addActiveHandler(exh) // .. and body aswell val ctx = finalizerCtx.enterHandler(exh) val exception = ctx.makeLocal(finalizer.pos, ThrowableClass.tpe, "exc") - if (settings.Xdce.value) ctx.bb.emit(LOAD_EXCEPTION()) + maybeLoadException(ctx) ctx.bb.emit(STORE_LOCAL(exception)); val ctx1 = genLoad(finalizer, ctx, UNIT); ctx1.bb.emit(LOAD_LOCAL(exception)); @@ -2103,7 +2116,7 @@ abstract class GenICode extends SubComponent { val exhs = handlers.map { handler => val exh = this.newHandler(handler._1, handler._2) var ctx1 = outerCtx.enterHandler(exh) - if (settings.Xdce.value) ctx1.bb.emit(LOAD_EXCEPTION()) + maybeLoadException(ctx1) ctx1 = handler._3(ctx1) // emit finalizer val ctx2 = emitFinalizer(ctx1) @@ -2157,7 +2170,7 @@ abstract class GenICode extends SubComponent { val exh = outerCtx.newHandler(NoSymbol, UNIT) this.addActiveHandler(exh) val ctx = finalizerCtx.enterHandler(exh) - if (settings.Xdce.value) ctx.bb.emit(LOAD_EXCEPTION()) + maybeLoadException(ctx) val ctx1 = genLoad(finalizer, ctx, UNIT) // need jump for the ICode to be valid. MSIL backend will emit `Endfinally` instead. ctx1.bb.closeWith(JUMP(afterCtx.bb)) @@ -2167,7 +2180,7 @@ abstract class GenICode extends SubComponent { for (handler <- handlers) { val exh = this.newHandler(handler._1, handler._2) var ctx1 = outerCtx.enterHandler(exh) - if (settings.Xdce.value) ctx1.bb.emit(LOAD_EXCEPTION()) + maybeLoadException(ctx1) ctx1 = handler._3(ctx1) // msil backend will emit `Leave` to jump out of a handler ctx1.bb.closeWith(JUMP(afterCtx.bb)) diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala index 30aaebc3c2..f1be717d12 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala @@ -40,6 +40,10 @@ abstract class ICodes extends AnyRef /** The ICode representation of classes */ var classes: HashMap[global.Symbol, IClass] = new HashMap() + /** Debugging flag */ + var isCheckerDebug: Boolean = global.settings.checkDebug.value + def checkerDebug(msg: String) = if (isCheckerDebug) println(msg) + /** The ICode linearizer. */ val linearizer: Linearizer = if (global.settings.Xlinearizer.value == "rpo") @@ -87,7 +91,8 @@ abstract class ICodes extends AnyRef val global: ICodes.this.global.type = ICodes.this.global } - lazy val AnyRefReference: TypeKind = REFERENCE(global.definitions.ObjectClass) + lazy val ObjectReference: TypeKind = REFERENCE(global.definitions.ObjectClass) + lazy val ThrowableReference: TypeKind = REFERENCE(global.definitions.ThrowableClass) object icodeReader extends ICodeReader { lazy val global: ICodes.this.global.type = ICodes.this.global diff --git a/src/compiler/scala/tools/nsc/backend/icode/Members.scala b/src/compiler/scala/tools/nsc/backend/icode/Members.scala index 68360776b3..9d9e3c92e4 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Members.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Members.scala @@ -89,8 +89,18 @@ trait Members { self: ICodes => } } + /** Common interface for IClass/IField/IMethod. */ + trait IMember extends Ordered[IMember] { + def symbol: Symbol + + def compare(other: IMember) = + if (symbol eq other.symbol) 0 + else if (symbol isLess other.symbol) -1 + else 1 + } + /** Represent a class in ICode */ - class IClass(val symbol: Symbol) { + class IClass(val symbol: Symbol) extends IMember { var fields: List[IField] = Nil var methods: List[IMethod] = Nil var cunit: CompilationUnit = _ @@ -122,7 +132,7 @@ trait Members { self: ICodes => } /** Represent a field in ICode */ - class IField(val symbol: Symbol) { } + class IField(val symbol: Symbol) extends IMember { } /** * Represents a method in ICode. Local variables contain @@ -134,7 +144,7 @@ trait Members { self: ICodes => * reversing them and putting them back, when the generation is * finished (GenICode does that). */ - class IMethod(val symbol: Symbol) { + class IMethod(val symbol: Symbol) extends IMember { var code: Code = null var native = false diff --git a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala index 7f2dd7684a..6c3ec7fc19 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala @@ -112,7 +112,7 @@ trait Opcodes { self: ICodes => */ case class THIS(clasz: Symbol) extends Instruction { /** Returns a string representation of this constant */ - override def toString(): String = "THIS" + override def toString = "THIS(" + clasz.name + ")" override def consumed = 0 override def produced = 1 @@ -125,9 +125,6 @@ trait Opcodes { self: ICodes => * ->: ...:constant */ case class CONSTANT(constant: Constant) extends Instruction { - /** Returns a string representation of this constant */ - override def toString(): String = "CONSTANT ("+constant.toString()+")" - override def consumed = 0 override def produced = 1 @@ -140,9 +137,6 @@ trait Opcodes { self: ICodes => * ->: ...:element(a) */ case class LOAD_ARRAY_ITEM(kind: TypeKind) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String = "LOAD_ARRAY_ITEM (" + kind + ")" - override def consumed = 2 override def produced = 1 @@ -155,9 +149,6 @@ trait Opcodes { self: ICodes => * ->: ...:value */ case class LOAD_LOCAL(local: Local) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String = "LOAD_LOCAL "+local.toString() //+isArgument?" (argument)":""; - override def consumed = 0 override def produced = 1 @@ -182,11 +173,9 @@ trait Opcodes { self: ICodes => } case class LOAD_MODULE(module: Symbol) extends Instruction { - assert(module != NoSymbol, - "Invalid module symbol"); + assert(module != NoSymbol, "Invalid module symbol") /** Returns a string representation of this instruction */ - override def toString(): String = - "LOAD_MODULE " + module.toString() + override def toString(): String = "LOAD_MODULE " + module override def consumed = 0 override def produced = 1 @@ -199,9 +188,6 @@ trait Opcodes { self: ICodes => * ->: ... */ case class STORE_ARRAY_ITEM(kind: TypeKind) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String = "STORE_ARRAY_ITEM (" + kind + ")" - override def consumed = 3 override def produced = 0 @@ -213,9 +199,6 @@ trait Opcodes { self: ICodes => * ->: ... */ case class STORE_LOCAL(local: Local) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String = "STORE_LOCAL "+local.toString(); //+isArgument?" (argument)":""; - override def consumed = 1 override def produced = 0 @@ -229,7 +212,7 @@ trait Opcodes { self: ICodes => case class STORE_FIELD(field: Symbol, isStatic: Boolean) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String = - "STORE_FIELD "+field.toString() + (if (isStatic) " (static)" else " (dynamic)"); + "STORE_FIELD "+field + (if (isStatic) " (static)" else " (dynamic)"); override def consumed = if(isStatic) 1 else 2; override def produced = 0; @@ -246,7 +229,6 @@ trait Opcodes { self: ICodes => * ->: ... */ case class STORE_THIS(kind: TypeKind) extends Instruction { - override def toString() = "STORE_THIS(" + kind + ")" override def consumed = 1 override def produced = 0 override def consumedTypes = List(kind) @@ -257,9 +239,6 @@ trait Opcodes { self: ICodes => * ->: ...:result */ case class CALL_PRIMITIVE(primitive: Primitive) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="CALL_PRIMITIVE "+primitive.toString() - override def consumed = primitive match { case Negation(_) => 1 case Test(_,_, true) => 1 @@ -322,7 +301,7 @@ trait Opcodes { self: ICodes => case class CALL_METHOD(method: Symbol, style: InvokeStyle) extends Instruction with ReferenceEquality { /** Returns a string representation of this instruction */ override def toString(): String = - "CALL_METHOD " + hostClass.fullName + method.fullName +" ("+style.toString()+")"; + "CALL_METHOD " + method.fullName +" ("+style+")" var hostClass: Symbol = method.owner def setHostClass(cls: Symbol): this.type = { hostClass = cls; this } @@ -342,7 +321,7 @@ trait Opcodes { self: ICodes => override def consumed = params.length + consumesInstance override def consumedTypes = { val args = params map toTypeKind - if (consumesInstance > 0) AnyRefReference :: args + if (consumesInstance > 0) ObjectReference :: args else args } @@ -371,7 +350,7 @@ trait Opcodes { self: ICodes => case class UNBOX(boxType: TypeKind) extends Instruction { override def toString(): String = "UNBOX " + boxType override def consumed = 1 - override def consumedTypes = AnyRefReference :: Nil + override def consumedTypes = ObjectReference :: Nil override def produced = 1 } @@ -397,7 +376,7 @@ trait Opcodes { self: ICodes => */ case class CREATE_ARRAY(elem: TypeKind, dims: Int) extends Instruction { /** Returns a string representation of this instruction */ - override def toString(): String ="CREATE_ARRAY "+elem.toString() + " x " + dims; + override def toString(): String ="CREATE_ARRAY "+elem + " x " + dims; override def consumed = dims; override def consumedTypes = List.fill(dims)(INT) @@ -410,10 +389,10 @@ trait Opcodes { self: ICodes => */ case class IS_INSTANCE(typ: TypeKind) extends Instruction { /** Returns a string representation of this instruction */ - override def toString(): String ="IS_INSTANCE "+typ.toString() + override def toString(): String ="IS_INSTANCE "+typ override def consumed = 1 - override def consumedTypes = AnyRefReference :: Nil + override def consumedTypes = ObjectReference :: Nil override def produced = 1 } @@ -423,11 +402,11 @@ trait Opcodes { self: ICodes => */ case class CHECK_CAST(typ: TypeKind) extends Instruction { /** Returns a string representation of this instruction */ - override def toString(): String ="CHECK_CAST "+typ.toString() + override def toString(): String ="CHECK_CAST "+typ override def consumed = 1 override def produced = 1 - override val consumedTypes = List(AnyRefReference) + override val consumedTypes = List(ObjectReference) override def producedTypes = List(typ) } @@ -473,7 +452,7 @@ trait Opcodes { self: ICodes => /** Returns a string representation of this instruction */ override def toString(): String = ( "CJUMP (" + kind + ")" + - cond.toString()+" ? "+successBlock.label+" : "+failureBlock.label + cond + " ? "+successBlock.label+" : "+failureBlock.label ); override def consumed = 2 @@ -492,7 +471,7 @@ trait Opcodes { self: ICodes => /** Returns a string representation of this instruction */ override def toString(): String = ( "CZJUMP (" + kind + ")" + - cond.toString()+" ? "+successBlock.label+" : "+failureBlock.label + cond + " ? "+successBlock.label+" : "+failureBlock.label ); override def consumed = 1 @@ -505,9 +484,6 @@ trait Opcodes { self: ICodes => * ->: ... */ case class RETURN(kind: TypeKind) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="RETURN (" + kind + ")" - override def consumed = if (kind == UNIT) 0 else 1 override def produced = 0 } @@ -530,7 +506,7 @@ trait Opcodes { self: ICodes => */ case class DROP (typ: TypeKind) extends Instruction { /** Returns a string representation of this instruction */ - override def toString(): String ="DROP "+typ.toString() + override def toString(): String ="DROP "+typ override def consumed = 1 override def produced = 0 @@ -541,9 +517,6 @@ trait Opcodes { self: ICodes => * ->: ...:something:something */ case class DUP (typ: TypeKind) extends Instruction { - /** Returns a string representation of this instruction */ - override def toString(): String ="DUP" - override def consumed = 1 override def produced = 2 } @@ -553,7 +526,6 @@ trait Opcodes { self: ICodes => * ->: ...: */ case class MONITOR_ENTER() extends Instruction { - /** Returns a string representation of this instruction */ override def toString(): String ="MONITOR_ENTER" @@ -566,7 +538,6 @@ trait Opcodes { self: ICodes => * ->: ...: */ case class MONITOR_EXIT() extends Instruction { - /** Returns a string representation of this instruction */ override def toString(): String ="MONITOR_EXIT"; @@ -603,7 +574,7 @@ trait Opcodes { self: ICodes => override def toString(): String = "LOAD_EXCEPTION" override def consumed = error("LOAD_EXCEPTION does clean the whole stack, no idea how many things it consumes!") override def produced = 1 - override def producedTypes = AnyRefReference :: Nil + override def producedTypes = ThrowableReference :: Nil } /** This class represents a method invocation style. */ @@ -627,18 +598,18 @@ trait Opcodes { self: ICodes => /** Is this an instance method call? */ def hasInstance: Boolean = this match { - case Dynamic => true + case Dynamic => true case Static(onInstance) => onInstance - case SuperCall(_) => true - case _ => false + case SuperCall(_) => true + case _ => false } /** Returns a string representation of this style. */ override def toString(): String = this match { - case Dynamic => "dynamic" - case InvokeDynamic => "invoke-dynamic" - case Static(false) => "static-class" - case Static(true) => "static-instance" + case Dynamic => "dynamic" + case InvokeDynamic => "invoke-dynamic" + case Static(false) => "static-class" + case Static(true) => "static-instance" case SuperCall(mix) => "super(" + mix + ")" } } @@ -663,7 +634,7 @@ trait Opcodes { self: ICodes => case class CIL_LOAD_LOCAL_ADDRESS(local: Local) extends Instruction { /** Returns a string representation of this instruction */ - override def toString(): String = "CIL_LOAD_LOCAL_ADDRESS "+local.toString() //+isArgument?" (argument)":""; + override def toString(): String = "CIL_LOAD_LOCAL_ADDRESS "+local //+isArgument?" (argument)":""; override def consumed = 0 override def produced = 1 @@ -674,7 +645,7 @@ trait Opcodes { self: ICodes => case class CIL_LOAD_FIELD_ADDRESS(field: Symbol, isStatic: Boolean) extends Instruction { /** Returns a string representation of this instruction */ override def toString(): String = - "CIL_LOAD_FIELD_ADDRESS " + (if (isStatic) field.fullName else field.toString()); + "CIL_LOAD_FIELD_ADDRESS " + (if (isStatic) field.fullName else field.toString) override def consumed = if (isStatic) 0 else 1 override def produced = 1 @@ -697,7 +668,7 @@ trait Opcodes { self: ICodes => case class CIL_UNBOX(valueType: TypeKind) extends Instruction { override def toString(): String = "CIL_UNBOX " + valueType override def consumed = 1 - override def consumedTypes = AnyRefReference :: Nil // actually consumes a 'boxed valueType' + override def consumedTypes = ObjectReference :: Nil // actually consumes a 'boxed valueType' override def produced = 1 override def producedTypes = List(msil_mgdptr(valueType)) } @@ -705,7 +676,7 @@ trait Opcodes { self: ICodes => case class CIL_INITOBJ(valueType: TypeKind) extends Instruction { override def toString(): String = "CIL_INITOBJ " + valueType override def consumed = 1 - override def consumedTypes = AnyRefReference :: Nil // actually consumes a managed pointer + override def consumedTypes = ObjectReference :: Nil // actually consumes a managed pointer override def produced = 0 } diff --git a/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala b/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala index 62042ce3ce..b18c0da2a9 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala @@ -3,7 +3,6 @@ * @author Martin Odersky */ - package scala.tools.nsc package backend package icode @@ -24,16 +23,15 @@ package icode */ -import scala.collection.mutable.{ Map, HashMap } - trait TypeKinds { self: ICodes => import global._ import definitions.{ ArrayClass, AnyRefClass, ObjectClass, NullClass, NothingClass } + import icodes.checkerDebug /** A map from scala primitive Types to ICode TypeKinds */ - lazy val primitiveTypeMap: collection.Map[Symbol, TypeKind] = { + lazy val primitiveTypeMap: Map[Symbol, TypeKind] = { import definitions._ - collection.Map( + Map( UnitClass -> UNIT, BooleanClass -> BOOL, CharClass -> CHAR, @@ -46,62 +44,68 @@ trait TypeKinds { self: ICodes => ) } /** Reverse map for toType */ - private lazy val reversePrimitiveMap: collection.Map[TypeKind, Symbol] = - collection.Map(primitiveTypeMap.toList map (_.swap) : _*) + private lazy val reversePrimitiveMap: Map[TypeKind, Symbol] = + primitiveTypeMap map (_.swap) toMap /** This class represents a type kind. Type kinds * represent the types that the VM know (or the ICode * view of what VMs know). */ sealed abstract class TypeKind { + def maxType(other: TypeKind): TypeKind - def toType: Type = (reversePrimitiveMap get this) match { - case Some(sym) => sym.tpe - case None => this match { - case REFERENCE(cls) => cls.tpe // typeRef(cls.typeConstructor.prefix, cls, Nil) - // case VALUE(cls) => typeRef(cls.typeConstructor.prefix, cls, Nil) + def toType: Type = (reversePrimitiveMap get this) map (_.tpe) getOrElse { + this match { + case REFERENCE(cls) => cls.tpe case ARRAY(elem) => typeRef(ArrayClass.typeConstructor.prefix, ArrayClass, List(elem.toType)) case _ => abort("Unknown type kind.") } } + def toTypeAt(ph: Phase): Type = atPhase(ph)(toType) - def isReferenceType: Boolean = false - def isArrayType: Boolean = false - def isValueType: Boolean = !isReferenceType && !isArrayType + def isReferenceType = false + def isArrayType = false + def isValueType = false + final def isRefOrArrayType = isReferenceType || isArrayType + final def isNothingType = this == REFERENCE(NothingClass) - - def isIntType: Boolean = this match { + def isIntegralType: Boolean = this match { case BYTE | SHORT | INT | LONG | CHAR => true - case _ => false + case _ => false } - def isRealType: Boolean = this match { case FLOAT | DOUBLE => true - case _ => false + case _ => false } - - def isNumericType: Boolean = isIntType | isRealType - - def maxType(other: TypeKind): TypeKind + def isNumericType: Boolean = isIntegralType | isRealType /** Simple subtyping check */ def <:<(other: TypeKind): Boolean = (this eq other) || (this match { - case BOOL | BYTE | SHORT | CHAR => - other match { - case INT | LONG => true - case _ => false - } - case _ => this eq other + case BOOL | BYTE | SHORT | CHAR => other == INT || other == LONG + case _ => this eq other }) /** Is this type a category 2 type in JVM terms? */ def isWideType: Boolean = this match { - case DOUBLE | LONG => true - case _ => false + case DOUBLE | LONG => true + case _ => false } /** The number of dimensions for array types. */ def dimensions: Int = 0 + + protected def uncomparable(thisKind: String, other: TypeKind): Nothing = + abort("Uncomparable type kinds: " + thisKind + " with " + other) + + protected def uncomparable(other: TypeKind): Nothing = + uncomparable(this.toString, other) + } + + sealed abstract class ValueTypeKind extends TypeKind { + override def isValueType = true + override def toString = { + this.getClass.getName stripSuffix "$" dropWhile (_ != '$') drop 1 + } } var lubs0 = 0 @@ -113,14 +117,34 @@ trait TypeKinds { self: ICodes => * The lub is based on the lub of scala types. */ def lub(a: TypeKind, b: TypeKind): TypeKind = { - def lub0(t1: Type, t2: Type): Type = { - //lubs0 += 1 - global.lub(t1 :: t2 :: Nil) + def lub0(tk1: TypeKind, tk2: TypeKind): Type = { + /** Returning existing implementation unless flag given. See #3872. */ + if (!isCheckerDebug) + return global.lub(List(tk1.toType, tk2.toType)) + + /** PP: Obviously looking for " with " is a bit short of the ideal robustness, + * but that's why it's only used under -Ycheck-debug. Correct fix is likely + * to change compound types to lead with the class type. + */ + atPhase(currentRun.typerPhase) { + val t1 = tk1.toType + val t2 = tk2.toType + val calculated = global.lub(List(t1, t2)) + checkerDebug("at Phase %s, lub0(%s, %s) == %s".format(global.globalPhase, t1, t2, calculated)) + calculated match { + case x: CompoundType => + val tps = x.baseTypeSeq.toList filterNot (_.toString contains " with ") + val id = global.erasure.erasure.intersectionDominator(tps) + checkerDebug("intersectionDominator(%s) == %s".format(tps.mkString(", "), id)) + id + case x => x + } + } } if (a == b) a - else if (a == REFERENCE(NothingClass)) b - else if (b == REFERENCE(NothingClass)) a + else if (a.isNothingType) b + else if (b.isNothingType) a else (a, b) match { case (BOXED(a1), BOXED(b1)) => if (a1 == b1) a else REFERENCE(AnyRefClass) case (BOXED(_), REFERENCE(_)) | (REFERENCE(_), BOXED(_)) => REFERENCE(AnyRefClass) @@ -130,123 +154,96 @@ trait TypeKinds { self: ICodes => case (CHAR, INT) | (INT, CHAR) => INT case (BOOL, INT) | (INT, BOOL) => INT case _ => - if ((a.isReferenceType || a.isArrayType) && - (b.isReferenceType || b.isArrayType)) - toTypeKind(lub0(a.toType, b.toType)) + if (a.isRefOrArrayType && b.isRefOrArrayType) + toTypeKind(lub0(a, b)) else throw new CheckerException("Incompatible types: " + a + " with " + b) } } /** The unit value */ - case object UNIT extends TypeKind { - def maxType(other: TypeKind): TypeKind = other match { - case UNIT => UNIT - case REFERENCE(NothingClass) => UNIT - case _ => abort("Uncomparable type kinds: UNIT with " + other) + case object UNIT extends ValueTypeKind { + def maxType(other: TypeKind) = other match { + case UNIT | REFERENCE(NothingClass) => UNIT + case _ => uncomparable(other) } } /** A boolean value */ - case object BOOL extends TypeKind { - override def maxType(other: TypeKind): TypeKind = other match { - case BOOL => BOOL - case REFERENCE(NothingClass) => BOOL - case _ => abort("Uncomparable type kinds: BOOL with " + other) + case object BOOL extends ValueTypeKind { + def maxType(other: TypeKind) = other match { + case BOOL | REFERENCE(NothingClass) => BOOL + case _ => uncomparable(other) } } - /** A 1-byte signed integer */ - case object BYTE extends TypeKind { - override def maxType(other: TypeKind): TypeKind = - other match { - case CHAR => INT - case BYTE | SHORT | INT | LONG | FLOAT | DOUBLE => other - case REFERENCE(NothingClass) => BYTE - case _ => abort("Uncomparable type kinds: BYTE with " + other) - } - } - /** Note that the max of Char/Byte and Char/Short is Int, because * neither strictly encloses the other due to unsignedness. * See ticket #2087 for a consequence. */ + /** A 1-byte signed integer */ + case object BYTE extends ValueTypeKind { + def maxType(other: TypeKind) = { + if (other == BYTE || other.isNothingType) BYTE + else if (other == CHAR) INT + else if (other.isNumericType) other + else uncomparable(other) + } + } + /** A 2-byte signed integer */ - case object SHORT extends TypeKind { - override def maxType(other: TypeKind): TypeKind = - other match { - case CHAR => INT - case BYTE | SHORT => SHORT - case REFERENCE(NothingClass) => SHORT - case INT | LONG | FLOAT | DOUBLE => other - case _ => abort("Uncomparable type kinds: SHORT with " + other) - } + case object SHORT extends ValueTypeKind { + override def maxType(other: TypeKind) = other match { + case BYTE | SHORT | REFERENCE(NothingClass) => SHORT + case CHAR => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable(other) + } } /** A 2-byte UNSIGNED integer */ - case object CHAR extends TypeKind { - override def maxType(other: TypeKind): TypeKind = - other match { - case CHAR => CHAR - case BYTE | SHORT => INT - case REFERENCE(NothingClass) => CHAR - case INT | LONG | FLOAT | DOUBLE => other - case _ => abort("Uncomparable type kinds: CHAR with " + other) - } + case object CHAR extends ValueTypeKind { + override def maxType(other: TypeKind) = other match { + case CHAR | REFERENCE(NothingClass) => CHAR + case BYTE | SHORT => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable(other) + } } - /** A 4-byte signed integer */ - case object INT extends TypeKind { - override def maxType(other: TypeKind): TypeKind = - other match { - case BYTE | SHORT | CHAR | INT => INT - case REFERENCE(NothingClass) => INT - case LONG | FLOAT | DOUBLE => other - case _ => abort("Uncomparable type kinds: INT with " + other) - } + case object INT extends ValueTypeKind { + override def maxType(other: TypeKind) = other match { + case BYTE | SHORT | CHAR | INT | REFERENCE(NothingClass) => INT + case LONG | FLOAT | DOUBLE => other + case _ => uncomparable(other) + } } /** An 8-byte signed integer */ - case object LONG extends TypeKind { + case object LONG extends ValueTypeKind { override def maxType(other: TypeKind): TypeKind = - other match { - case BYTE | SHORT | CHAR | INT | LONG => LONG - case REFERENCE(NothingClass) => LONG - case FLOAT | DOUBLE => DOUBLE - case _ => abort("Uncomparable type kinds: LONG with " + other) - } + if (other.isIntegralType || other.isNothingType) LONG + else if (other.isRealType) DOUBLE + else uncomparable(other) } /** A 4-byte floating point number */ - case object FLOAT extends TypeKind { - override def maxType(other: TypeKind): TypeKind = other match { - case BYTE | SHORT | CHAR | INT | LONG | FLOAT => FLOAT - case REFERENCE(NothingClass) => FLOAT - case DOUBLE => DOUBLE - case _ => abort("Uncomparable type kinds: FLOAT with " + other) - } + case object FLOAT extends ValueTypeKind { + override def maxType(other: TypeKind): TypeKind = + if (other == DOUBLE) DOUBLE + else if (other.isNumericType || other.isNothingType) FLOAT + else uncomparable(other) } /** An 8-byte floating point number */ - case object DOUBLE extends TypeKind { + case object DOUBLE extends ValueTypeKind { override def maxType(other: TypeKind): TypeKind = - if (other.isNumericType) - DOUBLE - else if (other == REFERENCE(NothingClass)) DOUBLE - else abort("Uncomparable type kinds: DOUBLE with " + other) + if (other.isNumericType || other.isNothingType) DOUBLE + else uncomparable(other) } - /** A string reference */ - // case object STRING extends TypeKind { - // override def maxType(other: TypeKind): TypeKind = other match { - // case STRING => STRING; - // case _ => - // abort("Uncomparable type kinds: STRING with " + other); - // } - // } - /** A class type. */ final case class REFERENCE(cls: Symbol) extends TypeKind { assert(cls ne null, @@ -256,70 +253,40 @@ trait TypeKinds { self: ICodes => assert(cls != NoSymbol, "REFERENCE to NoSymbol not allowed!") - override def toString(): String = - "REFERENCE(" + cls.fullName + ")" - /** * Approximate `lub'. The common type of two references is * always AnyRef. For 'real' least upper bound wrt to subclassing * use method 'lub'. */ - override def maxType(other: TypeKind): TypeKind = - other match { - case REFERENCE(_) | ARRAY(_) => - REFERENCE(AnyRefClass) - case _ => - abort("Uncomparable type kinds: REFERENCE with " + other) - } + override def maxType(other: TypeKind) = other match { + case REFERENCE(_) | ARRAY(_) => REFERENCE(AnyRefClass) + case _ => uncomparable("REFERENCE", other) + } /** Checks subtyping relationship. */ - override def <:<(other: TypeKind): Boolean = - if (cls == NothingClass) - true - else other match { - case REFERENCE(cls2) => - cls.tpe <:< cls2.tpe - case ARRAY(_) => - cls == NullClass - case _ => false - } - - override def isReferenceType: Boolean = true; + override def <:<(other: TypeKind) = isNothingType || (other match { + case REFERENCE(cls2) => cls.tpe <:< cls2.tpe + case ARRAY(_) => cls == NullClass + case _ => false + }) + override def isReferenceType = true } -// final case class VALUE(cls: Symbol) extends TypeKind { -// override def equals(other: Any): Boolean = other match { -// case VALUE(cls2) => cls == cls2; -// case _ => false; -// } - -// def maxType(other: TypeKind): TypeKind = -// abort(toString() + " maxType " + other.toString()); - -// override def toString(): String = -// "VALUE(" + cls.fullName + ")"; -// } - def ArrayN(elem: TypeKind, dims: Int): ARRAY = { assert(dims > 0) - if (dims == 1) - ARRAY(elem) - else - ARRAY(ArrayN(elem, dims - 1)) + if (dims == 1) ARRAY(elem) + else ARRAY(ArrayN(elem, dims - 1)) } final case class ARRAY(val elem: TypeKind) extends TypeKind { - override def toString(): String = - "ARRAY[" + elem + "]" - + override def toString = "ARRAY[" + elem + "]" override def isArrayType = true - - override def dimensions: Int = 1 + elem.dimensions + override def dimensions = 1 + elem.dimensions /** The ultimate element type of this array. */ def elementKind: TypeKind = elem match { - case a @ ARRAY(e1) => a.elementKind - case k => k + case a @ ARRAY(_) => a.elementKind + case k => k } /** @@ -327,58 +294,39 @@ trait TypeKinds { self: ICodes => * always AnyRef. For 'real' least upper bound wrt to subclassing * use method 'lub'. */ - override def maxType(other: TypeKind): TypeKind = - other match { - case REFERENCE(_) => - REFERENCE(AnyRefClass) - case ARRAY(elem2) => - if (elem == elem2) ARRAY(elem) - else REFERENCE(AnyRefClass) - case _ => - abort("Uncomparable type kinds: ARRAY with " + other) - } + override def maxType(other: TypeKind) = other match { + case ARRAY(elem2) if elem == elem2 => ARRAY(elem) + case ARRAY(_) | REFERENCE(_) => REFERENCE(AnyRefClass) + case _ => uncomparable("ARRAY", other) + } /** Array subtyping is covariant, as in Java. Necessary for checking * code that interacts with Java. */ - override def <:<(other: TypeKind): Boolean = - other match { - case ARRAY(elem2) => - elem <:< elem2 - case REFERENCE(AnyRefClass | ObjectClass) => - true // TODO: platform dependent! - case _ => false - } + override def <:<(other: TypeKind) = other match { + case ARRAY(elem2) => elem <:< elem2 + case REFERENCE(AnyRefClass | ObjectClass) => true // TODO: platform dependent! + case _ => false + } } /** A boxed value. */ case class BOXED(kind: TypeKind) extends TypeKind { - override def toString(): String = - "BOXED(" + kind + ")" - /** * Approximate `lub'. The common type of two references is * always AnyRef. For 'real' least upper bound wrt to subclassing * use method 'lub'. */ - override def maxType(other: TypeKind): TypeKind = - other match { - case REFERENCE(_) | ARRAY(_) | BOXED(_) => - REFERENCE(AnyRefClass) - case _ => - abort("Uncomparable type kinds: ARRAY with " + other) - } + override def maxType(other: TypeKind) = other match { + case REFERENCE(_) | ARRAY(_) | BOXED(_) => REFERENCE(AnyRefClass) + case _ => uncomparable("BOXED", other) + } /** Checks subtyping relationship. */ - override def <:<(other: TypeKind): Boolean = - other match { - case REFERENCE(AnyRefClass | ObjectClass) => - true // TODO: platform dependent! - - case BOXED(other) => - kind == other - - case _ => false - } + override def <:<(other: TypeKind) = other match { + case BOXED(other) => kind == other + case REFERENCE(AnyRefClass | ObjectClass) => true // TODO: platform dependent! + case _ => false + } } /** @@ -386,87 +334,61 @@ trait TypeKinds { self: ICodes => * way. For JVM it would have been a REFERENCE to 'StringBuffer'. */ case object ConcatClass extends TypeKind { - override def toString() = "ConcatClass" + override def toString = "ConcatClass" /** * Approximate `lub'. The common type of two references is * always AnyRef. For 'real' least upper bound wrt to subclassing * use method 'lub'. */ - override def maxType(other: TypeKind): TypeKind = - other match { - case REFERENCE(_) => - REFERENCE(AnyRefClass) - case _ => - abort("Uncomparable type kinds: ConcatClass with " + other) - } + override def maxType(other: TypeKind) = other match { + case REFERENCE(_) => REFERENCE(AnyRefClass) + case _ => uncomparable(other) + } /** Checks subtyping relationship. */ - override def <:<(other: TypeKind): Boolean = (this eq other) - - override def isReferenceType: Boolean = false + override def <:<(other: TypeKind) = this eq other } ////////////////// Conversions ////////////////////////////// - /** Return the TypeKind of the given type * * Call to .normalize fixes #3003 (follow type aliases). Otherwise, - * arrayOrClassType below would return AnyRefReference. + * arrayOrClassType below would return ObjectReference. */ def toTypeKind(t: Type): TypeKind = t.normalize match { - case ThisType(sym) => - if (sym == ArrayClass) - AnyRefReference - else - REFERENCE(sym) - - case SingleType(pre, sym) => - primitiveTypeMap.getOrElse(sym, REFERENCE(sym)) - - case ConstantType(value) => - toTypeKind(t.underlying) - - case TypeRef(_, sym, args) => - primitiveTypeMap.getOrElse(sym, arrayOrClassType(sym, args)) - - case ClassInfoType(_, _, sym) => - primitiveTypeMap get sym match { - case Some(k) => k - case None => - if (sym == ArrayClass) - abort("ClassInfoType to ArrayClass!") - else - REFERENCE(sym) - } - - case ExistentialType(tparams, t) => - toTypeKind(t) - - case AnnotatedType(_, t, _) => - toTypeKind(t) - - //case WildcardType => // bq: useful hack when wildcard types come here - // REFERENCE(definitions.ObjectClass) - - case _ => - abort("Unknown type: " + t + ", " + t.normalize + "[" + t.getClass + ", " + t.normalize.getClass + "]" + - " TypeRef? " + t.isInstanceOf[TypeRef] + ", " + t.normalize.isInstanceOf[TypeRef]) + case ThisType(ArrayClass) => ObjectReference + case ThisType(sym) => REFERENCE(sym) + case SingleType(_, sym) => primitiveOrRefType(sym) + case ConstantType(_) => toTypeKind(t.underlying) + case TypeRef(_, sym, args) => primitiveOrClassType(sym, args) + case ClassInfoType(_, _, ArrayClass) => abort("ClassInfoType to ArrayClass!") + case ClassInfoType(_, _, sym) => primitiveOrRefType(sym) + case ExistentialType(_, t) => toTypeKind(t) + case AnnotatedType(_, t, _) => toTypeKind(t) + // bq: useful hack when wildcard types come here + // case WildcardType => REFERENCE(ObjectClass) + case norm => abort( + "Unknown type: %s, %s [%s, %s] TypeRef? %s".format( + t, norm, t.getClass, norm.getClass, t.isInstanceOf[TypeRef] + ) + ) } /** Return the type kind of a class, possibly an array type. */ - private def arrayOrClassType(sym: Symbol, targs: List[Type]): TypeKind = { - if (sym == ArrayClass) - ARRAY(toTypeKind(targs.head)) - else if (sym.isClass) - REFERENCE(sym) - else { + private def arrayOrClassType(sym: Symbol, targs: List[Type]) = sym match { + case ArrayClass => ARRAY(toTypeKind(targs.head)) + case _ if sym.isClass => REFERENCE(sym) + case _ => assert(sym.isType, sym) // it must be compiling Array[a] - AnyRefReference - } + ObjectReference } + private def primitiveOrRefType(sym: Symbol) = + primitiveTypeMap.getOrElse(sym, REFERENCE(sym)) + private def primitiveOrClassType(sym: Symbol, targs: List[Type]) = + primitiveTypeMap.getOrElse(sym, arrayOrClassType(sym, targs)) def msil_mgdptr(tk: TypeKind): TypeKind = (tk: @unchecked) match { case REFERENCE(cls) => REFERENCE(loaders.clrTypes.mdgptrcls4clssym(cls)) diff --git a/src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala b/src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala index 4c52d36d33..74abec78bc 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/TypeStacks.scala @@ -23,15 +23,21 @@ trait TypeStacks { type Rep = List[TypeKind] class TypeStack(var types: Rep) { + if (types.nonEmpty) + checkerDebug("TypeStack init: " + types.mkString(", ")) + def this() = this(Nil) def this(that: TypeStack) = this(that.types) def length: Int = types.length + def isEmpty = length == 0 + def nonEmpty = length != 0 /** Push a type on the type stack. UNITs are ignored. */ - def push(t: TypeKind) = + def push(t: TypeKind) = { if (t != UNIT) types = t :: types + } def head: TypeKind = types.head @@ -72,7 +78,9 @@ trait TypeStacks { (types corresponds other.types)((t1, t2) => t1 <:< t2 || t2 <:< t1) /* This method returns a String representation of the stack */ - override def toString() = types.mkString("\n", "\n", "\n") + override def toString() = + if (types.isEmpty) "TypeStack()" + else types.mkString("TypeStack(" + types.size + " elems) {\n ", "\n ", "\n}") override def hashCode() = types.hashCode() override def equals(other: Any): Boolean = other match { diff --git a/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala index bc7c88fcb8..ba7e00901a 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/ClosureElimination.scala @@ -173,14 +173,14 @@ abstract class ClosureElimination extends SubComponent { val value = info.getBinding(loc1) value match { case Boxed(LocalVar(loc2)) => - bb.replaceInstruction(i, DROP(icodes.AnyRefReference) :: valueToInstruction(info.getBinding(loc2)) :: Nil) + bb.replaceInstruction(i, DROP(icodes.ObjectReference) :: valueToInstruction(info.getBinding(loc2)) :: Nil) log("replaced " + i + " with " + info.getBinding(loc2)) case _ => () } case Boxed(LocalVar(loc1)) :: _ => val loc2 = info.getAlias(loc1) - bb.replaceInstruction(i, DROP(icodes.AnyRefReference) :: valueToInstruction(Deref(LocalVar(loc2))) :: Nil) + bb.replaceInstruction(i, DROP(icodes.ObjectReference) :: valueToInstruction(Deref(LocalVar(loc2))) :: Nil) log("replaced " + i + " with " + LocalVar(loc2)) case _ => () diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index b175cb24ee..abf7f3e0de 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -106,6 +106,7 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { val Yhelp = BooleanSetting ("-Y", "Print a synopsis of private options") val browse = PhasesSetting ("-Ybrowse", "Browse the abstract syntax tree after") val check = PhasesSetting ("-Ycheck", "Check the tree at the end of") + val checkDebug = BooleanSetting ("-Ycheck-debug", "Lots of extra output for -Ycheck") val Xcloselim = BooleanSetting ("-Yclosure-elim", "Perform closure elimination") val Ycompacttrees = BooleanSetting ("-Ycompact-trees", "Use compact tree printer when displaying trees") val noCompletion = BooleanSetting ("-Yno-completion", "Disable tab-completion in the REPL") diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index a95c8ada8f..1f9f1c9701 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -79,13 +79,17 @@ abstract class SymbolLoaders { override def load(root: Symbol) { complete(root) } + private def markAbsent(sym: Symbol): Unit = { + val tpe: Type = if (ok) NoType else ErrorType + + if (sym != NoSymbol) + sym setInfo tpe + } private def initRoot(root: Symbol) { - if (root.rawInfo == this) { - def markAbsent(sym: Symbol) = - if (sym != NoSymbol) sym.setInfo(if (ok) NoType else ErrorType); - markAbsent(root) - markAbsent(root.moduleClass) - } else if (root.isClass && !root.isModuleClass) root.rawInfo.load(root) + if (root.rawInfo == this) + List(root, root.moduleClass) foreach markAbsent + else if (root.isClass && !root.isModuleClass) + root.rawInfo.load(root) } } diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index c1afe0c3d8..4c20a901ef 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -247,9 +247,13 @@ abstract class Constructors extends Transform with ast.TreeDSL { tree match { case DefDef(_, _, _, _, _, body) if (tree.symbol.isOuterAccessor && tree.symbol.owner == clazz && clazz.isFinal) => + log("outerAccessors += " + tree.symbol.fullName) outerAccessors ::= (tree.symbol, body) case Select(_, _) => - if (!mustbeKept(tree.symbol)) accessedSyms addEntry tree.symbol + if (!mustbeKept(tree.symbol)) { + log("accessedSyms += " + tree.symbol.fullName) + accessedSyms addEntry tree.symbol + } super.traverse(tree) case _ => super.traverse(tree) diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 72868774e5..61f5c6e5d0 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -50,12 +50,13 @@ abstract class ExplicitOuter extends InfoTransform clazz.owner.isClass && clazz.owner == parent.typeSymbol.owner && parent.prefix =:= clazz.owner.thisType + isInner(clazz) && !clazz.isTrait && - (clazz.info.parents.isEmpty || !hasSameOuter(clazz.info.parents.head)) + !(clazz.info.parents.headOption exists hasSameOuter) } private def outerField(clazz: Symbol): Symbol = { - val result = clazz.info.member(nme getterToLocal nme.OUTER) + val result = clazz.info.member(nme.OUTER_LOCAL) assert(result != NoSymbol, "no outer field in "+clazz+clazz.info.decls+" at "+phase) result @@ -113,7 +114,7 @@ abstract class ExplicitOuter extends InfoTransform if (sym.owner.isTrait && ((sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isModule)) { // 5 sym.makeNotPrivate(sym.owner) } - if (sym.owner.isTrait && (sym hasFlag PROTECTED)) sym setFlag notPROTECTED // 6 + if (sym.owner.isTrait && sym.isProtected) sym setFlag notPROTECTED // 6 if (sym.isClassConstructor && isInner(sym.owner)) { // 1 val p = sym.newValueParameter(sym.pos, "arg" + nme.OUTER) .setInfo(sym.owner.outerClass.thisType) @@ -300,6 +301,8 @@ abstract class ExplicitOuter extends InfoTransform * </p> */ class ExplicitOuterTransformer(unit: CompilationUnit) extends OuterPathTransformer(unit) { + transformer => + /** The definition tree of the outer accessor of current class */ def outerFieldDef: Tree = VAL(outerField(currentClass)) === EmptyTree @@ -328,13 +331,14 @@ abstract class ExplicitOuter extends InfoTransform val path = if (mixinClass.owner.isTerm) THIS(mixinClass.owner.enclClass) else gen.mkAttributedQualifier(currentClass.thisType baseType mixinClass prefix) - // Need to cast for nested outer refs in presence of self-types. See ticket #3274. - val rhs = gen.mkAsInstanceOf(ExplicitOuterTransformer.this.transform(path), - outerAcc.info.resultType) - // @S: atPos not good enough because of nested atPos in DefDef method, which gives position from wrong class! - rhs setPos currentClass.pos - typedPos(currentClass.pos) { (DEF(outerAcc) === rhs) setPos currentClass.pos } + localTyper typed { + DEF(outerAcc) === { + // Need to cast for nested outer refs in presence of self-types. See ticket #3274. + // @S: atPos not good enough because of nested atPos in DefDef method, which gives position from wrong class! + transformer.transform(path) AS_ANY outerAcc.info.resultType setPos currentClass.pos + } + } setPos currentClass.pos } /** If FLAG is set on symbol, sets notFLAG (this exists in anticipation of generalizing). */ @@ -422,7 +426,7 @@ abstract class ExplicitOuter extends InfoTransform case Template(parents, self, decls) => val newDefs = new ListBuffer[Tree] atOwner(tree, currentOwner) { - if (!(currentClass hasFlag INTERFACE) || (currentClass hasFlag lateINTERFACE)) { + if (!currentClass.isInterface || (currentClass hasFlag lateINTERFACE)) { if (isInner(currentClass)) { if (hasOuterField(currentClass)) newDefs += outerFieldDef // (1a) @@ -464,12 +468,12 @@ abstract class ExplicitOuter extends InfoTransform if (currentClass != sym.owner/* && currentClass != sym.moduleClass*/) // (3) sym.makeNotPrivate(sym.owner) val qsym = qual.tpe.widen.typeSymbol - if ((sym hasFlag PROTECTED) && //(4) + if (sym.isProtected && //(4) (qsym.isTrait || !(qual.isInstanceOf[Super] || (qsym isSubClass currentClass)))) sym setFlag notPROTECTED super.transform(tree) - case Apply(sel @ Select(qual, name), args) if (name == nme.CONSTRUCTOR && isInner(sel.symbol.owner)) => + case Apply(sel @ Select(qual, nme.CONSTRUCTOR), args) if isInner(sel.symbol.owner) => val outerVal = atPos(tree.pos)(qual match { // it's a call between constructors of same class case _: This => diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index 7791cc0b02..ecf68fa922 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -14,20 +14,16 @@ import scala.collection.mutable.{HashMap, LinkedHashMap, ListBuffer} abstract class LambdaLift extends InfoTransform { import global._ import definitions._ - import typer.{typed, typedOperator} /** the following two members override abstract members in Transform */ val phaseName: String = "lambdalift" private val lifted = new TypeMap { def apply(tp: Type): Type = tp match { - case TypeRef(pre, sym, args) => - if (pre == NoPrefix && sym.isClass && !sym.isPackageClass) { - assert(args.isEmpty); - typeRef(apply(sym.owner.enclClass.thisType), sym, args) - } else mapOver(tp) + case TypeRef(NoPrefix, sym, Nil) if sym.isClass && !sym.isPackageClass => + typeRef(apply(sym.owner.enclClass.thisType), sym, Nil) case ClassInfoType(parents, decls, clazz) => - val parents1 = parents mapConserve (this) + val parents1 = parents mapConserve this if (parents1 eq parents) tp else ClassInfoType(parents1, decls, clazz) case _ => @@ -62,15 +58,16 @@ abstract class LambdaLift extends InfoTransform { private type SymSet = TreeSet[Symbol] - private def newSymSet = new TreeSet[Symbol]((x, y) => x.isLess(y)) + private def newSymSet = new TreeSet[Symbol](_ isLess _) - private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = f.get(sym) match { - case Some(ss) => ss - case None => val ss = newSymSet; f(sym) = ss; ss - } + private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = + f.getOrElseUpdate(sym, newSymSet) private def outer(sym: Symbol): Symbol = - if (sym.isConstructor) sym.owner.owner else sym.owner; + if (sym.isConstructor) sym.owner.owner else sym.owner + + private def isSameOwnerEnclosure(sym: Symbol) = + enclMethOrClass(sym.owner) == enclMethOrClass(currentOwner) /** The method or class which logically encloses the current symbol. * If the symbol is defined in the initialization part of a template @@ -142,7 +139,7 @@ abstract class LambdaLift extends InfoTransform { else if (owner.isPackageClass || !markFree(sym, enclMethOrClass(outer(owner)))) false else { val ss = symSet(free, owner) - if (!(ss contains sym)) { + if (!ss(sym)) { ss addEntry sym renamable addEntry sym atPhase(currentRun.picklerPhase) { @@ -151,12 +148,12 @@ abstract class LambdaLift extends InfoTransform { // def closure(x: Int) = { () => x } // would have the signature // closure: (x$1: Int)() => Int - if (sym.hasFlag(PARAM) && sym.owner.info.paramss.exists(_.contains(sym))) + if (sym.isParameter && sym.owner.info.paramss.exists(_ contains sym)) sym.owner.setInfo(sym.owner.info.cloneInfo(sym.owner)) } changedFreeVars = true if (settings.debug.value) log("" + sym + " is free in " + owner); - if ((sym.isVariable || (sym.isValue && sym.hasFlag(LAZY))) && !(sym hasFlag CAPTURED)) { + if ((sym.isVariable || (sym.isValue && sym.isLazy)) && !sym.hasFlag(CAPTURED)) { sym setFlag CAPTURED val symClass = sym.tpe.typeSymbol; atPhase(phase.next) { @@ -191,10 +188,8 @@ abstract class LambdaLift extends InfoTransform { } } */ - def freeVars(sym: Symbol): Iterator[Symbol] = free.get(sym) match { - case Some(ss) => ss.iterator - case None => Iterator.empty - } + def freeVars(sym: Symbol): Iterator[Symbol] = + free get sym map (_.iterator) getOrElse Iterator.empty /** The traverse function */ private val freeVarTraverser = new Traverser { @@ -247,28 +242,29 @@ abstract class LambdaLift extends InfoTransform { do { changedFreeVars = false - for (caller <- called.keysIterator; - callee <- called(caller).iterator; + for (caller <- called.keys; + callee <- called(caller); fv <- freeVars(callee)) markFree(fv, caller) - } while (changedFreeVars); + } while (changedFreeVars) - for (sym <- renamable.iterator) { - val base = + for (sym <- renamable) { + val base = sym.name + "$" + ( if (sym.isAnonymousFunction && sym.owner.isMethod) - sym.name.toString() + "$" + sym.owner.name.toString() + "$" - else sym.name.toString() + "$" + sym.owner.name + "$" + else "" + ) val fresh = unit.fresh.newName(sym.pos, base) sym.name = if (sym.name.isTypeName) fresh.toTypeName else fresh if (settings.debug.value) log("renamed: " + sym.name) } atPhase(phase.next) { - for (owner <- free.keysIterator) { + for (owner <- free.keys) { if (settings.debug.value) - log("free(" + owner + owner.locationString + ") = " + free(owner).iterator.toList); + log("free(" + owner + owner.locationString + ") = " + free(owner).toList) proxies(owner) = - for (fv <- free(owner).iterator.toList) yield { + for (fv <- free(owner).toList) yield { val proxy = owner.newValue(owner.pos, fv.name) .setFlag(if (owner.isClass) PARAMACCESSOR | PRIVATE | LOCAL else PARAM) .setFlag(SYNTHETIC) @@ -297,7 +293,7 @@ abstract class LambdaLift extends InfoTransform { if (settings.debug.value) log("proxy " + sym + " in " + sym.owner + " from " + currentOwner.ownerChain + " " + enclMethOrClass(sym.owner));//debug - if (enclMethOrClass(sym.owner) == enclMethOrClass(currentOwner)) sym + if (isSameOwnerEnclosure(sym)) sym else searchIn(currentOwner) } @@ -411,7 +407,7 @@ abstract class LambdaLift extends InfoTransform { val tpt1 = TypeTree(sym.tpe) setPos tpt.pos val rhs1 = atPos(rhs.pos) { - typed { + typer typed { Apply(Select(New(TypeTree(sym.tpe)), nme.CONSTRUCTOR), List(rhs)) } } @@ -434,14 +430,14 @@ abstract class LambdaLift extends InfoTransform { if (sym != NoSymbol && sym.isTerm && !sym.isLabel) if (sym.isMethod) atPos(tree.pos)(memberRef(sym)) - else if (sym.isLocal && enclMethOrClass(sym.owner) != enclMethOrClass(currentOwner)) + else if (sym.isLocal && !isSameOwnerEnclosure(sym)) atPos(tree.pos)(proxyRef(sym)) else tree else tree; if (sym.isCapturedVariable) atPos(tree.pos) { val tp = tree.tpe - val elemTree = typed { Select(tree1 setType sym.tpe, nme.elem) } + val elemTree = typer typed Select(tree1 setType sym.tpe, nme.elem) if (elemTree.tpe.typeSymbol != tp.typeSymbol) gen.mkAttributedCast(elemTree, tp) else elemTree } else tree1 @@ -462,8 +458,7 @@ abstract class LambdaLift extends InfoTransform { stat, mods, name, tparams, treeCopy.Template(impl, parents, self, body ::: lifted)) liftedDefs -= stat.symbol result - case DefDef(mods, name, tp, vp, tpt, Block(Nil, expr)) - if !stat.symbol.isConstructor => + case DefDef(mods, name, tp, vp, tpt, Block(Nil, expr)) if !stat.symbol.isConstructor => treeCopy.DefDef(stat, mods, name, tp, vp, tpt, expr) case _ => stat diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index ad40e8568e..d22930c336 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -104,6 +104,13 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { "specialized overload " + sym + " in " + env } + + /** Just to mark uncheckable */ + override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = new SpecializationPhase(prev) + class SpecializationPhase(prev: scala.tools.nsc.Phase) extends super.Phase(prev) { + override def checkable = false + } + protected def newTransformer(unit: CompilationUnit): Transformer = new SpecializationTransformer(unit) diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index c4074d7efc..85db55472d 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -188,7 +188,7 @@ abstract class UnCurry extends InfoTransform with TypingTransformers { * additional parameter sections of a case class are skipped. */ def uncurryTreeType(tp: Type): Type = tp match { - case MethodType(params, MethodType(params1, restpe)) if (inPattern) => + case MethodType(params, MethodType(params1, restpe)) if inPattern => uncurryTreeType(MethodType(params, restpe)) case _ => uncurry(tp) @@ -204,14 +204,12 @@ abstract class UnCurry extends InfoTransform with TypingTransformers { private val nonLocalReturnKeys = new HashMap[Symbol, Symbol] /** Return non-local return key for given method */ - private def nonLocalReturnKey(meth: Symbol) = nonLocalReturnKeys.get(meth) match { - case Some(k) => k - case None => - val k = meth.newValue(meth.pos, unit.fresh.newName(meth.pos, "nonLocalReturnKey")) - .setFlag(SYNTHETIC).setInfo(ObjectClass.tpe) - nonLocalReturnKeys(meth) = k - k - } + private def nonLocalReturnKey(meth: Symbol) = + nonLocalReturnKeys.getOrElseUpdate(meth, { + meth.newValue(meth.pos, unit.fresh.newName(meth.pos, "nonLocalReturnKey")) + .setFlag (SYNTHETIC) + .setInfo (ObjectClass.tpe) + }) /** Generate a non-local return throw with given return expression from given method. * I.e. for the method's non-local return key, generate: diff --git a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala index 80f833f03d..e039b00ee8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala @@ -48,6 +48,7 @@ trait Analyzer extends AnyRef val runsRightAfter= Some("namer") def newPhase(_prev: Phase): StdPhase = new StdPhase(_prev) { + override val checkable = false import global._ val openPackageObjectsTraverser = new Traverser { diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 7973313786..4906838e36 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -265,7 +265,7 @@ trait Contexts { self: Analyzer => } private def unitError(pos: Position, msg: String) = - unit.error(pos, if (checking) "**** ERROR DURING INTERNAL CHECKING ****\n" + msg else msg) + unit.error(pos, if (checking) "\n**** ERROR DURING INTERNAL CHECKING ****\n" + msg else msg) def error(pos: Position, err: Throwable) = if (reportGeneralErrors) unitError(pos, addDiagString(err.getMessage())) diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index f8466aed33..62f78eddfe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -267,7 +267,21 @@ trait Infer { Console.println(tree) Console.println("" + pre + " " + sym.owner + " " + context.owner + " " + context.outer.enclClass.owner + " " + sym.owner.thisType + (pre =:= sym.owner.thisType)) } - accessError("") + accessError( + if (settings.check.isDefault) "" else { + "\n because of an internal error (no accessible symbol):" + + "\nsym = " + sym + + "\nunderlying(sym) = " + underlying(sym) + + "\npre = " + pre + + "\nsite = " + site + + "\ntree = " + tree + + "\nsym.accessBoundary(sym.owner) = " + sym.accessBoundary(sym.owner) + + "\nsym.ownerChain = " + sym.ownerChain + + "\nsym.owner.thisType = " + sym.owner.thisType + + "\ncontext.owner = " + context.owner + + "\ncontext.outer.enclClass.owner = " + context.outer.enclClass.owner + } + ) } else { if(sym1.isTerm) sym1.cookJavaRawInfo() // xform java rawtypes into existentials diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala index 2f271f237f..2bc3103854 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala @@ -20,9 +20,11 @@ abstract class TreeCheckers extends Analyzer { try p.source.path + ":" + p.line catch { case _: UnsupportedOperationException => p.toString } - def errorFn(pos: Position, msg: Any) = println("[%s] %s: %s".format(phase, posstr(pos), msg)) + def errorFn(msg: Any): Unit = println("[check: %s] %s".format(phase.prev, msg)) + def errorFn(pos: Position, msg: Any): Unit = errorFn(posstr(pos) + ": " + msg) + def assertFn(cond: Boolean, msg: => Any) = - if (!cond) errorFn(NoPosition, msg) + if (!cond) errorFn(msg) def checkTrees { if (settings.verbose.value) @@ -38,6 +40,14 @@ abstract class TreeCheckers extends Analyzer { global.printTypings = saved result } + def runWithUnit[T](unit: CompilationUnit)(body: => Unit): Unit = { + val unit0 = currentRun.currentUnit + currentRun.currentUnit = unit + body + currentRun.advanceUnit + assertFn(currentRun.currentUnit == unit, "currentUnit is " + currentRun.currentUnit + ", but unit is " + unit) + currentRun.currentUnit = unit0 + } def check(unit: CompilationUnit) { informProgress("checking "+unit) @@ -45,24 +55,22 @@ abstract class TreeCheckers extends Analyzer { context.checking = true tpeOfTree.clear val checker = new TreeChecker(context) - - val unit0 = currentRun.currentUnit - currentRun.currentUnit = unit - checker.precheck.traverse(unit.body) - // printingTypings(checker.typed(unit.body)) - checker.typed(unit.body) - checker.postcheck.traverse(unit.body) - currentRun.advanceUnit - assertFn(currentRun.currentUnit == unit, "currentRun.currentUnit == unit") - currentRun.currentUnit = unit0 + 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) { - private def treestr(t: Tree) = t + " [" + t.getClass() + "]" - private def ownerstr(s: Symbol) = "" + s + s.locationString + private def classstr(x: AnyRef) = x.getClass.getName split '.' last; + private def typestr(x: Type) = " (tpe = " + x + ")" + private def treestr(t: Tree) = t + " [" + classstr(t) + "]" + typestr(t.tpe) + private def ownerstr(s: Symbol) = "'" + s + "'" + s.locationString + // XXX check for tree.original on TypeTrees. private def treesDiffer(t1: Tree, t2: Tree) = errorFn(t1.pos, "trees differ\n old: " + treestr(t1) + "\n new: " + treestr(t2)) private def typesDiffer(tree: Tree, tp1: Type, tp2: Type) = @@ -72,6 +80,16 @@ abstract class TreeCheckers extends Analyzer { errorFn(tree.pos, sym + " has wrong owner: " + ownerstr(sym.owner) + ", should be: " + ownerstr(shouldBe)) } + /** 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)) + override def typed(tree: Tree, mode: Int, pt: Type): Tree = returning(tree) { case EmptyTree | TypeTree() => () case _ if tree.tpe != null => @@ -92,7 +110,7 @@ abstract class TreeCheckers extends Analyzer { override def traverse(tree: Tree) { val sym = tree.symbol def accessed = sym.accessed - def fail(msg: String) = errorFn(tree.pos, msg + tree.getClass + " / " + tree) + def fail(msg: String) = errorFn(tree.pos, msg + classstr(tree) + " / " + tree) tree match { case DefDef(_, _, _, _, _, _) => @@ -100,42 +118,48 @@ abstract class TreeCheckers extends Analyzer { sym.tpe.resultType match { case _: ConstantType => () case _ => - assertFn(accessed != NoSymbol, sym) - assertFn( - accessed.getter(sym.owner) == sym || accessed.setter(sym.owner) == sym, - "accessed.getter(sym.owner) == sym || accessed.setter(sym.owner) == sym" - ) + checkSym(tree) + /** XXX: lots of syms show up here with accessed == NoSymbol. */ + if (accessed != NoSymbol) { + val agetter = accessed.getter(sym.owner) + val asetter = accessed.setter(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) { - assertFn(sym.getter(sym.owner) != NoSymbol, sym) + if (sym.hasGetter && !sym.isOuterField) { + assertFn(sym.getter(sym.owner) != NoSymbol, ownerstr(sym) + " has getter but cannot be found. " + sym.ownerChain) } - case Apply(_, args) => - assertFn(args forall (_ != EmptyTree), args) + case Apply(fn, args) => + if (args exists (_ == EmptyTree)) + errorFn(tree.pos, "Apply arguments to " + fn + " contains an empty tree: " + args) + case Select(qual, name) => - if (sym == NoSymbol) - errorFn(tree.pos, "NoSymbol: " + tree) + checkSym(tree) case This(_) => - if (sym == NoSymbol) errorFn(tree.pos, "NoSymbol: " + tree) - else if (sym.isStatic && (sym hasFlag MODULE)) () + checkSym(tree) + if (sym.isStatic && (sym hasFlag MODULE)) () else if (currentOwner.ownerChain takeWhile (_ != sym) exists (_ == NoSymbol)) return fail("tree symbol "+sym+" does not point to enclosing class; tree = ") - /** Temporary while Import nodes are untyped. */ + /** XXX: temporary while Import nodes are arriving untyped. */ case Import(_, _) => return case _ => } if (tree.pos == NoPosition && tree != EmptyTree) - fail("tree without position: ") + noPos(tree) else if (tree.tpe == null && phase.id > currentRun.typerPhase.id) - fail("tree without type: ") + noType(tree) else if (tree.isDef) { - if (sym == NoSymbol) - fail("DefTree with NoSymbol: ") - else tree match { + checkSym(tree) + + tree match { case x: PackageDef => if (sym.ownerChain contains currentOwner) () else fail(sym + " owner chain does not contain currentOwner " + currentOwner) diff --git a/src/compiler/scala/tools/nsc/util/Set.scala b/src/compiler/scala/tools/nsc/util/Set.scala index c8ba3d27c9..a37a155907 100644 --- a/src/compiler/scala/tools/nsc/util/Set.scala +++ b/src/compiler/scala/tools/nsc/util/Set.scala @@ -16,6 +16,10 @@ abstract class Set[T <: AnyRef] { def iterator: Iterator[T] + def foreach[U](f: T => U): Unit = iterator foreach f + + def apply(x: T): Boolean = contains(x) + @deprecated("use `iterator' instead") def elements = iterator def contains(x: T): Boolean = |