diff options
Diffstat (limited to 'src/compiler')
53 files changed, 743 insertions, 376 deletions
diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala index 8905c94eeb..13bf0ef4c6 100644 --- a/src/compiler/scala/tools/ant/Scalac.scala +++ b/src/compiler/scala/tools/ant/Scalac.scala @@ -479,7 +479,7 @@ class Scalac extends ScalaMatchingTask with ScalacShared { /** Tests if a file exists and prints a warning in case it doesn't. Always * returns the file, even if it doesn't exist. - * @param file A file to test for existance. + * @param file A file to test for existence. * @return The same file. */ protected def existing(file: File): File = { if (!file.exists) diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 733664c30a..1c9dbad4dd 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -234,7 +234,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) /** Called by ScalaDocAnalyzer when a doc comment has been parsed. */ def signalParsedDocComment(comment: String, pos: Position) = { - // TODO: this is all very borken (only works for scaladoc comments, not regular ones) + // TODO: this is all very broken (only works for scaladoc comments, not regular ones) // --> add hooks to parser and refactor Interactive global to handle comments directly // in any case don't use reporter for parser hooks reporter.comment(pos, comment) @@ -1461,7 +1461,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) } - /** Caching member symbols that are def-s in Defintions because they might change from Run to Run. */ + /** Caching member symbols that are def-s in Definitions because they might change from Run to Run. */ val runDefinitions: definitions.RunDefinitions = new definitions.RunDefinitions /** Compile list of source files, diff --git a/src/compiler/scala/tools/nsc/PhaseAssembly.scala b/src/compiler/scala/tools/nsc/PhaseAssembly.scala index 1eb6c9da2c..e1cfa63960 100644 --- a/src/compiler/scala/tools/nsc/PhaseAssembly.scala +++ b/src/compiler/scala/tools/nsc/PhaseAssembly.scala @@ -18,7 +18,7 @@ trait PhaseAssembly { /** * Aux datastructure for solving the constraint system - * The depency graph container with helper methods for node and edge creation + * The dependency graph container with helper methods for node and edge creation */ private class DependencyGraph { diff --git a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala index f1517e56a0..96939e616c 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala @@ -425,11 +425,10 @@ trait MarkupParsers { if (ch != '/') ts append xPattern // child else return false // terminate - case '{' => // embedded Scala patterns - while (ch == '{') { - nextch() + case '{' if xCheckEmbeddedBlock => // embedded Scala patterns, if not double brace + do { ts ++= xScalaPatterns - } + } while (xCheckEmbeddedBlock) assert(!xEmbeddedBlock, "problem with embedded block") case SU => diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index a1cec2ee0b..72aa44d8d9 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -1077,7 +1077,7 @@ abstract class GenICode extends SubComponent { () case (_, UNIT) => ctx.bb.emit(DROP(from), pos) - // otherwise we'd better be doing a primtive -> primitive coercion or there's a problem + // otherwise we'd better be doing a primitive -> primitive coercion or there's a problem case _ if !from.isRefOrArrayType && !to.isRefOrArrayType => coerce(from, to) case _ => diff --git a/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala b/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala index f81c42d836..27bf836484 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala @@ -60,7 +60,7 @@ trait Primitives { self: ICodes => // type : (buf,el) => buf // range: lf,rg <- { BOOL, Ix, Ux, Rx, REF, STR } - // jvm : It should call the appropiate 'append' method on StringBuffer + // jvm : It should call the appropriate 'append' method on StringBuffer case class StringConcat(el: TypeKind) extends Primitive /** Signals the beginning of a series of concatenations. diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala index 676ee12683..b0ad5bdaf9 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala @@ -332,13 +332,13 @@ abstract class TypeFlowAnalysis { `remainingCALLs` also caches info about the typestack just before the callsite, so as to spare computing them again at inlining time. Besides caching, a further optimization involves skipping those basic blocks whose in-flow and out-flow isn't needed anyway (as explained next). - A basic block lacking a callsite in `remainingCALLs`, when visisted by the standard algorithm, won't cause any inlining. + A basic block lacking a callsite in `remainingCALLs`, when visited by the standard algorithm, won't cause any inlining. But as we know from the way type-flows are computed, computing the in- and out-flow for a basic block relies in general on those of other basic blocks. In detail, we want to focus on that sub-graph of the CFG such that control flow may reach a remaining candidate callsite. Those basic blocks not in that subgraph can be skipped altogether. That's why: - `forwardAnalysis()` in `MTFAGrowable` now checks for inclusion of a basic block in `relevantBBs` - same check is performed before adding a block to the worklist, and as part of choosing successors. - The bookkeeping supporting on-the-fly pruning of irrelevant blocks requires overridding most methods of the dataflow-analysis. + The bookkeeping supporting on-the-fly pruning of irrelevant blocks requires overriding most methods of the dataflow-analysis. The rest of the story takes place in Inliner, which does not visit all of the method's basic blocks but only on those represented in `remainingCALLs`. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index 7269910af6..75aa0fc984 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -5,10 +5,11 @@ package scala.tools.nsc.backend.jvm -import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, MethodNode} -import java.io.PrintWriter +import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode} +import java.io.{StringWriter, PrintWriter} import scala.tools.asm.util.{TraceClassVisitor, TraceMethodVisitor, Textifier} import scala.tools.asm.ClassReader +import scala.collection.convert.decorateAsScala._ object AsmUtils { @@ -36,19 +37,12 @@ object AsmUtils { def traceMethod(mnode: MethodNode): Unit = { println(s"Bytecode for method ${mnode.name}") - val p = new Textifier - val tracer = new TraceMethodVisitor(p) - mnode.accept(tracer) - val w = new PrintWriter(System.out) - p.print(w) - w.flush() + println(textify(mnode)) } def traceClass(cnode: ClassNode): Unit = { println(s"Bytecode for class ${cnode.name}") - val w = new PrintWriter(System.out) - cnode.accept(new TraceClassVisitor(w)) - w.flush() + println(textify(cnode)) } def traceClass(bytes: Array[Byte]): Unit = traceClass(readClass(bytes)) @@ -59,8 +53,56 @@ object AsmUtils { node } - def instructionString(instruction: AbstractInsnNode): String = instruction.getOpcode match { - case -1 => instruction.toString - case op => scala.tools.asm.util.Printer.OPCODES(op) + /** + * Returns a human-readable representation of the cnode ClassNode. + */ + def textify(cnode: ClassNode): String = { + val trace = new TraceClassVisitor(new PrintWriter(new StringWriter)) + cnode.accept(trace) + val sw = new StringWriter + val pw = new PrintWriter(sw) + trace.p.print(pw) + sw.toString } + + /** + * Returns a human-readable representation of the code in the mnode MethodNode. + */ + def textify(mnode: MethodNode): String = { + val trace = new TraceClassVisitor(new PrintWriter(new StringWriter)) + mnode.accept(trace) + val sw = new StringWriter + val pw = new PrintWriter(sw) + trace.p.print(pw) + sw.toString + } + + /** + * Returns a human-readable representation of the given instruction. + */ + def textify(insn: AbstractInsnNode): String = { + val trace = new TraceMethodVisitor(new Textifier) + insn.accept(trace) + val sw = new StringWriter + val pw = new PrintWriter(sw) + trace.p.print(pw) + sw.toString.trim + } + + /** + * Returns a human-readable representation of the given instruction sequence. + */ + def textify(insns: Iterator[AbstractInsnNode]): String = { + val trace = new TraceMethodVisitor(new Textifier) + insns.foreach(_.accept(trace)) + val sw: StringWriter = new StringWriter + val pw: PrintWriter = new PrintWriter(sw) + trace.p.print(pw) + sw.toString.trim + } + + /** + * Returns a human-readable representation of the given instruction sequence. + */ + def textify(insns: InsnList): String = textify(insns.iterator().asScala) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index 328ec8a033..a5f33aa786 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -162,4 +162,32 @@ final class BCodeAsmCommon[G <: Global](val global: G) { assoc.collectFirst { case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value }).flatten.getOrElse(AnnotationRetentionPolicyClassValue) + + def implementedInterfaces(classSym: Symbol): List[Symbol] = { + // Additional interface parents based on annotations and other cues + def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match { + case RemoteAttr => Some(RemoteInterfaceClass.tpe) + case _ => None + } + + def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait + + val allParents = classSym.info.parents ++ classSym.annotations.flatMap(newParentForAnnotation) + + // We keep the superClass when computing minimizeParents to eliminate more interfaces. + // Example: T can be eliminated from D + // trait T + // class C extends T + // class D extends C with T + val interfaces = erasure.minimizeParents(allParents) match { + case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) => + ifs + case ifs => + // minimizeParents removes the superclass if it's redundant, for example: + // trait A + // class C extends Object with A // minimizeParents removes Object + ifs + } + interfaces.map(_.typeSymbol) + } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 806d4b277c..8d1c37532e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -791,32 +791,28 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { assert(moduleClass.companionClass == NoSymbol, moduleClass) innerClassBufferASM.clear() this.cunit = cunit - val moduleName = internalName(moduleClass) // + "$" - val mirrorName = moduleName.substring(0, moduleName.length() - 1) - val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL) + val bType = mirrorClassClassBType(moduleClass) val mirrorClass = new asm.tree.ClassNode mirrorClass.visit( classfileVersion, - flags, - mirrorName, + bType.info.flags, + bType.internalName, null /* no java-generic-signature */, ObjectReference.internalName, EMPTY_STRING_ARRAY ) - if (emitSource) { - mirrorClass.visitSource("" + cunit.source, - null /* SourceDebugExtension */) - } + if (emitSource) + mirrorClass.visitSource("" + cunit.source, null /* SourceDebugExtension */) - val ssa = getAnnotPickle(mirrorName, moduleClass.companionSymbol) + val ssa = getAnnotPickle(bType.internalName, moduleClass.companionSymbol) mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa) - addForwarders(isRemote(moduleClass), mirrorClass, mirrorName, moduleClass) + addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass) - innerClassBufferASM ++= classBTypeFromSymbol(moduleClass).info.memberClasses + innerClassBufferASM ++= bType.info.nestedClasses addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) mirrorClass.visitEnd() @@ -932,7 +928,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments constructor.visitEnd() - innerClassBufferASM ++= classBTypeFromSymbol(cls).info.memberClasses + innerClassBufferASM ++= classBTypeFromSymbol(cls).info.nestedClasses addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) beanInfoClass.visitEnd() diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index d58368b19d..c3db28151b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -271,7 +271,7 @@ abstract class BCodeIdiomatic extends SubComponent { assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to") // We're done with BOOL already - from match { + (from: @unchecked) match { // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" @@ -361,7 +361,7 @@ abstract class BCodeIdiomatic extends SubComponent { assert(elem.isNonVoidPrimitiveType) val rand = { // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" - elem match { + (elem: @unchecked) match { case BOOL => Opcodes.T_BOOLEAN case BYTE => Opcodes.T_BYTE case SHORT => Opcodes.T_SHORT diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 03bc32061b..142c901c21 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -118,7 +118,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { addClassFields() - innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses + innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.nestedClasses gen(cd.impl) addInnerClassesASM(cnode, innerClassBufferASM.toList) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala index 7c95b7fc3b..b94208c1a5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala @@ -284,7 +284,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { * ------ */ - // a note on terminology: this is not "postHandlers", despite appearences. + // a note on terminology: this is not "postHandlers", despite appearances. // "postHandlers" as in the source-code view. And from that perspective, both (3.A) and (3.B) are invisible implementation artifacts. if (hasFinally) { nopIfNeeded(startTryBody) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 53ac5bfdc7..a9bce82acd 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -8,16 +8,35 @@ package backend.jvm import scala.tools.asm import asm.Opcodes +import scala.tools.asm.tree.{InnerClassNode, ClassNode} +import opt.ByteCodeRepository +import scala.collection.convert.decorateAsScala._ /** - * The BTypes component defines The BType class hierarchy. BTypes encapsulates all type information - * that is required after building the ASM nodes. This includes optimizations, geneartion of + * The BTypes component defines The BType class hierarchy. BTypes encapsulate all type information + * that is required after building the ASM nodes. This includes optimizations, generation of * InnerClass attributes and generation of stack map frames. * * This representation is immutable and independent of the compiler data structures, hence it can * be queried by concurrent threads. */ abstract class BTypes { + import BTypes.InternalName + + // Some core BTypes are required here, in class BType, where no Global instance is available. + // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual + // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol. + val coreBTypes: CoreBTypesProxyGlobalIndependent[this.type] + import coreBTypes._ + + /** + * Tools for parsing classfiles, used by the inliner. + */ + val byteCodeRepository: ByteCodeRepository + + // Allows to define per-run caches here and in the CallGraph component, which don't have a global + def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T + /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its * construction. @@ -29,30 +48,83 @@ abstract class BTypes { * Concurrent because stack map frames are computed when in the class writer, which might run * on multiple classes concurrently. */ - protected val classBTypeFromInternalNameMap: collection.concurrent.Map[String, ClassBType] + val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) /** - * The string represented by the `offset` / `length` values of a ClassBType, see comment of that - * class. + * Parse the classfile for `internalName` and construct the [[ClassBType]]. */ - protected def internalNameString(offset: Int, lenght: Int): String + def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { + classBTypeFromClassNode(byteCodeRepository.classNode(internalName)) + } /** - * Obtain a previously constructed ClassBType for a given internal name. + * Construct the [[ClassBType]] for a parsed classfile. */ - def classBTypeFromInternalName(internalName: String) = classBTypeFromInternalNameMap(internalName) + def classBTypeFromClassNode(classNode: ClassNode): ClassBType = { + classBTypeFromInternalName.getOrElse(classNode.name, { + setClassInfo(classNode, ClassBType(classNode.name)) + }) + } - // Some core BTypes are required here, in class BType, where no Global instance is available. - // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual - // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol. - val coreBTypes: CoreBTypesProxyGlobalIndependent[this.type] - import coreBTypes._ + private def setClassInfo(classNode: ClassNode, classBType: ClassBType): ClassBType = { + val superClass = classNode.superName match { + case null => + assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}") + None + case superName => + Some(classBTypeFromParsedClassfile(superName)) + } + + val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) + + val flags = classNode.access + + /** + * Find all nested classes of classNode. The innerClasses attribute contains all nested classes + * that are declared inside classNode or used in the bytecode of classNode. So some of them are + * nested in some other class than classNode, and we need to filter them. + * + * For member classes, innerClassNode.outerName is defined, so we compare that to classNode.name. + * + * For local and anonymous classes, innerClassNode.outerName is null. Such classes are required + * to have an EnclosingMethod attribute declaring the outer class. So we keep those local and + * anonymous classes whose outerClass is classNode.name. + * + */ + def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = { + (innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) || + (innerClassNode.outerName == null && byteCodeRepository.classNode(innerClassNode.name).outerClass == classNode.name) + } + + val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({ + case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name) + })(collection.breakOut) + + // if classNode is a nested class, it has an innerClass attribute for itself. in this + // case we build the NestedInfo. + val nestedInfo = classNode.innerClasses.asScala.find(_.name == classNode.name) map { + case innerEntry => + val enclosingClass = + if (innerEntry.outerName != null) { + // if classNode is a member class, the outerName is non-null + classBTypeFromParsedClassfile(innerEntry.outerName) + } else { + // for anonymous or local classes, the outerName is null, but the enclosing class is + // stored in the EnclosingMethod attribute (which ASM encodes in classNode.outerClass). + classBTypeFromParsedClassfile(classNode.outerClass) + } + val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0 + NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag) + } + classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo) + classBType + } /** - * A BType is either a primitve type, a ClassBType, an ArrayBType of one of these, or a MethodType + * A BType is either a primitive type, a ClassBType, an ArrayBType of one of these, or a MethodType * referring to BTypes. */ - /*sealed*/ trait BType { // Not sealed for now due to SI-8546 + sealed trait BType { final override def toString: String = this match { case UNIT => "V" case BOOL => "Z" @@ -171,6 +243,9 @@ abstract class BTypes { assert(other.isRef, s"Cannot compute maxType: $this, $other") // Approximate `lub`. The common type of two references is always ObjectReference. ObjectReference + + case _: MethodBType => + throw new AssertionError(s"unexpected method type when computing maxType: $this") } /** @@ -369,7 +444,7 @@ abstract class BTypes { * * - Initializer block (JLS 8.6 / 8.7): block of statements in a java class * - static initializer: executed before constructor body - * - instance initializer: exectued when class is initialized (instance creation, static + * - instance initializer: executed when class is initialized (instance creation, static * field access, ...) * * - A static nested class can be defined as @@ -540,7 +615,7 @@ abstract class BTypes { * * class A { * void f() { class B {} } - * static void g() { calss C {} } + * static void g() { class C {} } * } * * B has an outer pointer, C doesn't. Both B and C are NOT marked static in the InnerClass table. @@ -568,28 +643,14 @@ abstract class BTypes { /** * A ClassBType represents a class or interface type. The necessary information to build a * ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols. - * - * The `offset` and `length` fields are used to represent the internal name of the class. They - * are indices into some character array. The internal name can be obtained through the method - * `internalNameString`, which is abstract in this component. Name creation is assumed to be - * hash-consed, so if two ClassBTypes have the same internal name, they NEED to have the same - * `offset` and `length`. - * - * The actual implementation in subclass BTypesFromSymbols uses the global `chrs` array from the - * name table. This representation is efficient because the JVM class name is obtained through - * `classSymbol.javaBinaryName`. This already adds the necessary string to the `chrs` array, - * so it makes sense to reuse the same name table in the backend. - * - * ClassBType is not a case class because we want a custom equals method, and because the - * extractor extracts the internalName, which is what you typically need. */ - final class ClassBType(val offset: Int, val length: Int) extends RefBType { + final case class ClassBType(internalName: InternalName) extends RefBType { /** * Write-once variable allows initializing a cyclic graph of infos. This is required for * nested classes. Example: for the definition `class A { class B }` we have * * B.info.nestedInfo.outerClass == A - * A.info.memberClasses contains B + * A.info.nestedClasses contains B */ private var _info: ClassInfo = null @@ -604,7 +665,7 @@ abstract class BTypes { checkInfoConsistency() } - classBTypeFromInternalNameMap(internalName) = this + classBTypeFromInternalName(internalName) = this private def checkInfoConsistency(): Unit = { // we assert some properties. however, some of the linked ClassBType (members, superClass, @@ -612,7 +673,7 @@ abstract class BTypes { // best-effort verification. def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c) - def isJLO(t: ClassBType) = t.internalName == "java/lang/Object" + def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") @@ -627,16 +688,10 @@ abstract class BTypes { s"Invalid interfaces in $this: ${info.interfaces}" ) - assert(info.memberClasses.forall(c => ifInit(c)(_.isNestedClass)), info.memberClasses) + assert(info.nestedClasses.forall(c => ifInit(c)(_.isNestedClass)), info.nestedClasses) } /** - * The internal name of a class is the string returned by java.lang.Class.getName, with all '.' - * replaced by '/'. For example "java/lang/String". - */ - def internalName: String = internalNameString(offset, length) - - /** * @return The class name without the package prefix */ def simpleName: String = internalName.split("/").last @@ -661,8 +716,9 @@ abstract class BTypes { outerName.orNull, innerName.orNull, GenBCode.mkFlags( - info.flags, - if (isStaticNestedClass) asm.Opcodes.ACC_STATIC else 0 + // the static flag in the InnerClass table has a special meaning, see InnerClass comment + info.flags & ~Opcodes.ACC_STATIC, + if (isStaticNestedClass) Opcodes.ACC_STATIC else 0 ) & ClassBType.INNER_CLASSES_FLAGS ) } @@ -736,33 +792,10 @@ abstract class BTypes { } while (fcs == null) fcs } - - /** - * Custom equals / hashCode: we only compare the name (offset / length) - */ - override def equals(o: Any): Boolean = (this eq o.asInstanceOf[Object]) || (o match { - case c: ClassBType => c.offset == this.offset && c.length == this.length - case _ => false - }) - - override def hashCode: Int = { - import scala.runtime.Statics - var acc: Int = -889275714 - acc = Statics.mix(acc, offset) - acc = Statics.mix(acc, length) - Statics.finalizeHash(acc, 2) - } } object ClassBType { /** - * Pattern matching on a ClassBType extracts the `internalName` of the class. - */ - def unapply(c: ClassBType): Option[String] = - if (c == null) None - else Some(c.internalName) - - /** * Valid flags for InnerClass attribute entry. * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6 */ @@ -801,12 +834,12 @@ abstract class BTypes { * through the superclass. * @param flags The java flags, obtained through `javaFlags`. Used also to derive * the flags for InnerClass entries. - * @param memberClasses Classes nested in this class. Those need to be added to the + * @param nestedClasses Classes nested in this class. Those need to be added to the * InnerClass table, see the InnerClass spec summary above. * @param nestedInfo If this describes a nested class, information for the InnerClass table. */ - case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, - memberClasses: List[ClassBType], nestedInfo: Option[NestedInfo]) + final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, + nestedClasses: List[ClassBType], nestedInfo: Option[NestedInfo]) /** * Information required to add a class to an InnerClass table. @@ -820,13 +853,13 @@ abstract class BTypes { * * (*) Note that the STATIC flag in ClassInfo.flags, obtained through javaFlags(classSym), is not * correct for the InnerClass entry, see javaFlags. The static flag in the InnerClass describes - * a source-level propety: if the class is in a static context (does not have an outer pointer). + * a source-level property: if the class is in a static context (does not have an outer pointer). * This is checked when building the NestedInfo. */ - case class NestedInfo(enclosingClass: ClassBType, - outerName: Option[String], - innerName: Option[String], - isStaticNestedClass: Boolean) + final case class NestedInfo(enclosingClass: ClassBType, + outerName: Option[String], + innerName: Option[String], + isStaticNestedClass: Boolean) /** * This class holds the data for an entry in the InnerClass table. See the InnerClass summary @@ -839,9 +872,9 @@ abstract class BTypes { * @param innerName The simple name of the inner class, may be null. * @param flags The flags for this class in the InnerClass entry. */ - case class InnerClassEntry(name: String, outerName: String, innerName: String, flags: Int) + final case class InnerClassEntry(name: String, outerName: String, innerName: String, flags: Int) - case class ArrayBType(componentType: BType) extends RefBType { + final case class ArrayBType(componentType: BType) extends RefBType { def dimension: Int = componentType match { case a: ArrayBType => 1 + a.dimension case _ => 1 @@ -853,7 +886,7 @@ abstract class BTypes { } } - case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType + final case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType /* Some definitions that are required for the implementation of BTypes. They are abstract because * initializing them requires information from types / symbols, which is not accessible here in @@ -873,3 +906,12 @@ abstract class BTypes { */ def isCompilingPrimitive: Boolean } + +object BTypes { + /** + * A marker for strings that represent class internal names. + * Ideally the type would be incompatible with String, for example by making it a value class. + * But that would create overhead in a Collection[InternalName]. + */ + type InternalName = String +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 3b7cbd6392..94f9b585d9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -7,10 +7,14 @@ package scala.tools.nsc package backend.jvm import scala.tools.asm +import opt.ByteCodeRepository +import scala.tools.asm.tree.ClassNode +import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.Source +import BTypes.InternalName /** * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary - * information from a symbol and its type to create the correpsonding ClassBType. It requires + * information from a symbol and its type to create the corresponding ClassBType. It requires * access to the compiler (global parameter). * * The mixin CoreBTypes defines core BTypes that are used in the backend. Building these BTypes @@ -32,20 +36,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ - final def intializeCoreBTypes(): Unit = { - coreBTypes.setBTypes(new CoreBTypes[this.type](this)) - } + val byteCodeRepository = new ByteCodeRepository(global.classPath, recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, (ClassNode, Source)])) - def internalNameString(offset: Int, length: Int) = new String(global.chrs, offset, length) - - protected val classBTypeFromInternalNameMap = { - global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[String, ClassBType]) + final def initializeCoreBTypes(): Unit = { + coreBTypes.setBTypes(new CoreBTypes[this.type](this)) } - /** - * Cache for the method classBTypeFromSymbol. - */ - private val convertedClasses = perRunCaches.newMap[Symbol, ClassBType]() + def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T = perRunCaches.recordCache(cache) // helpers that need access to global. // TODO @lry create a separate component, they don't belong to BTypesFromSymbols @@ -89,13 +86,11 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { (classSym != NothingClass && classSym != NullClass), s"Cannot create ClassBType for special class symbol ${classSym.fullName}") - convertedClasses.getOrElse(classSym, { - val internalName = classSym.javaBinaryName.toTypeName - // We first create and add the ClassBType to the hash map before computing its info. This - // allows initializing cylic dependencies, see the comment on variable ClassBType._info. - val classBType = new ClassBType(internalName.start, internalName.length) - convertedClasses(classSym) = classBType - setClassInfo(classSym, classBType) + val internalName = classSym.javaBinaryName.toString + classBTypeFromInternalName.getOrElse(internalName, { + // The new ClassBType is added to the map in its constructor, before we set its info. This + // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. + setClassInfo(classSym, ClassBType(internalName)) }) } @@ -114,7 +109,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val superClass = if (superClassSym == NoSymbol) None else Some(classBTypeFromSymbol(superClassSym)) - val interfaces = getSuperInterfaces(classSym).map(classBTypeFromSymbol) + val interfaces = implementedInterfaces(classSym).map(classBTypeFromSymbol) val flags = javaFlags(classSym) @@ -126,25 +121,35 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * code generation, but those duplicates will be eliminated when emitting the InnerClass * attribute. * - * Why doe we need to collect classes into innerClassBufferASM at all? To collect references to + * Why do we need to collect classes into innerClassBufferASM at all? To collect references to * nested classes, but NOT nested in C, that are used within C. */ val nestedClassSymbols = { // The lambdalift phase lifts all nested classes to the enclosing class, so if we collect // member classes right after lambdalift, we obtain all nested classes, including local and // anonymous ones. - val nestedClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym)) + val nestedClasses = { + val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym)) + if (isTopLevelModuleClass(classSym)) { + // For Java compatibility, member classes of top-level objects are treated as members of + // the top-level companion class, see comment below. + val members = exitingPickler(memberClassesOf(classSym)) + nested diff members + } else { + nested + } + } - // If this is a top-level class, and it has a companion object, the member classes of the - // companion are added as members of the class. For example: + // If this is a top-level class, the member classes of the companion object are added as + // members of the class. For example: // class C { } // object C { // class D // def f = { class E } // } - // The class D is added as a member of class C. The reason is that the InnerClass attribute - // for D will containt class "C" and NOT the module class "C$" as the outer class of D. - // This is done by buildNestedInfo, the reason is Java compatibility, see comment in BTypes. + // The class D is added as a member of class C. The reason is: for Java compatibility, the + // InnerClass attribute for D has "C" (NOT the module class "C$") as the outer class of D + // (done by buildNestedInfo). See comment in BTypes. // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks // like D is a member of C, not C$. val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases @@ -174,75 +179,51 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } else true }) - val memberClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) + val nestedClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) val nestedInfo = buildNestedInfo(classSym) - classBType.info = ClassInfo(superClass, interfaces, flags, memberClasses, nestedInfo) + classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo) classBType } - /** - * All interfaces implemented by a class, except for those inherited through the superclass. - * - * TODO @lry share code with GenASM - */ - private def getSuperInterfaces(classSym: Symbol): List[Symbol] = { - - // Additional interface parents based on annotations and other cues - def newParentForAnnotation(ann: AnnotationInfo): Symbol = ann.symbol match { - case RemoteAttr => RemoteInterfaceClass - case _ => NoSymbol - } - - val superInterfaces0: List[Symbol] = classSym.mixinClasses - val superInterfaces = existingSymbols(superInterfaces0 ++ classSym.annotations.map(newParentForAnnotation)).distinct - - assert(!superInterfaces.contains(NoSymbol), s"found NoSymbol among: ${superInterfaces.mkString(", ")}") - assert(superInterfaces.forall(s => s.isInterface || s.isTrait), s"found non-interface among: ${superInterfaces.mkString(", ")}") - - erasure.minimizeInterfaces(superInterfaces.map(_.info)).map(_.typeSymbol) - } - private def buildNestedInfo(innerClassSym: Symbol): Option[NestedInfo] = { assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym") - val isNested = !innerClassSym.rawowner.isPackageClass - if (!isNested) None + val isTopLevel = innerClassSym.rawowner.isPackageClass + if (isTopLevel) None else { // See comment in BTypes, when is a class marked static in the InnerClass table. val isStaticNestedClass = isOriginallyStaticOwner(innerClassSym.originalOwner) // After lambdalift (which is where we are), the rawowoner field contains the enclosing class. - val enclosingClassSym = { - if (innerClassSym.isJavaDefined && innerClassSym.rawowner.isModuleClass) { - // Example java source: class C { static class D { } } - // The Scala compiler creates a class and a module symbol for C. Because D is a static - // nested class, the symbol for D is nested in the module class C (not in the class C). - // For the InnerClass attribute, we use the class symbol C, which represents the situation - // in the source code. - - // Cannot use innerClassSym.isStatic: this method looks at the owner, which is a package - // at this pahse (after lambdalift, flatten). - assert(isOriginallyStaticOwner(innerClassSym.originalOwner), innerClassSym.originalOwner) - + val enclosingClass = { + // (1) Example java source: class C { static class D { } } + // The Scala compiler creates a class and a module symbol for C. Because D is a static + // nested class, the symbol for D is nested in the module class C (not in the class C). + // For the InnerClass attribute, we use the class symbol C, which represents the situation + // in the source code. + + // (2) Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec. + if ((innerClassSym.isJavaDefined && innerClassSym.rawowner.isModuleClass) || // (1) + (!isAnonymousOrLocalClass(innerClassSym) && isTopLevelModuleClass(innerClassSym.rawowner))) { // (2) // phase travel for linkedCoC - does not always work in late phases - exitingPickler(innerClassSym.rawowner.linkedClassOfClass) + exitingPickler(innerClassSym.rawowner.linkedClassOfClass) match { + case NoSymbol => + // For top-level modules without a companion class, see doc of mirrorClassClassBType. + mirrorClassClassBType(exitingPickler(innerClassSym.rawowner)) + + case companionClass => + classBTypeFromSymbol(companionClass) + } + } else { + classBTypeFromSymbol(innerClassSym.rawowner) } - else innerClassSym.rawowner } - val enclosingClass: ClassBType = classBTypeFromSymbol(enclosingClassSym) val outerName: Option[String] = { - if (isAnonymousOrLocalClass(innerClassSym)) { - None - } else { - val outerName = innerClassSym.rawowner.javaBinaryName - // Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec. - val outerNameModule = if (isTopLevelModuleClass(innerClassSym.rawowner)) outerName.dropModule - else outerName - Some(outerNameModule.toString) - } + if (isAnonymousOrLocalClass(innerClassSym)) None + else Some(enclosingClass.internalName) } val innerName: Option[String] = { @@ -255,6 +236,29 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } /** + * For top-level objects without a companion class, the compilere generates a mirror class with + * static forwarders (Java compat). There's no symbol for the mirror class, but we still need a + * ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes). + */ + def mirrorClassClassBType(moduleClassSym: Symbol): ClassBType = { + assert(isTopLevelModuleClass(moduleClassSym), s"not a top-level module class: $moduleClassSym") + val internalName = moduleClassSym.javaBinaryName.dropModule.toString + classBTypeFromInternalName.getOrElse(internalName, { + val c = ClassBType(internalName) + // class info consistent with BCodeHelpers.genMirrorClass + val nested = exitingPickler(memberClassesOf(moduleClassSym)) map classBTypeFromSymbol + c.info = ClassInfo( + superClass = Some(ObjectReference), + interfaces = Nil, + flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, + nestedClasses = nested, + nestedInfo = None + ) + c + }) + } + + /** * True for module classes of package level objects. The backend will generate a mirror class for * such objects. */ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index fac3c93be2..246235f395 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -4,7 +4,7 @@ package backend.jvm import scala.annotation.switch /** - * Core BTypes and some other definitions. The initialization of these definitions requies access + * Core BTypes and some other definitions. The initialization of these definitions requires access * to symbols / types (global). * * The symbols used to initialize the ClassBTypes may change from one compiler run to the next. To @@ -18,11 +18,11 @@ import scala.annotation.switch * * The definitions in `CoreBTypes` need to be lazy vals to break an initialization cycle. When * creating a new instance to assign to the proxy, the `classBTypeFromSymbol` invoked in the - * constructor will actucally go through the proxy. The lazy vals make sure the instance is assigned + * constructor will actually go through the proxy. The lazy vals make sure the instance is assigned * in the proxy before the fields are initialized. * * Note: if we did not re-create the core BTypes on each compiler run, BType.classBTypeFromInternalNameMap - * could not be a perRunCache anymore: the classes defeined here need to be in that map, they are + * could not be a perRunCache anymore: the classes defined here need to be in that map, they are * added when the ClassBTypes are created. The per run cache removes them, so they would be missing * in the second run. */ @@ -192,7 +192,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { } /** - * This trait make some core BTypes availalbe that don't depend on a Global instance. Some core + * This trait make some core BTypes available that don't depend on a Global instance. Some core * BTypes are required to be accessible in the BTypes trait, which does not have access to Global. * * BTypes cannot refer to CoreBTypesProxy because some of its members depend on global, for example diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index e56a20c2e7..abe3bc512c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -677,7 +677,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } - def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor) { + def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor, isMirror: Boolean = false) { /* The outer name for this inner class. Note that it returns null * when the inner class should not get an index in the constant pool. * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. @@ -698,11 +698,19 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => else innerSym.rawname + innerSym.moduleSuffix - // This collects all inner classes of csym, including local and anonymous: lambdalift makes - // them members of their enclosing class. - innerClassBuffer ++= exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym)) + innerClassBuffer ++= { + val members = exitingPickler(memberClassesOf(csym)) + // lambdalift makes all classes (also local, anonymous) members of their enclosing class + val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym)) - // Add members of the companion object (if top-level). why, see comment in BTypes.scala. + // for the mirror class, we take the members of the companion module class (Java compat, + // see doc in BTypes.scala). for module classes, we filter out those members. + if (isMirror) members + else if (isTopLevelModule(csym)) allNested diff members + else allNested + } + + // If this is a top-level class, add members of the companion object. val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases if (isTopLevelModule(linkedClass)) { // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, @@ -1206,22 +1214,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => def serialVUID: Option[Long] = genBCode.serialVUID(clasz.symbol) - private def getSuperInterfaces(c: IClass): Array[String] = { - - // Additional interface parents based on annotations and other cues - def newParentForAttr(ann: AnnotationInfo): Symbol = ann.symbol match { - case RemoteAttr => RemoteInterfaceClass - case _ => NoSymbol - } - - val ps = c.symbol.info.parents - val superInterfaces0: List[Symbol] = if(ps.isEmpty) Nil else c.symbol.mixinClasses - val superInterfaces = existingSymbols(superInterfaces0 ++ c.symbol.annotations.map(newParentForAttr)).distinct - - if(superInterfaces.isEmpty) EMPTY_STRING_ARRAY - else mkArray(erasure.minimizeInterfaces(superInterfaces.map(_.info)).map(t => javaName(t.typeSymbol))) - } - var clasz: IClass = _ // this var must be assigned only by genClass() var jclass: asm.ClassWriter = _ // the classfile being emitted var thisName: String = _ // the internal name of jclass @@ -1242,7 +1234,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => val ps = c.symbol.info.parents val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol) - val ifaces = getSuperInterfaces(c) + val ifaces: Array[String] = implementedInterfaces(c.symbol).map(javaName)(collection.breakOut) val thisSignature = getGenericSignature(c.symbol, c.symbol.owner) val flags = mkFlags( @@ -2812,7 +2804,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) - addInnerClasses(modsym, mirrorClass) + addInnerClasses(modsym, mirrorClass, isMirror = true) mirrorClass.visitEnd() writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym) } @@ -2947,7 +2939,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => } // end of class JBeanInfoBuilder /** A namespace for utilities to normalize the code of an IMethod, over and beyond what IMethod.normalize() strives for. - * In particualr, IMethod.normalize() doesn't collapseJumpChains(). + * In particular, IMethod.normalize() doesn't collapseJumpChains(). * * TODO Eventually, these utilities should be moved to IMethod and reused from normalize() (there's nothing JVM-specific about them). */ @@ -3162,7 +3154,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => } } - // remove the unusued exception handler references + // remove the unused exception handler references if (settings.debug) for (exh <- unusedExceptionHandlers) debuglog(s"eliding exception handler $exh because it does not cover any reachable blocks") m.exh = m.exh filterNot unusedExceptionHandlers diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index a45f586666..d5e95c47cf 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -286,7 +286,7 @@ abstract class GenBCode extends BCodeSyncAndTry { val initStart = Statistics.startTimer(BackendStats.bcodeInitTimer) arrivalPos = 0 // just in case scalaPrimitives.init() - bTypes.intializeCoreBTypes() + bTypes.initializeCoreBTypes() Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart) // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala new file mode 100644 index 0000000000..7b424d2107 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -0,0 +1,112 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm +package opt + +import scala.tools.asm +import asm.tree._ +import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.io.AbstractFile +import scala.tools.nsc.util.ClassFileLookup +import OptimizerReporting._ +import ByteCodeRepository._ +import BTypes.InternalName + +/** + * The ByteCodeRepository provides utilities to read the bytecode of classfiles from the compilation + * classpath. Parsed classes are cached in the `classes` map. + * + * @param classPath The compiler classpath where classfiles are searched and read from. + * @param classes Cache for parsed ClassNodes. Also stores the source of the bytecode: + * [[Classfile]] if read from `classPath`, [[CompilationUnit]] if the bytecode + * corresponds to a class being compiled. + */ +class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, (ClassNode, Source)]) { + /** + * The class node and source for an internal name. If the class node is not yet available, it is + * parsed from the classfile on the compile classpath. + */ + def classNodeAndSource(internalName: InternalName): (ClassNode, Source) = { + classes.getOrElseUpdate(internalName, (parseClass(internalName), Classfile)) + } + + /** + * The class node for an internal name. If the class node is not yet available, it is parsed from + * the classfile on the compile classpath. + */ + def classNode(internalName: InternalName) = classNodeAndSource(internalName)._1 + + /** + * The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`. + * The declaration of the field may be in one of the superclasses. + * + * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring class. + */ + def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Option[(FieldNode, InternalName)] = { + val c = classNode(classInternalName) + c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse { + Option(c.superName).flatMap(n => fieldNode(n, name, descriptor)) + } + } + + /** + * The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`. + * The declaration of the method may be in one of the parents. + * + * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring class. + */ + def methodNode(classInternalName: InternalName, name: String, descriptor: String): Option[(MethodNode, InternalName)] = { + val c = classNode(classInternalName) + c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, classInternalName)) orElse { + val parents = Option(c.superName) ++ c.interfaces.asScala + // `view` to stop at the first result + parents.view.flatMap(methodNode(_, name, descriptor)).headOption + } + } + + private def parseClass(internalName: InternalName): ClassNode = { + val fullName = internalName.replace('/', '.') + classPath.findClassFile(fullName) map { classFile => + val classNode = new asm.tree.ClassNode() + val classReader = new asm.ClassReader(classFile.toByteArray) + // We don't need frames when inlining, but we want to keep the local variable table, so we + // don't use SKIP_DEBUG. + classReader.accept(classNode, asm.ClassReader.SKIP_FRAMES) + // SKIP_FRAMES leaves line number nodes. Remove them because they are not correct after + // inlining. + // TODO: we need to remove them also for classes that are not parsed from classfiles, why not simplify and do it once when inlining? + // OR: instead of skipping line numbers for inlined code, use write a SourceDebugExtension + // attribute that contains JSR-45 data that encodes debugging info. + // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11 + // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html + removeLineNumberNodes(classNode) + classNode + } getOrElse { + inlineFailure(s"Class file for class $fullName not found.") + } + } + + private def removeLineNumberNodes(classNode: ClassNode): Unit = { + for (method <- classNode.methods.asScala) { + val iter = method.instructions.iterator() + while (iter.hasNext) iter.next() match { + case _: LineNumberNode => iter.remove() + case _ => + } + } + } +} + +object ByteCodeRepository { + /** + * The source of a ClassNode in the ByteCodeRepository. Can be either [[CompilationUnit]] if the + * class is being compiled or [[Classfile]] if the class was parsed from the compilation classpath. + */ + sealed trait Source + object CompilationUnit extends Source + object Classfile extends Source +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala index 273112b93c..87ad715e4d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -19,16 +19,16 @@ import scala.tools.nsc.settings.ScalaSettings * Optimizations within a single method. * * unreachable code - * - removes instrucions of basic blocks to which no branch instruction points + * - removes instructions of basic blocks to which no branch instruction points * + enables eliminating some exception handlers and local variable descriptors * > eliminating them is required for correctness, as explained in `removeUnreachableCode` * * empty exception handlers * - removes exception handlers whose try block is empty * + eliminating a handler where the try block is empty and reachable will turn the catch block - * unreachble. in this case "unreachable code" is invoked recursively until reaching a fixpiont. + * unreachable. in this case "unreachable code" is invoked recursively until reaching a fixpoint. * > for try blocks that are unreachable, "unreachable code" removes also the instructions of the - * catch block, and the recrusive invocation is not necessary. + * catch block, and the recursive invocation is not necessary. * * simplify jumps * - various simplifications, see doc domments of individual optimizations @@ -52,10 +52,10 @@ class LocalOpt(settings: ScalaSettings) { * cleanups to the bytecode. * * @param clazz The class whose methods are optimized - * @return `true` if unreachable code was elminated in some method, `false` otherwise. + * @return `true` if unreachable code was eliminated in some method, `false` otherwise. */ def methodOptimizations(clazz: ClassNode): Boolean = { - settings.Yopt.value.nonEmpty && clazz.methods.asScala.foldLeft(false) { + !settings.YoptNone && clazz.methods.asScala.foldLeft(false) { case (changed, method) => methodOptimizations(method, clazz.name) || changed } } @@ -66,7 +66,7 @@ class LocalOpt(settings: ScalaSettings) { * We rely on dead code elimination provided by the ASM framework, as described in the ASM User * Guide (http://asm.ow2.org/index.html), Section 8.2.1. It runs a data flow analysis, which only * computes Frame information for reachable instructions. Instructions for which no Frame data is - * available after the analyis are unreachable. + * available after the analysis are unreachable. * * Also simplifies branching instructions, removes unused local variable descriptors, empty * exception handlers, unnecessary label declarations and empty line number nodes. @@ -240,7 +240,7 @@ class LocalOpt(settings: ScalaSettings) { } /** - * The number of local varialbe slots used for parameters and for the `this` reference. + * The number of local variable slots used for parameters and for the `this` reference. */ private def parametersSize(method: MethodNode): Int = { // Double / long fields occupy two slots, so we sum up the sizes. Since getSize returns 0 for @@ -322,7 +322,7 @@ class LocalOpt(settings: ScalaSettings) { * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM * framework only computes these values during bytecode generation. * - * Sicne there's currently no better way, we run a bytecode generator on the method and extract + * Since there's currently no better way, we run a bytecode generator on the method and extract * the computed values. This required changes to the ASM codebase: * - the [[MethodWriter]] class was made public * - accessors for maxLocals / maxStack were added to the MethodWriter class @@ -345,7 +345,7 @@ class LocalOpt(settings: ScalaSettings) { * Removes LineNumberNodes that don't describe any executable instructions. * * This method expects (and asserts) that the `start` label of each LineNumberNode is the - * lexically preceeding label declaration. + * lexically preceding label declaration. */ def removeEmptyLineNumbers(method: MethodNode): Boolean = { def isEmpty(node: AbstractInsnNode): Boolean = node.getNext match { @@ -510,7 +510,7 @@ class LocalOpt(settings: ScalaSettings) { * CondJump l; [nops, no labels]; GOTO m; [nops]; l: [...] * => NegatedCondJump m; [nops, no labels]; [nops]; l: [...] * - * Note that no label definitions are allowed in the first [nops] section. Otherwsie, there could + * Note that no label definitions are allowed in the first [nops] section. Otherwise, there could * be some other jump to the GOTO, and eliminating it would change behavior. * * For technical reasons, we cannot remove the GOTO here (*).Instead this method returns an Option @@ -542,7 +542,7 @@ class LocalOpt(settings: ScalaSettings) { * => xRETURN/ATHROW; [any ops]; l: xRETURN/ATHROW * * inlining is only done if the GOTO instruction is not part of a try block, otherwise the - * rewrite might change the behavior. For xRETURN, the reason is that return insructions may throw + * rewrite might change the behavior. For xRETURN, the reason is that return instructions may throw * an IllegalMonitorStateException, as described here: * http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.return */ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala new file mode 100644 index 0000000000..7002e43d98 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala @@ -0,0 +1,24 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm + +import scala.tools.asm +import asm.tree._ + +/** + * Reporting utilities used in the optimizer. + */ +object OptimizerReporting { + def methodSignature(className: String, methodName: String, methodDescriptor: String): String = { + className + "::" + methodName + methodDescriptor + } + + def methodSignature(className: String, method: MethodNode): String = methodSignature(className, method.name, method.desc) + + def inlineFailure(reason: String): Nothing = MissingRequirementError.signal(reason) + def assertionError(message: String): Nothing = throw new AssertionError(message) +} diff --git a/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala b/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala index c6e699373b..0e6ee76eb2 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/ConstantOptimization.scala @@ -18,7 +18,7 @@ import scala.annotation.tailrec * * With some more work it could be extended to * - cache stable values (final fields, modules) in locals - * - replace the copy propagation in ClosureElilmination + * - replace the copy propagation in ClosureElimination * - fold constants * - eliminate unnecessary stores and loads * - propagate knowledge gathered from conditionals for further optimization @@ -437,7 +437,7 @@ abstract class ConstantOptimization extends SubComponent { // TODO if we do all that we need to be careful in the // case that success and failure are the same target block // because we're using a Map and don't want one possible state to clobber the other - // alternative mayb we should just replace the conditional with a jump if both targets are the same + // alternative maybe we should just replace the conditional with a jump if both targets are the same def mightEqual = val1 mightEqual val2 def mightNotEqual = val1 mightNotEqual val2 diff --git a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala index 4b419b210c..3704acb055 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala @@ -223,7 +223,7 @@ abstract class DeadCodeElimination extends SubComponent { debuglog("Marking instr: \tBB_" + bb + ": " + idx + " " + bb(idx)) val instr = bb(idx) - // adds the instrutions that define the stack values about to be consumed to the work list to + // adds the instructions that define the stack values about to be consumed to the work list to // be marked useful def addDefs() = for ((bb1, idx1) <- rdef.findDefs(bb, idx, instr.consumed) if !useful(bb1)(idx1)) { debuglog(s"\t${bb1(idx1)} is consumed by $instr") diff --git a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala index aa18b26d93..8f6fc65706 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala @@ -290,7 +290,7 @@ abstract class Inliners extends SubComponent { /** * A transformation local to the body of the IMethod received as argument. - * An linining decision consists in replacing a callsite with the body of the callee. + * An inlining decision consists in replacing a callsite with the body of the callee. * Please notice that, because `analyzeMethod()` itself may modify a method body, * the particular callee bodies that end up being inlined depend on the particular order in which methods are visited * (no topological sorting over the call-graph is attempted). diff --git a/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala index 26b5429e23..cb201617d2 100644 --- a/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala +++ b/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala @@ -23,8 +23,8 @@ trait FlatClassPath extends ClassFileLookup[AbstractFile] { /** Allows to get entries for packages and classes merged with sources possibly in one pass. */ private[nsc] def list(inPackage: String): FlatClassPathEntries - // A default implementation which should be overriden, if we can create more efficient - // solution for given type of FlatClassPath + // A default implementation which should be overridden, if we can create the more efficient + // solution for a given type of FlatClassPath override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = { val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 18e639b81c..a5b722612d 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -168,7 +168,7 @@ trait ScalaSettings extends AbsScalaSettings val termConflict = ChoiceSetting ("-Yresolve-term-conflict", "strategy", "Resolve term conflicts", List("package", "object", "error"), "error") val inline = BooleanSetting ("-Yinline", "Perform inlining when possible.") val inlineHandlers = BooleanSetting ("-Yinline-handlers", "Perform exception handler inlining when possible.") - val YinlinerWarnings= BooleanSetting ("-Yinline-warnings", "Emit inlining warnings. (Normally surpressed due to high volume)") + val YinlinerWarnings= BooleanSetting ("-Yinline-warnings", "Emit inlining warnings. (Normally suppressed due to high volume)") val Xlinearizer = ChoiceSetting ("-Ylinearizer", "which", "Linearizer to use", List("normal", "dfs", "rpo", "dump"), "rpo") val log = PhasesSetting ("-Ylog", "Log operations during") val Ylogcp = BooleanSetting ("-Ylog-classpath", "Output information about what classpath is being applied.") @@ -199,7 +199,7 @@ trait ScalaSettings extends AbsScalaSettings val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup") val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects") val Yreploutdir = StringSetting ("-Yrepl-outdir", "path", "Write repl-generated classfiles to given output directory (use \"\" to generate a temporary dir)" , "") - val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.") + val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overridden methods.") val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.").withDeprecationMessage(removalIn212) val inferByName = BooleanSetting ("-Yinfer-by-name", "Allow inference of by-name types. This is a temporary option to ease transition. See SI-7899.").withDeprecationMessage(removalIn212) val YclasspathImpl = ChoiceSetting ("-YclasspathImpl", "implementation", "Choose classpath scanning method.", List(ClassPathRepresentationType.Recursive, ClassPathRepresentationType.Flat), ClassPathRepresentationType.Recursive) @@ -215,7 +215,7 @@ trait ScalaSettings extends AbsScalaSettings object YoptChoices extends MultiChoiceEnumeration { val unreachableCode = Choice("unreachable-code", "Eliminate unreachable code, exception handlers protecting no instructions, debug information of eliminated variables.") - val simplifyJumps = Choice("simplify-jumps", "Simplify branching instructions, eliminate unnecessery ones.") + val simplifyJumps = Choice("simplify-jumps", "Simplify branching instructions, eliminate unnecessary ones.") val recurseUnreachableJumps = Choice("recurse-unreachable-jumps", "Recursively apply unreachable-code and simplify-jumps (if enabled) until reaching a fixpoint.") val emptyLineNumbers = Choice("empty-line-numbers", "Eliminate unnecessary line number information.") val emptyLabels = Choice("empty-labels", "Eliminate and collapse redundant labels in the bytecode.") @@ -242,6 +242,7 @@ trait ScalaSettings extends AbsScalaSettings descr = "Enable optimizations", domain = YoptChoices) + def YoptNone = Yopt.isSetByUser && Yopt.value.isEmpty def YoptUnreachableCode = !Yopt.isSetByUser || Yopt.contains(YoptChoices.unreachableCode) def YoptSimplifyJumps = Yopt.contains(YoptChoices.simplifyJumps) def YoptRecurseUnreachableJumps = Yopt.contains(YoptChoices.recurseUnreachableJumps) diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index c400e8c29c..d174dc86c7 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -30,7 +30,7 @@ trait Warnings { // Experimental lint warnings that are turned off, but which could be turned on programmatically. // These warnings are said to blind those who dare enable them. // They are not activated by -Xlint and can't be enabled on the command line. - val warnValueOverrides = { // currently turned off as experimental. creaded using constructor (new BS), so not available on the command line. + val warnValueOverrides = { // Currently turned off as experimental. Created using constructor (new BS), so not available on the command line. val flag = new BooleanSetting("value-overrides", "Generated value class method overrides an implementation") flag.value = false flag diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index 9af3efbece..8fd2ea45e4 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -170,7 +170,7 @@ abstract class SymbolLoaders { } /** Create a new loader from a binary classfile. - * This is intented as a hook allowing to support loading symbols from + * This is intended as a hook allowing to support loading symbols from * files other than .class files. */ protected def newClassLoader(bin: AbstractFile): SymbolLoader = diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 1abbdb50b0..4d08be3c24 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -587,7 +587,7 @@ abstract class ClassfileParser { info = MethodType(newParams, clazz.tpe) } - // Note: the info may be overrwritten later with a generic signature + // Note: the info may be overwritten later with a generic signature // parsed from SignatureATTR sym setInfo info propagatePackageBoundary(jflags, sym) @@ -768,7 +768,7 @@ abstract class ClassfileParser { classTParams = tparams val parents = new ListBuffer[Type]() while (index < end) { - parents += sig2type(tparams, skiptvs = false) // here the variance doesnt'matter + parents += sig2type(tparams, skiptvs = false) // here the variance doesn't matter } ClassInfoType(parents.toList, instanceScope, sym) } diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index f471440293..362cbde04f 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -535,7 +535,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * whether `sym` denotes a param-accessor (ie a field) that fulfills all of: * (a) has stationary value, ie the same value provided via the corresponding ctor-arg; and * (b) isn't subject to specialization. We might be processing statements for: - * (b.1) the constructur in the generic (super-)class; or + * (b.1) the constructor in the generic (super-)class; or * (b.2) the constructor in the specialized (sub-)class. * (c) isn't part of a DelayedInit subclass. */ diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index f7b1021ea2..d2c511a2d1 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -9,7 +9,7 @@ import scala.reflect.internal.Symbols import scala.collection.mutable.LinkedHashMap /** - * This transformer is responisble for turning lambdas into anonymous classes. + * This transformer is responsible for turning lambdas into anonymous classes. * The main assumption it makes is that a lambda {args => body} has been turned into * {args => liftedBody()} where lifted body is a top level method that implements the body of the lambda. * Currently Uncurry is responsible for that transformation. @@ -17,7 +17,7 @@ import scala.collection.mutable.LinkedHashMap * From a lambda, Delambdafy will create * 1) a static forwarder at the top level of the class that contained the lambda * 2) a new top level class that - a) has fields and a constructor taking the captured environment (including possbily the "this" + a) has fields and a constructor taking the captured environment (including possibly the "this" * reference) * b) an apply method that calls the static forwarder * c) if needed a bridge method for the apply method @@ -99,7 +99,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre super.transform(newExpr) // when we encounter a template (basically the thing that holds body of a class/trait) - // we need to updated it to include newly created accesor methods after transforming it + // we need to updated it to include newly created accessor methods after transforming it case Template(_, _, _) => try { // during this call accessorMethods will be populated from the Function case @@ -249,7 +249,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre else "$" + funOwner.name + "$" ) val oldClassPart = oldClass.name.decode - // make sure the class name doesn't contain $anon, otherwsie isAnonymousClass/Function may be true + // make sure the class name doesn't contain $anon, otherwise isAnonymousClass/Function may be true val name = unit.freshTypeName(s"$oldClassPart$suffix".replace("$anon", "$nestedInAnon")) val lambdaClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation @@ -434,7 +434,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre } /** - * Get the symbol of the target lifted lambad body method from a function. I.e. if + * Get the symbol of the target lifted lambda body method from a function. I.e. if * the function is {args => anonfun(args)} then this method returns anonfun's symbol */ private def targetMethod(fun: Function): Symbol = fun match { diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index b6af19250e..5c72bb3258 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -185,22 +185,22 @@ abstract class Erasure extends AddInterfaces private def isErasedValueType(tpe: Type) = tpe.isInstanceOf[ErasedValueType] - /* Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents. + /* Drop redundant types (ones which are implemented by some other parent) from the immediate parents. * This is important on Android because there is otherwise an interface explosion. */ - def minimizeInterfaces(lstIfaces: List[Type]): List[Type] = { - var rest = lstIfaces - var leaves = List.empty[Type] - while(!rest.isEmpty) { + def minimizeParents(parents: List[Type]): List[Type] = { + var rest = parents + var leaves = collection.mutable.ListBuffer.empty[Type] + while(rest.nonEmpty) { val candidate = rest.head val nonLeaf = leaves exists { t => t.typeSymbol isSubClass candidate.typeSymbol } if(!nonLeaf) { - leaves = candidate :: (leaves filterNot { t => candidate.typeSymbol isSubClass t.typeSymbol }) + leaves = leaves filterNot { t => candidate.typeSymbol isSubClass t.typeSymbol } + leaves += candidate } rest = rest.tail } - - leaves.reverse + leaves.toList } @@ -220,7 +220,7 @@ abstract class Erasure extends AddInterfaces case _ => tps } - val minParents = minimizeInterfaces(parents) + val minParents = minimizeParents(parents) val validParents = if (isTraitSignature) // java is unthrilled about seeing interfaces inherit from classes @@ -430,7 +430,7 @@ abstract class Erasure extends AddInterfaces * a name clash. The present method guards against these name clashes. * * @param member The original member - * @param other The overidden symbol for which the bridge was generated + * @param other The overridden symbol for which the bridge was generated * @param bridge The bridge */ def checkBridgeOverrides(member: Symbol, other: Symbol, bridge: Symbol): Seq[(Position, String)] = { @@ -1153,7 +1153,7 @@ abstract class Erasure extends AddInterfaces } } - /** The main transform function: Pretransfom the tree, and then + /** The main transform function: Pretransform the tree, and then * re-type it at phase erasure.next. */ override def transform(tree: Tree): Tree = { diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index e47fdac938..f3cab8184c 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -441,7 +441,7 @@ abstract class ExplicitOuter extends InfoTransform else atPos(tree.pos)(outerPath(outerValue, currentClass.outerClass, sym)) // (5) case Select(qual, name) => - // make not private symbol acessed from inner classes, as well as + // make not private symbol accessed from inner classes, as well as // symbols accessed from @inline methods // // See SI-6552 for an example of why `sym.owner.enclMethod hasAnnotation ScalaInlineClass` diff --git a/src/compiler/scala/tools/nsc/transform/Flatten.scala b/src/compiler/scala/tools/nsc/transform/Flatten.scala index 4662ef6224..6149e40fa7 100644 --- a/src/compiler/scala/tools/nsc/transform/Flatten.scala +++ b/src/compiler/scala/tools/nsc/transform/Flatten.scala @@ -77,7 +77,7 @@ abstract class Flatten extends InfoTransform { if (sym.isTerm && !sym.isStaticModule) { decls1 enter sym if (sym.isModule) { - // In theory, we could assert(sym.isMethod), because nested, non-static moduls are + // In theory, we could assert(sym.isMethod), because nested, non-static modules are // transformed to methods (lateMETHOD flag added in RefChecks). But this requires // forcing sym.info (see comment on isModuleNotMethod), which forces stub symbols // too eagerly (SI-8907). diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index d69c9d9a65..fa0c1f797b 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -402,7 +402,7 @@ abstract class LambdaLift extends InfoTransform { } /* SI-6231: Something like this will be necessary to eliminate the implementation - * restiction from paramGetter above: + * restriction from paramGetter above: * We need to pass getters to the interface of an implementation class. private def fixTraitGetters(lifted: List[Tree]): List[Tree] = for (stat <- lifted) yield stat match { diff --git a/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala b/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala index c1c025ad48..e4082eb376 100644 --- a/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala +++ b/src/compiler/scala/tools/nsc/transform/OverridingPairs.scala @@ -35,7 +35,7 @@ abstract class OverridingPairs extends SymbolPairs { */ override protected def matches(lo: Symbol, high: Symbol) = lo.isType || ( (lo.owner != high.owner) // don't try to form pairs from overloaded members - && !high.isPrivate // private or private[this] members never are overriden + && !high.isPrivate // private or private[this] members never are overridden && !exclude(lo) // this admits private, as one can't have a private member that matches a less-private member. && relatively.matches(lo, high) ) // TODO we don't call exclude(high), should we? diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 9c81e31ad9..1691b01e3e 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -894,7 +894,6 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } val specMember = subst(outerEnv)(specializedOverload(owner, sym, spec)) - owner.info.decls.enter(specMember) typeEnv(specMember) = typeEnv(sym) ++ outerEnv ++ spec wasSpecializedForTypeVars(specMember) ++= spec collect { case (s, tp) if s.tpe == tp => s } @@ -1291,7 +1290,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { * // even in the specialized variant, the local X class * // doesn't extend Parent$mcI$sp, since its symbol has * // been created after specialization and was not seen - * // by specialzation's info transformer. + * // by specialization's info transformer. * ... * } * } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala index 75d2cfe0f2..0b53dc37de 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala @@ -122,9 +122,20 @@ trait Logic extends Debugging { // symbols are propositions final class Sym private[PropositionalLogic] (val variable: Var, val const: Const) extends Prop { + + override def equals(other: scala.Any): Boolean = other match { + case that: Sym => this.variable == that.variable && + this.const == that.const + case _ => false + } + + override def hashCode(): Int = { + variable.hashCode * 41 + const.hashCode + } + private val id: Int = Sym.nextSymId - override def toString = variable + "=" + const + "#" + id + override def toString = s"$variable=$const#$id" } object Sym { @@ -370,9 +381,11 @@ trait Logic extends Debugging { val EmptyModel: Model val NoModel: Model + final case class Solution(model: Model, unassigned: List[Sym]) + def findModelFor(solvable: Solvable): Model - def findAllModelsFor(solvable: Solvable): List[Model] + def findAllModelsFor(solvable: Solvable): List[Solution] } } @@ -622,7 +635,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis { if (!t.symbol.isStable) { // Create a fresh type for each unstable value, since we can never correlate it to another value. - // For example `case X => case X =>` should not complaing about the second case being unreachable, + // For example `case X => case X =>` should not complain about the second case being unreachable, // if X is mutable. freshExistentialSubtype(t.tpe) } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala index 8650f6ef90..34ebbc7463 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala @@ -6,6 +6,8 @@ package scala.tools.nsc.transform.patmat +import scala.annotation.tailrec +import scala.collection.immutable.{IndexedSeq, Iterable} import scala.language.postfixOps import scala.collection.mutable import scala.reflect.internal.util.Statistics @@ -266,7 +268,7 @@ trait MatchApproximation extends TreeAndTypeAnalysis with ScalaLogic with MatchT // the type of the binder passed to the first invocation // determines the type of the tree that'll be returned for that binder as of then final def binderToUniqueTree(b: Symbol) = - unique(accumSubst(normalize(CODE.REF(b))), b.tpe) + unique(accumSubst(normalize(gen.mkAttributedStableRef(b))), b.tpe) // note that the sequencing of operations is important: must visit in same order as match execution // binderToUniqueTree uses the type of the first symbol that was encountered as the type for all future binders @@ -363,7 +365,7 @@ trait MatchApproximation extends TreeAndTypeAnalysis with ScalaLogic with MatchT def handleUnknown(tm: TreeMaker) = handler(tm) } - // used for CSE -- rewrite all unknowns to False (the most conserative option) + // used for CSE -- rewrite all unknowns to False (the most conservative option) object conservative extends TreeMakerToProp { def handleUnknown(tm: TreeMaker) = False } @@ -514,8 +516,16 @@ trait MatchAnalysis extends MatchApproximation { // find the models (under which the match fails) val matchFailModels = findAllModelsFor(propToSolvable(matchFails)) + val scrutVar = Var(prevBinderTree) - val counterExamples = matchFailModels.flatMap(modelToCounterExample(scrutVar)) + val counterExamples = { + matchFailModels.flatMap { + model => + val varAssignments = expandModel(model) + varAssignments.flatMap(modelToCounterExample(scrutVar) _) + } + } + // sorting before pruning is important here in order to // keep neg/t7020.scala stable // since e.g. List(_, _) would cover List(1, _) @@ -587,6 +597,8 @@ trait MatchAnalysis extends MatchApproximation { case object WildcardExample extends CounterExample { override def toString = "_" } case object NoExample extends CounterExample { override def toString = "??" } + // returns a mapping from variable to + // equal and notEqual symbols def modelToVarAssignment(model: Model): Map[Var, (Seq[Const], Seq[Const])] = model.toSeq.groupBy{f => f match {case (sym, value) => sym.variable} }.mapValues{ xs => val (trues, falses) = xs.partition(_._2) @@ -600,20 +612,110 @@ trait MatchAnalysis extends MatchApproximation { v +"(="+ v.path +": "+ v.staticTpCheckable +") "+ assignment }.mkString("\n") - // return constructor call when the model is a true counter example - // (the variables don't take into account type information derived from other variables, - // so, naively, you might try to construct a counter example like _ :: Nil(_ :: _, _ :: _), - // since we didn't realize the tail of the outer cons was a Nil) - def modelToCounterExample(scrutVar: Var)(model: Model): Option[CounterExample] = { + /** + * The models we get from the DPLL solver need to be mapped back to counter examples. + * However there's no precalculated mapping model -> counter example. Even worse, + * not every valid model corresponds to a valid counter example. + * The reason is that restricting the valid models further would for example require + * a quadratic number of additional clauses. So to keep the optimistic case fast + * (i.e., all cases are covered in a pattern match), the infeasible counter examples + * are filtered later. + * + * The DPLL procedure keeps the literals that do not contribute to the solution + * unassigned, e.g., for `(a \/ b)` + * only {a = true} or {b = true} is required and the other variable can have any value. + * + * This function does a smart expansion of the model and avoids models that + * have conflicting mappings. + * + * For example for in case of the given set of symbols (taken from `t7020.scala`): + * "V2=2#16" + * "V2=6#19" + * "V2=5#18" + * "V2=4#17" + * "V2=7#20" + * + * One possibility would be to group the symbols by domain but + * this would only work for equality tests and would not be compatible + * with type tests. + * Another observation leads to a much simpler algorithm: + * Only one of these symbols can be set to true, + * since `V2` can at most be equal to one of {2,6,5,4,7}. + */ + def expandModel(solution: Solution): List[Map[Var, (Seq[Const], Seq[Const])]] = { + + val model = solution.model + // x1 = ... // x1.hd = ... // x1.tl = ... // x1.hd.hd = ... // ... val varAssignment = modelToVarAssignment(model) + debug.patmat("var assignment for model " + model + ":\n" + varAssignmentString(varAssignment)) + + // group symbols that assign values to the same variables (i.e., symbols are mutually exclusive) + // (thus the groups are sets of disjoint assignments to variables) + val groupedByVar: Map[Var, List[Sym]] = solution.unassigned.groupBy(_.variable) + + val expanded = for { + (variable, syms) <- groupedByVar.toList + } yield { + + val (equal, notEqual) = varAssignment.getOrElse(variable, Nil -> Nil) + + def addVarAssignment(equalTo: List[Const], notEqualTo: List[Const]) = { + Map(variable ->(equal ++ equalTo, notEqual ++ notEqualTo)) + } + + // this assignment is needed in case that + // there exists already an assign + val allNotEqual = addVarAssignment(Nil, syms.map(_.const)) - debug.patmat("var assignment for model "+ model +":\n"+ varAssignmentString(varAssignment)) + // this assignment is conflicting on purpose: + // a list counter example could contain wildcards: e.g. `List(_,_)` + val allEqual = addVarAssignment(syms.map(_.const), Nil) + if(equal.isEmpty) { + val oneHot = for { + s <- syms + } yield { + addVarAssignment(List(s.const), syms.filterNot(_ == s).map(_.const)) + } + allEqual :: allNotEqual :: oneHot + } else { + allEqual :: allNotEqual :: Nil + } + } + + if (expanded.isEmpty) { + List(varAssignment) + } else { + // we need the cartesian product here, + // since we want to report all missing cases + // (i.e., combinations) + val cartesianProd = expanded.reduceLeft((xs, ys) => + for {map1 <- xs + map2 <- ys} yield { + map1 ++ map2 + }) + + // add expanded variables + // note that we can just use `++` + // since the Maps have disjoint keySets + for { + m <- cartesianProd + } yield { + varAssignment ++ m + } + } + } + + // return constructor call when the model is a true counter example + // (the variables don't take into account type information derived from other variables, + // so, naively, you might try to construct a counter example like _ :: Nil(_ :: _, _ :: _), + // since we didn't realize the tail of the outer cons was a Nil) + def modelToCounterExample(scrutVar: Var)(varAssignment: Map[Var, (Seq[Const], Seq[Const])]): Option[CounterExample] = { // chop a path into a list of symbols def chop(path: Tree): List[Symbol] = path match { case Ident(_) => List(path.symbol) @@ -742,7 +844,7 @@ trait MatchAnalysis extends MatchApproximation { // then we can safely ignore these counter examples since we will eventually encounter // both counter examples separately case _ if inSameDomain => None - + // not a valid counter-example, possibly since we have a definite type but there was a field mismatch // TODO: improve reasoning -- in the mean time, a false negative is better than an annoying false positive case _ => Some(NoExample) @@ -761,12 +863,12 @@ trait MatchAnalysis extends MatchApproximation { } def analyzeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type, suppression: Suppression): Unit = { - if (!suppression.unreachable) { + if (!suppression.suppressUnreachable) { unreachableCase(prevBinder, cases, pt) foreach { caseIndex => reportUnreachable(cases(caseIndex).last.pos) } } - if (!suppression.exhaustive) { + if (!suppression.suppressExhaustive) { val counterExamples = exhaustive(prevBinder, cases, pt) if (counterExamples.nonEmpty) reportMissingCases(prevBinder.pos, counterExamples) diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 22661d6ccf..6302e34ac9 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -208,7 +208,7 @@ trait MatchTranslation { case _ => (cases, None) } - checkMatchVariablePatterns(nonSyntheticCases) + if (!settings.XnoPatmatAnalysis) checkMatchVariablePatterns(nonSyntheticCases) // we don't transform after uncurry // (that would require more sophistication when generating trees, @@ -248,7 +248,10 @@ trait MatchTranslation { if (caseDefs forall treeInfo.isCatchCase) caseDefs else { val swatches = { // switch-catches - val bindersAndCases = caseDefs map { caseDef => + // SI-7459 must duplicate here as we haven't commited to switch emission, and just figuring out + // if we can ends up mutating `caseDefs` down in the use of `substituteSymbols` in + // `TypedSubstitution#Substitution`. That is called indirectly by `emitTypeSwitch`. + val bindersAndCases = caseDefs.map(_.duplicate) map { caseDef => // generate a fresh symbol for each case, hoping we'll end up emitting a type-switch (we don't have a global scrut there) // if we fail to emit a fine-grained switch, have to do translateCase again with a single scrutSym (TODO: uniformize substitution on treemakers so we can avoid this) val caseScrutSym = freshSym(pos, pureType(ThrowableTpe)) @@ -518,7 +521,7 @@ trait MatchTranslation { // reference the (i-1)th case accessor if it exists, otherwise the (i-1)th tuple component override protected def tupleSel(binder: Symbol)(i: Int): Tree = { val accessors = binder.caseFieldAccessors - if (accessors isDefinedAt (i-1)) REF(binder) DOT accessors(i-1) + if (accessors isDefinedAt (i-1)) gen.mkAttributedStableRef(binder) DOT accessors(i-1) else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN } } @@ -580,7 +583,7 @@ trait MatchTranslation { // duplicated with the extractor Unapplied case Apply(x, List(i @ Ident(nme.SELECTOR_DUMMY))) => treeCopy.Apply(t, x, binderRef(i.pos) :: Nil) - // SI-7868 Account for numeric widening, e.g. <unappplySelector>.toInt + // SI-7868 Account for numeric widening, e.g. <unapplySelector>.toInt case Apply(x, List(i @ (sel @ Select(Ident(nme.SELECTOR_DUMMY), name)))) => treeCopy.Apply(t, x, treeCopy.Select(sel, binderRef(i.pos), name) :: Nil) case _ => diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala index 3fd9ce76f8..b703b5bc6d 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala @@ -21,9 +21,10 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { import global._ import definitions._ - final case class Suppression(exhaustive: Boolean, unreachable: Boolean) + final case class Suppression(suppressExhaustive: Boolean, suppressUnreachable: Boolean) object Suppression { val NoSuppression = Suppression(false, false) + val FullSuppression = Suppression(true, true) } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -166,8 +167,17 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { val usedBinders = new mutable.HashSet[Symbol]() // all potentially stored subpat binders val potentiallyStoredBinders = stored.unzip._1.toSet + def ref(sym: Symbol) = + if (potentiallyStoredBinders(sym)) usedBinders += sym // compute intersection of all symbols in the tree `in` and all potentially stored subpat binders - in.foreach(t => if (potentiallyStoredBinders(t.symbol)) usedBinders += t.symbol) + in.foreach { + case tt: TypeTree => + tt.tpe foreach { // SI-7459 e.g. case Prod(t) => new t.u.Foo + case SingleType(_, sym) => ref(sym) + case _ => + } + case t => ref(t.symbol) + } if (usedBinders.isEmpty) in else { @@ -517,7 +527,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { def removeSubstOnly(makers: List[TreeMaker]) = makers filterNot (_.isInstanceOf[SubstOnlyTreeMaker]) // a foldLeft to accumulate the localSubstitution left-to-right - // it drops SubstOnly tree makers, since their only goal in life is to propagate substitutions to the next tree maker, which is fullfilled by propagateSubstitution + // it drops SubstOnly tree makers, since their only goal in life is to propagate substitutions to the next tree maker, which is fulfilled by propagateSubstitution def propagateSubstitution(treeMakers: List[TreeMaker], initial: Substitution): List[TreeMaker] = { var accumSubst: Substitution = initial treeMakers foreach { maker => @@ -542,7 +552,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { debug.patmat("combining cases: "+ (casesNoSubstOnly.map(_.mkString(" >> ")).mkString("{", "\n", "}"))) val (suppression, requireSwitch): (Suppression, Boolean) = - if (settings.XnoPatmatAnalysis) (Suppression.NoSuppression, false) + if (settings.XnoPatmatAnalysis) (Suppression.FullSuppression, false) else scrut match { case Typed(tree, tpt) => val suppressExhaustive = tpt.tpe hasAnnotation UncheckedClass @@ -574,7 +584,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { (Suppression.NoSuppression, false) } - emitSwitch(scrut, scrutSym, casesNoSubstOnly, pt, matchFailGenOverride, suppression.exhaustive).getOrElse{ + emitSwitch(scrut, scrutSym, casesNoSubstOnly, pt, matchFailGenOverride, unchecked = suppression.suppressExhaustive).getOrElse{ if (requireSwitch) reporter.warning(scrut.pos, "could not emit switch for @switch annotated match") if (casesNoSubstOnly nonEmpty) { diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index ef50e083a1..d35aad964d 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -12,7 +12,7 @@ import scala.language.postfixOps import scala.tools.nsc.transform.TypingTransformers import scala.tools.nsc.transform.Transform import scala.reflect.internal.util.Statistics -import scala.reflect.internal.Types +import scala.reflect.internal.{Mode, Types} import scala.reflect.internal.util.Position /** Translate pattern matching. @@ -198,33 +198,57 @@ trait Interface extends ast.TreeDSL { } class Substitution(val from: List[Symbol], val to: List[Tree]) { - import global.{Transformer, Ident, NoType} + import global.{Transformer, Ident, NoType, TypeTree, SingleType} // We must explicitly type the trees that we replace inside some other tree, since the latter may already have been typed, // and will thus not be retyped. This means we might end up with untyped subtrees inside bigger, typed trees. def apply(tree: Tree): Tree = { // according to -Ystatistics 10% of translateMatch's time is spent in this method... // since about half of the typedSubst's end up being no-ops, the check below shaves off 5% of the time spent in typedSubst - if (!tree.exists { case i@Ident(_) => from contains i.symbol case _ => false}) tree - else (new Transformer { + val toIdents = to.forall(_.isInstanceOf[Ident]) + val containsSym = tree.exists { + case i@Ident(_) => from contains i.symbol + case tt: TypeTree => tt.tpe.exists { + case SingleType(_, sym) => + (from contains sym) && { + if (!toIdents) global.devWarning(s"Unexpected substitution of non-Ident into TypeTree `$tt`, subst= $this") + true + } + case _ => false + } + case _ => false + } + val toSyms = to.map(_.symbol) + object substIdentsForTrees extends Transformer { private def typedIfOrigTyped(to: Tree, origTp: Type): Tree = if (origTp == null || origTp == NoType) to // important: only type when actually substing and when original tree was typed // (don't need to use origTp as the expected type, though, and can't always do this anyway due to unknown type params stemming from polymorphic extractors) else typer.typed(to) + def typedStable(t: Tree) = typer.typed(t.shallowDuplicate, Mode.MonoQualifierModes | Mode.TYPEPATmode) + lazy val toTypes: List[Type] = to map (tree => typedStable(tree).tpe) + override def transform(tree: Tree): Tree = { def subst(from: List[Symbol], to: List[Tree]): Tree = if (from.isEmpty) tree - else if (tree.symbol == from.head) typedIfOrigTyped(to.head.shallowDuplicate.setPos(tree.pos), tree.tpe) + else if (tree.symbol == from.head) typedIfOrigTyped(typedStable(to.head).setPos(tree.pos), tree.tpe) else subst(from.tail, to.tail) - tree match { + val tree1 = tree match { case Ident(_) => subst(from, to) case _ => super.transform(tree) } + tree1.modifyType(_.substituteTypes(from, toTypes)) } - }).transform(tree) + } + if (containsSym) { + if (to.forall(_.isInstanceOf[Ident])) + tree.duplicate.substituteSymbols(from, to.map(_.symbol)) // SI-7459 catches `case t => new t.Foo` + else + substIdentsForTrees.transform(tree) + } + else tree } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Solving.scala b/src/compiler/scala/tools/nsc/transform/patmat/Solving.scala index 1ba13c0617..27217f0dc2 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/Solving.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/Solving.scala @@ -288,7 +288,7 @@ trait Solving extends Logic { val NoTseitinModel: TseitinModel = null // returns all solutions, if any (TODO: better infinite recursion backstop -- detect fixpoint??) - def findAllModelsFor(solvable: Solvable): List[Model] = { + def findAllModelsFor(solvable: Solvable): List[Solution] = { debug.patmat("find all models for\n"+ cnfString(solvable.cnf)) // we must take all vars from non simplified formula @@ -305,54 +305,12 @@ trait Solving extends Logic { relevantLits.map(lit => -lit) } - /** - * The DPLL procedure only returns a minimal mapping from literal to value - * such that the CNF formula is satisfied. - * E.g. for: - * `(a \/ b)` - * The DPLL procedure will find either {a = true} or {b = true} - * as solution. - * - * The expansion step will amend both solutions with the unassigned variable - * i.e., {a = true} will be expanded to {a = true, b = true} and {a = true, b = false}. - */ - def expandUnassigned(unassigned: List[Int], model: TseitinModel): List[TseitinModel] = { - // the number of solutions is doubled for every unassigned variable - val expandedModels = 1 << unassigned.size - var current = mutable.ArrayBuffer[TseitinModel]() - var next = mutable.ArrayBuffer[TseitinModel]() - current.sizeHint(expandedModels) - next.sizeHint(expandedModels) - - current += model - - // we use double buffering: - // read from `current` and create a two models for each model in `next` - for { - s <- unassigned - } { - for { - model <- current - } { - def force(l: Lit) = model + l - - next += force(Lit(s)) - next += force(Lit(-s)) - } - - val tmp = current - current = next - next = tmp - - next.clear() - } - - current.toList + final case class TseitinSolution(model: TseitinModel, unassigned: List[Int]) { + def projectToSolution(symForVar: Map[Int, Sym]) = Solution(projectToModel(model, symForVar), unassigned map symForVar) } - def findAllModels(clauses: Array[Clause], - models: List[TseitinModel], - recursionDepthAllowed: Int = global.settings.YpatmatExhaustdepth.value): List[TseitinModel]= + models: List[TseitinSolution], + recursionDepthAllowed: Int = global.settings.YpatmatExhaustdepth.value): List[TseitinSolution]= if (recursionDepthAllowed == 0) { val maxDPLLdepth = global.settings.YpatmatExhaustdepth.value reportWarning("(Exhaustivity analysis reached max recursion depth, not all missing cases are reported. " + @@ -368,17 +326,15 @@ trait Solving extends Logic { val unassigned: List[Int] = (relevantVars -- model.map(lit => lit.variable)).toList debug.patmat("unassigned "+ unassigned +" in "+ model) - val forced = expandUnassigned(unassigned, model) - debug.patmat("forced "+ forced) + val solution = TseitinSolution(model, unassigned) val negated = negateModel(model) - findAllModels(clauses :+ negated, forced ++ models, recursionDepthAllowed - 1) + findAllModels(clauses :+ negated, solution :: models, recursionDepthAllowed - 1) } else models } - val tseitinModels: List[TseitinModel] = findAllModels(solvable.cnf, Nil) - val models: List[Model] = tseitinModels.map(projectToModel(_, solvable.symbolMapping.symForVar)) - models + val tseitinSolutions = findAllModels(solvable.cnf, Nil) + tseitinSolutions.map(_.projectToSolution(solvable.symbolMapping.symForVar)) } private def withLit(res: TseitinModel, l: Lit): TseitinModel = { diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 866ca37303..5c36bd9d28 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -73,7 +73,7 @@ trait ContextErrors { // 2) provide the type of the implicit parameter for which we got diverging expansion // (pt at the point of divergence gives less information to the user) // Note: it is safe to delay error message generation in this case - // becasue we don't modify implicits' infos. + // because we don't modify implicits' infos. case class DivergentImplicitTypeError(underlyingTree: Tree, pt0: Type, sym: Symbol) extends TreeTypeError { def errMsg: String = errMsgForPt(pt0) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index a21c7cc27e..b3e207b334 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -145,7 +145,7 @@ trait Contexts { self: Analyzer => * - A variety of bits that track the current error reporting policy (more on this later); * whether or not implicits/macros are enabled, whether we are in a self or super call or * in a constructor suffix. These are represented as bits in the mask `contextMode`. - * - Some odds and ends: undetermined type pararameters of the current line of type inference; + * - Some odds and ends: undetermined type parameters of the current line of type inference; * contextual augmentation for error messages, tracking of the nesting depth. * * And behaviour: @@ -154,19 +154,19 @@ trait Contexts { self: Analyzer => * to buffer these for use in 'silent' type checking, when some recovery might be possible. * - `Context` is something of a Zipper for the tree were are typechecking: it `enclosingContextChain` * is the path back to the root. This is exactly what we need to resolve names (`lookupSymbol`) - * and to collect in-scope implicit defintions (`implicitss`) + * and to collect in-scope implicit definitions (`implicitss`) * Supporting these are `imports`, which represents all `Import` trees in in the enclosing context chain. - * - In a similar vein, we can assess accessiblity (`isAccessible`.) + * - In a similar vein, we can assess accessibility (`isAccessible`.) * * More on error buffering: * When are type errors recoverable? In quite a few places, it turns out. Some examples: * trying to type an application with/without the expected type, or with/without implicit views * enabled. This is usually mediated by `Typer.silent`, `Inferencer#tryTwice`. * - * Intially, starting from the `typer` phase, the contexts either buffer or report errors; + * Initially, starting from the `typer` phase, the contexts either buffer or report errors; * afterwards errors are thrown. This is configured in `rootContext`. Additionally, more * fine grained control is needed based on the kind of error; ambiguity errors are often - * suppressed during exploraratory typing, such as determining whether `a == b` in an argument + * suppressed during exploratory typing, such as determining whether `a == b` in an argument * position is an assignment or a named argument, when `Infererencer#isApplicableSafe` type checks * applications with and without an expected type, or whtn `Typer#tryTypedApply` tries to fit arguments to * a function type with/without implicit views. diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 7ed4fe1f88..4435ed0b60 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -3,7 +3,7 @@ * @author Martin Odersky */ -//todo: rewrite or disllow new T where T is a mixin (currently: <init> not a member of T) +//todo: rewrite or disallow new T where T is a mixin (currently: <init> not a member of T) //todo: use inherited type info also for vars and values //todo: disallow C#D in superclass //todo: treat :::= correctly @@ -159,7 +159,7 @@ trait Implicits { * @param tree The tree representing the implicit * @param subst A substituter that represents the undetermined type parameters * that were instantiated by the winning implicit. - * @param undetparams undeterminted type parameters + * @param undetparams undetermined type parameters */ class SearchResult(val tree: Tree, val subst: TreeTypeSubstituter, val undetparams: List[Symbol]) { override def toString = "SearchResult(%s, %s)".format(tree, diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 8979b26719..cf97474d9a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1017,7 +1017,7 @@ trait Infer extends Checkable { /** Substitute free type variables `undetparams` of type constructor * `tree` in pattern, given prototype `pt`. * - * @param tree the constuctor that needs to be instantiated + * @param tree the constructor that needs to be instantiated * @param undetparams the undetermined type parameters * @param pt0 the expected result type of the instance */ diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 0bb94be636..711cfba24d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -171,7 +171,7 @@ trait Namers extends MethodSynthesis { val newFlags = (sym.flags & LOCKED) | flags sym.rawInfo match { case tr: TypeRef => - // !!! needed for: pos/t5954d; the uniques type cache will happilly serve up the same TypeRef + // !!! needed for: pos/t5954d; the uniques type cache will happily serve up the same TypeRef // over this mutated symbol, and we witness a stale cache for `parents`. tr.invalidateCaches() case _ => diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index b6387fd56b..50f658f68d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -384,7 +384,7 @@ trait NamesDefaults { self: Analyzer => * of arguments. * * @param args The list of arguments - * @param params The list of parameter sybols of the invoked method + * @param params The list of parameter symbols of the invoked method * @param argName A function that extracts the name of an argument expression, if it is a named argument. */ def missingParams[T](args: List[T], params: List[Symbol], argName: T => Option[Name]): (List[Symbol], Boolean) = { diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index bb8c3c3c6d..fa4a764f1b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -336,7 +336,7 @@ trait PatternTypers { val app = atPos(uncheckedPattern.pos)(Apply(classTagExtractor, args)) // must call doTypedUnapply directly, as otherwise we get undesirable rewrites // and re-typechecks of the target of the unapply call in PATTERNmode, - // this breaks down when the classTagExtractor (which defineds the unapply member) is not a simple reference to an object, + // this breaks down when the classTagExtractor (which defines the unapply member) is not a simple reference to an object, // but an arbitrary tree as is the case here val res = doTypedUnapply(app, classTagExtractor, classTagExtractor, args, PATTERNmode, pt) diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala index c03fd3345f..8f13507fa9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala @@ -153,7 +153,7 @@ trait SyntheticMethods extends ast.TreeDSL { def thatCast(eqmeth: Symbol): Tree = gen.mkCast(Ident(eqmeth.firstParam), clazz.tpe) - /* The equality method core for case classes and inline clases. + /* The equality method core for case classes and inline classes. * 1+ args: * (that.isInstanceOf[this.C]) && { * val x$1 = that.asInstanceOf[this.C] diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index efe40b11f2..2dd79075ee 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -950,7 +950,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // Ignore type errors raised in later phases that are due to mismatching types with existential skolems // We have lift crashing in 2.9 with an adapt failure in the pattern matcher. - // Here's my hypothsis why this happens. The pattern matcher defines a variable of type + // Here's my hypothesis why this happens. The pattern matcher defines a variable of type // // val x: T = expr // @@ -3285,6 +3285,22 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } handleOverloaded + case _ if isPolymorphicSignature(fun.symbol) => + // Mimic's Java's treatment of polymorphic signatures as described in + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.3 + // + // One can think of these methods as being infinitely overloaded. We create + // a ficticious new cloned method symbol for each call site that takes on a signature + // governed by a) the argument types and b) the expected type + val args1 = typedArgs(args, forArgMode(fun, mode)) + val pts = args1.map(_.tpe.deconst) + val clone = fun.symbol.cloneSymbol + val cloneParams = pts map (pt => clone.newValueParameter(currentUnit.freshTermName()).setInfo(pt)) + val resultType = if (isFullyDefined(pt)) pt else ObjectTpe + clone.modifyInfo(mt => copyMethodType(mt, cloneParams, resultType)) + val fun1 = fun.setSymbol(clone).setType(clone.info) + doTypedApply(tree, fun1, args1, mode, resultType).setType(resultType) + case mt @ MethodType(params, _) => val paramTypes = mt.paramTypes // repeat vararg as often as needed, remove by-name @@ -3780,8 +3796,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case TypeRef(pre, sym, args) => if (sym.isAliasType && containsLocal(tp) && (tp.dealias ne tp)) apply(tp.dealias) else { - if (pre.isVolatile) - InferTypeWithVolatileTypeSelectionError(tree, pre) + if (pre.isVolatile) pre match { + case SingleType(_, sym) if sym.isSynthetic && isPastTyper => + debuglog(s"ignoring volatility of prefix in pattern matcher generated inferred type: $tp") // See pos/t7459c.scala + case _ => + InferTypeWithVolatileTypeSelectionError(tree, pre) + } mapOver(tp) } case _ => diff --git a/src/compiler/scala/tools/nsc/util/DocStrings.scala b/src/compiler/scala/tools/nsc/util/DocStrings.scala index ba44126df2..352816803f 100755 --- a/src/compiler/scala/tools/nsc/util/DocStrings.scala +++ b/src/compiler/scala/tools/nsc/util/DocStrings.scala @@ -8,7 +8,7 @@ package util import scala.reflect.internal.Chars._ -/** Utilitity methods for doc comment strings +/** Utility methods for doc comment strings */ object DocStrings { |