diff options
Diffstat (limited to 'src')
29 files changed, 1632 insertions, 1731 deletions
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 3e72dc5e4a..82ffb35c3f 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -404,7 +404,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) reporter.cancelled || unit.isJava && this.id > maxJavaPhase } - final def applyPhase(unit: CompilationUnit) { + final def withCurrentUnit(unit: CompilationUnit)(task: => Unit) { if ((unit ne null) && unit.exists) lastSeenSourceFile = unit.source @@ -416,7 +416,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) currentRun.currentUnit = unit if (!cancelled(unit)) { currentRun.informUnitStarting(this, unit) - apply(unit) + task } currentRun.advanceUnit() } finally { @@ -424,6 +424,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) currentRun.currentUnit = unit0 } } + + final def applyPhase(unit: CompilationUnit) = withCurrentUnit(unit)(apply(unit)) } // phaseName = "parser" @@ -1178,7 +1180,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val erasurePhase = phaseNamed("erasure") val posterasurePhase = phaseNamed("posterasure") // val lazyvalsPhase = phaseNamed("lazyvals") - // val lambdaliftPhase = phaseNamed("lambdalift") + val lambdaliftPhase = phaseNamed("lambdalift") // val constructorsPhase = phaseNamed("constructors") val flattenPhase = phaseNamed("flatten") val mixinPhase = phaseNamed("mixin") diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala new file mode 100644 index 0000000000..e5b4c4a6c2 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -0,0 +1,130 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc.backend.jvm + +import scala.tools.nsc.Global +import PartialFunction._ + +/** + * This trait contains code shared between GenBCode and GenASM that depends on types defined in + * the compiler cake (Global). + */ +final class BCodeAsmCommon[G <: Global](val global: G) { + import global._ + + /** + * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a + * member class. This method is used to decide if we should emit an EnclosingMethod attribute. + * It is also used to decide whether the "owner" field in the InnerClass attribute should be + * null. + */ + def isAnonymousOrLocalClass(classSym: Symbol): Boolean = { + def isDelambdafyLambdaClass(classSym: Symbol): Boolean = { + classSym.isAnonymousFunction && (settings.Ydelambdafy.value == "method") + } + + assert(classSym.isClass, s"not a class: $classSym") + + !isDelambdafyLambdaClass(classSym) && + (classSym.isAnonymousClass || !classSym.originalOwner.isClass) + } + + /** + * Returns the enclosing method for non-member classes. In the following example + * + * class A { + * def f = { + * class B { + * class C + * } + * } + * } + * + * the method returns Some(f) for B, but None for C, because C is a member class. For non-member + * classes that are not enclosed by a method, it returns None: + * + * class A { + * { class B } + * } + * + * In this case, for B, we return None. + * + * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes). + * This is a source-level property, so we need to use the originalOwner chain to reconstruct it. + */ + private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = { + assert(classSym.isClass, classSym) + def enclosingMethod(sym: Symbol): Option[Symbol] = { + if (sym.isClass || sym == NoSymbol) None + else if (sym.isMethod) Some(sym) + else enclosingMethod(sym.originalOwner) + } + enclosingMethod(classSym.originalOwner) + } + + /** + * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level + * property, this method looks at the originalOwner chain. See doc in BTypes. + */ + private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = { + assert(classSym.isClass, classSym) + def enclosingClass(sym: Symbol): Symbol = { + if (sym.isClass) sym + else enclosingClass(sym.originalOwner) + } + enclosingClass(classSym.originalOwner) + } + + final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String) + + /** + * If data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not + * an anonymous or local class). See doc in BTypes. + * + * The class is parametrized by two functions to obtain a bytecode class descriptor for a class + * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend + * on the implementation of GenASM / GenBCode, so they need to be passed in. + */ + def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = { + if (isAnonymousOrLocalClass(classSym)) { + val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) + debuglog(s"enclosing method for $classSym is $methodOpt (in ${methodOpt.map(_.enclClass)})") + Some(EnclosingMethodEntry( + classDesc(enclosingClassForEnclosingMethodAttribute(classSym)), + methodOpt.map(_.javaSimpleName.toString).orNull, + methodOpt.map(methodDesc).orNull)) + } else { + None + } + } + + /** + * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain. + * + * The problem is that we are interested in a source-level property. Various phases changed the + * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner. + * Therefore, `sym.isStatic` is not what we want. For example, in + * object T { def f { object U } } + * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here. + */ + def isOriginallyStaticOwner(sym: Symbol): Boolean = { + sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner) + } + + /** + * The member classes of a class symbol. Note that the result of this method depends on the + * current phase, for example, after lambdalift, all local classes become member of the enclosing + * class. + */ + def memberClassesOf(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ + case sym if sym.isClass => + sym + case sym if sym.isModule => + val r = exitingPickler(sym.moduleClass) + assert(r != NoSymbol, sym.fullLocationString) + r + })(collection.breakOut) +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 2d1030121e..397171049f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -23,8 +23,9 @@ import scala.tools.asm abstract class BCodeBodyBuilder extends BCodeSkelBuilder { import global._ import definitions._ - import bCodeICodeCommon._ import bTypes._ + import bCodeICodeCommon._ + import coreBTypes._ /* * Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions. @@ -92,7 +93,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val thrownKind = tpeTK(expr) // `throw null` is valid although scala.Null (as defined in src/libray-aux) isn't a subtype of Throwable. // Similarly for scala.Nothing (again, as defined in src/libray-aux). - assert(thrownKind.isNullType || thrownKind.isNothingType || exemplars.get(thrownKind).isSubtypeOf(ThrowableReference)) + assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference)) genLoad(expr, thrownKind) lineNumber(expr) emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level. @@ -123,7 +124,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // binary operation case rarg :: Nil => - resKind = maxType(tpeTK(larg), tpeTK(rarg)) + resKind = tpeTK(larg).maxType(tpeTK(rarg)) if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code)) { assert(resKind.isIntegralType || (resKind == BOOL), s"$resKind incompatible with arithmetic modulo operation.") @@ -321,7 +322,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) generatedType = if (tree.symbol == ArrayClass) ObjectReference - else ClassBType(thisName) // inner class (if any) for claszSymbol already tracked. + else classBTypeFromSymbol(claszSymbol) } case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) => @@ -459,9 +460,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case ClazzTag => val toPush: BType = { - val kind = toTypeKind(const.typeValue) - if (kind.isPrimitive) classLiteral(kind) - else kind + toTypeKind(const.typeValue) match { + case kind: PrimitiveBType => boxedClassOfPrimitive(kind) + case kind => kind + } } mnode.visitLdcInsn(toPush.toASMType) @@ -558,7 +560,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { abort(s"Erasure should have added an unboxing operation to prevent this cast. Tree: $app") } else if (r.isPrimitive) { - bc isInstance classLiteral(r) + bc isInstance boxedClassOfPrimitive(r.asPrimitiveBType) } else { assert(r.isRef, r) // ensure that it's not a method @@ -619,7 +621,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } case rt: ClassBType => - assert(exemplar(ctor.owner).c == rt, s"Symbol ${ctor.owner.fullName} is different from $rt") + assert(classBTypeFromSymbol(ctor.owner) == rt, s"Symbol ${ctor.owner.fullName} is different from $rt") mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName) bc dup generatedType genLoadArguments(args, paramTKs(app)) @@ -632,16 +634,16 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) => val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) - val MethodNameAndType(mname, mdesc) = asmBoxTo(nativeKind) - bc.invokestatic(BoxesRunTime.internalName, mname, mdesc) + val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind) + bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor) generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) => genLoad(expr) val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) generatedType = boxType - val MethodNameAndType(mname, mdesc) = asmUnboxTo(boxType) - bc.invokestatic(BoxesRunTime.internalName, mname, mdesc) + val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType) + bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor) case app @ Apply(fun, args) => val sym = fun.symbol @@ -807,7 +809,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } def adapt(from: BType, to: BType) { - if (!conforms(from, to)) { + if (!from.conformsTo(to)) { to match { case UNIT => bc drop from case _ => bc.emitT2T(from, to) @@ -948,7 +950,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genCallMethod(method: Symbol, style: InvokeStyle, hostClass0: Symbol = null, pos: Position = NoPosition) { val siteSymbol = claszSymbol - val hostSymbol = if (hostClass0 == null) method.owner else hostClass0; + val hostSymbol = if (hostClass0 == null) method.owner else hostClass0 val methodOwner = method.owner // info calls so that types are up to date; erasure may add lateINTERFACE to traits hostSymbol.info ; methodOwner.info @@ -966,8 +968,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { || methodOwner == definitions.ObjectClass ) val receiver = if (useMethodOwner) methodOwner else hostSymbol - val bmOwner = asmClassType(receiver) - val jowner = bmOwner.internalName + val jowner = internalName(receiver) val jname = method.javaSimpleName.toString val bmType = asmMethodType(method) val mdescr = bmType.descriptor @@ -1100,7 +1101,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { genCZJUMP(success, failure, op, ObjectReference) } else { - val tk = maxType(tpeTK(l), tpeTK(r)) + val tk = tpeTK(l).maxType(tpeTK(r)) genLoad(l, tk) genLoad(r, tk) genCJUMP(success, failure, op, tk) @@ -1206,7 +1207,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { genCZJUMP(success, failure, icodes.NE, BOOL) } else { // l == r -> if (l eq null) r eq null else l.equals(r) - val eqEqTempLocal = locals.makeLocal(AnyRefReference, nme.EQEQ_LOCAL_VAR.toString) + val eqEqTempLocal = locals.makeLocal(ObjectReference, nme.EQEQ_LOCAL_VAR.toString) val lNull = new asm.Label val lNonNull = new asm.Label diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index de1587c7c3..5670715cd3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -8,8 +8,7 @@ package tools.nsc package backend.jvm import scala.tools.asm -import scala.annotation.switch -import scala.collection.{ immutable, mutable } +import scala.collection.mutable import scala.tools.nsc.io.AbstractFile /* @@ -19,9 +18,10 @@ import scala.tools.nsc.io.AbstractFile * @version 1.0 * */ -abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { +abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { import global._ import bTypes._ + import coreBTypes._ /* * must-single-thread @@ -53,24 +53,6 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { // https://issues.scala-lang.org/browse/SI-3872 // ----------------------------------------------------------------------------------------- - /* - * can-multi-thread - */ - def firstCommonSuffix(as: List[Tracked], bs: List[Tracked]): ClassBType = { - var chainA = as - var chainB = bs - var fcs: Tracked = null - do { - if (chainB contains chainA.head) fcs = chainA.head - else if (chainA contains chainB.head) fcs = chainB.head - else { - chainA = chainA.tail - chainB = chainB.tail - } - } while (fcs == null) - fcs.c - } - /* An `asm.ClassWriter` that uses `jvmWiseLUB()` * The internal name of the least common ancestor of the types given by inameA and inameB. * It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow @@ -78,57 +60,17 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { final class CClassWriter(flags: Int) extends asm.ClassWriter(flags) { /** - * This method is thread re-entrant because chrs never grows during its operation (that's - * because all TypeNames being looked up have already been entered). - * To stress this point, rather than using `newTypeName()` we use `lookupTypeName()` - * - * can-multi-thread + * This method is thread-safe: it depends only on the BTypes component, which does not depend + * on global. TODO @lry move to a different place where no global is in scope, on bTypes. */ override def getCommonSuperClass(inameA: String, inameB: String): String = { - val a = ClassBType(lookupTypeName(inameA.toCharArray)) - val b = ClassBType(lookupTypeName(inameB.toCharArray)) - val lca = jvmWiseLUB(a, b) - val lcaName = lca.internalName // don't call javaName because that side-effects innerClassBuffer. - assert(lcaName != "scala/Any") - - lcaName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things. - } - - } - - /** - * Finding the least upper bound in agreement with the bytecode verifier (given two internal names - * handed out by ASM) - * Background: - * http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf - * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 - * https://issues.scala-lang.org/browse/SI-3872 - * - * can-multi-thread - */ - def jvmWiseLUB(a: ClassBType, b: ClassBType): ClassBType = { - - assert(a.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $a") - assert(b.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $b") - - val ta = exemplars.get(a) - val tb = exemplars.get(b) - - val res = (ta.isInterface, tb.isInterface) match { - case (true, true) => - // exercised by test/files/run/t4761.scala - if (tb.isSubtypeOf(ta.c)) ta.c - else if (ta.isSubtypeOf(tb.c)) tb.c - else ObjectReference - case (true, false) => - if (tb.isSubtypeOf(a)) a else ObjectReference - case (false, true) => - if (ta.isSubtypeOf(b)) b else ObjectReference - case _ => - firstCommonSuffix(ta :: ta.superClasses, tb :: tb.superClasses) + val a = classBTypeFromInternalName(inameA) + val b = classBTypeFromInternalName(inameB) + val lub = a.jvmWiseLUB(b) + val lubName = lub.internalName + assert(lubName != "scala/Any") + lubName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things. } - assert(res.isNonSpecial, "jvmWiseLUB() returned a non-plain-class.") - res } /* @@ -230,7 +172,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { def fieldSymbols(cls: Symbol): List[Symbol] = { for (f <- cls.info.decls.toList ; if !f.isMethod && f.isTerm && !f.isModule - ) yield f; + ) yield f } /* @@ -261,38 +203,16 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * * can-multi-thread */ - final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: Iterable[BType]) { - // used to detect duplicates. - val seen = mutable.Map.empty[String, String] - // result without duplicates, not yet sorted. - val result = mutable.Set.empty[InnerClassEntry] - - for(s: BType <- refedInnerClasses; - e: InnerClassEntry <- exemplars.get(s).innersChain) { - - assert(e.name != null, "saveInnerClassesFor() is broken.") // documentation - val doAdd = seen.get(e.name) match { - // TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute) - case Some(prevOName) => - // this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State, - // i.e. for them it must be the case that oname == java/lang/Thread - assert(prevOName == e.outerName, "duplicate") - false - case None => true - } - - if (doAdd) { - seen += (e.name -> e.outerName) - result += e - } - + final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) { + val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain).distinct + + // sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler + for (nestedClass <- allNestedClasses.sortBy(_.internalName.toString)) { + // Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes. + val Some(e) = nestedClass.innerClassAttributeEntry + jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags) } - // sorting ensures inner classes are listed after their enclosing class thus satisfying the Eclipse Java compiler - for(e <- result.toList sortBy (_.name.toString)) { - jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.access) - } - - } // end of method addInnerClassesASM() + } /* * Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only @@ -323,8 +243,8 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * can-multi-thread */ def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = { - val dest = new Array[Byte](len); - System.arraycopy(b, offset, dest, 0, len); + val dest = new Array[Byte](len) + System.arraycopy(b, offset, dest, 0, len) new asm.CustomAttr(name, dest) } @@ -385,9 +305,9 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { def debugLevel = settings.debuginfo.indexOfChoice - val emitSource = debugLevel >= 1 - val emitLines = debugLevel >= 2 - val emitVars = debugLevel >= 3 + final val emitSource = debugLevel >= 1 + final val emitLines = debugLevel >= 2 + final val emitVars = debugLevel >= 3 /* * Contains class-symbols that: @@ -396,155 +316,135 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * * In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated". */ - val innerClassBufferASM = mutable.Set.empty[BType] + final val innerClassBufferASM = mutable.Set.empty[ClassBType] - /* - * Tracks (if needed) the inner class given by `sym`. - * - * must-single-thread + /** + * The class internal name for a given class symbol. If the symbol describes a nested class, the + * ClassBType is added to the innerClassBufferASM. */ - final def internalName(sym: Symbol): String = asmClassType(sym).internalName + final def internalName(sym: Symbol): String = { + // For each java class, the scala compiler creates a class and a module (thus a module class). + // If the `sym` is a java module class, we use the java class instead. This ensures that we + // register the class (instead of the module class) in innerClassBufferASM. + // The two symbols have the same name, so the resulting internalName is the same. + val classSym = if (sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass else sym + getClassBTypeAndRegisterInnerClass(classSym).internalName + } - /* - * Tracks (if needed) the inner class given by `sym`. + private def assertClassNotArray(sym: Symbol): Unit = { + assert(sym.isClass, sym) + assert(sym != definitions.ArrayClass || isCompilingArray, sym) + } + + private def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = { + assertClassNotArray(sym) + assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym) + } + + /** + * The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the + * innerClassBufferASM. * - * must-single-thread + * The class symbol scala.Nothing is mapped to the class scala.runtime.Nothing$. Similarly, + * scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files + * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes + * in the classfile method signature. + * + * Note that the referenced class symbol may be an implementation class. For example when + * compiling a mixed-in method that forwards to the static method in the implementation class, + * the class descriptor of the receiver (the implementation class) is obtained by creating the + * ClassBType. */ - final def asmClassType(sym: Symbol): ClassBType = { - assert( - hasInternalName(sym), - { - val msg0 = if (sym.isAbstractType) "An AbstractTypeSymbol (SI-7122) " else "A symbol "; - msg0 + s"has reached the bytecode emitter, for which no JVM-level internal name can be found: ${sym.fullName}" - } - ) - val phantOpt = phantomTypeMap.get(sym) - if (phantOpt.isDefined) { - return phantOpt.get - } - val tracked = exemplar(sym) - val tk = tracked.c - if (tracked.isInnerClass) { - innerClassBufferASM += tk - } + final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = { + assertClassNotArrayNotPrimitive(sym) - tk + if (sym == definitions.NothingClass) RT_NOTHING + else if (sym == definitions.NullClass) RT_NULL + else { + val r = classBTypeFromSymbol(sym) + if (r.isNestedClass) innerClassBufferASM += r + r + } } - /* - * Returns the BType for the given type. - * Tracks (if needed) the inner class given by `t`. + /** + * This method returns the BType for a type reference, for example a parameter type. * - * must-single-thread + * If the result is a ClassBType for a nested class, it is added to the innerClassBufferASM. + * + * If `t` references a class, toTypeKind ensures that the class is not an implementation class. + * See also comment on getClassBTypeAndRegisterInnerClass, which is invoked for implementation + * classes. */ final def toTypeKind(t: Type): BType = { + import definitions.ArrayClass - /* Interfaces have to be handled delicately to avoid introducing spurious errors, - * but if we treat them all as AnyRef we lose too much information. + /** + * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int. + * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType. */ - def newReference(sym0: Symbol): BType = { - assert(!primitiveTypeMap.contains(sym0), "Use primitiveTypeMap instead.") - assert(sym0 != definitions.ArrayClass, "Use arrayOf() instead.") - - if (sym0 == definitions.NullClass) return RT_NULL; - if (sym0 == definitions.NothingClass) return RT_NOTHING; - - val sym = ( - if (!sym0.isPackageClass) sym0 - else sym0.info.member(nme.PACKAGE) match { - case NoSymbol => abort(s"SI-5604: Cannot use package as value: ${sym0.fullName}") - case s => abort(s"SI-5604: found package class where package object expected: $s") - } - ) - - // Can't call .toInterface (at this phase) or we trip an assertion. - // See PackratParser#grow for a method which fails with an apparent mismatch - // between "object PackratParsers$class" and "trait PackratParsers" - // TODO @lry do we have a test for that? - if (sym.isImplClass) { - // pos/spec-List.scala is the sole failure if we don't check for NoSymbol - val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name)) - if (traitSym != NoSymbol) { - // this tracks the inner class in innerClassBufferASM, if needed. - return asmClassType(traitSym) - } - } - - // TODO @lry: code duplication between here and method asmClassType. - - assert(hasInternalName(sym), s"Invoked for a symbol lacking JVM internal name: ${sym.fullName}") - assert(!phantomTypeMap.contains(sym), "phantom types not supposed to reach here.") - - val tracked = exemplar(sym) - val tk = tracked.c - if (tracked.isInnerClass) { - innerClassBufferASM += tk - } - - tk - } - - def primitiveOrRefType(sym: Symbol): BType = { - assert(sym != definitions.ArrayClass, "Use primitiveOrArrayOrRefType() instead.") - - primitiveTypeMap.getOrElse(sym, newReference(sym)) + def primitiveOrClassToBType(sym: Symbol): BType = { + assertClassNotArray(sym) + assert(!sym.isImplClass, sym) + primitiveTypeMap.getOrElse(sym, getClassBTypeAndRegisterInnerClass(sym)) } - def primitiveOrRefType2(sym: Symbol): BType = { - primitiveTypeMap.get(sym) match { - case Some(pt) => pt - case None => - sym match { - case definitions.NullClass => RT_NULL - case definitions.NothingClass => RT_NOTHING - case _ if sym.isClass => newReference(sym) - case _ => - assert(sym.isType, sym) // it must be compiling Array[a] - ObjectReference - } - } + /** + * When compiling Array.scala, the type parameter T is not erased and shows up in method + * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. + */ + def nonClassTypeRefToBType(sym: Symbol): ClassBType = { + assert(sym.isType && isCompilingArray, sym) + ObjectReference } - import definitions.ArrayClass - - // Call to .normalize fixes #3003 (follow type aliases). Otherwise, primitiveOrArrayOrRefType() would return ObjectReference. - t.normalize match { - - case ThisType(sym) => - if (sym == ArrayClass) ObjectReference - else phantomTypeMap.getOrElse(sym, exemplar(sym).c) - - case SingleType(_, sym) => primitiveOrRefType(sym) - - case _: ConstantType => toTypeKind(t.underlying) - - case TypeRef(_, sym, args) => - if (sym == ArrayClass) ArrayBType(toTypeKind(args.head)) - else primitiveOrRefType2(sym) - - case ClassInfoType(_, _, sym) => - assert(sym != ArrayClass, "ClassInfoType to ArrayClass!") - primitiveOrRefType(sym) - - // TODO @lry check below comments / todo's - // !!! Iulian says types which make no sense after erasure should not reach here, which includes the ExistentialType, AnnotatedType, RefinedType. - case ExistentialType(_, t) => toTypeKind(t) // TODO shouldn't get here but the following does: akka-actor/src/main/scala/akka/util/WildcardTree.scala - case AnnotatedType(_, w) => toTypeKind(w) // TODO test/files/jvm/annotations.scala causes an AnnotatedType to reach here. - case RefinedType(parents, _) => parents.map(toTypeKind(_).asClassBType) reduceLeft jvmWiseLUB - - // For sure WildcardTypes shouldn't reach here either, but when debugging such situations this may come in handy. - // case WildcardType => REFERENCE(ObjectClass) - case norm => abort( - s"Unknown type: $t, $norm [${t.getClass}, ${norm.getClass}] TypeRef? ${t.isInstanceOf[TypeRef]}" - ) + t.dealiasWiden match { + case TypeRef(_, ArrayClass, List(arg)) => ArrayBType(toTypeKind(arg)) // Array type such as Array[Int] (kept by erasure) + case TypeRef(_, sym, _) if !sym.isClass => nonClassTypeRefToBType(sym) // See comment on nonClassTypeRefToBType + case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String + case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes toTypeKind(moduleClassSymbol.info) + + /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for + * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. + * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. + */ + case a @ AnnotatedType(_, t) => + debuglog(s"typeKind of annotated type $a") + toTypeKind(t) + + /* ExistentialType should (probably) be eliminated by erasure. We know they get here for + * classOf constants: + * class C[T] + * class T { final val k = classOf[C[_]] } + */ + case e @ ExistentialType(_, t) => + debuglog(s"typeKind of existential type $e") + toTypeKind(t) + + /* The cases below should probably never occur. They are kept for now to avoid introducing + * new compiler crashes, but we added a warning. The compiler / library bootstrap and the + * test suite don't produce any warning. + */ + + case tp => + currentUnit.warning(tp.typeSymbol.pos, + s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " + + "If possible, please file a bug on issues.scala-lang.org.") + + tp match { + case ThisType(ArrayClass) => ObjectReference // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test + case ThisType(sym) => getClassBTypeAndRegisterInnerClass(sym) + case SingleType(_, sym) => primitiveOrClassToBType(sym) + case ConstantType(_) => toTypeKind(t.underlying) + case RefinedType(parents, _) => parents.map(toTypeKind(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) + } } - - } // end of method toTypeKind() + } /* * must-single-thread */ - def asmMethodType(msym: Symbol): MethodBType = { + final def asmMethodType(msym: Symbol): MethodBType = { assert(msym.isMethod, s"not a method-symbol: $msym") val resT: BType = if (msym.isClassConstructor || msym.isConstructor) UNIT @@ -552,45 +452,17 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { MethodBType(msym.tpe.paramTypes map toTypeKind, resT) } - /* - * Returns all direct member inner classes of `csym`, - * thus making sure they get entries in the InnerClasses JVM attribute - * even if otherwise not mentioned in the class being built. - * - * must-single-thread - */ - final def trackMemberClasses(csym: Symbol, lateClosuresBTs: List[BType]): List[BType] = { - val lateInnerClasses = exitingErasure { - for (sym <- List(csym, csym.linkedClassOfClass); memberc <- sym.info.decls.map(innerClassSymbolFor) if memberc.isClass) - yield memberc - } - // as a precaution, do the following outside the above `exitingErasure` otherwise funny internal names might be computed. - val result = for(memberc <- lateInnerClasses) yield { - val tracked = exemplar(memberc) - val memberCTK = tracked.c - assert(tracked.isInnerClass, s"saveInnerClassesFor() says this was no inner-class after all: ${memberc.fullName}") - - memberCTK - } - - exemplar(csym).directMemberClasses = result - - result - } - - /* - * Tracks (if needed) the inner class given by `t`. - * - * must-single-thread + /** + * The jvm descriptor of a type. If `t` references a nested class, its ClassBType is added to + * the innerClassBufferASM. */ final def descriptor(t: Type): String = { toTypeKind(t).descriptor } - /* - * Tracks (if needed) the inner class given by `sym`. - * - * must-single-thread + /** + * The jvm descriptor for a symbol. If `sym` represents a nested class, its ClassBType is added + * to the innerClassBufferASM. */ - final def descriptor(sym: Symbol): String = { asmClassType(sym).descriptor } + final def descriptor(sym: Symbol): String = { getClassBTypeAndRegisterInnerClass(sym).descriptor } } // end of trait BCInnerClassGen @@ -795,7 +667,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { */ // TODO: evaluate the other flags we might be dropping on the floor here. // TODO: ACC_SYNTHETIC ? - val flags = PublicStatic | ( + val flags = GenBCode.PublicStatic | ( if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0 ) @@ -901,62 +773,13 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { def addSerialVUID(id: Long, jclass: asm.ClassVisitor) { // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)` jclass.visitField( - PublicStaticFinal, + GenBCode.PublicStaticFinal, "serialVersionUID", "J", null, // no java-generic-signature new java.lang.Long(id) ).visitEnd() } - - /* - * @param owner internal name of the enclosing class of the class. - * - * @param name the name of the method that contains the class. - - * @param methodType the method that contains the class. - */ - case class EnclMethodEntry(owner: String, name: String, methodType: BType) - - /* - * @return null if the current class is not internal to a method - * - * Quoting from JVMS 4.7.7 The EnclosingMethod Attribute - * A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class. - * A class may have no more than one EnclosingMethod attribute. - * - * must-single-thread - */ - def getEnclosingMethodAttribute(clazz: Symbol): EnclMethodEntry = { // JVMS 4.7.7 - - def newEEE(eClass: Symbol, m: Symbol) = { - EnclMethodEntry( - internalName(eClass), - m.javaSimpleName.toString, - asmMethodType(m) - ) - } - - var res: EnclMethodEntry = null - val sym = clazz.originalEnclosingMethod - if (sym.isMethod) { - debuglog(s"enclosing method for $clazz is $sym (in ${sym.enclClass})") - res = newEEE(sym.enclClass, sym) - } else if (clazz.isAnonymousClass) { - val enclClass = clazz.rawowner - assert(enclClass.isClass, enclClass) - val sym = enclClass.primaryConstructor - if (sym == NoSymbol) { - log(s"Ran out of room looking for an enclosing method for $clazz: no constructor here: $enclClass.") - } else { - debuglog(s"enclosing method for $clazz is $sym (in $enclClass)") - res = newEEE(enclClass, sym) - } - } - - res - } - } // end of trait BCClassGen /* basic functionality for class file building of plain, mirror, and beaninfo classes. */ @@ -985,11 +808,12 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * * must-single-thread */ - def genMirrorClass(modsym: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { - assert(modsym.companionClass == NoSymbol, modsym) + def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { + assert(moduleClass.isModuleClass) + assert(moduleClass.companionClass == NoSymbol, moduleClass) innerClassBufferASM.clear() this.cunit = cunit - val moduleName = internalName(modsym) // + "$" + 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) @@ -999,7 +823,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { flags, mirrorName, null /* no java-generic-signature */, - JAVA_LANG_OBJECT.internalName, + ObjectReference.internalName, EMPTY_STRING_ARRAY ) @@ -1008,18 +832,18 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { null /* SourceDebugExtension */) } - val ssa = getAnnotPickle(mirrorName, modsym.companionSymbol) + val ssa = getAnnotPickle(mirrorName, moduleClass.companionSymbol) mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) - emitAnnotations(mirrorClass, modsym.annotations ++ ssa) + emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa) - addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) + addForwarders(isRemote(moduleClass), mirrorClass, mirrorName, moduleClass) - innerClassBufferASM ++= trackMemberClasses(modsym, Nil /* TODO what about Late-Closure-Classes */ ) + innerClassBufferASM ++= classBTypeFromSymbol(moduleClass).info.memberClasses addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) mirrorClass.visitEnd() - ("" + modsym.name) // this side-effect is necessary, really. + ("" + moduleClass.name) // this side-effect is necessary, really. mirrorClass } @@ -1042,10 +866,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { innerClassBufferASM.clear() - val flags = mkFlags( - javaFlags(cls), - if (isDeprecated(cls)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) + val flags = javaFlags(cls) val beanInfoName = (internalName(cls) + "BeanInfo") val beanInfoClass = new asm.tree.ClassNode @@ -1091,9 +912,9 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { EMPTY_STRING_ARRAY // no throwable exceptions ) - val stringArrayJType: BType = ArrayBType(JAVA_LANG_STRING) + val stringArrayJType: BType = ArrayBType(StringReference) val conJType: BType = MethodBType( - exemplar(definitions.ClassClass).c :: stringArrayJType :: stringArrayJType :: Nil, + classBTypeFromSymbol(definitions.ClassClass) :: stringArrayJType :: stringArrayJType :: Nil, UNIT ) @@ -1104,7 +925,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { constructor.visitLdcInsn(new java.lang.Integer(fi)) if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } else { constructor.visitLdcInsn(f) } - constructor.visitInsn(JAVA_LANG_STRING.typedOpcode(asm.Opcodes.IASTORE)) + constructor.visitInsn(StringReference.typedOpcode(asm.Opcodes.IASTORE)) fi += 1 } } @@ -1113,16 +934,16 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { constructor.visitVarInsn(asm.Opcodes.ALOAD, 0) // push the class - constructor.visitLdcInsn(exemplar(cls).c.toASMType) + constructor.visitLdcInsn(classBTypeFromSymbol(cls).toASMType) // push the string array of field information constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.internalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName) push(fieldList) // push the string array of method information constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.internalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName) push(methodList) // invoke the superclass constructor, which will do the @@ -1133,7 +954,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments constructor.visitEnd() - innerClassBufferASM ++= trackMemberClasses(cls, Nil /* TODO what about Late-Closure-Classes */ ) + innerClassBufferASM ++= classBTypeFromSymbol(cls).info.memberClasses addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) beanInfoClass.visitEnd() @@ -1165,11 +986,11 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { */ def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) { // this tracks the inner class in innerClassBufferASM, if needed. - val androidCreatorType = asmClassType(AndroidCreatorClass) + val androidCreatorType = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass) val tdesc_creator = androidCreatorType.descriptor cnode.visitField( - PublicStaticFinal, + GenBCode.PublicStaticFinal, "CREATOR", tdesc_creator, null, // no java-generic-signature @@ -1206,5 +1027,4 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { } } // end of trait JAndroidBuilder - } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index 2343d378db..d58368b19d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -19,16 +19,11 @@ import scala.collection.mutable * */ abstract class BCodeIdiomatic extends SubComponent { - protected val bCodeICodeCommon: BCodeICodeCommon[global.type] = new BCodeICodeCommon(global) - - val bTypes = new BTypes[global.type](global) { - def chrs = global.chrs - override type BTypeName = global.TypeName - override def createNewName(s: String) = global.newTypeName(s) - } + val bTypes = new BTypesFromSymbols[global.type](global) import global._ import bTypes._ + import coreBTypes._ val classfileVersion: Int = settings.target.value match { case "jvm-1.5" => asm.Opcodes.V1_5 @@ -40,9 +35,7 @@ abstract class BCodeIdiomatic extends SubComponent { val majorVersion: Int = (classfileVersion & 0xFF) val emitStackMapFrame = (majorVersion >= 50) - def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) - - val extraProc: Int = mkFlags( + val extraProc: Int = GenBCode.mkFlags( asm.ClassWriter.COMPUTE_MAXS, if (emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 ) @@ -52,15 +45,6 @@ abstract class BCodeIdiomatic extends SubComponent { val CLASS_CONSTRUCTOR_NAME = "<clinit>" val INSTANCE_CONSTRUCTOR_NAME = "<init>" - val ObjectReference = ClassBType("java/lang/Object") - val AnyRefReference = ObjectReference - val objArrayReference = ArrayBType(ObjectReference) - - val JAVA_LANG_OBJECT = ObjectReference - val JAVA_LANG_STRING = ClassBType("java/lang/String") - - var StringBuilderReference: BType = null - val EMPTY_STRING_ARRAY = Array.empty[String] val EMPTY_INT_ARRAY = Array.empty[Int] val EMPTY_LABEL_ARRAY = Array.empty[asm.Label] @@ -239,7 +223,7 @@ abstract class BCodeIdiomatic extends SubComponent { final def genStringConcat(el: BType) { val jtype = - if (el.isArray || el.isClass) JAVA_LANG_OBJECT + if (el.isArray || el.isClass) ObjectReference else el val bt = MethodBType(List(jtype), StringBuilderReference) @@ -266,7 +250,7 @@ abstract class BCodeIdiomatic extends SubComponent { assert( from.isNonVoidPrimitiveType && to.isNonVoidPrimitiveType, - s"Cannot emit primitive conversion from $from to $to" + s"Cannot emit primitive conversion from $from to $to - ${global.currentUnit}" ) def pickOne(opcs: Array[Int]) { // TODO index on to.sort @@ -537,7 +521,7 @@ abstract class BCodeIdiomatic extends SubComponent { final def emitTypeBased(opcs: Array[Int], tk: BType) { assert(tk != UNIT, tk) val opc = { - if (tk.isRef) { opcs(0) } + if (tk.isRef) { opcs(0) } else if (tk.isIntSizedType) { (tk: @unchecked) match { case BOOL | BYTE => opcs(1) @@ -648,7 +632,7 @@ abstract class BCodeIdiomatic extends SubComponent { */ final def coercionTo(code: Int): BType = { import scalaPrimitives._ - (code: @scala.annotation.switch) match { + (code: @switch) match { case B2B | C2B | S2B | I2B | L2B | F2B | D2B => BYTE case B2C | C2C | S2C | I2C | L2C | F2C | D2C => CHAR case B2S | C2S | S2S | I2S | L2S | F2S | D2S => SHORT @@ -659,21 +643,6 @@ abstract class BCodeIdiomatic extends SubComponent { } } - final val typeOfArrayOp: Map[Int, BType] = { - import scalaPrimitives._ - Map( - (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++ - (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++ - (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++ - (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++ - (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT)) ++ - (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ - (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ - (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ - (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _* - ) - } - /* * Collects (in `result`) all LabelDef nodes enclosed (directly or not) by each node it visits. * diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 0d67a07e0f..4592031a31 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -26,6 +26,8 @@ import java.io.PrintWriter abstract class BCodeSkelBuilder extends BCodeHelpers { import global._ import bTypes._ + import coreBTypes._ + import bCodeAsmCommon._ /* * There's a dedicated PlainClassBuilder for each CompilationUnit, @@ -116,7 +118,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { addClassFields() - innerClassBufferASM ++= trackMemberClasses(claszSymbol, Nil) + innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses gen(cd.impl) addInnerClassesASM(cnode, innerClassBufferASM.toList) @@ -134,40 +136,28 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { private def initJClass(jclass: asm.ClassVisitor) { val ps = claszSymbol.info.parents - val superClass: String = if (ps.isEmpty) JAVA_LANG_OBJECT.internalName else internalName(ps.head.typeSymbol); - val ifaces: Array[String] = { - val arrIfacesTr: Array[Tracked] = exemplar(claszSymbol).ifaces - val arrIfaces = new Array[String](arrIfacesTr.length) - var i = 0 - while (i < arrIfacesTr.length) { - val ifaceTr = arrIfacesTr(i) - val bt = ifaceTr.c - if (ifaceTr.isInnerClass) { innerClassBufferASM += bt } - arrIfaces(i) = bt.internalName - i += 1 - } - arrIfaces + val superClass: String = if (ps.isEmpty) ObjectReference.internalName else internalName(ps.head.typeSymbol) + val interfaceNames = classBTypeFromSymbol(claszSymbol).info.interfaces map { + case classBType => + if (classBType.isNestedClass) { innerClassBufferASM += classBType } + classBType.internalName } - // `internalName()` tracks inner classes. - val flags = mkFlags( - javaFlags(claszSymbol), - if (isDeprecated(claszSymbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) + val flags = javaFlags(claszSymbol) val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner) cnode.visit(classfileVersion, flags, thisName, thisSignature, - superClass, ifaces) + superClass, interfaceNames.toArray) if (emitSource) { cnode.visitSource(cunit.source.toString, null /* SourceDebugExtension */) } - val enclM = getEnclosingMethodAttribute(claszSymbol) - if (enclM != null) { - val EnclMethodEntry(className, methodName, methodType) = enclM - cnode.visitOuterClass(className, methodName, methodType.descriptor) + enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { + case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) => + cnode.visitOuterClass(className, methodName, methodDescriptor) + case _ => () } val ssa = getAnnotPickle(thisName, claszSymbol) @@ -207,7 +197,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { */ private def addModuleInstanceField() { val fv = - cnode.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + cnode.visitField(GenBCode.PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED strMODULE_INSTANCE_FIELD, "L" + thisName + ";", null, // no java-generic-signature @@ -223,7 +213,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { private def fabricateStaticInit() { val clinit: asm.MethodVisitor = cnode.visitMethod( - PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + GenBCode.PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED CLASS_CONSTRUCTOR_NAME, "()V", null, // no java-generic-signature @@ -254,10 +244,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { */ for (f <- fieldSymbols(claszSymbol)) { val javagensig = getGenericSignature(f, claszSymbol) - val flags = mkFlags( - javaFieldFlags(f), - if (isDeprecated(f)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) + val flags = javaFieldFlags(f) val jfield = new asm.tree.FieldNode( flags, @@ -576,12 +563,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val isNative = methSymbol.hasAnnotation(definitions.NativeAttr) val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface) - val flags = mkFlags( + val flags = GenBCode.mkFlags( javaFlags(methSymbol), if (claszSymbol.isInterface) asm.Opcodes.ACC_ABSTRACT else 0, if (methSymbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, - if (isNative) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes - if (isDeprecated(methSymbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + if (isNative) asm.Opcodes.ACC_NATIVE else 0 // native methods of objects are generated in mirror classes ) // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } @@ -695,7 +681,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { // android creator code if (isCZParcelable) { // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator - val andrFieldDescr = asmClassType(AndroidCreatorClass).descriptor + val andrFieldDescr = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass).descriptor cnode.visitField( asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL, "CREATOR", diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala index c271e7b129..7c95b7fc3b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala @@ -21,7 +21,7 @@ import scala.tools.asm abstract class BCodeSyncAndTry extends BCodeBodyBuilder { import global._ import bTypes._ - + import coreBTypes._ /* * Functionality to lower `synchronized` and `try` expressions. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala deleted file mode 100644 index b373f8d74d..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala +++ /dev/null @@ -1,930 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Martin Odersky - */ - -package scala -package tools.nsc -package backend.jvm - -import scala.tools.asm -import scala.collection.{ immutable, mutable } - -/* - * Utilities to mediate between types as represented in Scala ASTs and ASM trees. - * - * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded - * @version 1.0 - * - */ -abstract class BCodeTypes extends BCodeIdiomatic { - import global._ - import bTypes._ - - // Used only for assertions. When compiling the Scala library, some assertions don't hold - // (e.g., scala.Boolean has null superClass although it's not an interface) - private val isCompilingStdLib = !(settings.sourcepath.isDefault) - - // special names - var StringReference : ClassBType = null - var ThrowableReference : ClassBType = null - var jlCloneableReference : ClassBType = null // java/lang/Cloneable - var jlNPEReference : ClassBType = null // java/lang/NullPointerException - var jioSerializableReference : ClassBType = null // java/io/Serializable - var scalaSerializableReference : ClassBType = null // scala/Serializable - var classCastExceptionReference : ClassBType = null // java/lang/ClassCastException - - /* A map from scala primitive type-symbols to BTypes */ - var primitiveTypeMap: Map[Symbol, BType] = null - /* A map from scala type-symbols for Nothing and Null to (runtime version) BTypes */ - var phantomTypeMap: Map[Symbol, ClassBType] = null - /* Maps the method symbol for a box method to the boxed type of the result. - * For example, the method symbol for `Byte.box()`) is mapped to the BType `Ljava/lang/Integer;`. */ - var boxResultType: Map[Symbol, BType] = null - /* Maps the method symbol for an unbox method to the primitive type of the result. - * For example, the method symbol for `Byte.unbox()`) is mapped to the BType BYTE. */ - var unboxResultType: Map[Symbol, BType] = null - - var hashMethodSym: Symbol = null // scala.runtime.ScalaRunTime.hash - - var AndroidParcelableInterface: Symbol = null - var AndroidCreatorClass : Symbol = null // this is an inner class, use asmType() to get hold of its BType while tracking in innerClassBufferASM - - var BeanInfoAttr: Symbol = null - - /* The Object => String overload. */ - var String_valueOf: Symbol = null - - var ArrayInterfaces: Set[Tracked] = null - - // scala.FunctionX and scala.runtim.AbstractFunctionX - val FunctionReference = new Array[Tracked](definitions.MaxFunctionArity + 1) - val AbstractFunctionReference = new Array[Tracked](definitions.MaxFunctionArity + 1) - val abstractFunctionArityMap = mutable.Map.empty[BType, Int] - - var PartialFunctionReference: ClassBType = null // scala.PartialFunction - var AbstractPartialFunctionReference: ClassBType = null // scala.runtime.AbstractPartialFunction - - var BoxesRunTime: ClassBType = null - - /* - * must-single-thread - */ - def initBCodeTypes() { - import definitions._ - - primitiveTypeMap = - Map( - UnitClass -> UNIT, - BooleanClass -> BOOL, - CharClass -> CHAR, - ByteClass -> BYTE, - ShortClass -> SHORT, - IntClass -> INT, - LongClass -> LONG, - FloatClass -> FLOAT, - DoubleClass -> DOUBLE - ) - - phantomTypeMap = - Map( - NothingClass -> RT_NOTHING, - NullClass -> RT_NULL, - NothingClass -> RT_NOTHING, // we map on purpose to RT_NOTHING, getting rid of the distinction compile-time vs. runtime for NullClass. - NullClass -> RT_NULL // ditto. - ) - - boxResultType = - for((csym, msym) <- currentRun.runDefinitions.boxMethod) - yield (msym -> classLiteral(primitiveTypeMap(csym))) - - unboxResultType = - for((csym, msym) <- currentRun.runDefinitions.unboxMethod) - yield (msym -> primitiveTypeMap(csym)) - - // boxed classes are looked up in the `exemplars` map by jvmWiseLUB(). - // Other than that, they aren't needed there (e.g., `isSubtypeOf()` special-cases boxed classes, similarly for others). - val boxedClasses = List(BoxedBooleanClass, BoxedCharacterClass, BoxedByteClass, BoxedShortClass, BoxedIntClass, BoxedLongClass, BoxedFloatClass, BoxedDoubleClass) - for(csym <- boxedClasses) { - val key = ClassBType(csym.javaBinaryName.toTypeName) - val tr = buildExemplar(key, csym) - symExemplars.put(csym, tr) - exemplars.put(tr.c, tr) - } - - // reversePrimitiveMap = (primitiveTypeMap map { case (s, pt) => (s.tpe, pt) } map (_.swap)).toMap - - hashMethodSym = getMember(ScalaRunTimeModule, nme.hash_) - - // TODO avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540 - AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable") - AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator") - - // the following couldn't be an eager vals in Phase constructors: - // that might cause cycles before Global has finished initialization. - BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") - - String_valueOf = { - getMember(StringModule, nme.valueOf) filter (sym => - sym.info.paramTypes match { - case List(pt) => pt.typeSymbol == ObjectClass - case _ => false - } - ) - } - - exemplar(JavaCloneableClass) - exemplar(JavaSerializableClass) - exemplar(SerializableClass) - - StringReference = exemplar(StringClass).c - StringBuilderReference = exemplar(StringBuilderClass).c - ThrowableReference = exemplar(ThrowableClass).c - jlCloneableReference = exemplar(JavaCloneableClass).c - jlNPEReference = exemplar(NullPointerExceptionClass).c - jioSerializableReference = exemplar(JavaSerializableClass).c - scalaSerializableReference = exemplar(SerializableClass).c - classCastExceptionReference = exemplar(ClassCastExceptionClass).c - - PartialFunctionReference = exemplar(PartialFunctionClass).c - for(idx <- 0 to definitions.MaxFunctionArity) { - FunctionReference(idx) = exemplar(FunctionClass(idx)) - AbstractFunctionReference(idx) = exemplar(AbstractFunctionClass(idx)) - abstractFunctionArityMap += (AbstractFunctionReference(idx).c -> idx) - AbstractPartialFunctionReference = exemplar(AbstractPartialFunctionClass).c - } - - BoxesRunTime = ClassBType("scala/runtime/BoxesRunTime") - } - - /* - * must-single-thread - */ - def clearBCodeTypes() { - symExemplars.clear() - exemplars.clear() - } - - val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC - val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL - - val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString - - // ------------------------------------------------ - // accessory maps tracking the isInterface, innerClasses, superClass, and supportedInterfaces relations, - // allowing answering `conforms()` without resorting to typer. - // ------------------------------------------------ - - /** - * Type information for classBTypes. - * - * TODO rename Tracked - */ - val exemplars = new java.util.concurrent.ConcurrentHashMap[ClassBType, Tracked] - - /** - * Maps class symbols to their corresponding `Tracked` instance. - * - * This map is only used during the first backend phase (Worker1) where ClassDef trees are - * transformed into ClassNode asm trees. In this phase, ClassBTypes and their Tracked are created - * and added to the `exemplars` map. The `symExemplars` map is only used to know if a symbol has - * already been visited. - * - * TODO move this map to the builder class. it's only used during building. can be gc'd with the builder. - */ - val symExemplars = new java.util.concurrent.ConcurrentHashMap[Symbol, Tracked] - - /** - * A `Tracked` instance stores information about a BType. This allows ansering type questions - * without resolving to the compiler, in a thread-safe manner, in particular isSubtypeOf. - * - * @param c the BType described by this `Tracked` - * @param flags the java flags for the type, computed by BCodeTypes#javaFlags - * @param sc the bytecode-level superclass if any, null otherwise - * @param ifaces the interfaces explicitly declared. Not included are those transitively - * supported, but the utility method `allLeafIfaces()` can be used for that. - * @param innersChain the containing classes for a non-package-level class `c`, null otherwise. - * - * Note: the optimizer may inline anonymous closures, thus eliding those inner classes (no - * physical class file is emitted for elided classes). Before committing `innersChain` to - * bytecode, cross-check with the list of elided classes (SI-6546). - * - * All methods of this class can-multi-thread - * - * TODO @lry c: ClassBType. rename to ClassBTypeInfo - */ - case class Tracked(c: ClassBType, flags: Int, sc: Tracked, ifaces: Array[Tracked], innersChain: Array[InnerClassEntry]) { - - // not a case-field because we initialize it only for JVM classes we emit. - // TODO @lry make it an Option[List[BType]] - // TODO: this is currently not used. a commit in the optimizer branch uses this field to - // re-compute inner classes (ee4c185). leaving it in for now. - private var _directMemberClasses: List[BType] = null - - def directMemberClasses: List[BType] = { - assert(_directMemberClasses != null, s"getter directMemberClasses() invoked too early for $c") - _directMemberClasses - } - - def directMemberClasses_=(bs: List[BType]) { - if (_directMemberClasses != null) { - // TODO we enter here when both mirror class and plain class are emitted for the same ModuleClassSymbol. - assert(_directMemberClasses.sameElements(bs)) - } - _directMemberClasses = bs - } - - /* `isCompilingStdLib` saves the day when compiling: - * (1) scala.Nothing (the test `c.isNonSpecial` fails for it) - * (2) scala.Boolean (it has null superClass and is not an interface) - */ - assert(c.isNonSpecial || isCompilingStdLib /*(1)*/, s"non well-formed plain-type: $this") - assert( - if (sc == null) { (c == ObjectReference) || isInterface || isCompilingStdLib /*(2)*/ } - else { (c != ObjectReference) && !sc.isInterface } - , "non well-formed plain-type: " + this - ) - assert(ifaces.forall(i => i.c.isNonSpecial && i.isInterface), s"non well-formed plain-type: $this") - - import asm.Opcodes._ - def hasFlags(mask: Int) = (flags & mask) != 0 - def isInterface = hasFlags(ACC_INTERFACE) - def isFinal = hasFlags(ACC_FINAL) - def isInnerClass = { innersChain != null } - def isLambda = { - // ie isLCC || isTraditionalClosureClass - isFinal && (c.simpleName.contains(tpnme.ANON_FUN_NAME.toString)) && isFunctionType(c) - } - - /* can-multi-thread */ - def superClasses: List[Tracked] = { - if (sc == null) Nil else sc :: sc.superClasses - } - - /* can-multi-thread */ - def isSubtypeOf(other: BType): Boolean = { - assert(other.isNonSpecial, "so called special cases have to be handled in BCodeTypes.conforms()") - - if (c == other) return true; - - val otherIsIface = exemplars.get(other).isInterface - - if (this.isInterface) { - if (other == ObjectReference) return true; - if (!otherIsIface) return false; - } - else { - if (sc != null && sc.isSubtypeOf(other)) return true; - if (!otherIsIface) return false; - } - - var idx = 0 - while (idx < ifaces.length) { - if (ifaces(idx).isSubtypeOf(other)) return true; - idx += 1 - } - - false - } - - /* - * The `ifaces` field lists only those interfaces declared by `c` - * From the set of all supported interfaces, this method discards those which are supertypes of others in the set. - */ - def allLeafIfaces: Set[Tracked] = { - if (sc == null) { ifaces.toSet } - else { minimizeInterfaces(ifaces.toSet ++ sc.allLeafIfaces) } - } - - /* - * This type may not support in its entirety the interface given by the argument, however it may support some of its super-interfaces. - * We visualize each such supported subset of the argument's functionality as a "branch". This method returns all such branches. - * - * In other words, let Ri be a branch supported by `ib`, - * this method returns all Ri such that this <:< Ri, where each Ri is maximally deep. - */ - def supportedBranches(ib: Tracked): Set[Tracked] = { - assert(ib.isInterface, s"Non-interface argument: $ib") - - val result: Set[Tracked] = - if (this.isSubtypeOf(ib.c)) { Set(ib) } - else { ib.ifaces.toSet[Tracked].flatMap( bi => supportedBranches(bi) ) } - - checkAllInterfaces(result) - - result - } - - override def toString = { c.toString } - - } - - /* must-single-thread */ - final def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } - - /* must-single-thread */ - final def hasInternalName(sym: Symbol) = sym.isClass || sym.isModuleNotMethod - - /* must-single-thread */ - def getSuperInterfaces(csym: Symbol): List[Symbol] = { - - // Additional interface parents based on annotations and other cues - def newParentForAttr(ann: AnnotationInfo): Symbol = ann.symbol match { - case definitions.RemoteAttr => definitions.RemoteInterfaceClass - case _ => NoSymbol - } - - /* Drop redundant interfaces (which are implemented by some other parent) from the immediate parents. - * In other words, no two interfaces in the result are related by subtyping. - * This method works on Symbols, a similar one (not duplicate) works on Tracked instances. - */ - def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = { - var rest = lstIfaces - var leaves = List.empty[Symbol] - while (!rest.isEmpty) { - val candidate = rest.head - val nonLeaf = leaves exists { lsym => lsym isSubClass candidate } - if (!nonLeaf) { - leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym }) - } - rest = rest.tail - } - - leaves - } - - val superInterfaces0: List[Symbol] = csym.mixinClasses - val superInterfaces = existingSymbols(superInterfaces0 ++ csym.annotations.map(newParentForAttr)).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(", ")}") - - minimizeInterfaces(superInterfaces) - } - - /* - * Records the superClass and supportedInterfaces relations, - * so that afterwards queries can be answered without resorting to typer. - * This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that. - * On the other hand, this method does record the inner-class status of the argument, via `buildExemplar()`. - * - * must-single-thread - */ - final def exemplar(csym0: Symbol): Tracked = { - assert(csym0 != NoSymbol, "NoSymbol can't be tracked") - - val csym = { - if (csym0.isJavaDefined && csym0.isModuleClass) csym0.linkedClassOfClass - else if (csym0.isModule) csym0.moduleClass - else csym0 // we track only module-classes and plain-classes - } - - assert(!primitiveTypeMap.contains(csym) || isCompilingStdLib, s"primitive types not tracked here: ${csym.fullName}") - assert(!phantomTypeMap.contains(csym), s"phantom types not tracked here: ${csym.fullName}") - - val opt = symExemplars.get(csym) - if (opt != null) { - return opt - } - val key = new ClassBType(csym.javaBinaryName.toTypeName) - assert(key.isNonSpecial || isCompilingStdLib, s"Not a class to track: ${csym.fullName}") - - // TODO accomodate the fix for SI-5031 of https://github.com/scala/scala/commit/0527b2549bcada2fda2201daa630369b377d0877 - // TODO Weaken this assertion? buildExemplar() needs to be updated, too. In the meantime, pos/t5031_3 has been moved to test/disabled/pos. - val whatWasInExemplars = exemplars.get(key) - assert(whatWasInExemplars == null, "Maps `symExemplars` and `exemplars` got out of synch.") - val tr = buildExemplar(key, csym) - symExemplars.put(csym, tr) - if (csym != csym0) { symExemplars.put(csym0, tr) } - exemplars.put(tr.c, tr) // tr.c is the hash-consed, internalized, canonical representative for csym's key. - tr - } - - /* - * must-single-thread - */ - private def buildExemplar(key: ClassBType, csym: Symbol): Tracked = { - val sc = - if (csym.isImplClass) definitions.ObjectClass - else csym.superClass - assert( - if (csym == definitions.ObjectClass) - sc == NoSymbol - else if (csym.isInterface) - sc == definitions.ObjectClass - else - ((sc != NoSymbol) && !sc.isInterface) || isCompilingStdLib, - "superClass out of order" - ) - val ifacesArr = getSuperInterfaces(csym).map(exemplar).toArray - - val flags = mkFlags( - javaFlags(csym), - if (isDeprecated(csym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag - ) - - val tsc = if (sc == NoSymbol) null else exemplar(sc) - - val innersChain = saveInnerClassesFor(csym, key) - - Tracked(key, flags, tsc, ifacesArr, innersChain) - } - - // ---------------- utilities around interfaces represented by Tracked instances. ---------------- - - /* Drop redundant interfaces (those which are implemented by some other). - * In other words, no two interfaces in the result are related by subtyping. - * This method works on Tracked elements, a similar one (not duplicate) works on Symbols. - */ - def minimizeInterfaces(lstIfaces: Set[Tracked]): Set[Tracked] = { - checkAllInterfaces(lstIfaces) - var rest = lstIfaces.toList - var leaves = List.empty[Tracked] - while (!rest.isEmpty) { - val candidate = rest.head - val nonLeaf = leaves exists { leaf => leaf.isSubtypeOf(candidate.c) } - if (!nonLeaf) { - leaves = candidate :: (leaves filterNot { leaf => candidate.isSubtypeOf(leaf.c) }) - } - rest = rest.tail - } - - leaves.toSet - } - - def allInterfaces(is: Iterable[Tracked]): Boolean = { is forall { i => i.isInterface } } - def nonInterfaces(is: Iterable[Tracked]): Iterable[Tracked] = { is filterNot { i => i.isInterface } } - - def checkAllInterfaces(ifaces: Iterable[Tracked]) { - assert(allInterfaces(ifaces), s"Non-interfaces: ${nonInterfaces(ifaces).mkString}") - } - - /* - * Subtype check `a <:< b` on BTypes that takes into account the JVM built-in numeric promotions (e.g. BYTE to INT). - * Its operation can be visualized more easily in terms of the Java bytecode type hierarchy. - * This method used to be called, in the ICode world, TypeKind.<:<() - * - * can-multi-thread - */ - final def conforms(a: BType, b: BType): Boolean = { - if (a.isArray) { // may be null - /* Array subtyping is covariant here, as in Java bytecode. Also necessary for Java interop. */ - if ((b == jlCloneableReference) || - (b == jioSerializableReference) || - (b == AnyRefReference)) { true } - else if (b.isArray) { conforms(a.asArrayBType.componentType, // TODO @lry change to pattern match, get rid of casts - b.asArrayBType.componentType) } - else { false } - } - else if (a.isBoxed) { // may be null - if (b.isBoxed) { a == b } - else if (b == AnyRefReference) { true } - else if (!(b.isClass)) { false } - else { exemplars.get(a).isSubtypeOf(b) } // e.g., java/lang/Double conforms to java/lang/Number - } - else if (a.isNullType) { // known to be null - if (b.isNothingType) { false } - else if (b.isPrimitive) { false } - else { true } - } - else if (a.isNothingType) { // known to be Nothing - true - } - else if (a == UNIT) { - b == UNIT - } - else if (a.isClass) { // may be null - if (a.isNothingType) { true } - else if (b.isClass) { exemplars.get(a).isSubtypeOf(b) } - else if (b.isArray) { a.isNullType } // documentation only, because `if(a.isNullType)` (above) covers this case already. - else { false } - } - else { - - def msg = s"(a: $a, b: $b)" - - assert(a.isNonVoidPrimitiveType, s"a isn't a non-Unit value type. $msg") - assert(b.isPrimitive, s"b isn't a value type. $msg") - - (a eq b) || (a match { - case BOOL | BYTE | SHORT | CHAR => b == INT || b == LONG // TODO Actually, BOOL does NOT conform to LONG. Even with adapt(). - case _ => a == b - }) - } - } - - /* The maxValueType of (Char, Byte) and of (Char, Short) is Int, to encompass the negative values of Byte and Short. See ticket #2087. - * - * can-multi-thread - */ - def maxValueType(a: BType, other: BType): BType = { - assert(a.isPrimitive, "maxValueType() is defined only for 1st arg valuetypes (2nd arg doesn't matter).") - - def uncomparable: Nothing = { - abort(s"Uncomparable BTypes: $a with $other") - } - - if (a.isNothingType) return other; - if (other.isNothingType) return a; - if (a == other) return a; - - a match { - - case UNIT => uncomparable - case BOOL => uncomparable - - case BYTE => - if (other == CHAR) INT - else if (other.isNumericType) other - else uncomparable - - case SHORT => - other match { - case BYTE => SHORT - case CHAR => INT - case INT | LONG | FLOAT | DOUBLE => other - case _ => uncomparable - } - - case CHAR => - other match { - case BYTE | SHORT => INT - case INT | LONG | FLOAT | DOUBLE => other - case _ => uncomparable - } - - case INT => - other match { - case BYTE | SHORT | CHAR => INT - case LONG | FLOAT | DOUBLE => other - case _ => uncomparable - } - - case LONG => - if (other.isIntegralType) LONG - else if (other.isRealType) DOUBLE - else uncomparable - - case FLOAT => - if (other == DOUBLE) DOUBLE - else if (other.isNumericType) FLOAT - else uncomparable - - case DOUBLE => - if (other.isNumericType) DOUBLE - else uncomparable - - case _ => uncomparable - } - } - - /* Takes promotions of numeric primitives into account. - * - * can-multi-thread - */ - final def maxType(a: BType, other: BType): BType = { - if (a.isPrimitive) { maxValueType(a, other) } - else { - if (a.isNothingType) return other; - if (other.isNothingType) return a; - if (a == other) return a; - // Approximate `lub`. The common type of two references is always AnyRef. - // For 'real' least upper bound wrt to subclassing use method 'lub'. - assert(a.isArray || a.isBoxed || a.isClass, s"This is not a valuetype and it's not something else, what is it? $a") - // TODO For some reason, ICode thinks `REFERENCE(...).maxType(BOXED(whatever))` is `uncomparable`. Here, that has maxType AnyRefReference. - // BTW, when swapping arguments, ICode says BOXED(whatever).maxType(REFERENCE(...)) == AnyRefReference, so I guess the above was an oversight in REFERENCE.maxType() - if (other.isRef) { AnyRefReference } - else { abort(s"Uncomparable BTypes: $a with $other") } - } - } - - /* - * Whether the argument is a subtype of - * scala.PartialFunction[-A, +B] extends (A => B) - * N.B.: this method returns true for a scala.runtime.AbstractPartialFunction - * - * can-multi-thread - */ - def isPartialFunctionType(t: BType): Boolean = { - (t.isClass) && exemplars.get(t).isSubtypeOf(PartialFunctionReference) - } - - /* - * Whether the argument is a subtype of scala.FunctionX where 0 <= X <= definitions.MaxFunctionArity - * - * can-multi-thread - */ - def isFunctionType(t: BType): Boolean = { - if (!t.isClass) return false - var idx = 0 - val et: Tracked = exemplars.get(t) - while (idx <= definitions.MaxFunctionArity) { - if (et.isSubtypeOf(FunctionReference(idx).c)) { - return true - } - idx += 1 - } - false - } - - /** - * must-single-thread - * - * True for module classes of package level objects. The backend will generate a mirror class for - * such objects. - */ - def isTopLevelModuleClass(sym: Symbol): Boolean = exitingPickler { - // phase travel to pickler required for isNestedClass (looks at owner) - val r = sym.isModuleClass && !sym.isNestedClass - // The mixin phase adds the `lateMODULE` flag to trait implementation classes. Since the flag - // is late, it should not be visible here inside the time travel. We check this. - if (r) assert(!sym.isImplClass, s"isModuleClass should be false for impl class $sym") - r - } - - /** - * must-single-thread - * - * True for module classes of modules that are top-level or owned only by objects. Module classes - * for such objects will get a MODULE$ flag and a corresponding static initializer. - */ - def isStaticModuleClass(sym: Symbol): Boolean = { - /* The implementation of this method is tricky because it is a source-level property. Various - * phases changed the symbol's properties in the meantime. - * - * (1) Phase travel to to pickler is required to exclude implementation classes; they have the - * lateMODULEs after mixin, so isModuleClass would be true. - * - * (2) We cannot use `sym.isStatic` because lambdalift modified (destructively) the owner. For - * example, in - * object T { def f { object U } } - * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here. - * So we basically re-implement `sym.isStaticOwner`, but using the original owner chain. - */ - - def isOriginallyStaticOwner(sym: Symbol): Boolean = { - sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner) - } - - exitingPickler { // (1) - sym.isModuleClass && - isOriginallyStaticOwner(sym.originalOwner) // (2) - } - } - - - // --------------------------------------------------------------------- - // ---------------- InnerClasses attribute (JVMS 4.7.6) ---------------- - // --------------------------------------------------------------------- - - val INNER_CLASSES_FLAGS = - (asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | - asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_INTERFACE | asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_FINAL) - - /* - * @param name the internal name of an inner class. - * @param outerName the internal name of the class to which the inner class belongs. - * May be `null` for non-member inner classes (ie for a Java local class or a Java anonymous class). - * @param innerName the (simple) name of the inner class inside its enclosing class. It's `null` for anonymous inner classes. - * @param access the access flags of the inner class as originally declared in the enclosing class. - */ - case class InnerClassEntry(name: String, outerName: String, innerName: String, access: Int) { - assert(name != null, "Null isn't good as class name in an InnerClassEntry.") - } - - /* For given symbol return a symbol corresponding to a class that should be declared as inner class. - * - * For example: - * class A { - * class B - * object C - * } - * - * then method will return: - * NoSymbol for A, - * the same symbol for A.B (corresponding to A$B class), and - * A$C$ symbol for A.C. - * - * must-single-thread - */ - def innerClassSymbolFor(s: Symbol): Symbol = - if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol - - /* - * Computes the chain of inner-class (over the is-member-of relation) for the given argument. - * The resulting chain will be cached in `exemplars`. - * - * The chain thus cached is valid during this compiler run, see in contrast - * `innerClassBufferASM` for a cache that is valid only for the class being emitted. - * - * The argument can be any symbol, but given that this method is invoked only from `buildExemplar()`, - * in practice it has been vetted to be a class-symbol. - * - * Returns: - * - * - a non-empty array of entries for an inner-class argument. - * The array's first element is the outermost top-level class, - * the array's last element corresponds to csym. - * - * - null otherwise. - * - * This method does not add to `innerClassBufferASM`, use instead `exemplar()` for that. - * - * must-single-thread - */ - final def saveInnerClassesFor(csym: Symbol, csymTK: BType): Array[InnerClassEntry] = { - - val ics = innerClassSymbolFor(csym) - if (ics == NoSymbol) { - return null - } - assert(ics == csym, s"Disagreement between innerClassSymbolFor() and exemplar()'s tracked symbol for the same input: ${csym.fullName}") - - var chain: List[Symbol] = Nil - var x = ics - while (x ne NoSymbol) { - assert(x.isClass, s"not a class symbol: ${x.fullName}") - // Uses `rawowner` because `owner` reflects changes in the owner chain due to flattening. - // The owner chain of a class only contains classes. This is because the lambdalift phase - // changes the `rawowner` destructively to point to the enclosing class. Before, the owner - // might be for example a method. - val isInner = !x.rawowner.isPackageClass - if (isInner) { - chain ::= x - x = innerClassSymbolFor(x.rawowner) - } else { - x = NoSymbol - } - } - - if (chain.isEmpty) null - else chain.map(toInnerClassEntry).toArray - } - - /* - * must-single-thread - */ - private def toInnerClassEntry(innerSym: Symbol): InnerClassEntry = { - - /* 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. - */ - def outerName(innerSym: Symbol): Name = { - if (innerSym.originalEnclosingMethod != NoSymbol) - null - else { - val outerName = innerSym.rawowner.javaBinaryName - if (isTopLevelModuleClass(innerSym.rawowner)) nme.stripModuleSuffix(outerName) - else outerName - } - } - - def innerName(innerSym: Symbol): String = { - if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction) - null - else - innerSym.rawname + innerSym.moduleSuffix - } - - // TODO @lry compare with table in spec: for example, deprecated should not be there it seems. - // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6-300-D.1-D.1 - // including "deprecated" was added in the initial commit of GenASM, but it was never in GenJVM. - val flags: Int = mkFlags( - // TODO @lry adding "static" whenever the class is owned by a module seems wrong. - // class C { object O { class I } } - // here, I is marked static in the InnerClass attribute. But the I constructor takes an outer instance. - // was added in 0469d41 - // what should it be? check what would make sense for java reflection. - // member of top-level object should be static? how about anonymous / local class that has - // been lifted to a top-level object? - // member that is only nested in objects should be static? - // verify: will ICodeReader still work after that? the code was introduced because of icode reader. - if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0, - javaFlags(innerSym), - if (isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag - ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) - - val jname = innerSym.javaBinaryName.toString // never null - val oname = { // null when method-enclosed - val on = outerName(innerSym) - if (on == null) null else on.toString - } - val iname = { // null for anonymous inner class - val in = innerName(innerSym) - if (in == null) null else in.toString - } - - InnerClassEntry(jname, oname, iname, flags) - } - - // -------------------------------------------- - // ---------------- Java flags ---------------- - // -------------------------------------------- - - /* - * can-multi-thread - */ - final def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0) - - /* - * must-single-thread - */ - final def isRemote(s: Symbol) = (s hasAnnotation definitions.RemoteAttr) - - /* - * Return the Java modifiers for the given symbol. - * Java modifiers for classes: - * - public, abstract, final, strictfp (not used) - * for interfaces: - * - the same as for classes, without 'final' - * for fields: - * - public, private (*) - * - static, final - * for methods: - * - the same as for fields, plus: - * - abstract, synchronized (not used), strictfp (not used), native (not used) - * - * (*) protected cannot be used, since inner classes 'see' protected members, - * and they would fail verification after lifted. - * - * must-single-thread - */ - def javaFlags(sym: Symbol): Int = { - // constructors of module classes should be private. introduced in b06edbc, probably to prevent - // creating module instances from java. for nested modules, the constructor needs to be public - // since they are created by the outer class and stored in a field. a java client can create - // new instances via outerClassInstance.new InnerModuleClass$(). - // TODO: do this early, mark the symbol private. - val privateFlag = - sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModuleClass(sym.owner)) - - // Symbols marked in source as `final` have the FINAL flag. (In the past, the flag was also - // added to modules and module classes, not anymore since 296b706). - // Note that the presence of the `FINAL` flag on a symbol does not correspond 1:1 to emitting - // ACC_FINAL in bytecode. - // - // Top-level modules are marked ACC_FINAL in bytecode (even without the FINAL flag). Nested - // objects don't get the flag to allow overriding (under -Yoverride-objects, SI-5676). - // - // For fields, only eager val fields can receive ACC_FINAL. vars or lazy vals can't: - // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 - // "Another problem is that the specification allows aggressive - // optimization of final fields. Within a thread, it is permissible to - // reorder reads of a final field with those modifications of a final - // field that do not take place in the constructor." - // - // A var or lazy val which is marked final still has meaning to the - // scala compiler. The word final is heavily overloaded unfortunately; - // for us it means "not overridable". At present you can't override - // vars regardless; this may change. - // - // The logic does not check .isFinal (which checks flags for the FINAL flag, - // and includes symbols marked lateFINAL) instead inspecting rawflags so - // we can exclude lateFINAL. Such symbols are eligible for inlining, but to - // avoid breaking proxy software which depends on subclassing, we do not - // emit ACC_FINAL. - - val finalFlag = ( - (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModuleClass(sym)) - && !sym.enclClass.isInterface - && !sym.isClassConstructor - && !sym.isMutable // lazy vals and vars both - ) - - // Primitives are "abstract final" to prohibit instantiation - // without having to provide any implementations, but that is an - // illegal combination of modifiers at the bytecode level so - // suppress final if abstract if present. - import asm.Opcodes._ - mkFlags( - if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, - if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, - if (sym.isInterface) ACC_INTERFACE else 0, - if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, - if (sym.isStaticMember) ACC_STATIC else 0, - if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, - if (sym.isArtifact) ACC_SYNTHETIC else 0, - if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, - if (sym.hasEnumFlag) ACC_ENUM else 0, - if (sym.isVarargsMethod) ACC_VARARGS else 0, - if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 - ) - // TODO @lry should probably also check / add "deprectated" - // all call sites of "javaFlags" seem to check for deprecation rigth after. - // Exception: the call below in javaFieldFlags. However, the caller of javaFieldFlags then - // does the check. - } - - /* - * must-single-thread - */ - def javaFieldFlags(sym: Symbol) = { - javaFlags(sym) | mkFlags( - if (sym hasAnnotation definitions.TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0, - if (sym hasAnnotation definitions.VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0, - if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL - ) - } - -} // end of class BCodeTypes diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 15bc068533..7bf61b4f51 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -1,37 +1,57 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + package scala.tools.nsc package backend.jvm -import scala.collection.immutable -import scala.annotation.switch import scala.tools.asm import asm.Opcodes -import scala.collection.mutable.ListBuffer /** - * BTypes is a backend component that defines the class BType, a number of basic instances and - * some utilities. + * 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 + * InnerClass attributes and generation of stack map frames. * - * A BType is essentially an slice of the array `chrs` denoting the name of the type, and a field - * denoting the kind (object, array, method, or one of the primitive types). - * - * BTypes depends on Global just because it re-uses hash-consing of Name. It would be cleaner to - * create an interface for BTypeName and extend it in scala.reflect.internal.Names#Name, that - * would simplify testing BTypes (no Global needed). + * This representation is immutable and independent of the compiler data structures, hence it can + * be queried by concurrent threads. */ -abstract class BTypes[G <: Global](val __global_dont_use: G) { - def chrs: Array[Char] +abstract class BTypes { + /** + * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its + * construction. + * + * This map is used when computing stack map frames. The asm.ClassWriter invokes the method + * `getCommonSuperClass`. In this method we need to obtain the ClassBType for a given internal + * name. The method assumes that every class type that appears in the bytecode exists in the map. + * + * 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] /** - * Interface for names stored in `chrs` + * The string represented by the `offset` / `length` values of a ClassBType, see comment of that + * class. */ - type BTypeName <: __global_dont_use.Name + protected def internalNameString(offset: Int, lenght: Int): String /** - * Create a new name in `chrs`. Names are assumed to be hash-consed. Equality on BType will use - * reference equality to compare the names. + * Obtain a previously constructed ClassBType for a given internal name. */ - def createNewName(s: String): BTypeName + def classBTypeFromInternalName(internalName: String) = classBTypeFromInternalNameMap(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._ + + /** + * A BType is either a primitve 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 final override def toString: String = this match { case UNIT => "V" @@ -73,18 +93,11 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { final def isMethod: Boolean = this.isInstanceOf[MethodBType] final def isNonVoidPrimitiveType = isPrimitive && this != UNIT - // TODO @lry should also include !isMethod in isNonSpecial? in this case it would be equivalent to isClass, so we could get rid of it. - final def isNonSpecial = !isPrimitive && !isArray && !isPhantomType - final def isNullType = this == RT_NULL || this == CT_NULL - final def isNothingType = this == RT_NOTHING || this == CT_NOTHING - final def isPhantomType = isNullType || isNothingType - - final def isBoxed = this match { - case BOXED_UNIT | BOXED_BOOLEAN | BOXED_CHAR | - BOXED_BYTE | BOXED_SHORT | BOXED_INT | - BOXED_FLOAT | BOXED_LONG | BOXED_DOUBLE => true - case _ => false - } + + final def isNullType = this == RT_NULL + final def isNothingType = this == RT_NOTHING + + final def isBoxed = this.isClass && boxedClasses(this.asClassBType) final def isIntSizedType = this == BOOL || this == CHAR || this == BYTE || this == SHORT || this == INT @@ -94,6 +107,72 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { final def isNumericType = isIntegralType || isRealType final def isWideType = size == 2 + /* + * Subtype check `this <:< other` on BTypes that takes into account the JVM built-in numeric + * promotions (e.g. BYTE to INT). Its operation can be visualized more easily in terms of the + * Java bytecode type hierarchy. + */ + final def conformsTo(other: BType): Boolean = { + assert(isRef || isPrimitive, s"conformsTo cannot handle $this") + assert(other.isRef || other.isPrimitive, s"conformsTo cannot handle $other") + + this match { + case ArrayBType(component) => + if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true + else other match { + case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent) + case _ => false + } + + case classType: ClassBType => + if (isBoxed) { + if (other.isBoxed) this == other + else if (other == ObjectReference) true + else other match { + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) // e.g., java/lang/Double conforms to java/lang/Number + case _ => false + } + } else if (isNullType) { + if (other.isNothingType) false + else if (other.isPrimitive) false + else true // Null conforms to all classes (except Nothing) and arrays. + } else if (isNothingType) { + true + } else other match { + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) + // case ArrayBType(_) => this.isNullType // documentation only, because `if (isNullType)` above covers this case + case _ => + // isNothingType || // documentation only, because `if (isNothingType)` above covers this case + false + } + + case UNIT => + other == UNIT + case BOOL | BYTE | SHORT | CHAR => + this == other || other == INT || other == LONG // TODO Actually, BOOL does NOT conform to LONG. Even with adapt(). + case _ => + assert(isPrimitive && other.isPrimitive, s"Expected primitive types $this - $other") + this == other + } + } + + /** + * Compute the upper bound of two types. + * Takes promotions of numeric primitives into account. + */ + final def maxType(other: BType): BType = this match { + case pt: PrimitiveBType => pt.maxValueType(other) + + case _: ArrayBType | _: ClassBType => + if (isNothingType) return other + if (other.isNothingType) return this + if (this == other) return this + + assert(other.isRef, s"Cannot compute maxType: $this, $other") + // Approximate `lub`. The common type of two references is always ObjectReference. + ObjectReference + } + /** * See documentation of [[typedOpcode]]. * The numbers are taken from asm.Type.VOID_TYPE ff., the values are those shifted by << 8. @@ -165,45 +244,77 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { case m: MethodBType => asm.Type.getMethodType(m.descriptor) } - def asRefBType : RefBType = this.asInstanceOf[RefBType] - def asArrayBType: ArrayBType = this.asInstanceOf[ArrayBType] - def asClassBType: ClassBType = this.asInstanceOf[ClassBType] + def asRefBType : RefBType = this.asInstanceOf[RefBType] + def asArrayBType : ArrayBType = this.asInstanceOf[ArrayBType] + def asClassBType : ClassBType = this.asInstanceOf[ClassBType] + def asPrimitiveBType : PrimitiveBType = this.asInstanceOf[PrimitiveBType] } - object BType { + sealed trait PrimitiveBType extends BType { + /** - * @param chars The character array containing the descriptor - * @param start The position where the descriptor starts - * @return The BType and the index of the first character after the consumed descriptor + * The upper bound of two primitive types. The `other` type has to be either a primitive + * type or Nothing. + * + * The maxValueType of (Char, Byte) and of (Char, Short) is Int, to encompass the negative + * values of Byte and Short. See ticket #2087. */ - private[BTypes] def fromNonMethodDescriptor(chars: Array[Char], start: Int): (BType, Int) = { - chars(start) match { - case 'L' => - var i = start - while (chars(i) != ';') { i += 1 } - // Example: chars = "IILpkg/Cls;I" - // ^ ^ - // start=2 i=10 - // `start + 1` to exclude the 'L', `i - start - 1` excludes the ';' - (new ClassBType(new String(chars, start + 1, i - start - 1)), i + 1) - case '[' => - val (res, next) = fromNonMethodDescriptor(chars, start + 1) - (ArrayBType(res), next) - case 'V' => (UNIT, start + 1) - case 'Z' => (BOOL, start + 1) - case 'C' => (CHAR, start + 1) - case 'B' => (BYTE, start + 1) - case 'S' => (SHORT, start + 1) - case 'I' => (INT, start + 1) - case 'F' => (FLOAT, start + 1) - case 'J' => (LONG, start + 1) - case 'D' => (DOUBLE, start + 1) + final def maxValueType(other: BType): BType = { + + def uncomparable: Nothing = throw new AssertionError(s"Cannot compute maxValueType: $this, $other") + + if (!other.isPrimitive && !other.isNothingType) uncomparable + + if (other.isNothingType) return this + if (this == other) return this + + this match { + case BYTE => + if (other == CHAR) INT + else if (other.isNumericType) other + else uncomparable + + case SHORT => + other match { + case BYTE => SHORT + case CHAR => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case CHAR => + other match { + case BYTE | SHORT => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case INT => + other match { + case BYTE | SHORT | CHAR => INT + case LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case LONG => + if (other.isIntegralType) LONG + else if (other.isRealType) DOUBLE + else uncomparable + + case FLOAT => + if (other == DOUBLE) DOUBLE + else if (other.isNumericType) FLOAT + else uncomparable + + case DOUBLE => + if (other.isNumericType) DOUBLE + else uncomparable + + case UNIT | BOOL => uncomparable } } } - sealed trait PrimitiveBType extends BType - case object UNIT extends PrimitiveBType case object BOOL extends PrimitiveBType case object CHAR extends PrimitiveBType @@ -261,6 +372,14 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * - instance initializer: exectued when class is initialized (instance creation, static * field access, ...) * + * - A static nested class can be defined as + * - a static member class (explicitly static), or + * - a member class of an interface (implicitly static) + * - local classes are never static, even if they are defined in a static method. + * + * Note: it is NOT the case that all inner classes (non-static) have an outer pointer. Example: + * class C { static void foo { class D {} } } + * The class D is an inner class (non-static), but javac does not add an outer pointer to it. * * InnerClass * ---------- @@ -271,8 +390,8 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * The JLS 13.1, points 9. / 10. requires: a class must reference (in the CP) * - its immediately enclosing class * - all of its member classes - * - all local and anonymous classes that appear elsewhere (method, constructor, initializer - * block, field initializer) + * - all local and anonymous classes that are referenced (or declared) elsewhere (method, + * constructor, initializer block, field initializer) * * In a comment, the 4.7.6 spec says: this implies an entry in the InnerClass attribute for * - All enclosing classes (except the outermost, which is top-level) @@ -284,9 +403,11 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * Fields in the InnerClass entries: * - inner class: the (nested) class C we are talking about * - outer class: the class of which C is a member. Has to be null for non-members, i.e. for - * local and anonymous classes. + * local and anonymous classes. NOTE: this co-incides with the presence of an + * EnclosingMethod attribute (see below) * - inner name: A string with the simple name of the inner class. Null for anonymous classes. - * - flags: access property flags, details in JVMS, table in 4.7.6. + * - flags: access property flags, details in JVMS, table in 4.7.6. Static flag: see + * discussion below. * * * Note 1: when a nested class is present in the InnerClass attribute, all of its enclosing @@ -327,6 +448,13 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * JVMS 4.7.7: the attribute must be present "if and only if it represents a local class * or an anonymous class" (i.e. not for member classes). * + * The attribute is mis-named, it should be called "EnclosingClass". It has to be defined for all + * local and anonymous classes, no matter if there is an enclosing method or not. Accordingly, the + * "class" field (see below) must be always defined, while the "method" field may be null. + * + * NOTE: When a EnclosingMethod attribute is requried (local and anonymous classes), the "outer" + * field in the InnerClass table must be null. + * * Fields: * - class: the enclosing class * - method: the enclosing method (or constructor). Null if the class is not enclosed by a @@ -337,9 +465,8 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * Note: the field is required for anonymous classes defined within local variable * initializers (within a method), Java example below (**). * - * Currently, the Scala compiler sets "method" to the class constructor for classes - * defined in initializer blocks or field initializers. This is probably OK, since the - * Scala compiler desugars these statements into to the primary constructor. + * For local and anonymous classes in initializer blocks or field initializers, and + * class-level anonymous classes, the scala compiler sets the "method" field to null. * * * (*) @@ -401,30 +528,37 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { * STATIC flag * ----------- * - * Java: static nested classes have the "static" flag in the InnerClass attribute. This is not the - * case for local classes defined within a static method, even though such classes, as they are - * defined in a static context, don't take an "outer" instance. - * Non-static nested classes (inner classes, including local classes defined in a non-static - * method) take an "outer" instance on construction. + * Java: static member classes have the static flag in the InnerClass attribute, for example B in + * class A { static class B { } } + * + * The spec is not very clear about when the static flag should be emitted. It says: "Marked or + * implicitly static in source." + * + * The presence of the static flag does NOT coincide with the absence of an "outer" field in the + * class. The java compiler never puts the static flag for local classes, even if they don't have + * an outer pointer: + * + * class A { + * void f() { class B {} } + * static void g() { calss C {} } + * } + * + * B has an outer pointer, C doesn't. Both B and C are NOT marked static in the InnerClass table. + * + * It seems sane to follow the same principle in the Scala compiler. So: * - * Scala: Explicitouter adds an "outer" parameter to nested classes, except for classes defined - * in a static context, i.e. when all outer classes are module classes. * package p * object O1 { - * class C1 // static - * object O2 { + * class C1 // static inner class + * object O2 { // static inner module * def f = { - * class C2 { // static - * class C3 // non-static, needs outer + * class C2 { // non-static inner class, even though there's no outer pointer + * class C3 // non-static, has an outer pointer * } * } * } * } * - * Int the InnerClass attribute, the `static` flag is added for all classes defined in a static - * context, i.e. also for C2. This is different than in Java. - * - * * Mirror Classes * -------------- * @@ -432,64 +566,177 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { */ /** - * Class or Interface type. - * - * The information for creating a ClassBType (superClass, interfaces, etc) is obtained - * - either from a ClassSymbol, for classes being compiled or referenced from source (see - * BCodeTypes) - * - or, during inlining, from ASM ClassNodes that are parsed from class files. - * - * The class name is represented as a slice of the `chrs` array. 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. - * - * Not a case class because that would expose the constructor that takes (offset, length) - * parameters (I didn't find a way to make it private, also the factory in the companion). - * - * @param offset See below - * @param length The class name is represented as offset and length in the `chrs` array. - * The (public) constructors of ClassBType take a BTypeName, which are - * hash-consed. This ensures that two ClassBType instances for the same name - * have the same offset and length. - * - * Not a case class because that would expose the (Int, Int) constructor (didn't find a way to - * make it private, also the factory in the companion). + * 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. */ - class ClassBType private(val offset: Int, val length: Int) extends RefBType { + final class ClassBType(val offset: Int, val length: Int) extends RefBType { /** - * Construct a ClassBType from the (intenred) internal name of a class. + * 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 * - * @param internalName The internal name as a slice of the `chrs` array. The internal name does - * not have the surrounding 'L' and ';'. Note that - * `classSymbol.javaBinaryName` returns exactly such a name. + * B.info.nestedInfo.outerClass == A + * A.info.memberClasses contains B */ - def this(internalName: BTypeName) = this(internalName.start, internalName.length) + private var _info: ClassInfo = null - /** - * Construct a ClassBType from the internal name of a class. - * - * @param internalName The internal name of a class has the form "java/lang/String", without the - * surrounding 'L' and ';'. - */ - def this(internalName: String) = this({ - assert(!(internalName.head == 'L' && internalName.last == ';'), s"Descriptor instead of internal name: $internalName") - createNewName(internalName) - }) + def info: ClassInfo = { + assert(_info != null, s"ClassBType.info not yet assigned: $this") + _info + } + + def info_=(i: ClassInfo): Unit = { + assert(_info == null, s"Cannot set ClassBType.info multiple times: $this") + _info = i + checkInfoConsistency() + } + + classBTypeFromInternalNameMap(internalName) = this + + private def checkInfoConsistency(): Unit = { + // we assert some properties. however, some of the linked ClassBType (members, superClass, + // interfaces) may not yet have an `_info` (initialization of cyclic structures). so we do a + // 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" + + assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") + + assert( + if (info.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } + else if (isInterface) isJLO(info.superClass.get) + else !isJLO(this) && ifInit(info.superClass.get)(!_.isInterface), + s"Invalid superClass in $this: ${info.superClass}" + ) + assert( + info.interfaces.forall(c => ifInit(c)(_.isInterface)), + s"Invalid interfaces in $this: ${info.interfaces}" + ) + + assert(info.memberClasses.forall(c => ifInit(c)(_.isNestedClass)), info.memberClasses) + } /** * 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 = new String(chrs, offset, length) + def internalName: String = internalNameString(offset, length) /** * @return The class name without the package prefix */ def simpleName: String = internalName.split("/").last + def isInterface = (info.flags & asm.Opcodes.ACC_INTERFACE) != 0 + + def superClassesTransitive: List[ClassBType] = info.superClass match { + case None => Nil + case Some(sc) => sc :: sc.superClassesTransitive + } + + def isNestedClass = info.nestedInfo.isDefined + + def enclosingNestedClassesChain: List[ClassBType] = + if (isNestedClass) this :: info.nestedInfo.get.enclosingClass.enclosingNestedClassesChain + else Nil + + def innerClassAttributeEntry: Option[InnerClassEntry] = info.nestedInfo map { + case NestedInfo(_, outerName, innerName, isStaticNestedClass) => + InnerClassEntry( + internalName, + outerName.orNull, + innerName.orNull, + GenBCode.mkFlags( + info.flags, + if (isStaticNestedClass) asm.Opcodes.ACC_STATIC else 0 + ) & ClassBType.INNER_CLASSES_FLAGS + ) + } + + def isSubtypeOf(other: ClassBType): Boolean = { + if (this == other) return true + + if (isInterface) { + if (other == ObjectReference) return true // interfaces conform to Object + if (!other.isInterface) return false // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. + // else: this and other are both interfaces. continue to (*) + } else { + val sc = info.superClass + if (sc.isDefined && sc.get.isSubtypeOf(other)) return true // the superclass of this class conforms to other + if (!other.isInterface) return false // this and other are both classes, and the superclass of this does not conform + // else: this is a class, the other is an interface. continue to (*) + } + + // (*) check if some interface of this class conforms to other. + info.interfaces.exists(_.isSubtypeOf(other)) + } + /** - * Custom equals / hashCode are needed because this is not a case class. + * Finding the least upper bound in agreement with the bytecode verifier + * Background: + * http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf + * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 + * https://issues.scala-lang.org/browse/SI-3872 + */ + def jvmWiseLUB(other: ClassBType): ClassBType = { + def isNotNullOrNothing(c: ClassBType) = !c.isNullType && !c.isNothingType + assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLub for null or nothing: $this - $other") + + val res: ClassBType = (this.isInterface, other.isInterface) match { + case (true, true) => + // exercised by test/files/run/t4761.scala + if (other.isSubtypeOf(this)) this + else if (this.isSubtypeOf(other)) other + else ObjectReference + + case (true, false) => + if (other.isSubtypeOf(this)) this else ObjectReference + + case (false, true) => + if (this.isSubtypeOf(other)) other else ObjectReference + + case _ => + // TODO @lry I don't really understand the reasoning here. + // Both this and other are classes. The code takes (transitively) all superclasses and + // finds the first common one. + firstCommonSuffix(this :: this.superClassesTransitive, other :: other.superClassesTransitive) + } + + assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res") + res + } + + private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType]): ClassBType = { + var chainA = as + var chainB = bs + var fcs: ClassBType = null + do { + if (chainB contains chainA.head) fcs = chainA.head + else if (chainA contains chainB.head) fcs = chainB.head + else { + chainA = chainA.tail + chainB = chainB.tail + } + } 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 @@ -506,17 +753,92 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { } object ClassBType { - def apply(internalName: BTypeName): ClassBType = new ClassBType(internalName) - def apply(internalName: String): ClassBType = new ClassBType(internalName) - /** * 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 + */ + private val INNER_CLASSES_FLAGS = { + asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | + asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE | + asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION | + asm.Opcodes.ACC_ENUM + } + + // Primitive classes have no super class. A ClassBType for those is only created when + // they are actually being compiled (e.g., when compiling scala/Boolean.scala). + private val hasNoSuper = Set( + "scala/Unit", + "scala/Boolean", + "scala/Char", + "scala/Byte", + "scala/Short", + "scala/Int", + "scala/Float", + "scala/Long", + "scala/Double" + ) + + private val isInternalPhantomType = Set( + "scala/Null", + "scala/Nothing" + ) } + /** + * The type info for a class. Used for symboltable-independent subtype checks in the backend. + * + * @param superClass The super class, not defined for class java/lang/Object. + * @param interfaces All transitively implemented interfaces, except for those inherited + * 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 + * 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]) + + /** + * Information required to add a class to an InnerClass table. + * The spec summary above explains what information is required for the InnerClass entry. + * + * @param enclosingClass The enclosing class, if it is also nested. When adding a class + * to the InnerClass table, enclosing nested classes are also added. + * @param outerName The outerName field in the InnerClass entry, may be None. + * @param innerName The innerName field, may be None. + * @param isStaticNestedClass True if this is a static nested class (not inner class) (*) + * + * (*) 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). + * This is checked when building the NestedInfo. + */ + 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 + * above in this file. + * + * There's some overlap with the class NestedInfo, but it's not exactly the same and cleaner to + * keep separate. + * @param name The internal name of the class. + * @param outerName The internal name of the outer class, may be null. + * @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) + case class ArrayBType(componentType: BType) extends RefBType { def dimension: Int = componentType match { case a: ArrayBType => 1 + a.dimension @@ -529,107 +851,23 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { } } - case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType { - private def this(types: (List[BType], BType)) = this(types._1, types._2) - def this(descriptor: String) = this(MethodBType.decomposeMethodDescriptor(descriptor)) - } - - object MethodBType { - private def decomposeMethodDescriptor(descriptor: String): (List[BType], BType) = { - val chars = descriptor.toCharArray - assert(chars(0) == '(', s"Not a valid method descriptor: $descriptor") - var i = 1 - val argTypes = new ListBuffer[BType] - while (chars(i) != ')') { - val (argType, next) = BType.fromNonMethodDescriptor(chars, i) - argTypes += argType - i = next - } - val (resType, _) = BType.fromNonMethodDescriptor(chars, i + 1) // `i + 1` to skip the ')' - (argTypes.toList, resType) - } - def apply(descriptor: String) = { - val (argTypes, resType) = decomposeMethodDescriptor(descriptor) - new MethodBType(argTypes, resType) - } - } + case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType - val BOXED_UNIT = ClassBType("java/lang/Void") - val BOXED_BOOLEAN = ClassBType("java/lang/Boolean") - val BOXED_BYTE = ClassBType("java/lang/Byte") - val BOXED_SHORT = ClassBType("java/lang/Short") - val BOXED_CHAR = ClassBType("java/lang/Character") - val BOXED_INT = ClassBType("java/lang/Integer") - val BOXED_LONG = ClassBType("java/lang/Long") - val BOXED_FLOAT = ClassBType("java/lang/Float") - val BOXED_DOUBLE = ClassBType("java/lang/Double") - - /* - * RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in - * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. - * - * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal - * names of NothingClass and NullClass can't be emitted as-is. + /* 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 + * BTypes. + * + * They are defs (not vals) because they are implemented using vars (see comment on CoreBTypes). */ - val RT_NOTHING = ClassBType("scala/runtime/Nothing$") - val RT_NULL = ClassBType("scala/runtime/Null$") - val CT_NOTHING = ClassBType("scala/Nothing") - val CT_NULL = ClassBType("scala/Null") - - val srBooleanRef = ClassBType("scala/runtime/BooleanRef") - val srByteRef = ClassBType("scala/runtime/ByteRef") - val srCharRef = ClassBType("scala/runtime/CharRef") - val srIntRef = ClassBType("scala/runtime/IntRef") - val srLongRef = ClassBType("scala/runtime/LongRef") - val srFloatRef = ClassBType("scala/runtime/FloatRef") - val srDoubleRef = ClassBType("scala/runtime/DoubleRef") /** - * Map from type kinds to the Java reference types. - * Useful when pushing class literals onto the operand stack (ldc instruction taking a class - * literal). - * @see Predef.classOf - * @see genConstant() - * - * TODO @lry rename to "boxedClassOfPrimitive" or so, check usages + * Just a named pair, used in CoreBTypes.asmBoxTo/asmUnboxTo. */ - val classLiteral = immutable.Map[BType, ClassBType]( - UNIT -> BOXED_UNIT, - BOOL -> BOXED_BOOLEAN, - BYTE -> BOXED_BYTE, - SHORT -> BOXED_SHORT, - CHAR -> BOXED_CHAR, - INT -> BOXED_INT, - LONG -> BOXED_LONG, - FLOAT -> BOXED_FLOAT, - DOUBLE -> BOXED_DOUBLE - ) - - case class MethodNameAndType(name: String, descriptor: String) - - val asmBoxTo: immutable.Map[BType, MethodNameAndType] = { - Map( - BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) , - BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) , - CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") , - SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) , - INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) , - LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) , - FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) , - DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" ) - ) - } + final case class MethodNameAndType(name: String, methodType: MethodBType) - val asmUnboxTo: immutable.Map[BType, MethodNameAndType] = { - Map( - BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") , - BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") , - CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") , - SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") , - INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") , - LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") , - FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") , - DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D") - ) - } + /** + * True if the current compilation unit is of a primitive class (scala.Boolean et al). + * Used only in assertions. Abstract here because its implementation depends on global. + */ + def isCompilingPrimitive: Boolean } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala new file mode 100644 index 0000000000..0e2f938602 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -0,0 +1,396 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2014 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package backend.jvm + +import scala.tools.asm + +/** + * 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 + * access to the compiler (global parameter). + * + * The mixin CoreBTypes defines core BTypes that are used in the backend. Building these BTypes + * uses classBTypeFromSymbol, hence requires access to the compiler (global). + * + * BTypesFromSymbols extends BTypes because the implementation of BTypes requires access to some + * of the core btypes. They are declared in BTypes as abstract members. Note that BTypes does + * not have access to the compiler instance. + */ +class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { + import global._ + import definitions._ + + val bCodeICodeCommon: BCodeICodeCommon[global.type] = new BCodeICodeCommon(global) + val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global) + import bCodeAsmCommon._ + + // Why the proxy, see documentation of class [[CoreBTypes]]. + val coreBTypes = new CoreBTypesProxy[this.type](this) + import coreBTypes._ + + final def intializeCoreBTypes(): Unit = { + coreBTypes.setBTypes(new CoreBTypes[this.type](this)) + } + + def internalNameString(offset: Int, length: Int) = new String(global.chrs, offset, length) + + protected val classBTypeFromInternalNameMap = { + global.perRunCaches.recordCache(collection.concurrent.TrieMap.empty[String, ClassBType]) + } + + /** + * Cache for the method classBTypeFromSymbol. + */ + private val convertedClasses = perRunCaches.newMap[Symbol, ClassBType]() + + // helpers that need access to global. + // TODO @lry create a separate component, they don't belong to BTypesFromSymbols + + final val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString + + private val primitiveCompilationUnits = Set( + "Unit.scala", + "Boolean.scala", + "Char.scala", + "Byte.scala", + "Short.scala", + "Int.scala", + "Float.scala", + "Long.scala", + "Double.scala" + ) + + /** + * True if the current compilation unit is of a primitive class (scala.Boolean et al). + * Used only in assertions. + */ + def isCompilingPrimitive = { + primitiveCompilationUnits(currentUnit.source.file.name) + } + + def isCompilingArray = { + currentUnit.source.file.name == "Array.scala" + } + + // end helpers + + /** + * The ClassBType for a class symbol `sym`. + */ + final def classBTypeFromSymbol(classSym: Symbol): ClassBType = { + assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol") + assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym") + assert( + (!primitiveTypeMap.contains(classSym) || isCompilingPrimitive) && + (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) + }) + } + + private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { + val superClassSym = if (classSym.isImplClass) ObjectClass else classSym.superClass + assert( + if (classSym == ObjectClass) + superClassSym == NoSymbol + else if (classSym.isInterface) + superClassSym == ObjectClass + else + // A ClassBType for a primitive class (scala.Boolean et al) is only created when compiling these classes. + ((superClassSym != NoSymbol) && !superClassSym.isInterface) || (isCompilingPrimitive && primitiveTypeMap.contains(classSym)), + s"Bad superClass for $classSym: $superClassSym" + ) + val superClass = if (superClassSym == NoSymbol) None + else Some(classBTypeFromSymbol(superClassSym)) + + val interfaces = getSuperInterfaces(classSym).map(classBTypeFromSymbol) + + val flags = javaFlags(classSym) + + /* The InnerClass table of a class C must contain all nested classes of C, even if they are only + * declared but not otherwise referenced in C (from the bytecode or a method / field signature). + * We collect them here. + * + * Nested classes that are also referenced in C will be added to the innerClassBufferASM during + * 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 + * 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)) + + // 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: + // 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. + // 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 + val companionModuleMembers = { + // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, + // not local classes of the companion module (E in the exmaple) that were lifted by lambdalift. + if (isTopLevelModuleClass(linkedClass)) exitingPickler(memberClassesOf(linkedClass)) + else Nil + } + + nestedClasses ++ companionModuleMembers + } + + /** + * For nested java classes, the scala compiler creates both a class and a module (and therefore + * a module class) symbol. For example, in `class A { class B {} }`, the nestedClassSymbols + * for A contain both the class B and the module class B. + * Here we get rid of the module class B, making sure that the class B is present. + */ + val nestedClassSymbolsNoJavaModuleClasses = nestedClassSymbols.filter(s => { + if (s.isJavaDefined && s.isModuleClass) { + // We could also search in nestedClassSymbols for s.linkedClassOfClass, but sometimes that + // returns NoSymbol, so it doesn't work. + val nb = nestedClassSymbols.count(mc => mc.name == s.name && mc.owner == s.owner) + assert(nb == 2, s"Java member module without member class: $s - $nestedClassSymbols") + false + } else true + }) + + val memberClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) + + val nestedInfo = buildNestedInfo(classSym) + + classBType.info = ClassInfo(superClass, interfaces, flags, memberClasses, 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 + } + + /** + * Drop redundant interfaces (which are implemented by some other parent) from the immediate + * parents. In other words, no two interfaces in the result are related by subtyping. + */ + def dropRedundantInterfaces(lstIfaces: List[Symbol]): List[Symbol] = { + var rest = lstIfaces + var leaves = List.empty[Symbol] + while (!rest.isEmpty) { + val candidate = rest.head + val nonLeaf = leaves exists { lsym => lsym isSubClass candidate } + if (!nonLeaf) { + leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym }) + } + rest = rest.tail + } + + leaves + } + + 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(", ")}") + + dropRedundantInterfaces(superInterfaces) + } + + 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 + 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) + + // phase travel for linkedCoC - does not always work in late phases + exitingPickler(innerClassSym.rawowner.linkedClassOfClass) + } + 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) + } + } + + val innerName: Option[String] = { + if (innerClassSym.isAnonymousClass || innerClassSym.isAnonymousFunction) None + else Some(innerClassSym.rawname + innerClassSym.moduleSuffix) // moduleSuffix for module classes + } + + Some(NestedInfo(enclosingClass, outerName, innerName, isStaticNestedClass)) + } + } + + /** + * True for module classes of package level objects. The backend will generate a mirror class for + * such objects. + */ + final def isTopLevelModuleClass(sym: Symbol): Boolean = exitingPickler { + // phase travel to pickler required for isNestedClass (looks at owner) + val r = sym.isModuleClass && !sym.isNestedClass + // The mixin phase adds the `lateMODULE` flag to trait implementation classes. Since the flag + // is late, it should not be visible here inside the time travel. We check this. + if (r) assert(!sym.isImplClass, s"isModuleClass should be false for impl class $sym") + r + } + + /** + * True for module classes of modules that are top-level or owned only by objects. Module classes + * for such objects will get a MODULE$ flag and a corresponding static initializer. + */ + final def isStaticModuleClass(sym: Symbol): Boolean = { + /* (1) Phase travel to to pickler is required to exclude implementation classes; they have the + * lateMODULEs after mixin, so isModuleClass would be true. + * (2) isStaticModuleClass is a source-level property. See comment on isOriginallyStaticOwner. + */ + exitingPickler { // (1) + sym.isModuleClass && + isOriginallyStaticOwner(sym.originalOwner) // (2) + } + } + + // legacy, to be removed when the @remote annotation gets removed + final def isRemote(s: Symbol) = (s hasAnnotation definitions.RemoteAttr) + final def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0) + + /** + * Return the Java modifiers for the given symbol. + * Java modifiers for classes: + * - public, abstract, final, strictfp (not used) + * for interfaces: + * - the same as for classes, without 'final' + * for fields: + * - public, private (*) + * - static, final + * for methods: + * - the same as for fields, plus: + * - abstract, synchronized (not used), strictfp (not used), native (not used) + * for all: + * - deprecated + * + * (*) protected cannot be used, since inner classes 'see' protected members, + * and they would fail verification after lifted. + */ + final def javaFlags(sym: Symbol): Int = { + // constructors of module classes should be private. introduced in b06edbc, probably to prevent + // creating module instances from java. for nested modules, the constructor needs to be public + // since they are created by the outer class and stored in a field. a java client can create + // new instances via outerClassInstance.new InnerModuleClass$(). + // TODO: do this early, mark the symbol private. + val privateFlag = + sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModuleClass(sym.owner)) + + // Symbols marked in source as `final` have the FINAL flag. (In the past, the flag was also + // added to modules and module classes, not anymore since 296b706). + // Note that the presence of the `FINAL` flag on a symbol does not correspond 1:1 to emitting + // ACC_FINAL in bytecode. + // + // Top-level modules are marked ACC_FINAL in bytecode (even without the FINAL flag). Nested + // objects don't get the flag to allow overriding (under -Yoverride-objects, SI-5676). + // + // For fields, only eager val fields can receive ACC_FINAL. vars or lazy vals can't: + // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 + // "Another problem is that the specification allows aggressive + // optimization of final fields. Within a thread, it is permissible to + // reorder reads of a final field with those modifications of a final + // field that do not take place in the constructor." + // + // A var or lazy val which is marked final still has meaning to the + // scala compiler. The word final is heavily overloaded unfortunately; + // for us it means "not overridable". At present you can't override + // vars regardless; this may change. + // + // The logic does not check .isFinal (which checks flags for the FINAL flag, + // and includes symbols marked lateFINAL) instead inspecting rawflags so + // we can exclude lateFINAL. Such symbols are eligible for inlining, but to + // avoid breaking proxy software which depends on subclassing, we do not + // emit ACC_FINAL. + + val finalFlag = ( + (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModuleClass(sym)) + && !sym.enclClass.isInterface + && !sym.isClassConstructor + && !sym.isMutable // lazy vals and vars both + ) + + // Primitives are "abstract final" to prohibit instantiation + // without having to provide any implementations, but that is an + // illegal combination of modifiers at the bytecode level so + // suppress final if abstract if present. + import asm.Opcodes._ + GenBCode.mkFlags( + if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, + if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (sym.isInterface) ACC_INTERFACE else 0, + if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, + if (sym.isStaticMember) ACC_STATIC else 0, + if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, + if (sym.isArtifact) ACC_SYNTHETIC else 0, + if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, + if (sym.hasEnumFlag) ACC_ENUM else 0, + if (sym.isVarargsMethod) ACC_VARARGS else 0, + if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0, + if (sym.isDeprecated) asm.Opcodes.ACC_DEPRECATED else 0 + ) + } + + def javaFieldFlags(sym: Symbol) = { + javaFlags(sym) | GenBCode.mkFlags( + if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0, + if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0, + if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL + ) + } +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala new file mode 100644 index 0000000000..fac3c93be2 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -0,0 +1,293 @@ +package scala.tools.nsc +package backend.jvm + +import scala.annotation.switch + +/** + * Core BTypes and some other definitions. The initialization of these definitions requies access + * to symbols / types (global). + * + * The symbols used to initialize the ClassBTypes may change from one compiler run to the next. To + * make sure the definitions are consistent with the symbols in the current run, the + * `intializeCoreBTypes` method in BTypesFromSymbols creates a new instance of CoreBTypes in each + * compiler run. + * + * The class BTypesFromSymbols does not directly reference CoreBTypes, but CoreBTypesProxy. The + * reason is that having a `var bTypes: CoreBTypes` would not allow `import bTypes._`. Instead, the + * proxy class holds a `CoreBTypes` in a variable field and forwards to this instance. + * + * 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 + * 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 + * added when the ClassBTypes are created. The per run cache removes them, so they would be missing + * in the second run. + */ +class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { + import bTypes._ + import global._ + import rootMirror.{requiredClass, getClassIfDefined} + import definitions._ + + /** + * Maps primitive types to their corresponding PrimitiveBType. The map is defined lexically above + * the first use of `classBTypeFromSymbol` because that method looks at the map. + */ + lazy val primitiveTypeMap: Map[Symbol, PrimitiveBType] = Map( + UnitClass -> UNIT, + BooleanClass -> BOOL, + CharClass -> CHAR, + ByteClass -> BYTE, + ShortClass -> SHORT, + IntClass -> INT, + LongClass -> LONG, + FloatClass -> FLOAT, + DoubleClass -> DOUBLE + ) + + lazy val BOXED_UNIT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Void]) + lazy val BOXED_BOOLEAN : ClassBType = classBTypeFromSymbol(BoxedBooleanClass) + lazy val BOXED_BYTE : ClassBType = classBTypeFromSymbol(BoxedByteClass) + lazy val BOXED_SHORT : ClassBType = classBTypeFromSymbol(BoxedShortClass) + lazy val BOXED_CHAR : ClassBType = classBTypeFromSymbol(BoxedCharacterClass) + lazy val BOXED_INT : ClassBType = classBTypeFromSymbol(BoxedIntClass) + lazy val BOXED_LONG : ClassBType = classBTypeFromSymbol(BoxedLongClass) + lazy val BOXED_FLOAT : ClassBType = classBTypeFromSymbol(BoxedFloatClass) + lazy val BOXED_DOUBLE : ClassBType = classBTypeFromSymbol(BoxedDoubleClass) + + /** + * Map from primitive types to their boxed class type. Useful when pushing class literals onto the + * operand stack (ldc instruction taking a class literal), see genConstant. + */ + lazy val boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = Map( + UNIT -> BOXED_UNIT, + BOOL -> BOXED_BOOLEAN, + BYTE -> BOXED_BYTE, + SHORT -> BOXED_SHORT, + CHAR -> BOXED_CHAR, + INT -> BOXED_INT, + LONG -> BOXED_LONG, + FLOAT -> BOXED_FLOAT, + DOUBLE -> BOXED_DOUBLE + ) + + lazy val boxedClasses: Set[ClassBType] = boxedClassOfPrimitive.values.toSet + + /** + * Maps the method symbol for a box method to the boxed type of the result. For example, the + * method symbol for `Byte.box()` is mapped to the ClassBType `java/lang/Byte`. + */ + lazy val boxResultType: Map[Symbol, ClassBType] = { + for ((valueClassSym, boxMethodSym) <- currentRun.runDefinitions.boxMethod) + yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeMap(valueClassSym)) + } + + /** + * Maps the method symbol for an unbox method to the primitive type of the result. + * For example, the method symbol for `Byte.unbox()`) is mapped to the PrimitiveBType BYTE. */ + lazy val unboxResultType: Map[Symbol, PrimitiveBType] = { + for ((valueClassSym, unboxMethodSym) <- currentRun.runDefinitions.unboxMethod) + yield unboxMethodSym -> primitiveTypeMap(valueClassSym) + } + + /* + * RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in + * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. + * + * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal + * names of NothingClass and NullClass can't be emitted as-is. + * TODO @lry Once there's a 2.11.3 starr, use the commented argument list. The current starr crashes on the type literal `scala.runtime.Nothing$` + */ + lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Nothing$")) // (requiredClass[scala.runtime.Nothing$]) + lazy val RT_NULL : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Null$")) // (requiredClass[scala.runtime.Null$]) + + lazy val ObjectReference : ClassBType = classBTypeFromSymbol(ObjectClass) + lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference) + + lazy val StringReference : ClassBType = classBTypeFromSymbol(StringClass) + lazy val StringBuilderReference : ClassBType = classBTypeFromSymbol(StringBuilderClass) + lazy val ThrowableReference : ClassBType = classBTypeFromSymbol(ThrowableClass) + lazy val jlCloneableReference : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable + lazy val jlNPEReference : ClassBType = classBTypeFromSymbol(NullPointerExceptionClass) // java/lang/NullPointerException + lazy val jioSerializableReference : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable + lazy val scalaSerializableReference : ClassBType = classBTypeFromSymbol(SerializableClass) // scala/Serializable + lazy val classCastExceptionReference : ClassBType = classBTypeFromSymbol(ClassCastExceptionClass) // java/lang/ClassCastException + + lazy val srBooleanRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BooleanRef]) + lazy val srByteRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.ByteRef]) + lazy val srCharRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.CharRef]) + lazy val srIntRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.IntRef]) + lazy val srLongRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LongRef]) + lazy val srFloatRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.FloatRef]) + lazy val srDoubleRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.DoubleRef]) + + lazy val hashMethodSym: Symbol = getMember(ScalaRunTimeModule, nme.hash_) + + // TODO @lry avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540 + lazy val AndroidParcelableInterface : Symbol = getClassIfDefined("android.os.Parcelable") + lazy val AndroidCreatorClass : Symbol = getClassIfDefined("android.os.Parcelable$Creator") + + lazy val BeanInfoAttr: Symbol = requiredClass[scala.beans.BeanInfo] + + /* The Object => String overload. */ + lazy val String_valueOf: Symbol = { + getMember(StringModule, nme.valueOf) filter (sym => sym.info.paramTypes match { + case List(pt) => pt.typeSymbol == ObjectClass + case _ => false + }) + } + + // scala.FunctionX and scala.runtim.AbstractFunctionX + lazy val FunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(FunctionClass(i)))(collection.breakOut) + lazy val AbstractFunctionReference : Vector[ClassBType] = (0 to MaxFunctionArity).map(i => classBTypeFromSymbol(AbstractFunctionClass(i)))(collection.breakOut) + lazy val AbstractFunctionArityMap : Map[ClassBType, Int] = AbstractFunctionReference.zipWithIndex.toMap + + lazy val PartialFunctionReference : ClassBType = classBTypeFromSymbol(PartialFunctionClass) + lazy val AbstractPartialFunctionReference : ClassBType = classBTypeFromSymbol(AbstractPartialFunctionClass) + + lazy val BoxesRunTime: ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime]) + + /** + * Methods in scala.runtime.BoxesRuntime + */ + lazy val asmBoxTo : Map[BType, MethodNameAndType] = Map( + BOOL -> MethodNameAndType("boxToBoolean", MethodBType(List(BOOL), BOXED_BOOLEAN)), + BYTE -> MethodNameAndType("boxToByte", MethodBType(List(BYTE), BOXED_BYTE)), + CHAR -> MethodNameAndType("boxToCharacter", MethodBType(List(CHAR), BOXED_CHAR)), + SHORT -> MethodNameAndType("boxToShort", MethodBType(List(SHORT), BOXED_SHORT)), + INT -> MethodNameAndType("boxToInteger", MethodBType(List(INT), BOXED_INT)), + LONG -> MethodNameAndType("boxToLong", MethodBType(List(LONG), BOXED_LONG)), + FLOAT -> MethodNameAndType("boxToFloat", MethodBType(List(FLOAT), BOXED_FLOAT)), + DOUBLE -> MethodNameAndType("boxToDouble", MethodBType(List(DOUBLE), BOXED_DOUBLE)) + ) + + lazy val asmUnboxTo: Map[BType, MethodNameAndType] = Map( + BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectReference), BOOL)), + BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectReference), BYTE)), + CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectReference), CHAR)), + SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectReference), SHORT)), + INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectReference), INT)), + LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectReference), LONG)), + FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectReference), FLOAT)), + DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectReference), DOUBLE)) + ) + + lazy val typeOfArrayOp: Map[Int, BType] = { + import scalaPrimitives._ + Map( + (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++ + (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++ + (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++ + (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++ + (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT)) ++ + (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ + (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ + (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ + (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _* + ) + } +} + +/** + * This trait make some core BTypes availalbe 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 + * the type Symbol in + * def primitiveTypeMap: Map[Symbol, PrimitiveBType] + */ +trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] { + val bTypes: BTS + import bTypes._ + + def boxedClasses: Set[ClassBType] + + def RT_NOTHING : ClassBType + def RT_NULL : ClassBType + + def ObjectReference : ClassBType + def jlCloneableReference : ClassBType + def jioSerializableReference : ClassBType +} + +/** + * See comment in class [[CoreBTypes]]. + */ +final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) extends CoreBTypesProxyGlobalIndependent[BTFS] { + import bTypes._ + import global._ + + private[this] var _coreBTypes: CoreBTypes[bTypes.type] = _ + def setBTypes(coreBTypes: CoreBTypes[BTFS]): Unit = { + _coreBTypes = coreBTypes.asInstanceOf[CoreBTypes[bTypes.type]] + } + + def primitiveTypeMap: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeMap + + def BOXED_UNIT : ClassBType = _coreBTypes.BOXED_UNIT + def BOXED_BOOLEAN : ClassBType = _coreBTypes.BOXED_BOOLEAN + def BOXED_BYTE : ClassBType = _coreBTypes.BOXED_BYTE + def BOXED_SHORT : ClassBType = _coreBTypes.BOXED_SHORT + def BOXED_CHAR : ClassBType = _coreBTypes.BOXED_CHAR + def BOXED_INT : ClassBType = _coreBTypes.BOXED_INT + def BOXED_LONG : ClassBType = _coreBTypes.BOXED_LONG + def BOXED_FLOAT : ClassBType = _coreBTypes.BOXED_FLOAT + def BOXED_DOUBLE : ClassBType = _coreBTypes.BOXED_DOUBLE + + def boxedClasses: Set[ClassBType] = _coreBTypes.boxedClasses + + def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = _coreBTypes.boxedClassOfPrimitive + + def boxResultType: Map[Symbol, ClassBType] = _coreBTypes.boxResultType + + def unboxResultType: Map[Symbol, PrimitiveBType] = _coreBTypes.unboxResultType + + def RT_NOTHING : ClassBType = _coreBTypes.RT_NOTHING + def RT_NULL : ClassBType = _coreBTypes.RT_NULL + + def ObjectReference : ClassBType = _coreBTypes.ObjectReference + def objArrayReference : ArrayBType = _coreBTypes.objArrayReference + + def StringReference : ClassBType = _coreBTypes.StringReference + def StringBuilderReference : ClassBType = _coreBTypes.StringBuilderReference + def ThrowableReference : ClassBType = _coreBTypes.ThrowableReference + def jlCloneableReference : ClassBType = _coreBTypes.jlCloneableReference + def jlNPEReference : ClassBType = _coreBTypes.jlNPEReference + def jioSerializableReference : ClassBType = _coreBTypes.jioSerializableReference + def scalaSerializableReference : ClassBType = _coreBTypes.scalaSerializableReference + def classCastExceptionReference : ClassBType = _coreBTypes.classCastExceptionReference + + def srBooleanRef : ClassBType = _coreBTypes.srBooleanRef + def srByteRef : ClassBType = _coreBTypes.srByteRef + def srCharRef : ClassBType = _coreBTypes.srCharRef + def srIntRef : ClassBType = _coreBTypes.srIntRef + def srLongRef : ClassBType = _coreBTypes.srLongRef + def srFloatRef : ClassBType = _coreBTypes.srFloatRef + def srDoubleRef : ClassBType = _coreBTypes.srDoubleRef + + def hashMethodSym: Symbol = _coreBTypes.hashMethodSym + + def AndroidParcelableInterface : Symbol = _coreBTypes.AndroidParcelableInterface + def AndroidCreatorClass : Symbol = _coreBTypes.AndroidCreatorClass + + def BeanInfoAttr: Symbol = _coreBTypes.BeanInfoAttr + + def String_valueOf: Symbol = _coreBTypes.String_valueOf + + def FunctionReference : Vector[ClassBType] = _coreBTypes.FunctionReference + def AbstractFunctionReference : Vector[ClassBType] = _coreBTypes.AbstractFunctionReference + def AbstractFunctionArityMap : Map[ClassBType, Int] = _coreBTypes.AbstractFunctionArityMap + + def PartialFunctionReference : ClassBType = _coreBTypes.PartialFunctionReference + def AbstractPartialFunctionReference : ClassBType = _coreBTypes.AbstractPartialFunctionReference + + def BoxesRunTime: ClassBType = _coreBTypes.BoxesRunTime + + def asmBoxTo : Map[BType, MethodNameAndType] = _coreBTypes.asmBoxTo + def asmUnboxTo: Map[BType, MethodNameAndType] = _coreBTypes.asmUnboxTo + + def typeOfArrayOp: Map[Int, BType] = _coreBTypes.typeOfArrayOp +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 2392033760..2593903b9d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -26,6 +26,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { import icodes.opcodes._ import definitions._ + val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global) + import bCodeAsmCommon._ + // Strangely I can't find this in the asm code // 255, but reserving 1 for "this" final val MaximumJvmParameters = 254 @@ -621,7 +624,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. */ def outerName(innerSym: Symbol): String = { - if (innerSym.originalEnclosingMethod != NoSymbol) + if (isAnonymousOrLocalClass(innerSym)) null else { val outerName = javaName(innerSym.rawowner) @@ -636,13 +639,16 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { else innerSym.rawname + innerSym.moduleSuffix - // add inner classes which might not have been referenced yet - // TODO @lry according to the spec, all nested classes should be added, also local and - // anonymous. This seems to add only member classes - or not? it's exitingErasure, so maybe - // local / anonymous classes have been lifted by lambdalift. are they in the "decls" though? - exitingErasure { - for (sym <- List(csym, csym.linkedClassOfClass); m <- sym.info.decls.map(innerClassSymbolFor) if m.isClass) - innerClassBuffer += m + // 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)) + + // Add members of the companion object (if top-level). why, see comment in BTypes.scala. + 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, + // not local classes that were lifted by lambdalift. + innerClassBuffer ++= exitingPickler(memberClassesOf(linkedClass)) } val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures @@ -656,7 +662,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { // sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ?? val flagsWithFinal: Int = mkFlags( - if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0, + // See comment in BTypes, when is a class marked static in the InnerClass table. + if (isOriginallyStaticOwner(innerSym.originalOwner)) asm.Opcodes.ACC_STATIC else 0, javaFlags(innerSym), if(isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) @@ -1222,10 +1229,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { null /* SourceDebugExtension */) } - val enclM = getEnclosingMethodAttribute() - if(enclM != null) { - val EnclMethodEntry(className, methodName, methodType) = enclM - jclass.visitOuterClass(className, methodName, methodType.getDescriptor) + enclosingMethodAttribute(clasz.symbol, javaName, javaType(_).getDescriptor) match { + case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) => + jclass.visitOuterClass(className, methodName, methodDescriptor) + case _ => () } // typestate: entering mode with valid call sequences: @@ -1286,45 +1293,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol) } - /** - * @param owner internal name of the enclosing class of the class. - * - * @param name the name of the method that contains the class. - - * @param methodType the method that contains the class. - */ - case class EnclMethodEntry(owner: String, name: String, methodType: asm.Type) - - /** - * @return null if the current class is not internal to a method - * - * Quoting from JVMS 4.7.7 The EnclosingMethod Attribute - * A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class. - * A class may have no more than one EnclosingMethod attribute. - * - */ - private def getEnclosingMethodAttribute(): EnclMethodEntry = { // JVMS 4.7.7 - var res: EnclMethodEntry = null - val clazz = clasz.symbol - val sym = clazz.originalEnclosingMethod - if (sym.isMethod) { - debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, sym.enclClass)) - res = EnclMethodEntry(javaName(sym.enclClass), javaName(sym), javaType(sym)) - } else if (clazz.isAnonymousClass) { - val enclClass = clazz.rawowner - assert(enclClass.isClass, enclClass) - val sym = enclClass.primaryConstructor - if (sym == NoSymbol) { - log("Ran out of room looking for an enclosing method for %s: no constructor here.".format(enclClass)) - } else { - debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, enclClass)) - res = EnclMethodEntry(javaName(enclClass), javaName(sym), javaType(sym)) - } - } - - res - } - def genField(f: IField) { debuglog("Adding field: " + f.symbol.fullName) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index 89866b7ce9..0a7c894a69 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -46,6 +46,9 @@ import scala.tools.asm abstract class GenBCode extends BCodeSyncAndTry { import global._ + import bTypes._ + import coreBTypes._ + val phaseName = "jvm" override def newPhase(prev: Phase) = new BCodePhase(prev) @@ -130,7 +133,7 @@ abstract class GenBCode extends BCodeSyncAndTry { return } else { - try { visit(item) } + try { withCurrentUnit(item.cunit)(visit(item)) } catch { case ex: Throwable => ex.printStackTrace() @@ -272,7 +275,7 @@ abstract class GenBCode extends BCodeSyncAndTry { arrivalPos = 0 // just in case scalaPrimitives.init() - initBCodeTypes() + bTypes.intializeCoreBTypes() // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. bytecodeWriter = initBytecodeWriter(cleanup.getEntryPoints) @@ -297,9 +300,6 @@ abstract class GenBCode extends BCodeSyncAndTry { * (2) if requested, check-java-signatures, over and beyond the syntactic checks in `getGenericSignature()` * */ - - // clearing maps - clearBCodeTypes() } /* @@ -385,3 +385,10 @@ abstract class GenBCode extends BCodeSyncAndTry { } // end of class BCodePhase } // end of class GenBCode + +object GenBCode { + def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) + + final val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC + final val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL +} diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index bec068b56a..4c37633301 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -38,6 +38,7 @@ trait Warnings { warnMissingInterpolator, warnDocDetached, warnPrivateShadow, + warnTypeParameterShadow, warnPolyImplicitOverload, warnOptionImplicit, warnDelayedInit, @@ -83,6 +84,8 @@ trait Warnings { "A ScalaDoc comment appears to be detached from its element.") val warnPrivateShadow = lintflag("private-shadow", "A private field (or class parameter) shadows a superclass field.") + val warnTypeParameterShadow = lintflag("type-parameter-shadow", + "A local type parameter shadows a type already in scope.") val warnPolyImplicitOverload = lintflag("poly-implicit-overload", "Parameterized overloaded implicit methods are not visible as view bounds") val warnOptionImplicit = lintflag("option-implicit", diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 1f8ab7e887..9cc8d779a0 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -37,6 +37,15 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre /** the following two members override abstract members in Transform */ val phaseName: String = "delambdafy" + override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { + if (settings.Ydelambdafy.value == "method") new Phase(prev) + else new SkipPhase(prev) + } + + class SkipPhase(prev: scala.tools.nsc.Phase) extends StdPhase(prev) { + def apply(unit: global.CompilationUnit): Unit = () + } + protected def newTransformer(unit: CompilationUnit): Transformer = new DelambdafyTransformer(unit) @@ -252,39 +261,39 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre captureProxies2 += ((capture, sym)) } - // the Optional proxy that will hold a reference to the 'this' - // object used by the lambda, if any. NoSymbol if there is no this proxy - val thisProxy = { - val target = targetMethod(originalFunction) - if (thisReferringMethods contains target) { - val sym = anonClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC) - sym.info = oldClass.tpe - sym - } else NoSymbol - } + // the Optional proxy that will hold a reference to the 'this' + // object used by the lambda, if any. NoSymbol if there is no this proxy + val thisProxy = { + val target = targetMethod(originalFunction) + if (thisReferringMethods contains target) { + val sym = anonClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC) + sym.info = oldClass.tpe + sym + } else NoSymbol + } - val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, anonClass, originalFunction.symbol.pos, thisProxy) + val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, anonClass, originalFunction.symbol.pos, thisProxy) - val accessorMethod = createAccessorMethod(thisProxy, originalFunction) + val accessorMethod = createAccessorMethod(thisProxy, originalFunction) - val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function] + val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function] - val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member => - anonClass.info.decls enter member - ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos - } + val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member => + anonClass.info.decls enter member + ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos + } - // constructor - val constr = createConstructor(anonClass, members) + // constructor + val constr = createConstructor(anonClass, members) - // apply method with same arguments and return type as original lambda. - val applyMethodDef = createApplyMethod(anonClass, decapturedFunction, accessorMethod, thisProxy) + // apply method with same arguments and return type as original lambda. + val applyMethodDef = createApplyMethod(anonClass, decapturedFunction, accessorMethod, thisProxy) - val bridgeMethod = createBridgeMethod(anonClass, originalFunction, applyMethodDef) + val bridgeMethod = createBridgeMethod(anonClass, originalFunction, applyMethodDef) - def fulldef(sym: Symbol) = - if (sym == NoSymbol) sym.toString - else s"$sym: ${sym.tpe} in ${sym.owner}" + def fulldef(sym: Symbol) = + if (sym == NoSymbol) sym.toString + else s"$sym: ${sym.tpe} in ${sym.owner}" bridgeMethod foreach (bm => // TODO SI-6260 maybe just create the apply method with the signature (Object => Object) in all cases diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 54bcc9e93e..3d8b2f02f3 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -468,8 +468,12 @@ abstract class Erasure extends AddInterfaces if (!bridgeNeeded) return - val newFlags = (member.flags | BRIDGE | ARTIFACT) & ~(ACCESSOR | DEFERRED | LAZY | lateDEFERRED) - val bridge = other.cloneSymbolImpl(root, newFlags) setPos root.pos + var newFlags = (member.flags | BRIDGE | ARTIFACT) & ~(ACCESSOR | DEFERRED | LAZY | lateDEFERRED) + // If `member` is a ModuleSymbol, the bridge should not also be a ModuleSymbol. Otherwise we + // end up with two module symbols with the same name in the same scope, which is surprising + // when implementing later phases. + if (member.isModule) newFlags = (newFlags | METHOD) & ~(MODULE | lateMETHOD | STABLE) + val bridge = other.cloneSymbolImpl(root, newFlags) setPos root.pos debuglog("generating bridge from %s (%s): %s to %s: %s".format( other, flagsToString(newFlags), diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index f85d8222f0..d69c9d9a65 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -449,6 +449,8 @@ abstract class LambdaLift extends InfoTransform { if (sym.isClass) sym.owner = sym.owner.toInterface if (sym.isMethod) sym setFlag LIFTED liftedDefs(sym.owner) ::= tree + // TODO: this modifies the ClassInfotype of the enclosing class, which is associated with another phase (explicitouter). + // This breaks type history: in a phase travel to before lambda lift, the ClassInfoType will contain lifted classes. sym.owner.info.decls enterUnique sym debuglog("lifted: " + sym + " from " + oldOwner + " to " + sym.owner) EmptyTree diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 47465875e9..1cb775959d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1633,7 +1633,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans if (settings.warnNullaryUnit) checkNullaryMethodReturnType(sym) if (settings.warnInaccessible) { - if (!sym.isConstructor && !sym.isEffectivelyFinal && !sym.isSynthetic) + if (!sym.isConstructor && !sym.isEffectivelyFinalOrNotOverridden && !sym.isSynthetic) checkAccessibilityOfReferencedTypes(tree) } tree match { diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 89b064e7c1..8d29d28908 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -600,6 +600,23 @@ trait TypeDiagnostics { ) } + // warn about class/method/type-members' type parameters that shadow types already in scope + def warnTypeParameterShadow(tparams: List[TypeDef], sym: Symbol): Unit = + if (settings.warnTypeParameterShadow && !isPastTyper && !sym.isSynthetic) { + def enclClassOrMethodOrTypeMember(c: Context): Context = + if (!c.owner.exists || c.owner.isClass || c.owner.isMethod || (c.owner.isType && !c.owner.isParameter)) c + else enclClassOrMethodOrTypeMember(c.outer) + + val tt = tparams.filter(_.name != typeNames.WILDCARD).foreach { tp => + // we don't care about type params shadowing other type params in the same declaration + enclClassOrMethodOrTypeMember(context).outer.lookupSymbol(tp.name, s => s != tp.symbol && s.hasRawInfo && reallyExists(s)) match { + case LookupSucceeded(_, sym2) => context.warning(tp.pos, + s"type parameter ${tp.name} defined in $sym shadows $sym2 defined in ${sym2.owner}. You may want to rename your type parameter, or possibly remove it.") + case _ => + } + } + } + /** Report a type error. * * @param pos The position where to report the error diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 0621fbefe4..422b940cd3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1726,6 +1726,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper |you want, you must write the annotation class in Java.""".stripMargin) } + warnTypeParameterShadow(tparams1, clazz) + if (!isPastTyper) { for (ann <- clazz.getAnnotation(DeprecatedAttr)) { val m = companionSymbolOf(clazz, context) @@ -2128,6 +2130,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val tparams1 = ddef.tparams mapConserve typedTypeDef val vparamss1 = ddef.vparamss mapConserve (_ mapConserve typedValDef) + warnTypeParameterShadow(tparams1, meth) + meth.annotations.map(_.completeInfo()) for (vparams1 <- vparamss1; vparam1 <- vparams1 dropRight 1) @@ -2204,6 +2208,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val typedMods = typedModifiers(tdef.mods) tdef.symbol.annotations.map(_.completeInfo()) + warnTypeParameterShadow(tparams1, tdef.symbol) + // @specialized should not be pickled when compiling with -no-specialize if (settings.nospecialization && currentRun.compiles(tdef.symbol)) { tdef.symbol.removeAnnotation(definitions.SpecializedClass) diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 95027a26b1..174254d523 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -142,8 +142,6 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") // don't keep the original owner in presentation compiler runs // (the map will grow indefinitely, and the only use case is the backend) override protected def saveOriginalOwner(sym: Symbol) { } - override protected def originalEnclosingMethod(sym: Symbol) = - abort("originalOwner is not kept in presentation compiler runs.") override def forInteractive = true override protected def synchronizeNames = true diff --git a/src/library/scala/collection/TraversableLike.scala b/src/library/scala/collection/TraversableLike.scala index d3a7db6968..a8731a51b1 100644 --- a/src/library/scala/collection/TraversableLike.scala +++ b/src/library/scala/collection/TraversableLike.scala @@ -253,7 +253,7 @@ trait TraversableLike[+A, +Repr] extends Any b.result } - private def filterImpl(p: A => Boolean, isFlipped: Boolean): Repr = { + private[scala] def filterImpl(p: A => Boolean, isFlipped: Boolean): Repr = { val b = newBuilder for (x <- this) if (p(x) != isFlipped) b += x diff --git a/src/library/scala/collection/immutable/Stream.scala b/src/library/scala/collection/immutable/Stream.scala index 37a0b373da..1f97c4c769 100644 --- a/src/library/scala/collection/immutable/Stream.scala +++ b/src/library/scala/collection/immutable/Stream.scala @@ -468,6 +468,16 @@ self => ) else super.flatMap(f)(bf) + override private[scala] def filterImpl(p: A => Boolean, isFlipped: Boolean): Stream[A] = { + // optimization: drop leading prefix of elems for which f returns false + // var rest = this dropWhile (!p(_)) - forget DRY principle - GC can't collect otherwise + var rest = this + while (!rest.isEmpty && p(rest.head) == isFlipped) rest = rest.tail + // private utility func to avoid `this` on stack (would be needed for the lazy arg) + if (rest.nonEmpty) Stream.filteredTail(rest, p, isFlipped) + else Stream.Empty + } + /** Returns all the elements of this `Stream` that satisfy the predicate `p` * in a new `Stream` - i.e., it is still a lazy data structure. The order of * the elements is preserved @@ -481,15 +491,7 @@ self => * // produces * }}} */ - override def filter(p: A => Boolean): Stream[A] = { - // optimization: drop leading prefix of elems for which f returns false - // var rest = this dropWhile (!p(_)) - forget DRY principle - GC can't collect otherwise - var rest = this - while (!rest.isEmpty && !p(rest.head)) rest = rest.tail - // private utility func to avoid `this` on stack (would be needed for the lazy arg) - if (rest.nonEmpty) Stream.filteredTail(rest, p) - else Stream.Empty - } + override def filter(p: A => Boolean): Stream[A] = filterImpl(p, isFlipped = false) // This override is only left in 2.11 because of binary compatibility, see PR #3925 override final def withFilter(p: A => Boolean): StreamWithFilter = new StreamWithFilter(p) @@ -1187,8 +1189,8 @@ object Stream extends SeqFactory[Stream] { else cons(start, range(start + step, end, step)) } - private[immutable] def filteredTail[A](stream: Stream[A], p: A => Boolean) = { - cons(stream.head, stream.tail filter p) + private[immutable] def filteredTail[A](stream: Stream[A], p: A => Boolean, isFlipped: Boolean) = { + cons(stream.head, stream.tail.filterImpl(p, isFlipped)) } private[immutable] def collectedTail[A, B, That](head: B, stream: Stream[A], pf: PartialFunction[A, B], bf: CanBuildFrom[Stream[A], B, That]) = { diff --git a/src/library/scala/runtime/SeqCharSequence.scala b/src/library/scala/runtime/SeqCharSequence.scala index ce7d7afc9e..74e67bb9e7 100644 --- a/src/library/scala/runtime/SeqCharSequence.scala +++ b/src/library/scala/runtime/SeqCharSequence.scala @@ -44,5 +44,10 @@ final class ArrayCharSequence(val xs: Array[Char], start: Int, end: Int) extends new ArrayCharSequence(xs, start1, start1 + newlen) } } - override def toString = xs drop start take length mkString "" + override def toString = { + val start = math.max(this.start, 0) + val end = math.min(xs.length, start + length) + + if (start >= end) "" else new String(xs, start, end - start) + } } diff --git a/src/reflect/scala/reflect/internal/AnnotationInfos.scala b/src/reflect/scala/reflect/internal/AnnotationInfos.scala index f814a746f5..fcef4dd6be 100644 --- a/src/reflect/scala/reflect/internal/AnnotationInfos.scala +++ b/src/reflect/scala/reflect/internal/AnnotationInfos.scala @@ -388,11 +388,11 @@ trait AnnotationInfos extends api.Annotations { self: SymbolTable => case Literal(const) => LiteralAnnotArg(const) case Apply(ArrayModule, args) => ArrayAnnotArg(args map encodeJavaArg toArray) case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => NestedAnnotArg(treeToAnnotation(arg)) - case _ => throw new Exception("unexpected java argument shape $arg: literals, arrays and nested annotations are supported") + case _ => throw new Exception(s"unexpected java argument shape $arg: literals, arrays and nested annotations are supported") } def encodeJavaArgs(args: List[Tree]): List[(Name, ClassfileAnnotArg)] = args match { case AssignOrNamedArg(Ident(name), arg) :: rest => (name, encodeJavaArg(arg)) :: encodeJavaArgs(rest) - case arg :: rest => throw new Exception("unexpected java argument shape $arg: only AssignOrNamedArg trees are supported") + case arg :: rest => throw new Exception(s"unexpected java argument shape $arg: only AssignOrNamedArg trees are supported") case Nil => Nil } val atp = tpt.tpe diff --git a/src/reflect/scala/reflect/internal/ReificationSupport.scala b/src/reflect/scala/reflect/internal/ReificationSupport.scala index 2caa30d27e..759bd2e791 100644 --- a/src/reflect/scala/reflect/internal/ReificationSupport.scala +++ b/src/reflect/scala/reflect/internal/ReificationSupport.scala @@ -866,7 +866,7 @@ trait ReificationSupport { self: SymbolTable => protected def mkCases(cases: List[Tree]): List[CaseDef] = cases.map { case c: CaseDef => c - case tree => throw new IllegalArgumentException("$tree is not valid representation of pattern match case") + case tree => throw new IllegalArgumentException(s"$tree is not valid representation of pattern match case") } object SyntacticPartialFunction extends SyntacticPartialFunctionExtractor { diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index b6cce4524b..2670faa22d 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -74,15 +74,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => } } - protected def originalEnclosingMethod(sym: Symbol): Symbol = { - if (sym.isMethod || sym == NoSymbol) sym - else { - val owner = sym.originalOwner - if (sym.isLocalDummy) owner.enclClass.primaryConstructor - else originalEnclosingMethod(owner) - } - } - def symbolOf[T: WeakTypeTag]: TypeSymbol = weakTypeOf[T].typeSymbolDirect.asType abstract class SymbolContextApiImpl extends SymbolApi { @@ -795,7 +786,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => info.firstParent.typeSymbol == AnyValClass && !isPrimitiveValueClass final def isMethodWithExtension = - isMethod && owner.isDerivedValueClass && !isParamAccessor && !isConstructor && !hasFlag(SUPERACCESSOR) && !isMacro + isMethod && owner.isDerivedValueClass && !isParamAccessor && !isConstructor && !hasFlag(SUPERACCESSOR) && !isMacro && !isSpecialized final def isAnonymousFunction = isSynthetic && (name containsName tpnme.ANON_FUN_NAME) final def isDefinedInPackage = effectiveOwner.isPackageClass @@ -2122,16 +2113,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => * is not one. */ def enclosingPackage: Symbol = enclosingPackageClass.companionModule - /** Return the original enclosing method of this symbol. It should return - * the same thing as enclMethod when called before lambda lift, - * but it preserves the original nesting when called afterwards. - * - * @note This method is NOT available in the presentation compiler run. The - * originalOwner map is not populated for memory considerations (the symbol - * may hang on to lazy types and in turn to whole (outdated) compilation units. - */ - def originalEnclosingMethod: Symbol = Symbols.this.originalEnclosingMethod(this) - /** The method or class which logically encloses the current symbol. * If the symbol is defined in the initialization part of a template * this is the template's primary constructor, otherwise it is @@ -3532,7 +3513,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def rawInfo: Type = NoType override def accessBoundary(base: Symbol): Symbol = enclosingRootClass def cloneSymbolImpl(owner: Symbol, newFlags: Long) = abort("NoSymbol.clone()") - override def originalEnclosingMethod = this } protected def makeNoSymbol: NoSymbol = new NoSymbol diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 6584d80de3..b7f229b6e5 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -807,31 +807,26 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive // field containing the cache for structural calls. if (mods.isStatic) module.moduleClass.orElse(clazz) else clazz - /** Methods which need to be treated with care - * because they either are getSimpleName or call getSimpleName: + /** + * Certain method of the Java reflection api cannot be used on classfiles created by Scala. + * See the comment in test/files/jvm/javaReflection/Test.scala. The methods are * * public String getSimpleName() * public boolean isAnonymousClass() * public boolean isLocalClass() * public String getCanonicalName() - * - * A typical manifestation: - * - * // java.lang.Error: sOwner(class Test$A$1) has failed - * // Caused by: java.lang.InternalError: Malformed class name - * // at java.lang.Class.getSimpleName(Class.java:1133) - * // at java.lang.Class.isAnonymousClass(Class.java:1188) - * // at java.lang.Class.isLocalClass(Class.java:1199) - * // (see t5256c.scala for more details) + * public boolean isSynthetic() * * TODO - find all such calls and wrap them. * TODO - create mechanism to avoid the recurrence of unwrapped calls. */ implicit class RichClass(jclazz: jClass[_]) { - // `jclazz.isLocalClass` doesn't work because of problems with `getSimpleName` - // hence we have to approximate by removing the `isAnonymousClass` check -// def isLocalClass0: Boolean = jclazz.isLocalClass - def isLocalClass0: Boolean = jclazz.getEnclosingMethod != null || jclazz.getEnclosingConstructor != null + // As explained in the javaReflection test, Class.isLocalClass is true for all non-member + // nested classes in Scala. This is fine per se, however the implementation may throw an + // InternalError. We therefore re-implement it here. + // TODO: this method should be renamed to `isLocalOrAnonymousClass`. + // due to bin compat that's only possible in 2.12, we cannot introduce a new alias in 2.11. + def isLocalClass0: Boolean = jclazz.getEnclosingClass != null && !jclazz.isMemberClass } /** diff --git a/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala b/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala index f4cbcb50fe..a37cdc2ec8 100644 --- a/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala +++ b/src/repl/scala/tools/nsc/interpreter/MemberHandlers.scala @@ -116,7 +116,7 @@ trait MemberHandlers { else any2stringOf(path, maxStringElements) val vidString = - if (replProps.vids) s"""" + " @ " + "%%8x".format(System.identityHashCode($path)) + " """.trim + if (replProps.vids) s"""" + f"@$${System.identityHashCode($path)}%8x" + """" else "" """ + "%s%s: %s = " + %s""".format(string2code(prettyName), vidString, string2code(req typeOf name), resultString) |