summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2016-06-06 14:24:38 +1000
committerAdriaan Moors <adriaan.moors@typesafe.com>2016-06-28 09:18:34 -0700
commit7d51b3fd1569917cb804363bd418466a306f5c89 (patch)
treeede84e6a0dda8750276d7c0986ffc6d15c9fb1dc
parent91b066aac5edf53ca18603f8486eb255514b3118 (diff)
downloadscala-7d51b3fd1569917cb804363bd418466a306f5c89.tar.gz
scala-7d51b3fd1569917cb804363bd418466a306f5c89.tar.bz2
scala-7d51b3fd1569917cb804363bd418466a306f5c89.zip
Emit trait method bodies in statics
And use this as the target of the default methods or statically resolved super or $init calls. The call-site change is predicated on `-Yuse-trait-statics` as a stepping stone for experimentation / bootstrapping. I have performed this transformation in the backend, rather than trying to reflect this in the view from Scala symbols + ASTs. We also need to add an restriction related to invokespecial to Java parents: to support a super call to one of these to implement a super accessor, the interface must be listed as a direct parent of the class. The static method names has a trailing $ added to avoid duplicate name and signature errors in classfiles.
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeGen.scala3
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala49
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala71
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala22
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala17
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala22
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala80
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala14
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala9
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala7
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala13
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala28
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala28
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala28
-rw-r--r--test/files/instrumented/InstrumentationTest.check2
-rw-r--r--test/files/neg/trait-defaults-super.check4
-rw-r--r--test/files/neg/trait-defaults-super.scala21
-rw-r--r--test/files/pos/trait-defaults-super.scala21
-rw-r--r--test/files/run/t4891.check3
-rw-r--r--test/files/run/t5652.check3
-rw-r--r--test/files/run/t7700.check5
-rw-r--r--test/files/run/t7700.scala16
-rw-r--r--test/files/run/t7932.check4
-rw-r--r--test/files/run/t7932.scala10
-rw-r--r--test/files/run/trait-static-clash.scala10
-rw-r--r--test/junit/scala/lang/traits/BytecodeTest.scala9
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala5
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala35
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala23
-rw-r--r--test/junit/scala/tools/testing/BytecodeTesting.scala15
33 files changed, 385 insertions, 206 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
index 14ee7d7a78..bc89609a59 100644
--- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala
+++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
@@ -336,12 +336,13 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
* - are associating the RHS with a cloned symbol, but intend for the original
* method to remain and for recursive calls to target it.
*/
- final def mkStatic(orig: DefDef, maybeClone: Symbol => Symbol): DefDef = {
+ final def mkStatic(orig: DefDef, newName: Name, maybeClone: Symbol => Symbol): DefDef = {
assert(phase.erasedTypes, phase)
assert(!orig.symbol.hasFlag(SYNCHRONIZED), orig.symbol.defString)
val origSym = orig.symbol
val origParams = orig.symbol.info.params
val newSym = maybeClone(orig.symbol)
+ newSym.setName(newName)
newSym.setFlag(STATIC)
// Add an explicit self parameter
val selfParamSym = newSym.newSyntheticValueParam(newSym.owner.typeConstructor, nme.SELF).setFlag(ARTIFACT)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
index d7106ae908..55fe47bde6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
@@ -11,10 +11,10 @@ package jvm
import scala.annotation.switch
import scala.reflect.internal.Flags
-
import scala.tools.asm
import GenBCode._
import BackendReporting._
+import scala.tools.asm.Opcodes
import scala.tools.asm.tree.MethodInsnNode
import scala.tools.nsc.backend.jvm.BCodeHelpers.{InvokeStyle, TestOp}
@@ -637,7 +637,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val nativeKind = tpeTK(expr)
genLoad(expr, nativeKind)
val MethodNameAndType(mname, methodType) = srBoxesRuntimeBoxToMethods(nativeKind)
- bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos)
+ bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos)
generatedType = boxResultType(fun.symbol)
case Apply(fun, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) =>
@@ -645,7 +645,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val boxType = unboxResultType(fun.symbol)
generatedType = boxType
val MethodNameAndType(mname, methodType) = srBoxesRuntimeUnboxToMethods(boxType)
- bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos)
+ bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos)
case app @ Apply(fun, args) =>
val sym = fun.symbol
@@ -1058,31 +1058,40 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits
- val receiverName = internalName(receiverClass)
-
- // super calls are only allowed to direct parents
- if (style.isSuper && receiverClass.isTraitOrInterface && !cnode.interfaces.contains(receiverName)) {
- thisBType.info.get.inlineInfo.lateInterfaces += receiverName
- cnode.interfaces.add(receiverName)
- }
+ val receiverBType = classBTypeFromSymbol(receiverClass)
+ val receiverName = receiverBType.internalName
def needsInterfaceCall(sym: Symbol) = {
sym.isTraitOrInterface ||
sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass)
}
- val jname = method.javaSimpleName.toString
- val bmType = methodBTypeFromSymbol(method)
- val mdescr = bmType.descriptor
+ val jname = method.javaSimpleName.toString
+ val bmType = methodBTypeFromSymbol(method)
+ val mdescr = bmType.descriptor
+ val isInterface = receiverBType.isInterface.get
import InvokeStyle._
- style match {
- case Static => bc.invokestatic (receiverName, jname, mdescr, pos)
- case Special => bc.invokespecial (receiverName, jname, mdescr, pos)
- case Virtual =>
- if (needsInterfaceCall(receiverClass)) bc.invokeinterface(receiverName, jname, mdescr, pos)
- else bc.invokevirtual (receiverName, jname, mdescr, pos)
- case Super => bc.invokespecial (receiverName, jname, mdescr, pos)
+ if (style == Super) {
+ assert(receiverClass == methodOwner, s"for super call, expecting $receiverClass == $methodOwner")
+ if (receiverClass.isTrait && !receiverClass.isJavaDefined) {
+ val staticDesc = MethodBType(typeToBType(method.owner.info) :: bmType.argumentTypes, bmType.returnType).descriptor
+ val staticName = traitImplMethodName(method).toString
+ bc.invokestatic(receiverName, staticName, staticDesc, isInterface, pos)
+ } else {
+ if (receiverClass.isTraitOrInterface) {
+ // An earlier check in Mixin reports an error in this case, so it doesn't reach the backend
+ assert(cnode.interfaces.contains(receiverName), s"cannot invokespecial $receiverName.$jname, the interface is not a direct parent.")
+ }
+ bc.invokespecial(receiverName, jname, mdescr, isInterface, pos)
+ }
+ } else {
+ val opc = style match {
+ case Static => Opcodes.INVOKESTATIC
+ case Special => Opcodes.INVOKESPECIAL
+ case Virtual => if (isInterface) Opcodes.INVOKEINTERFACE else Opcodes.INVOKEVIRTUAL
+ }
+ bc.emitInvoke(opc, receiverName, jname, mdescr, isInterface, pos)
}
bmType.returnType
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
index 5a5747c81f..df3c2cb3d5 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
@@ -11,6 +11,7 @@ import scala.tools.asm
import scala.tools.nsc.io.AbstractFile
import GenBCode._
import BackendReporting._
+import scala.reflect.internal.Flags
/*
* Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes.
@@ -49,6 +50,14 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
}
}
+ def needsStaticImplMethod(sym: Symbol) = sym.hasAttachment[global.mixer.NeedStaticImpl.type]
+
+ final def traitImplMethodName(sym: Symbol): Name = {
+ val name = sym.javaSimpleName
+ if (sym.isMixinConstructor) name
+ else name.append(nme.NAME_JOIN_STRING)
+ }
+
/**
* 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.
@@ -230,58 +239,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
sym.isErroneous
}
- /**
- * Build the [[InlineInfo]] for a class symbol.
- */
- def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = {
- val isEffectivelyFinal = classSym.isEffectivelyFinal
-
- val sam = {
- if (classSym.isEffectivelyFinal) None
- else {
- // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an
- // empty parameter list in later phases and would therefore be picked as SAM.
- val samSym = exitingPickler(definitions.samOf(classSym.tpe))
- if (samSym == NoSymbol) None
- else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym))
- }
- }
-
- var warning = Option.empty[ClassSymbolInfoFailureSI9111]
-
- // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
- // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
- val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
- case methodSym =>
- if (completeSilentlyAndCheckErroneous(methodSym)) {
- // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
- if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
- warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
- None
- } else {
- val name = methodSym.javaSimpleName.toString // same as in genDefDef
- val signature = name + methodSymToDescriptor(methodSym)
-
- // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the
- // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded
- // so they are not marked final in the InlineInfo attribute.
- //
- // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not
- // work, the abstract accessor for O will be marked effectivelyFinal.
- val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred
-
- val info = MethodInlineInfo(
- effectivelyFinal = effectivelyFinal,
- annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
- annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass)
- )
- Some((signature, info))
- }
- }).toMap
-
- InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning)
- }
-
/*
* must-single-thread
*/
@@ -568,15 +525,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
/**
* The class internal name for a given class symbol.
*/
- 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 the
- // ClassBType is created from the main class (instead of the module class).
- // The two symbols have the same name, so the resulting internalName is the same.
- // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting)
- val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
- classBTypeFromSymbol(classSym).internalName
- }
+ final def internalName(sym: Symbol): String = classBTypeFromSymbol(sym).internalName
} // end of trait BCInnerClassGen
trait BCAnnotGen extends BCInnerClassGen {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
index ed1b4ec325..e3d45a9b3e 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
@@ -190,6 +190,7 @@ abstract class BCodeIdiomatic extends SubComponent {
JavaStringBuilderClassName,
INSTANCE_CONSTRUCTOR_NAME,
"()V",
+ itf = false,
pos
)
}
@@ -373,30 +374,27 @@ abstract class BCodeIdiomatic extends SubComponent {
final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread
// can-multi-thread
- final def invokespecial(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos)
+ final def invokespecial(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, itf, pos)
}
// can-multi-thread
- final def invokestatic(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos)
+ final def invokestatic(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKESTATIC, owner, name, desc, itf, pos)
}
// can-multi-thread
- final def invokeinterface(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos)
+ final def invokeinterface(owner: String, name: String, desc: String, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, itf = true, pos)
}
// can-multi-thread
- final def invokevirtual(owner: String, name: String, desc: String, pos: Position) {
- addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos)
+ final def invokevirtual(owner: String, name: String, desc: String, pos: Position): Unit = {
+ emitInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf = false, pos)
}
- private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = {
+ def emitInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = {
val node = new MethodInsnNode(opcode, owner, name, desc, itf)
jmethod.instructions.add(node)
if (settings.optInlinerEnabled) callsitePositions(node) = pos
}
- final def invokedynamic(owner: String, name: String, desc: String) {
- jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc)
- }
// can-multi-thread
final def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
index bddc41e5c6..1bff8519ec 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -488,7 +488,22 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
case ValDef(mods, name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()`
- case dd : DefDef => genDefDef(dd)
+ case dd : DefDef =>
+ val sym = dd.symbol
+ if (needsStaticImplMethod(sym)) {
+ val staticDefDef = global.gen.mkStatic(dd, traitImplMethodName(sym), _.cloneSymbol)
+ val forwarderDefDef = {
+ val forwarderBody = Apply(global.gen.mkAttributedRef(staticDefDef.symbol), This(sym.owner).setType(sym.owner.typeConstructor) :: dd.vparamss.head.map(p => global.gen.mkAttributedIdent(p.symbol))).setType(sym.info.resultType)
+ // we don't want to the optimizer to inline the static method into the forwarder. Instead,
+ // the backend has a special case to transitively inline into a callsite of the forwarder
+ // when the forwarder itself is inlined.
+ forwarderBody.updateAttachment(NoInlineCallsiteAttachment)
+ deriveDefDef(dd)(_ => global.atPos(dd.pos)(forwarderBody))
+ }
+ genDefDef(staticDefDef)
+ if (!sym.isMixinConstructor)
+ genDefDef(forwarderDefDef)
+ } else genDefDef(dd)
case Template(_, _, body) => body foreach gen
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index a708feb0a7..7b2686e7a9 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -225,8 +225,7 @@ abstract class BTypes {
val inlineInfo = inlineInfoFromClassfile(classNode)
- val classfileInterfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
- val interfaces = classfileInterfaces.filterNot(i => inlineInfo.lateInterfaces.contains(i.internalName))
+ val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut)
classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo))
classBType
@@ -1147,25 +1146,6 @@ object BTypes {
sam: Option[String],
methodInfos: Map[String, MethodInlineInfo],
warning: Option[ClassInlineInfoWarning]) {
- /**
- * A super call (invokespecial) to a default method T.m is only allowed if the interface T is
- * a direct parent of the class. Super calls are introduced for example in Mixin when generating
- * forwarder methods:
- *
- * trait T { override def clone(): Object = "hi" }
- * trait U extends T
- * class C extends U
- *
- * The class C gets a forwarder that invokes T.clone(). During code generation the interface T
- * is added as direct parent to class C. Note that T is not a (direct) parent in the frontend
- * type of class C.
- *
- * All interfaces that are added to a class during code generation are added to this buffer and
- * stored in the InlineInfo classfile attribute. This ensures that the ClassBTypes for a
- * specific class is the same no matter if it's constructed from a Symbol or from a classfile.
- * This is tested in BTypesFromClassfileTest.
- */
- val lateInterfaces: ListBuffer[InternalName] = ListBuffer.empty
}
val EmptyInlineInfo = InlineInfo(false, None, Map.empty, None)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index 21ea351a99..1a4590e7d1 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -97,11 +97,19 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
* for the Nothing / Null. If used for example as a parameter type, we use the runtime classes
* in the classfile method signature.
*/
- final def classBTypeFromSymbol(classSym: Symbol): ClassBType = {
+ final def classBTypeFromSymbol(sym: Symbol): ClassBType = {
+ // 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 the
+ // ClassBType is created from the main class (instead of the module class).
+ // The two symbols have the same name, so the resulting internalName is the same.
+ // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting)
+ val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
+
assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol")
assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym")
assertClassNotArrayNotPrimitive(classSym)
assert(!primitiveTypeToBType.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym")
+
if (classSym == NothingClass) srNothingRef
else if (classSym == NullClass) srNullRef
else {
@@ -509,7 +517,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
* classfile attribute.
*/
private def buildInlineInfo(classSym: Symbol, internalName: InternalName): InlineInfo = {
- def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym, classBTypeFromSymbol(_).internalName, methodBTypeFromSymbol(_).descriptor)
+ def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym)
// phase travel required, see implementation of `compiles`. for nested classes, it checks if the
// enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level,
@@ -531,6 +539,74 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
}
/**
+ * Build the [[InlineInfo]] for a class symbol.
+ */
+ def buildInlineInfoFromClassSymbol(classSym: Symbol): InlineInfo = {
+ val isEffectivelyFinal = classSym.isEffectivelyFinal
+
+ val sam = {
+ if (classSym.isEffectivelyFinal) None
+ else {
+ // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an
+ // empty parameter list in later phases and would therefore be picked as SAM.
+ val samSym = exitingPickler(definitions.samOf(classSym.tpe))
+ if (samSym == NoSymbol) None
+ else Some(samSym.javaSimpleName.toString + methodBTypeFromSymbol(samSym).descriptor)
+ }
+ }
+
+ var warning = Option.empty[ClassSymbolInfoFailureSI9111]
+
+ // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
+ // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
+ val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
+ case methodSym =>
+ if (completeSilentlyAndCheckErroneous(methodSym)) {
+ // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
+ if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
+ warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
+ Nil
+ } else {
+ val name = methodSym.javaSimpleName.toString // same as in genDefDef
+ val signature = name + methodBTypeFromSymbol(methodSym).descriptor
+
+ // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the
+ // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded
+ // so they are not marked final in the InlineInfo attribute.
+ //
+ // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not
+ // work, the abstract accessor for O will be marked effectivelyFinal.
+ val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred
+
+ val info = MethodInlineInfo(
+ effectivelyFinal = effectivelyFinal,
+ annotatedInline = methodSym.hasAnnotation(ScalaInlineClass),
+ annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass))
+
+ if (needsStaticImplMethod(methodSym)) {
+ val staticName = traitImplMethodName(methodSym).toString
+ val selfParam = methodSym.newSyntheticValueParam(methodSym.owner.typeConstructor, nme.SELF)
+ val staticMethodType = methodSym.info match {
+ case mt @ MethodType(params, res) => copyMethodType(mt, selfParam :: params, res)
+ }
+ val staticMethodSignature = staticName + methodBTypeFromMethodType(staticMethodType, isConstructor = false)
+ val staticMethodInfo = MethodInlineInfo(
+ effectivelyFinal = true,
+ annotatedInline = info.annotatedInline,
+ annotatedNoInline = info.annotatedNoInline)
+ if (methodSym.isMixinConstructor)
+ List((staticMethodSignature, staticMethodInfo))
+ else
+ List((signature, info), (staticMethodSignature, staticMethodInfo))
+ } else
+ List((signature, info))
+ }
+ }).toMap
+
+ InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning)
+ }
+
+ /**
* For top-level objects without a companion class, the compiler generates a mirror class with
* static forwarders (Java compat). There's no symbol for the mirror class, but we still need a
* ClassBType (its info.nestedClasses will hold the InnerClass entries, see comment in BTypes).
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
index 513c71fe2e..539435a326 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
@@ -2,18 +2,18 @@ package scala.tools.nsc
package backend.jvm
package analysis
+import java.lang.invoke.LambdaMetafactory
+
import scala.annotation.switch
-import scala.tools.asm.{Handle, Type}
+import scala.collection.JavaConverters._
+import scala.collection.mutable
import scala.tools.asm.Opcodes._
import scala.tools.asm.tree._
import scala.tools.asm.tree.analysis._
-import GenBCode._
+import scala.tools.asm.{Handle, Type}
import scala.tools.nsc.backend.jvm.BTypes._
+import scala.tools.nsc.backend.jvm.GenBCode._
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
-import java.lang.invoke.LambdaMetafactory
-
-import scala.collection.mutable
-import scala.collection.JavaConverters._
/**
* This component hosts tools and utilities used in the backend that require access to a `BTypes`
@@ -39,7 +39,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
case ae: AnalyzerException =>
throw new AnalyzerException(null, "While processing " + classInternalName + "." + methodNode.name, ae)
}
- def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode)
+ def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode)
}
/**
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
index 63906d80e5..e21c46dbe9 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -93,6 +93,15 @@ object BytecodeUtils {
op == INVOKESPECIAL || op == INVOKESTATIC
}
+ def isVirtualCall(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ op == INVOKEVIRTUAL || op == INVOKEINTERFACE
+ }
+
+ def isCall(instruction: AbstractInsnNode): Boolean = {
+ isNonVirtualCall(instruction) || isVirtualCall(instruction)
+ }
+
def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0
def isConstructor(methodNode: MethodNode): Boolean = {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
index 40344809bf..d6942d9ff9 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
@@ -9,11 +9,11 @@ package opt
import scala.collection.immutable.IntMap
import scala.reflect.internal.util.{NoPosition, Position}
-import scala.tools.asm.{Opcodes, Type, Handle}
+import scala.tools.asm.{Handle, Opcodes, Type}
import scala.tools.asm.tree._
import scala.collection.{concurrent, mutable}
import scala.collection.JavaConverters._
-import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo}
import scala.tools.nsc.backend.jvm.BackendReporting._
import scala.tools.nsc.backend.jvm.analysis._
import BytecodeUtils._
@@ -67,6 +67,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
}
def containsCallsite(callsite: Callsite): Boolean = callsites(callsite.callsiteMethod) contains callsite.callsiteInstruction
+ def findCallSite(method: MethodNode, call: MethodInsnNode): Option[Callsite] = callsites.getOrElse(method, Map.empty).get(call)
def removeClosureInstantiation(indy: InvokeDynamicInsnNode, methodNode: MethodNode): Option[ClosureInstantiation] = {
val methodClosureInits = closureInstantiations(methodNode)
@@ -356,7 +357,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
"Invocation of" +
s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" +
s"@${callsiteMethod.instructions.indexOf(callsiteInstruction)}" +
- s" in ${callsiteClass.internalName}.${callsiteMethod.name}"
+ s" in ${callsiteClass.internalName}.${callsiteMethod.name}${callsiteMethod.desc}"
}
final case class ClonedCallsite(callsite: Callsite, clonedWhenInlining: Callsite)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
index 79d26b0b4e..5ce7072c60 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala
@@ -51,7 +51,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
if (inlineInfo.isEffectivelyFinal) flags |= 1
// flags |= 2 // no longer written
if (inlineInfo.sam.isDefined) flags |= 4
- if (inlineInfo.lateInterfaces.nonEmpty) flags |= 8
result.putByte(flags)
for (samNameDesc <- inlineInfo.sam) {
@@ -79,9 +78,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
result.putByte(inlineInfo)
}
- result.putShort(inlineInfo.lateInterfaces.length)
- for (i <- inlineInfo.lateInterfaces) result.putShort(cw.newUTF8(i))
-
result
}
@@ -105,7 +101,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
val isFinal = (flags & 1) != 0
val hasSelf = (flags & 2) != 0
val hasSam = (flags & 4) != 0
- val hasLateInterfaces = (flags & 8) != 0
if (hasSelf) nextUTF8() // no longer used
@@ -128,13 +123,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI
(name + desc, MethodInlineInfo(isFinal, isInline, isNoInline))
}).toMap
- val lateInterfaces = if (!hasLateInterfaces) Nil else {
- val numLateInterfaces = nextShort()
- (0 until numLateInterfaces).map(_ => nextUTF8())
- }
-
val info = InlineInfo(isFinal, sam, infos, None)
- info.lateInterfaces ++= lateInterfaces
InlineInfoAttribute(info)
} else {
val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version)
@@ -161,8 +150,6 @@ object InlineInfoAttribute {
* [u2] name (reference)
* [u2] descriptor (reference)
* [u1] isFinal (<< 0), traitMethodWithStaticImplementation (<< 1), hasInlineAnnotation (<< 2), hasNoInlineAnnotation (<< 3)
- * [u2]? numLateInterfaces
- * [u2] lateInterface (reference)
*/
final val VERSION: Byte = 1
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
index 7b4cfe2a18..9c5a1a9f98 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
@@ -106,6 +106,8 @@ class Inliner[BT <: BTypes](val btypes: BT) {
val elided = mutable.Set.empty[InlineRequest]
def nonElidedRequests(methodNode: MethodNode): Set[InlineRequest] = requestsByMethod(methodNode) diff elided
+ def allCallees(r: InlineRequest): Set[MethodNode] = r.post.flatMap(allCallees).toSet + r.callsite.callee.get.callee
+
/**
* Break cycles in the inline request graph by removing callsites.
*
@@ -114,20 +116,20 @@ class Inliner[BT <: BTypes](val btypes: BT) {
*/
def breakInlineCycles: List[InlineRequest] = {
// is there a path of inline requests from start to goal?
- def isReachable(start: MethodNode, goal: MethodNode): Boolean = {
- @tailrec def reachableImpl(check: List[MethodNode], visited: Set[MethodNode]): Boolean = check match {
- case x :: xs =>
+ def isReachable(start: Set[MethodNode], goal: MethodNode): Boolean = {
+ @tailrec def reachableImpl(check: Set[MethodNode], visited: Set[MethodNode]): Boolean = {
+ if (check.isEmpty) false
+ else {
+ val x = check.head
if (x == goal) true
- else if (visited(x)) reachableImpl(xs, visited)
+ else if (visited(x)) reachableImpl(check - x, visited)
else {
- val callees = nonElidedRequests(x).map(_.callsite.callee.get.callee)
- reachableImpl(xs ::: callees.toList, visited + x)
+ val callees = nonElidedRequests(x).flatMap(allCallees)
+ reachableImpl(check - x ++ callees, visited + x)
}
-
- case Nil =>
- false
+ }
}
- reachableImpl(List(start), Set.empty)
+ reachableImpl(start, Set.empty)
}
val result = new mutable.ListBuffer[InlineRequest]()
@@ -136,7 +138,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
java.util.Arrays.sort(requests, callsiteOrdering)
for (r <- requests) {
// is there a chain of inlining requests that would inline the callsite method into the callee?
- if (isReachable(r.callsite.callee.get.callee, r.callsite.callsiteMethod))
+ if (isReachable(allCallees(r), r.callsite.callsiteMethod))
elided += r
else
result += r
@@ -150,8 +152,8 @@ class Inliner[BT <: BTypes](val btypes: BT) {
if (requests.isEmpty) Nil
else {
val (leaves, others) = requests.partition(r => {
- val inlineRequestsForCallee = nonElidedRequests(r.callsite.callee.get.callee)
- inlineRequestsForCallee.forall(visited)
+ val inlineRequestsForCallees = allCallees(r).flatMap(nonElidedRequests)
+ inlineRequestsForCallees.forall(visited)
})
assert(leaves.nonEmpty, requests)
leaves ::: leavesFirst(others, visited ++ leaves)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
index 009742501e..79e74f3eb7 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala
@@ -7,14 +7,14 @@ package scala.tools.nsc
package backend.jvm
package opt
-import scala.tools.asm.tree.MethodNode
-import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.collection.JavaConverters._
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree.{MethodInsnNode, MethodNode}
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.tools.nsc.backend.jvm.BackendReporting.OptimizerWarning
class InlinerHeuristics[BT <: BTypes](val bTypes: BT) {
import bTypes._
- import inliner._
import callGraph._
case class InlineRequest(callsite: Callsite, post: List[InlineRequest], reason: String) {
@@ -93,7 +93,27 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) {
val callee = callsite.callee.get
def requestIfCanInline(callsite: Callsite, reason: String): Either[OptimizerWarning, InlineRequest] = inliner.earlyCanInlineCheck(callsite) match {
case Some(w) => Left(w)
- case None => Right(InlineRequest(callsite, Nil, reason))
+ case None =>
+ val callee = callsite.callee.get
+ val postInlineRequest: List[InlineRequest] = callee.calleeDeclarationClass.isInterface match {
+ case Right(true) =>
+ // Treat the pair of trait interface method and static method as one for the purposes of inlining:
+ // if we inline invokeinterface, invoke the invokestatic, too.
+ val calls = callee.callee.instructions.iterator().asScala.filter(BytecodeUtils.isCall).take(2).toList
+ calls match {
+ case List(x: MethodInsnNode) if x.getOpcode == Opcodes.INVOKESTATIC && x.name == (callee.callee.name + "$") =>
+ callGraph.addIfMissing(callee.callee, callee.calleeDeclarationClass)
+ val maybeNodeToCallsite1 = callGraph.findCallSite(callee.callee, x)
+ maybeNodeToCallsite1.toList.flatMap(x => requestIfCanInline(x, reason).right.toOption)
+ case _ =>
+ Nil
+
+ }
+ case _ => Nil
+ }
+
+ Right(InlineRequest(callsite, postInlineRequest, reason))
+
}
compilerSettings.YoptInlineHeuristics.value match {
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 2dd8def53e..804bcddb7b 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -261,7 +261,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
def pretransform(tree: Tree): Tree = tree match {
case dd: DefDef if dd.symbol.isDelambdafyTarget =>
if (!dd.symbol.hasFlag(STATIC) && methodReferencesThis(dd.symbol)) {
- gen.mkStatic(dd, sym => sym)
+ gen.mkStatic(dd, dd.symbol.name, sym => sym)
} else {
dd.symbol.setFlag(STATIC)
dd
@@ -276,8 +276,10 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
case dd: DefDef if dd.symbol.isLiftedMethod && !dd.symbol.isDelambdafyTarget =>
// SI-9390 emit lifted methods that don't require a `this` reference as STATIC
// delambdafy targets are excluded as they are made static by `transformFunction`.
- if (!dd.symbol.hasFlag(STATIC) && !methodReferencesThis(dd.symbol))
+ if (!dd.symbol.hasFlag(STATIC) && !methodReferencesThis(dd.symbol)) {
dd.symbol.setFlag(STATIC)
+ dd.symbol.removeAttachment[mixer.NeedStaticImpl.type]
+ }
super.transform(tree)
case Apply(fun, outer :: rest) if shouldElideOuterArg(fun.symbol, outer) =>
val nullOuter = gen.mkZero(outer.tpe)
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala
index 6df0b992ed..d62b77dac2 100644
--- a/src/compiler/scala/tools/nsc/transform/Mixin.scala
+++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala
@@ -130,6 +130,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
*/
def addMember(clazz: Symbol, member: Symbol): Symbol = {
debuglog(s"mixing into $clazz: ${member.defString}")
+ // This attachment is used to instruct the backend about which methids in traits require
+ // a static trait impl method. We remove this from the new symbol created for the method
+ // mixed into the subclass.
+ member.removeAttachment[NeedStaticImpl.type]
clazz.info.decls enter member setFlag MIXEDIN resetFlag JAVA_DEFAULTMETHOD
}
def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol =
@@ -344,6 +348,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
reporter.error(clazz.pos, "Member %s of mixin %s is missing a concrete super implementation.".format(
mixinMember.alias, mixinClass))
case alias1 =>
+ if (alias1.owner.isJavaDefined && alias1.owner.isInterface && !clazz.parentSymbols.contains(alias1.owner)) {
+ val suggestedParent = exitingTyper(clazz.info.baseType(alias1.owner))
+ reporter.error(clazz.pos, s"Unable to implement a super accessor required by trait ${mixinClass.name} unless $suggestedParent is directly extended by $clazz.")
+ }
superAccessor.asInstanceOf[TermSymbol] setAlias alias1
}
}
@@ -1001,13 +1009,20 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
val parents1 = currentOwner.info.parents map (t => TypeTree(t) setPos tree.pos)
// mark fields which can be nulled afterward
lazyValNullables = nullableFields(templ) withDefaultValue Set()
+ // Remove bodies of accessors in traits - TODO: after PR #5141 (fields refactoring), this might be a no-op
val bodyEmptyAccessors = if (!sym.enclClass.isTrait) body else body mapConserve {
case dd: DefDef if dd.symbol.isAccessor && !dd.symbol.isLazy =>
deriveDefDef(dd)(_ => EmptyTree)
case tree => tree
}
// add all new definitions to current class or interface
- treeCopy.Template(tree, parents1, self, addNewDefs(currentOwner, bodyEmptyAccessors))
+ val body1 = addNewDefs(currentOwner, bodyEmptyAccessors)
+ body1 foreach {
+ case dd: DefDef if isTraitMethodRequiringStaticImpl(dd) =>
+ dd.symbol.updateAttachment(NeedStaticImpl)
+ case _ =>
+ }
+ treeCopy.Template(tree, parents1, self, body1)
case Select(qual, name) if sym.owner.isTrait && !sym.isMethod =>
// refer to fields in some trait an abstract getter in the interface.
@@ -1023,7 +1038,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
typedPos(tree.pos)((qual DOT setter)(rhs))
-
case _ =>
tree
}
@@ -1042,4 +1056,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
finally localTyper = saved
}
}
+
+ private def isTraitMethodRequiringStaticImpl(dd: DefDef): Boolean = {
+ val sym = dd.symbol
+ dd.rhs.nonEmpty &&
+ sym.owner.isTrait &&
+ !sym.isPrivate && // no need to put implementations of private methods into a static method
+ !sym.hasFlag(Flags.STATIC)
+ }
+
+ case object NeedStaticImpl extends PlainAttachment
}
diff --git a/test/files/instrumented/InstrumentationTest.check b/test/files/instrumented/InstrumentationTest.check
index 74f9c9d268..d317fc4207 100644
--- a/test/files/instrumented/InstrumentationTest.check
+++ b/test/files/instrumented/InstrumentationTest.check
@@ -6,5 +6,5 @@ Method call statistics:
1 instrumented/Foo2.someMethod()I
1 scala/DeprecatedConsole.<init>()V
1 scala/Predef$.println(Ljava/lang/Object;)V
- 1 scala/io/AnsiColor.$init$()V
+ 1 scala/io/AnsiColor.$init$(Lscala/io/AnsiColor;)V
1 scala/runtime/BoxesRunTime.boxToBoolean(Z)Ljava/lang/Boolean;
diff --git a/test/files/neg/trait-defaults-super.check b/test/files/neg/trait-defaults-super.check
new file mode 100644
index 0000000000..2b19402828
--- /dev/null
+++ b/test/files/neg/trait-defaults-super.check
@@ -0,0 +1,4 @@
+trait-defaults-super.scala:14: error: Unable to implement a super accessor required by trait T unless Iterable[String] is directly extended by class C.
+class C extends T
+ ^
+one error found
diff --git a/test/files/neg/trait-defaults-super.scala b/test/files/neg/trait-defaults-super.scala
new file mode 100644
index 0000000000..def271e8e7
--- /dev/null
+++ b/test/files/neg/trait-defaults-super.scala
@@ -0,0 +1,21 @@
+trait T extends java.lang.Iterable[String] {
+
+ override def spliterator(): java.util.Spliterator[String] = {
+ super[Iterable].spliterator
+ super.spliterator
+ null
+ }
+ def foo = {
+ super[Iterable].spliterator
+ super.spliterator
+ }
+ def iterator(): java.util.Iterator[String] = java.util.Collections.emptyList().iterator()
+}
+class C extends T
+object Test {
+ def main(args: Array[String]): Unit = {
+ val t: T = new C
+ t.spliterator
+ t.foo
+ }
+}
diff --git a/test/files/pos/trait-defaults-super.scala b/test/files/pos/trait-defaults-super.scala
new file mode 100644
index 0000000000..8f867ab563
--- /dev/null
+++ b/test/files/pos/trait-defaults-super.scala
@@ -0,0 +1,21 @@
+trait T extends java.lang.Iterable[String] {
+
+ override def spliterator(): java.util.Spliterator[String] = {
+ super[Iterable].spliterator
+ super.spliterator
+ null
+ }
+ def foo = {
+ super[Iterable].spliterator
+ super.spliterator
+ }
+ def iterator(): java.util.Iterator[String] = java.util.Collections.emptyList().iterator()
+}
+class C extends T with java.lang.Iterable[String] // super accessor is okay with Iterable as a direct parent
+object Test {
+ def main(args: Array[String]): Unit = {
+ val t: T = new C
+ t.spliterator
+ t.foo
+ }
+}
diff --git a/test/files/run/t4891.check b/test/files/run/t4891.check
index 1b1108e9ee..a460569fd9 100644
--- a/test/files/run/t4891.check
+++ b/test/files/run/t4891.check
@@ -1,6 +1,7 @@
test.generic.T1
- (m) public default void test.generic.T1.$init$()
+ (m) public static void test.generic.T1.$init$(test.generic.T1)
(m) public default A test.generic.T1.t1(A)
+ (m) public static java.lang.Object test.generic.T1.t1$(test.generic.T1,java.lang.Object)
test.generic.C1
(m) public void test.generic.C1.m1()
test.generic.C2
diff --git a/test/files/run/t5652.check b/test/files/run/t5652.check
index 7c65ba6698..3c039d68aa 100644
--- a/test/files/run/t5652.check
+++ b/test/files/run/t5652.check
@@ -1,6 +1,7 @@
public default int T1.f0()
-public default void T1.$init$()
public static int T1.T1$$g$1()
+public static int T1.f0$(T1)
+public static void T1.$init$(T1)
public int A1.f1()
public static final int A1.A1$$g$2()
public int A2.f2()
diff --git a/test/files/run/t7700.check b/test/files/run/t7700.check
index 1d51e68877..7d18dbfcb4 100644
--- a/test/files/run/t7700.check
+++ b/test/files/run/t7700.check
@@ -1,3 +1,4 @@
-public default void C.$init$()
+public static void C.$init$(C)
public default java.lang.Object C.bar(java.lang.Object)
-public abstract java.lang.Object C.foo(java.lang.Object)
+public static java.lang.Object C.bar$(C,java.lang.Object)
+public abstract java.lang.Object C.foo(java.lang.Object) \ No newline at end of file
diff --git a/test/files/run/t7700.scala b/test/files/run/t7700.scala
index 76d16b808c..fd13666467 100644
--- a/test/files/run/t7700.scala
+++ b/test/files/run/t7700.scala
@@ -7,11 +7,13 @@ trait C[@specialized U] {
def bar[A](u: U) = u
}
-object Test extends App {
- val declared = classOf[C[_]].getDeclaredMethods.sortBy(_.getName)
- println(declared.mkString("\n"))
- object CInt extends C[Int] { def foo(i: Int) = i }
- object CAny extends C[Any] { def foo(a: Any) = a }
- assert(CInt.foo(1) == 1)
- assert(CAny.foo("") == "")
+object Test {
+ def main(args: Array[String]) {
+ val declared = classOf[C[_]].getDeclaredMethods.sortBy(_.getName)
+ println(declared.mkString("\n"))
+ object CInt extends C[Int] { def foo(i: Int) = i }
+ object CAny extends C[Any] { def foo(a: Any) = a }
+ assert(CInt.foo(1) == 1)
+ assert(CAny.foo("") == "")
+ }
}
diff --git a/test/files/run/t7932.check b/test/files/run/t7932.check
index a2ad84cd46..76968fd179 100644
--- a/test/files/run/t7932.check
+++ b/test/files/run/t7932.check
@@ -2,5 +2,9 @@ public Category<?> C.category()
public Category<scala.Tuple2> C.category1()
public default Category<java.lang.Object> M1.category()
public default Category<scala.Tuple2> M1.category1()
+public static Category M1.category$(M1)
+public static Category M1.category1$(M1)
public default Category<java.lang.Object> M2.category()
public default Category<scala.Tuple2> M2.category1()
+public static Category M2.category$(M2)
+public static Category M2.category1$(M2) \ No newline at end of file
diff --git a/test/files/run/t7932.scala b/test/files/run/t7932.scala
index e6bdbf2417..40b0b9989b 100644
--- a/test/files/run/t7932.scala
+++ b/test/files/run/t7932.scala
@@ -17,12 +17,14 @@ trait M2[F] { self: M1[F] =>
abstract class C extends M1[Float] with M2[Float]
-object Test extends App {
+object Test {
def t(c: Class[_]) = {
val ms = c.getMethods.filter(_.getName.startsWith("category"))
println(ms.map(_.toGenericString).sorted.mkString("\n"))
}
- t(classOf[C])
- t(classOf[M1[_]])
- t(classOf[M2[_]])
+ def main(args: Array[String]) {
+ t(classOf[C])
+ t(classOf[M1[_]])
+ t(classOf[M2[_]])
+ }
}
diff --git a/test/files/run/trait-static-clash.scala b/test/files/run/trait-static-clash.scala
new file mode 100644
index 0000000000..603cf6b6e5
--- /dev/null
+++ b/test/files/run/trait-static-clash.scala
@@ -0,0 +1,10 @@
+trait T {
+ def foo = 1
+ def foo(t: T) = 2
+}
+object Test extends T {
+ def main(args: Array[String]) {
+ assert(foo == 1)
+ assert(foo(this) == 2)
+ }
+}
diff --git a/test/junit/scala/lang/traits/BytecodeTest.scala b/test/junit/scala/lang/traits/BytecodeTest.scala
index f47fc9c127..ec8508df99 100644
--- a/test/junit/scala/lang/traits/BytecodeTest.scala
+++ b/test/junit/scala/lang/traits/BytecodeTest.scala
@@ -9,6 +9,7 @@ import scala.collection.JavaConverters._
import scala.tools.asm.Opcodes
import scala.tools.asm.Opcodes._
import scala.tools.asm.tree.ClassNode
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils
import scala.tools.partest.ASMConverters._
import scala.tools.testing.BytecodeTesting
import scala.tools.testing.BytecodeTesting._
@@ -18,8 +19,8 @@ class BytecodeTest extends BytecodeTesting {
import compiler._
def checkForwarder(classes: Map[String, ClassNode], clsName: Symbol, target: String) = {
- val List(f) = getMethods(classes(clsName.name), "f")
- assertSameCode(f, List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, target, "f", "()I", false), Op(IRETURN)))
+ val f = getMethod(classes(clsName.name), "f")
+ assertSameCode(f, List(VarOp(ALOAD, 0), Invoke(INVOKESTATIC, target, "f$", s"(L$target;)I", true), Op(IRETURN)))
}
@Test
@@ -88,7 +89,7 @@ class BytecodeTest extends BytecodeTesting {
assertSameSummary(getMethod(c("C18"), "f"), List(BIPUSH, IRETURN))
checkForwarder(c, 'C19, "T7")
assertSameCode(getMethod(c("C19"), "T7$$super$f"), List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, "C18", "f", "()I", false), Op(IRETURN)))
- assertInvoke(getMethod(c("C20"), "clone"), "T8", "clone") // mixin forwarder
+ assertInvoke(getMethod(c("C20"), "clone"), "T8", "clone$") // mixin forwarder
}
@Test
@@ -141,7 +142,7 @@ class BytecodeTest extends BytecodeTesting {
def invocationReceivers(): Unit = {
val List(c1, c2, t, u) = compileClasses(invocationReceiversTestCode.definitions("Object"))
// mixin forwarder in C1
- assertSameCode(getMethod(c1, "clone"), List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, "T", "clone", "()Ljava/lang/Object;", false), Op(ARETURN)))
+ assertSameCode(getMethod(c1, "clone"), List(VarOp(ALOAD, 0), Invoke(INVOKESTATIC, "T", "clone$", "(LT;)Ljava/lang/Object;", true), Op(ARETURN)))
assertInvoke(getMethod(c1, "f1"), "T", "clone")
assertInvoke(getMethod(c1, "f2"), "T", "clone")
assertInvoke(getMethod(c1, "f3"), "C1", "clone")
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala
index c9a958ee4f..841e850b49 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala
@@ -5,6 +5,7 @@ import org.junit.Test
import scala.collection.JavaConverters
import scala.collection.JavaConverters._
+import scala.reflect.internal.Flags
import scala.tools.asm.Opcodes
import scala.tools.asm.tree.ClassNode
import scala.tools.testing.BytecodeTesting
@@ -21,7 +22,7 @@ class DefaultMethodTest extends BytecodeTesting {
/** Transforms a single tree. */
override def transform(tree: global.Tree): global.Tree = tree match {
case dd @ DefDef(_, Foo, _, _, _, _) =>
- dd.symbol.setFlag(reflect.internal.Flags.JAVA_DEFAULTMETHOD)
+ dd.symbol.setFlag(Flags.JAVA_DEFAULTMETHOD).resetFlag(Flags.DEFERRED)
copyDefDef(dd)(rhs = Literal(Constant(1)).setType(definitions.IntTpe))
case _ => super.transform(tree)
}
@@ -31,6 +32,4 @@ class DefaultMethodTest extends BytecodeTesting {
assertTrue("default method should not be abstract", (foo.access & Opcodes.ACC_ABSTRACT) == 0)
assertTrue("default method body emitted", foo.instructions.size() > 0)
}
-
-
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
index a28599cd92..38285fbce1 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
@@ -1,7 +1,9 @@
package scala.tools.nsc.backend.jvm
+import java.nio.file.{Files, Paths}
+
import org.junit.Assert._
-import org.junit.Test
+import org.junit.{Ignore, Test}
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
index a2513cacdc..85df42e069 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
@@ -97,7 +97,7 @@ class InlinerSeparateCompilationTest {
""".stripMargin
val List(a, t) = compileClassesSeparately(List(codeA, assembly), args)
- assertNoInvoke(getMethod(t, "f"))
- assertNoInvoke(getMethod(a, "n"))
+ assertNoInvoke(getMethod(t, "f$"))
+ assertNoInvoke(getMethod(a, "n$"))
}
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
index 9173a1d189..f531ce9322 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
@@ -475,11 +475,9 @@ class InlinerTest extends BytecodeTesting {
| def t2 = this.f
|}
""".stripMargin
- val warns = Set(
- "C::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
- "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden")
+ val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
var count = 0
- val List(c, t) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
+ val List(c, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
assert(count == 2, count)
assertInvoke(getMethod(c, "t1"), "T", "f")
assertInvoke(getMethod(c, "t2"), "C", "f")
@@ -520,7 +518,7 @@ class InlinerTest extends BytecodeTesting {
val List(c, oMirror, oModule, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
assert(count == 1, count)
- assertNoInvoke(getMethod(t, "f"))
+ assertNoInvoke(getMethod(t, "f$"))
assertNoInvoke(getMethod(c, "t1"))
assertNoInvoke(getMethod(c, "t2"))
@@ -546,9 +544,9 @@ class InlinerTest extends BytecodeTesting {
val List(assembly, c, t) = compile(code)
- assertNoInvoke(getMethod(t, "f"))
+ assertNoInvoke(getMethod(t, "f$"))
- assertNoInvoke(getMethod(assembly, "n"))
+ assertNoInvoke(getMethod(assembly, "n$"))
assertNoInvoke(getMethod(c, "t1"))
assertNoInvoke(getMethod(c, "t2"))
@@ -624,8 +622,8 @@ class InlinerTest extends BytecodeTesting {
val List(ca, cb, t1, t2a, t2b) = compile(code, allowMessage = i => {count += 1; i.msg contains warning})
assert(count == 4, count) // see comments, f is not inlined 4 times
- assertNoInvoke(getMethod(t2a, "g2a"))
- assertInvoke(getMethod(t2b, "g2b"), "T1", "f")
+ assertNoInvoke(getMethod(t2a, "g2a$"))
+ assertInvoke(getMethod(t2b, "g2b$"), "T1", "f")
assertInvoke(getMethod(ca, "m1a"), "T1", "f")
assertNoInvoke(getMethod(ca, "m2a")) // no invoke, see comment on def g2a
@@ -684,8 +682,8 @@ class InlinerTest extends BytecodeTesting {
|}
""".stripMargin
val List(c, t) = compile(code)
- val t1 = getMethod(t, "t1")
- val t2 = getMethod(t, "t2")
+ val t1 = getMethod(t, "t1$")
+ val t2 = getMethod(t, "t2$")
val cast = TypeOp(CHECKCAST, "C")
Set(t1, t2).foreach(m => assert(m.instructions.contains(cast), m.instructions))
}
@@ -1574,4 +1572,19 @@ class InlinerTest extends BytecodeTesting {
Label(0), LineNumber(9, Label(0)), VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "fx", "()V", false),
Label(4), LineNumber(10, Label(4)), Op(ICONST_1), Op(IRETURN), Label(8)))
}
+
+ @Test
+ def traitHO(): Unit = {
+ val code =
+ """trait T {
+ | def foreach(f: Int => Unit): Unit = f(1)
+ |}
+ |final class C extends T {
+ | def cons(x: Int): Unit = ()
+ | def t1 = foreach(cons)
+ |}
+ """.stripMargin
+ val List(c, t) = compile(code)
+ assertNoIndy(getMethod(c, "t1"))
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
index 4791a29bfb..54f4c805c1 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
@@ -31,6 +31,14 @@ class ScalaInlineInfoTest extends BytecodeTesting {
r.toString
}
+ def assertSameMethods(c: ClassNode, nameAndSigs: Set[String]): Unit = {
+ val r = new StringBuilder
+ val inClass = c.methods.iterator.asScala.map(m => m.name + m.desc).toSet
+ for (m <- inClass.diff(nameAndSigs)) r.append(s"method in classfile found, but no inline info: $m")
+ for (m <- nameAndSigs.diff(inClass)) r.append(s"inline info found, but no method in classfile: $m")
+ assert(r.isEmpty, r.toString)
+ }
+
@Test
def traitMembersInlineInfo(): Unit = {
val code =
@@ -79,26 +87,32 @@ class ScalaInlineInfoTest extends BytecodeTesting {
("T$$super$toString()Ljava/lang/String;", MethodInlineInfo(true ,false,false)),
("T$_setter_$x1_$eq(I)V", MethodInlineInfo(false,false,false)),
("f1()I", MethodInlineInfo(false,false,false)),
- ("f2()I", MethodInlineInfo(true, false,false)),
+ ("f1$(LT;)I", MethodInlineInfo(true ,false,false)),
+ ("f2()I", MethodInlineInfo(true ,false,false)), // no static impl method for private method f2
("f3()I", MethodInlineInfo(false,false,false)),
+ ("f3$(LT;)I", MethodInlineInfo(true ,false,false)),
("f4()Ljava/lang/String;", MethodInlineInfo(false,true, false)),
+ ("f4$(LT;)Ljava/lang/String;", MethodInlineInfo(true ,true, false)),
("f5()I", MethodInlineInfo(true ,false,false)),
- ("f6()I", MethodInlineInfo(false,false,true )),
+ ("f5$(LT;)I", MethodInlineInfo(true ,false,false)),
+ ("f6()I", MethodInlineInfo(false,false,true )), // no static impl method for abstract method f6
("x1()I", MethodInlineInfo(false,false,false)),
("y2()I", MethodInlineInfo(false,false,false)),
("y2_$eq(I)V", MethodInlineInfo(false,false,false)),
("x3()I", MethodInlineInfo(false,false,false)),
("x3_$eq(I)V", MethodInlineInfo(false,false,false)),
("x4()I", MethodInlineInfo(false,false,false)),
+ ("x4$(LT;)I", MethodInlineInfo(true ,false,false)),
("x5()I", MethodInlineInfo(true, false,false)),
("L$lzycompute$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true, false,false)),
("L$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true ,false,false)),
("nest$1()I", MethodInlineInfo(true, false,false)),
- ("$init$()V", MethodInlineInfo(false,false,false))),
+ ("$init$(LT;)V", MethodInlineInfo(true,false,false))),
None // warning
)
assert(infoT == expectT, mapDiff(expectT.methodInfos, infoT.methodInfos) + infoT)
+ assertSameMethods(t, expectT.methodInfos.keySet)
val infoC = inlineInfo(c)
val expectC = InlineInfo(false, None, Map(
@@ -119,6 +133,7 @@ class ScalaInlineInfoTest extends BytecodeTesting {
None)
assert(infoC == expectC, mapDiff(expectC.methodInfos, infoC.methodInfos) + infoC)
+ assertSameMethods(c, expectC.methodInfos.keySet)
}
@Test
@@ -156,7 +171,6 @@ class ScalaInlineInfoTest extends BytecodeTesting {
("F",None),
("T",Some("h(Ljava/lang/String;)I")),
("U",None)))
-
}
@Test
@@ -169,5 +183,6 @@ class ScalaInlineInfoTest extends BytecodeTesting {
"O$lzycompute()LC$O$;" -> MethodInlineInfo(true,false,false),
"O()LC$O$;" -> MethodInlineInfo(true,false,false))
assert(infoC.methodInfos == expected, mapDiff(infoC.methodInfos, expected))
+ assertSameMethods(c, expected.keySet)
}
}
diff --git a/test/junit/scala/tools/testing/BytecodeTesting.scala b/test/junit/scala/tools/testing/BytecodeTesting.scala
index 4ddb6580df..c0fdb8010f 100644
--- a/test/junit/scala/tools/testing/BytecodeTesting.scala
+++ b/test/junit/scala/tools/testing/BytecodeTesting.scala
@@ -12,6 +12,7 @@ import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, MethodNode}
import scala.tools.cmd.CommandLineParser
import scala.tools.nsc.backend.jvm.AsmUtils
import scala.tools.nsc.backend.jvm.AsmUtils._
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.reporters.StoreReporter
import scala.tools.nsc.{Global, Settings}
@@ -247,11 +248,19 @@ object BytecodeTesting {
def getAsmMethod(c: ClassNode, name: String): MethodNode = {
val methods = getAsmMethods(c, name)
+ def fail() = {
+ val allNames = getAsmMethods(c, _ => true).map(_.name)
+ throw new AssertionFailedError(s"Could not find method named $name among ${allNames}")
+ }
methods match {
case List(m) => m
- case ms =>
- val allNames = getAsmMethods(c, _ => true).map(_.name)
- throw new AssertionFailedError(s"Could not find method named $name among ${allNames}")
+ case ms @ List(m1, m2) if BytecodeUtils.isInterface(c) =>
+ val (statics, nonStatics) = ms.partition(BytecodeUtils.isStaticMethod)
+ (statics, nonStatics) match {
+ case (List(staticMethod), List(_)) => m1 // prefer the static method of the pair if methods in traits
+ case _ => fail()
+ }
+ case ms => fail()
}
}