From 001e910f9774b2da00da2d56b7ba92d78a9c20ce Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 7 Oct 2010 22:18:59 +0000 Subject: Did a bunch of symbol oriented work on checkers. changes in Global and Typer to accomodate this, and renamed "Checkers" to "ICodeCheckers" to make some less confusing space for the future "SymbolCheckers". No review. --- src/compiler/scala/tools/nsc/Global.scala | 30 +- .../scala/tools/nsc/backend/icode/Checkers.scala | 650 --------------------- .../tools/nsc/backend/icode/ICodeCheckers.scala | 650 +++++++++++++++++++++ src/compiler/scala/tools/nsc/symtab/Types.scala | 14 +- .../scala/tools/nsc/transform/ExplicitOuter.scala | 1 + .../scala/tools/nsc/typechecker/TreeCheckers.scala | 109 +++- .../scala/tools/nsc/typechecker/Typers.scala | 11 +- test/checker-tests/fail12.scala | 20 + 8 files changed, 812 insertions(+), 673 deletions(-) delete mode 100644 src/compiler/scala/tools/nsc/backend/icode/Checkers.scala create mode 100644 src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala create mode 100644 test/checker-tests/fail12.scala diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index ff3a5cdc0b..d3f505d0e2 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -24,7 +24,7 @@ import ast.parser._ import typechecker._ import transform._ -import backend.icode.{ ICodes, GenICode, Checkers } +import backend.icode.{ ICodes, GenICode, ICodeCheckers } import backend.{ ScalaPrimitives, Platform, MSILPlatform, JavaPlatform } import backend.jvm.GenJVM import backend.opt.{ Inliners, ClosureElimination, DeadCodeElimination } @@ -78,11 +78,6 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable val global: Global.this.type = Global.this } with ConstantFolder - /** Tree checker (used for testing and debugging) */ - object checker extends { - val global: Global.this.type = Global.this - } with TreeCheckers - /** ICode generator */ object icodes extends { val global: Global.this.type = Global.this @@ -98,11 +93,6 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable val global: Global.this.type = Global.this } with CopyPropagation - /** Icode verification */ - object checkers extends { - val global: Global.this.type = Global.this - } with Checkers - /** Some statistics (normally disabled) */ object statistics extends { val global: Global.this.type = Global.this @@ -515,7 +505,21 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable val runsRightAfter = None } with SampleTransform - object icodeChecker extends checkers.ICodeChecker() + /** The checkers are for validating the compiler data structures + * at phase boundaries. + */ + + /** Tree checker */ + object treeChecker extends { + val global: Global.this.type = Global.this + } with TreeCheckers + + /** Icode verification */ + object icodeCheckers extends { + val global: Global.this.type = Global.this + } with ICodeCheckers + + object icodeChecker extends icodeCheckers.ICodeChecker() object typer extends analyzer.Typer( analyzer.NoContext.make(EmptyTree, Global.this.definitions.RootClass, new Scope)) @@ -777,7 +781,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable phase = globalPhase inform("[Now checking: " + phase.prev.name + "]") if (globalPhase.id >= icodePhase.id) icodeChecker.checkICodes - else checker.checkTrees + else treeChecker.checkTrees } else inform("[Not checkable: " + globalPhase.prev.name + "]") } diff --git a/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala b/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala deleted file mode 100644 index 61f2d8dd25..0000000000 --- a/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala +++ /dev/null @@ -1,650 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package backend -package icode - -import scala.collection.mutable.{Buffer, ListBuffer, Map, HashMap} -import scala.tools.nsc.symtab._ - -abstract class Checkers { - val global: Global - import global._ - - /**

- * This class performs a set of checks similar to what the bytecode - * verifier does. For each basic block, it checks that: - *

- * - *

- * For a control flow graph it checks that type stacks at entry to - * each basic block 'agree': - *

- * - * - * @author Iulian Dragos - * @version 1.0, 06/09/2005 - * - * @todo Better checks for MONITOR_ENTER/EXIT - * Better checks for local var initializations - */ - class ICodeChecker { - import icodes._ - import opcodes._ - - var clasz: IClass = _ - var method: IMethod = _ - var code: Code = _ - - val in: Map[BasicBlock, TypeStack] = new HashMap() - val out: Map[BasicBlock, TypeStack] = new HashMap() - - val emptyStack = new TypeStack() - - val STRING = REFERENCE(definitions.StringClass) - val BOXED_UNIT = REFERENCE(definitions.BoxedUnitClass) - 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) - println("[[consistency check at the beginning of phase " + globalPhase.name + "]]") - classes.values foreach check - } - - private def posStr(p: Position) = - if (p.isDefined) p.line.toString else "" - - private def indent(s: String, spaces: Int): String = indent(s, " " * spaces) - private def indent(s: String, prefix: String): String = { - val lines = s split "\\n" - lines map (prefix + _) mkString "\n" - } - private def blockAsString(bl: BasicBlock) = { - val s = bl.toList map (instr => posStr(instr.pos) + "\t" + instr) mkString (bl.fullString + " {\n ", "\n ", "\n}") - indent(s, "// ") - } - - /** 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) { - logChecker("\n** Checking class " + cls) - clasz = cls - - 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) - - 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) { - logChecker("\n** Checking method " + m) - method = m - if (!m.isDeferred) - check(m.code) - } - - def check(c: Code) { - var worklist: Buffer[BasicBlock] = new ListBuffer() - - def append(elems: List[BasicBlock]) = elems foreach appendBlock; - def appendBlock(bl: BasicBlock) = - if (!(worklist contains bl)) - worklist += bl - - in.clear; - out.clear; - code = c; - worklist += c.startBlock - for (bl <- c.blocks) { - in += (bl -> emptyStack) - out += (bl -> emptyStack) - } - - while (worklist.nonEmpty) { - val block = worklist(0); - worklist.trimStart(1); - val output = check(block, in(block)); - 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 - } - } - } - - /** - * Apply the meet operator of the stack lattice on bl's predecessors. - * :-). Compute the input to bl by checking that all stacks have the - * same length, and taking the lub of types at the same positions. - */ - def meet(bl: BasicBlock) { - val preds = bl.predecessors - - /** XXX workaround #1: one stack empty, the other has BoxedUnit. - * One example where this arises is: - * - * def f(b: Boolean): Unit = synchronized { if (b) () } - */ - def isAllUnits(s1: TypeStack, s2: TypeStack) = { - List(s1, s2) forall (x => x.types forall (_ == BOXED_UNIT)) - } - /** XXX workaround #2: different stacks heading into an exception - * handler which will clear them anyway. Examples where it arises: - * - * var bippy: Int = synchronized { if (b) 5 else 10 } - */ - def isHandlerBlock() = bl.exceptionHandlerStart - - /** The presence of emptyStack means that path has not yet been checked - * (and may not be empty) thus the reference eq tests. - */ - def meet2(s1: TypeStack, s2: TypeStack): TypeStack = { - def workaround(msg: String) = { - checkerDebug(msg + ": " + method + " at block " + bl) - checkerDebug(" s1: " + s1) - checkerDebug(" s2: " + s2) - new TypeStack() - } - def incompatibleString = ( - "Incompatible stacks: " + s1 + " and " + s2 + " in " + method + " at entry to:\n" + - blockAsString(bl) - ) - - if (s1 eq emptyStack) s2 - else if (s2 eq emptyStack) s1 - else if (s1.length != s2.length) { - if (isAllUnits(s1, s2)) - workaround("Ignoring mismatched boxed units") - else if (isHandlerBlock) - workaround("Ignoring mismatched stacks entering exception handler") - else - throw new CheckerException(incompatibleString) - } - else { - val newStack = new TypeStack((s1.types, s2.types).zipped map lub) - if (newStack.nonEmpty) - checkerDebug("Checker created new stack:\n (%s, %s) => %s".format(s1, s2, newStack)) - - newStack - } - } - - if (preds.nonEmpty) { - in(bl) = (preds map out.apply) reduceLeft meet2; - log("Input changed for block: " + bl +" to: " + in(bl)); - } - } - - private var instruction: Instruction = null - private var basicBlock: BasicBlock = null - - /** - * Check the basic block to be type correct and return the - * produced type stack. - */ - def check(b: BasicBlock, initial: TypeStack): TypeStack = { - 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) { - /** PP: I admit I'm not yet figuring out how the stacks will balance when - * we ignore UNIT, but I expect this knowledge will emerge naturally. - * In the meantime I'm logging it to help me out. - */ - logChecker("Ignoring pushed UNIT") - } - else { - stack push x - checkerDebug(sizeString(true) + x) - } - } - } - - this.basicBlock = b - - def typeError(k1: TypeKind, k2: TypeKind) { - error("\n expected: " + k1 + "\n found: " + k2) - } - - def subtypeTest(k1: TypeKind, k2: TypeKind): Unit = - if (k1 <:< k2) () - else typeError(k2, k1) - - for (instr <- b) { - - def checkLocal(local: Local): Unit = { - (method lookupLocal local.sym.name) getOrElse { - error(" " + local + " is not defined in method " + method) - } - } - - def checkField(obj: TypeKind, field: Symbol) { - obj match { - case REFERENCE(sym) => - if (sym.info.member(field.name) == NoSymbol) - error(" " + field + " is not defined in class " + clasz); - case _ => - error(" expected reference type, but " + obj + " found"); - } - } - - /** 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 + " 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) = popStack2 - checkType(a, kind) - checkType(b, kind) - } - - /** Check that arguments on the stack match method params. */ - def checkMethodArgs(method: Symbol) { - val params = method.info.paramTypes - checkStack(params.length) - params.reverse foreach (tpe => checkType(popStack, toTypeKind(tpe))) - } - - /** Checks that the object passed as receiver has a method - * method and that it is callable from the current method. - * - * @param receiver ... - * @param method ... - */ - def checkMethod(receiver: TypeKind, method: Symbol) = - receiver match { - case REFERENCE(sym) => - checkBool(sym.info.member(method.name) != NoSymbol, - "Method " + method + " does not exist in " + sym.fullName); - if (method.isPrivate) - checkBool(method.owner == clasz.symbol, - "Cannot call private method of " + method.owner.fullName - + " from " + clasz.symbol.fullName); - else if (method.isProtected) - checkBool(clasz.symbol isSubClass method.owner, - "Cannot call protected method of " + method.owner.fullName - + " from " + clasz.symbol.fullName); - - case ARRAY(_) => - checkBool(receiver.toType.member(method.name) != NoSymbol, - "Method " + method + " does not exist in " + receiver) - - case t => - error("Not a reference type: " + t) - } - - def checkBool(cond: Boolean, msg: String) = - if (!cond) error(msg) - - this.instruction = instr - - if (settings.debug.value) { - log("PC: " + instr) - log("stack: " + stack) - log("================") - } - instr match { - case THIS(clasz) => - pushStack(toTypeKind(clasz.tpe)) - - case CONSTANT(const) => - pushStack(toTypeKind(const.tpe)) - - case LOAD_ARRAY_ITEM(kind) => - popStack2 match { - case (INT, ARRAY(elem)) => - subtypeTest(elem, kind) - pushStack(elem) - case (a, b) => - error(" expected and INT and a array reference, but " + - a + ", " + b + " found"); - } - - case LOAD_LOCAL(local) => - checkLocal(local) - pushStack(local.kind) - - case LOAD_FIELD(field, 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 - 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)); - 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) => - popStack3 match { - case (k, INT, ARRAY(elem)) => - subtypeTest(k, kind) - subtypeTest(k, elem) - case (a, b, c) => - error(" expected and array reference, and int and " + kind + - " but " + a + ", " + b + ", " + c + " found"); - } - - case STORE_LOCAL(local) => - checkLocal(local) - 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) - primitive match { - case Negation(kind) => - checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE) - checkType(popStack, kind) - pushStack(kind) - - case Test(op, kind, zero) => - if (zero) checkType(popStack, kind) - else checkBinop(kind) - - pushStack(BOOL) - - case Comparison(op, kind) => - checkNumeric(kind) - checkBinop(kind) - pushStack(INT) - - case Arithmetic(op, kind) => - checkNumeric(kind) - if (op == NOT) - checkType(popStack, kind) - else - checkBinop(kind) - pushStack(kind) - - case Logical(op, kind) => - checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG) - checkBinop(kind) - pushStack(kind) - - case Shift(op, kind) => - checkType(kind, BYTE, CHAR, SHORT, INT, LONG) - val (a, b) = popStack2 - checkType(a, INT) - checkType(b, kind) - pushStack(kind) - - case Conversion(src, dst) => - checkNumeric(src) - checkNumeric(dst) - checkType(popStack, src) - pushStack(dst) - - case ArrayLength(kind) => - popStack match { - case ARRAY(elem) => checkType(elem, kind) - case arr => error(" array reference expected, but " + arr + " found") - } - pushStack(INT) - - case StartConcat => - pushStack(ConcatClass) - - case EndConcat => - checkType(popStack, ConcatClass) - pushStack(STRING) - - case StringConcat(el) => - 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 + paramCount) - checkMethodArgs(method) - checkMethod(popStack, method) - pushReturnType - - case Static(onInstance) => - if (onInstance) { - checkStack(1 + paramCount) - checkBool(method.isPrivate || method.isConstructor, - "Static call to non-private method.") - checkMethodArgs(method) - checkMethod(popStack, method) - if (!method.isConstructor) - pushReturnType - } - else { - checkStack(paramCount); - checkMethodArgs(method); - pushReturnType - } - - case SuperCall(mix) => - checkStack(1 + paramCount) - checkMethodArgs(method) - checkMethod(popStack, method) - pushReturnType - } - - case NEW(kind) => - pushStack(kind) - - case CREATE_ARRAY(elem, dims) => - checkStack(dims) - stack.pop(dims) foreach (checkType(_, INT)) - pushStack(ARRAY(elem)) - - case IS_INSTANCE(tpe) => - 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 = 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(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), - "Switch target cannot be found in code.") - - case JUMP(whereto) => - checkBool(code.blocks contains whereto, - "Jump to non-existant block " + whereto) - - case CJUMP(success, failure, cond, kind) => - checkBool(code.blocks contains success, - "Jump to non-existant block " + success) - checkBool(code.blocks contains failure, - "Jump to non-existant block " + failure) - checkBinop(kind) - - case CZJUMP(success, failure, cond, kind) => - checkBool(code.blocks contains success, - "Jump to non-existant block " + success) - checkBool(code.blocks contains failure, - "Jump to non-existant block " + failure) - checkType(popStack, kind) - - case RETURN(UNIT) => () - case RETURN(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 = popStack - checkBool(thrown.toType <:< definitions.ThrowableClass.tpe, - "Element on top of stack should implement 'Throwable': " + thrown); - pushStack(SCALA_NOTHING) - - case DROP(kind) => - checkType(popStack, kind) - - case DUP(kind) => - val top = popStack - checkType(top, kind) - pushStack(top) - pushStack(top) - - case MONITOR_ENTER() => - checkBool(popStack.isReferenceType, "MONITOR_ENTER on non-reference type") - - case MONITOR_EXIT() => - checkBool(popStack.isReferenceType, "MONITOR_EXIT on non-reference type") - - case BOX(kind) => - checkType(popStack, kind) - pushStack(icodes.ObjectReference) - - case UNBOX(kind) => - popStack - pushStack(kind) - - case LOAD_EXCEPTION() => - clearStack() - pushStack(ThrowableReference) - - case SCOPE_ENTER(_) | SCOPE_EXIT(_) => - () - - case _ => - abort("Unknown instruction: " + instr) - } - } - stack - } - - //////////////// Error reporting ///////////////////////// - - def error(msg: String) { - Checkers.this.global.error("!! ICode checker fatality in " + method + " at:" + blockAsString(basicBlock) + ":\n " + msg) - } - - def error(msg: String, stack: TypeStack) { - error(msg + "\n type stack: " + stack) - } - - //////////////////// Checking ///////////////////////////// - - /** Return true if k1 is a subtype of any of the following - * types. - */ - def isOneOf(k1: TypeKind, kinds: TypeKind*) = kinds exists (k1 <:< _) - } -} diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala new file mode 100644 index 0000000000..53205e26ca --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala @@ -0,0 +1,650 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend +package icode + +import scala.collection.mutable.{Buffer, ListBuffer, Map, HashMap} +import scala.tools.nsc.symtab._ + +abstract class ICodeCheckers { + val global: Global + import global._ + + /**

+ * This class performs a set of checks similar to what the bytecode + * verifier does. For each basic block, it checks that: + *

+ * + *

+ * For a control flow graph it checks that type stacks at entry to + * each basic block 'agree': + *

+ * + * + * @author Iulian Dragos + * @version 1.0, 06/09/2005 + * + * @todo Better checks for MONITOR_ENTER/EXIT + * Better checks for local var initializations + */ + class ICodeChecker { + import icodes._ + import opcodes._ + + var clasz: IClass = _ + var method: IMethod = _ + var code: Code = _ + + val in: Map[BasicBlock, TypeStack] = new HashMap() + val out: Map[BasicBlock, TypeStack] = new HashMap() + + val emptyStack = new TypeStack() + + val STRING = REFERENCE(definitions.StringClass) + val BOXED_UNIT = REFERENCE(definitions.BoxedUnitClass) + 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) + println("[[consistency check at the beginning of phase " + globalPhase.name + "]]") + classes.values foreach check + } + + private def posStr(p: Position) = + if (p.isDefined) p.line.toString else "" + + private def indent(s: String, spaces: Int): String = indent(s, " " * spaces) + private def indent(s: String, prefix: String): String = { + val lines = s split "\\n" + lines map (prefix + _) mkString "\n" + } + private def blockAsString(bl: BasicBlock) = { + val s = bl.toList map (instr => posStr(instr.pos) + "\t" + instr) mkString (bl.fullString + " {\n ", "\n ", "\n}") + indent(s, "// ") + } + + /** 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) { + logChecker("\n** Checking class " + cls) + clasz = cls + + for (f1 <- cls.fields ; f2 <- cls.fields ; if f1 < f2) + if (isConfict(f1, f2, false)) + ICodeCheckers.this.global.error("Repetitive field name: " + f1.symbol.fullName) + + for (m1 <- cls.methods ; m2 <- cls.methods ; if m1 < m2) + if (isConfict(m1, m2, true)) + ICodeCheckers.this.global.error("Repetitive method: " + m1.symbol.fullName) + + clasz.methods foreach check + } + + def check(m: IMethod) { + logChecker("\n** Checking method " + m) + method = m + if (!m.isDeferred) + check(m.code) + } + + def check(c: Code) { + var worklist: Buffer[BasicBlock] = new ListBuffer() + + def append(elems: List[BasicBlock]) = elems foreach appendBlock; + def appendBlock(bl: BasicBlock) = + if (!(worklist contains bl)) + worklist += bl + + in.clear; + out.clear; + code = c; + worklist += c.startBlock + for (bl <- c.blocks) { + in += (bl -> emptyStack) + out += (bl -> emptyStack) + } + + while (worklist.nonEmpty) { + val block = worklist(0); + worklist.trimStart(1); + val output = check(block, in(block)); + 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 + } + } + } + + /** + * Apply the meet operator of the stack lattice on bl's predecessors. + * :-). Compute the input to bl by checking that all stacks have the + * same length, and taking the lub of types at the same positions. + */ + def meet(bl: BasicBlock) { + val preds = bl.predecessors + + /** XXX workaround #1: one stack empty, the other has BoxedUnit. + * One example where this arises is: + * + * def f(b: Boolean): Unit = synchronized { if (b) () } + */ + def isAllUnits(s1: TypeStack, s2: TypeStack) = { + List(s1, s2) forall (x => x.types forall (_ == BOXED_UNIT)) + } + /** XXX workaround #2: different stacks heading into an exception + * handler which will clear them anyway. Examples where it arises: + * + * var bippy: Int = synchronized { if (b) 5 else 10 } + */ + def isHandlerBlock() = bl.exceptionHandlerStart + + /** The presence of emptyStack means that path has not yet been checked + * (and may not be empty) thus the reference eq tests. + */ + def meet2(s1: TypeStack, s2: TypeStack): TypeStack = { + def workaround(msg: String) = { + checkerDebug(msg + ": " + method + " at block " + bl) + checkerDebug(" s1: " + s1) + checkerDebug(" s2: " + s2) + new TypeStack() + } + def incompatibleString = ( + "Incompatible stacks: " + s1 + " and " + s2 + " in " + method + " at entry to:\n" + + blockAsString(bl) + ) + + if (s1 eq emptyStack) s2 + else if (s2 eq emptyStack) s1 + else if (s1.length != s2.length) { + if (isAllUnits(s1, s2)) + workaround("Ignoring mismatched boxed units") + else if (isHandlerBlock) + workaround("Ignoring mismatched stacks entering exception handler") + else + throw new CheckerException(incompatibleString) + } + else { + val newStack = new TypeStack((s1.types, s2.types).zipped map lub) + if (newStack.nonEmpty) + checkerDebug("Checker created new stack:\n (%s, %s) => %s".format(s1, s2, newStack)) + + newStack + } + } + + if (preds.nonEmpty) { + in(bl) = (preds map out.apply) reduceLeft meet2; + log("Input changed for block: " + bl +" to: " + in(bl)); + } + } + + private var instruction: Instruction = null + private var basicBlock: BasicBlock = null + + /** + * Check the basic block to be type correct and return the + * produced type stack. + */ + def check(b: BasicBlock, initial: TypeStack): TypeStack = { + 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) { + /** PP: I admit I'm not yet figuring out how the stacks will balance when + * we ignore UNIT, but I expect this knowledge will emerge naturally. + * In the meantime I'm logging it to help me out. + */ + logChecker("Ignoring pushed UNIT") + } + else { + stack push x + checkerDebug(sizeString(true) + x) + } + } + } + + this.basicBlock = b + + def typeError(k1: TypeKind, k2: TypeKind) { + error("\n expected: " + k1 + "\n found: " + k2) + } + + def subtypeTest(k1: TypeKind, k2: TypeKind): Unit = + if (k1 <:< k2) () + else typeError(k2, k1) + + for (instr <- b) { + + def checkLocal(local: Local): Unit = { + (method lookupLocal local.sym.name) getOrElse { + error(" " + local + " is not defined in method " + method) + } + } + + def checkField(obj: TypeKind, field: Symbol) { + obj match { + case REFERENCE(sym) => + if (sym.info.member(field.name) == NoSymbol) + error(" " + field + " is not defined in class " + clasz); + case _ => + error(" expected reference type, but " + obj + " found"); + } + } + + /** 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 + " 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) = popStack2 + checkType(a, kind) + checkType(b, kind) + } + + /** Check that arguments on the stack match method params. */ + def checkMethodArgs(method: Symbol) { + val params = method.info.paramTypes + checkStack(params.length) + params.reverse foreach (tpe => checkType(popStack, toTypeKind(tpe))) + } + + /** Checks that the object passed as receiver has a method + * method and that it is callable from the current method. + * + * @param receiver ... + * @param method ... + */ + def checkMethod(receiver: TypeKind, method: Symbol) = + receiver match { + case REFERENCE(sym) => + checkBool(sym.info.member(method.name) != NoSymbol, + "Method " + method + " does not exist in " + sym.fullName); + if (method.isPrivate) + checkBool(method.owner == clasz.symbol, + "Cannot call private method of " + method.owner.fullName + + " from " + clasz.symbol.fullName); + else if (method.isProtected) + checkBool(clasz.symbol isSubClass method.owner, + "Cannot call protected method of " + method.owner.fullName + + " from " + clasz.symbol.fullName); + + case ARRAY(_) => + checkBool(receiver.toType.member(method.name) != NoSymbol, + "Method " + method + " does not exist in " + receiver) + + case t => + error("Not a reference type: " + t) + } + + def checkBool(cond: Boolean, msg: String) = + if (!cond) error(msg) + + this.instruction = instr + + if (settings.debug.value) { + log("PC: " + instr) + log("stack: " + stack) + log("================") + } + instr match { + case THIS(clasz) => + pushStack(toTypeKind(clasz.tpe)) + + case CONSTANT(const) => + pushStack(toTypeKind(const.tpe)) + + case LOAD_ARRAY_ITEM(kind) => + popStack2 match { + case (INT, ARRAY(elem)) => + subtypeTest(elem, kind) + pushStack(elem) + case (a, b) => + error(" expected and INT and a array reference, but " + + a + ", " + b + " found"); + } + + case LOAD_LOCAL(local) => + checkLocal(local) + pushStack(local.kind) + + case LOAD_FIELD(field, 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 + 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)); + 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) => + popStack3 match { + case (k, INT, ARRAY(elem)) => + subtypeTest(k, kind) + subtypeTest(k, elem) + case (a, b, c) => + error(" expected and array reference, and int and " + kind + + " but " + a + ", " + b + ", " + c + " found"); + } + + case STORE_LOCAL(local) => + checkLocal(local) + 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) + primitive match { + case Negation(kind) => + checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE) + checkType(popStack, kind) + pushStack(kind) + + case Test(op, kind, zero) => + if (zero) checkType(popStack, kind) + else checkBinop(kind) + + pushStack(BOOL) + + case Comparison(op, kind) => + checkNumeric(kind) + checkBinop(kind) + pushStack(INT) + + case Arithmetic(op, kind) => + checkNumeric(kind) + if (op == NOT) + checkType(popStack, kind) + else + checkBinop(kind) + pushStack(kind) + + case Logical(op, kind) => + checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG) + checkBinop(kind) + pushStack(kind) + + case Shift(op, kind) => + checkType(kind, BYTE, CHAR, SHORT, INT, LONG) + val (a, b) = popStack2 + checkType(a, INT) + checkType(b, kind) + pushStack(kind) + + case Conversion(src, dst) => + checkNumeric(src) + checkNumeric(dst) + checkType(popStack, src) + pushStack(dst) + + case ArrayLength(kind) => + popStack match { + case ARRAY(elem) => checkType(elem, kind) + case arr => error(" array reference expected, but " + arr + " found") + } + pushStack(INT) + + case StartConcat => + pushStack(ConcatClass) + + case EndConcat => + checkType(popStack, ConcatClass) + pushStack(STRING) + + case StringConcat(el) => + 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 + paramCount) + checkMethodArgs(method) + checkMethod(popStack, method) + pushReturnType + + case Static(onInstance) => + if (onInstance) { + checkStack(1 + paramCount) + checkBool(method.isPrivate || method.isConstructor, + "Static call to non-private method.") + checkMethodArgs(method) + checkMethod(popStack, method) + if (!method.isConstructor) + pushReturnType + } + else { + checkStack(paramCount); + checkMethodArgs(method); + pushReturnType + } + + case SuperCall(mix) => + checkStack(1 + paramCount) + checkMethodArgs(method) + checkMethod(popStack, method) + pushReturnType + } + + case NEW(kind) => + pushStack(kind) + + case CREATE_ARRAY(elem, dims) => + checkStack(dims) + stack.pop(dims) foreach (checkType(_, INT)) + pushStack(ARRAY(elem)) + + case IS_INSTANCE(tpe) => + 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 = 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(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), + "Switch target cannot be found in code.") + + case JUMP(whereto) => + checkBool(code.blocks contains whereto, + "Jump to non-existant block " + whereto) + + case CJUMP(success, failure, cond, kind) => + checkBool(code.blocks contains success, + "Jump to non-existant block " + success) + checkBool(code.blocks contains failure, + "Jump to non-existant block " + failure) + checkBinop(kind) + + case CZJUMP(success, failure, cond, kind) => + checkBool(code.blocks contains success, + "Jump to non-existant block " + success) + checkBool(code.blocks contains failure, + "Jump to non-existant block " + failure) + checkType(popStack, kind) + + case RETURN(UNIT) => () + case RETURN(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 = popStack + checkBool(thrown.toType <:< definitions.ThrowableClass.tpe, + "Element on top of stack should implement 'Throwable': " + thrown); + pushStack(SCALA_NOTHING) + + case DROP(kind) => + checkType(popStack, kind) + + case DUP(kind) => + val top = popStack + checkType(top, kind) + pushStack(top) + pushStack(top) + + case MONITOR_ENTER() => + checkBool(popStack.isReferenceType, "MONITOR_ENTER on non-reference type") + + case MONITOR_EXIT() => + checkBool(popStack.isReferenceType, "MONITOR_EXIT on non-reference type") + + case BOX(kind) => + checkType(popStack, kind) + pushStack(icodes.ObjectReference) + + case UNBOX(kind) => + popStack + pushStack(kind) + + case LOAD_EXCEPTION() => + clearStack() + pushStack(ThrowableReference) + + case SCOPE_ENTER(_) | SCOPE_EXIT(_) => + () + + case _ => + abort("Unknown instruction: " + instr) + } + } + stack + } + + //////////////// Error reporting ///////////////////////// + + def error(msg: String) { + ICodeCheckers.this.global.error("!! ICode checker fatality in " + method + " at:" + blockAsString(basicBlock) + ":\n " + msg) + } + + def error(msg: String, stack: TypeStack) { + error(msg + "\n type stack: " + stack) + } + + //////////////////// Checking ///////////////////////////// + + /** Return true if k1 is a subtype of any of the following + * types. + */ + def isOneOf(k1: TypeKind, kinds: TypeKind*) = kinds exists (k1 <:< _) + } +} diff --git a/src/compiler/scala/tools/nsc/symtab/Types.scala b/src/compiler/scala/tools/nsc/symtab/Types.scala index 21da19c526..9dac3c530b 100644 --- a/src/compiler/scala/tools/nsc/symtab/Types.scala +++ b/src/compiler/scala/tools/nsc/symtab/Types.scala @@ -814,9 +814,19 @@ trait Types extends reflect.generic.Types { self: SymbolTable => // of a subtyping/equality judgement, which can lead to recursive types being constructed. // See (t0851) for a situation where this happens. if (!this.isGround) { + // PP: The foreach below was formerly expressed as: + // for(tv @ TypeVar(_, _) <- this) { suspension suspend tv } + // + // The tree checker failed this saying a TypeVar is required, but a (Type @unchecked) was found. + // This is a consequence of using a pattern match and variable binding + ticket #1503, which + // was addressed by weakening the type of bindings in pattern matches if they occur on the right. + // So I'm not quite sure why this works at all, as the checker is right that it is mistyped. + // For now I modified it as below, which achieves the same without error. + // // make each type var in this type use its original type for comparisons instead of collecting constraints - for(tv@TypeVar(_, _) <- this) { - suspension suspend tv + this foreach { + case tv: TypeVar => suspension suspend tv + case _ => () } } diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 1403377367..0e8faa1f1e 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -9,6 +9,7 @@ package transform import symtab._ import Flags.{ CASE => _, _ } import scala.collection.mutable.ListBuffer +import scala.collection.mutable import matching.{ Patterns, ParallelMatching } /** This class ... diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala index 2bc3103854..16961db77b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala @@ -8,12 +8,108 @@ package typechecker import scala.tools.nsc.symtab.Flags._ import scala.collection.mutable -import mutable.HashMap +import mutable.{ HashMap, HashSet, ListBuffer } import util.returning abstract class TreeCheckers extends Analyzer { import global._ + 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 + private def wholetreestr(t: Tree) = nodeToString(t) + "\n" + + private def beststr(t: Tree) = "<" + { + if (t.symbol != null && t.symbol != NoSymbol) "sym=" + ownerstr(t.symbol) + else if (t.tpe.isComplete) "tpe=" + typestr(t.tpe) + else t match { + case x: DefTree => "name=" + x.name + case x: RefTree => "reference=" + x.name + case _ => "clazz=" + classstr(t) + } + } + ">" + + /** This is a work in progress, don't take it too seriously. + */ + object SymbolTracker extends Traverser { + type PhaseMap = HashMap[Symbol, List[Tree]] + val maps: ListBuffer[(Phase, PhaseMap)] = ListBuffer() + def prev = maps.init.last._2 + def latest = maps.last._2 + val defSyms = new HashMap[Symbol, List[DefTree]] + val newSyms = new HashSet[Symbol] + val movedMsgs = new ListBuffer[String] + def sortedNewSyms = newSyms.toList.distinct sortBy (_.name.toString) + + def inPrev(sym: Symbol) = { + (maps.size >= 2) && (prev contains sym) + } + def record(sym: Symbol, tree: Tree) = { + if (latest contains sym) latest(sym) = latest(sym) :+ tree + else latest(sym) = List(tree) + + if (inPrev(sym)) { + val prevTrees = prev(sym) + + if (prevTrees exists (t => (t eq tree) || (t.symbol == sym))) () + else if (prevTrees exists (_.symbol.owner == sym.owner.implClass)) { + errorFn("Noticed " + ownerstr(sym) + " moving to implementation class.") + } + else { + val s1 = (prevTrees map wholetreestr).sorted.distinct + val s2 = wholetreestr(tree) + if (s1 contains s2) () + else movedMsgs += ("\n** %s moved:\n** Previously:\n%s\n** Currently:\n%s".format(ownerstr(sym), s1 mkString ", ", s2)) + } + } + else newSyms += sym + } + def reportChanges(): Unit = { + // new symbols + if (newSyms.nonEmpty) { + val str = + if (settings.debug.value) "New symbols: " + (sortedNewSyms mkString " ") + else newSyms.size + " new symbols." + + newSyms.clear() + errorFn(str) + } + + // moved symbols + movedMsgs foreach errorFn + movedMsgs.clear() + + // duplicate defs + for ((sym, defs) <- defSyms ; if defs.size > 1) { + errorFn("%s DefTrees with symbol '%s': %s".format(defs.size, ownerstr(sym), defs map beststr mkString ", ")) + } + defSyms.clear() + } + + def check(ph: Phase, unit: CompilationUnit): Unit = { + if (maps.isEmpty || maps.last._1 != ph) + maps += ((ph, new PhaseMap)) + + traverse(unit.body) + reportChanges() + } + override def traverse(tree: Tree): Unit = { + val sym = tree.symbol + if (sym != null && sym != NoSymbol) { + record(sym, tree) + tree match { + case x: DefTree => + if (defSyms contains sym) defSyms(sym) = defSyms(sym) :+ x + else defSyms(sym) = List(x) + case _ => () + } + } + + super.traverse(tree) + } + } + lazy val tpeOfTree = new HashMap[Tree, Type] def posstr(p: Position) = @@ -48,12 +144,12 @@ abstract class TreeCheckers extends Analyzer { assertFn(currentRun.currentUnit == unit, "currentUnit is " + currentRun.currentUnit + ", but unit is " + unit) currentRun.currentUnit = unit0 } - def check(unit: CompilationUnit) { informProgress("checking "+unit) val context = rootContext(unit) context.checking = true tpeOfTree.clear + SymbolTracker.check(phase, unit) val checker = new TreeChecker(context) runWithUnit(unit) { checker.precheck.traverse(unit.body) @@ -65,10 +161,11 @@ abstract class TreeCheckers extends Analyzer { override def newTyper(context: Context): Typer = new TreeChecker(context) class TreeChecker(context0: Context) extends Typer(context0) { - 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 + override protected def typerAddSyntheticMethods(templ: Template, clazz: Symbol, context: Context): Template = { + // If we don't intercept this all the synthetics get added at every phase, + // with predictably unfortunate results. + templ + } // XXX check for tree.original on TypeTrees. private def treesDiffer(t1: Tree, t2: Tree) = diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 6a67fecd80..647e5e422d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1301,7 +1301,7 @@ trait Typers { self: Analyzer => val tparams1 = cdef.tparams mapConserve (typedTypeDef) val impl1 = newTyper(context.make(cdef.impl, clazz, new Scope)) .typedTemplate(cdef.impl, parentTypes(cdef.impl)) - val impl2 = addSyntheticMethods(impl1, clazz, context) + val impl2 = typerAddSyntheticMethods(impl1, clazz, context) if ((clazz != ClassfileAnnotationClass) && (clazz isNonBottomSubClass ClassfileAnnotationClass)) unit.warning (cdef.pos, @@ -1338,7 +1338,7 @@ trait Typers { self: Analyzer => assert(clazz != NoSymbol) val impl1 = newTyper(context.make(mdef.impl, clazz, new Scope)) .typedTemplate(mdef.impl, parentTypes(mdef.impl)) - val impl2 = addSyntheticMethods(impl1, clazz, context) + val impl2 = typerAddSyntheticMethods(impl1, clazz, context) if (mdef.name == nme.PACKAGEkw) for (m <- mdef.symbol.info.members) @@ -1348,6 +1348,13 @@ trait Typers { self: Analyzer => treeCopy.ModuleDef(mdef, typedMods, mdef.name, impl2) setType NoType } + /** In order to override this in the TreeCheckers Typer so synthetics aren't re-added + * all the time, it is exposed here the module/class typing methods go through it. + */ + protected def typerAddSyntheticMethods(templ: Template, clazz: Symbol, context: Context): Template = { + addSyntheticMethods(templ, clazz, context) + } + /** * @param stat ... * @return ... diff --git a/test/checker-tests/fail12.scala b/test/checker-tests/fail12.scala new file mode 100644 index 0000000000..7568311454 --- /dev/null +++ b/test/checker-tests/fail12.scala @@ -0,0 +1,20 @@ +class A { + def f(b: Boolean) = { + locally { + while (b == false) () + // or: + // do () while (b == false) + } + } +} +// +// [Now checking: erasure] +// [check: erasure] New symbols: BoxedUnit UNIT runtime scala +// /tmp/fail.scala:4: error: +// **** ERROR DURING INTERNAL CHECKING **** +// type mismatch; +// found : scala.runtime.BoxedUnit +// required: Unit +// while (b == false) () +// ^ +// one error found -- cgit v1.2.3