Skip to content

Commit 7a09dcf

Browse files
committed
Allow BCode to emit default interface methods
A DefDef owned by a trait may now have have a method body. Such a method must be emitted without ACC_ABSTRACT, and with the code attribute. Tested by intercepting the compile pipeline and adding the DEFAULTMETHOD flag and a method body before running the backend.
1 parent f6756ea commit 7a09dcf

File tree

6 files changed

+67
-5
lines changed

6 files changed

+67
-5
lines changed

src/compiler/scala/tools/nsc/backend/icode/Members.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package backend
99
package icode
1010

1111
import scala.collection.{ mutable, immutable }
12+
import scala.reflect.internal.Flags
1213
import scala.reflect.internal.util.{ SourceFile, NoSourceFile }
1314

1415
trait ReferenceEquality {
@@ -217,7 +218,7 @@ trait Members {
217218

218219
/** Is this method deferred ('abstract' in Java sense)?
219220
*/
220-
def isAbstractMethod = symbol.isDeferred || symbol.owner.isInterface || native
221+
def isAbstractMethod = symbol.isDeferred || (symbol.owner.isInterface && !symbol.hasFlag(Flags.DEFAULTMETHOD)) || native
221222

222223
def isStatic: Boolean = symbol.isStaticMember
223224

src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,10 +587,10 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
587587
}
588588

589589
val isNative = methSymbol.hasAnnotation(definitions.NativeAttr)
590-
val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface)
590+
val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface) && !methSymbol.hasFlag(Flags.DEFAULTMETHOD)
591591
val flags = GenBCode.mkFlags(
592592
javaFlags(methSymbol),
593-
if (claszSymbol.isInterface) asm.Opcodes.ACC_ABSTRACT else 0,
593+
if (isAbstractMethod) asm.Opcodes.ACC_ABSTRACT else 0,
594594
if (methSymbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0,
595595
if (isNative) asm.Opcodes.ACC_NATIVE else 0 // native methods of objects are generated in mirror classes
596596
)

src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
560560
import asm.Opcodes._
561561
GenBCode.mkFlags(
562562
if (privateFlag) ACC_PRIVATE else ACC_PUBLIC,
563-
if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0,
563+
if ((sym.isDeferred && !sym.hasFlag(symtab.Flags.DEFAULTMETHOD))|| sym.hasAbstractFlag) ACC_ABSTRACT else 0,
564564
if (sym.isInterface) ACC_INTERFACE else 0,
565565
if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0,
566566
if (sym.isStaticMember) ACC_STATIC else 0,

src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1391,10 +1391,11 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
13911391
var resTpe: asm.Type = javaType(m.symbol.tpe.resultType)
13921392
if (m.symbol.isClassConstructor)
13931393
resTpe = asm.Type.VOID_TYPE
1394+
val isAbstractTraitMeth = isJInterface && !m.symbol.hasFlag(Flags.DEFAULTMETHOD)
13941395

13951396
val flags = mkFlags(
13961397
javaFlags(m.symbol),
1397-
if (isJInterface) asm.Opcodes.ACC_ABSTRACT else 0,
1398+
if (isAbstractTraitMeth) asm.Opcodes.ACC_ABSTRACT else 0,
13981399
if (m.symbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0,
13991400
if (method.native) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes
14001401
if(isDeprecated(m.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag

test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,23 @@ object CodeGenTools {
9393
getGeneratedClassfiles(compiler.settings.outputDirs.getSingleOutput.get)
9494
}
9595

96+
def compileTransformed(compiler: Global)(scalaCode: String, javaCode: List[(String, String)] = Nil, beforeBackend: compiler.Tree => compiler.Tree): List[(String, Array[Byte])] = {
97+
compiler.settings.stopBefore.value = "jvm" :: Nil
98+
val run = newRun(compiler)
99+
import compiler._
100+
val scalaUnit = newCompilationUnit(scalaCode, "unitTestSource.scala")
101+
val javaUnits = javaCode.map(p => newCompilationUnit(p._1, p._2))
102+
val units = scalaUnit :: javaUnits
103+
run.compileUnits(units, run.parserPhase)
104+
compiler.settings.stopBefore.value = Nil
105+
scalaUnit.body = beforeBackend(scalaUnit.body)
106+
checkReport(compiler, _ => false)
107+
val run1 = newRun(compiler)
108+
run1.compileUnits(units, run1.phaseNamed("jvm"))
109+
checkReport(compiler, _ => false)
110+
getGeneratedClassfiles(compiler.settings.outputDirs.getSingleOutput.get)
111+
}
112+
96113
/**
97114
* Compile multiple Scala files separately into a single output directory.
98115
*
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package scala.tools.nsc.backend.jvm
2+
3+
import org.junit.Assert._
4+
import org.junit.Test
5+
6+
import scala.collection.JavaConverters
7+
import scala.tools.asm.Opcodes
8+
import scala.tools.asm.tree.ClassNode
9+
import scala.tools.nsc.backend.jvm.CodeGenTools._
10+
import JavaConverters._
11+
import scala.tools.testing.ClearAfterClass
12+
13+
object DefaultMethodTest extends ClearAfterClass.Clearable {
14+
var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode")
15+
def clear(): Unit = { compiler = null }
16+
}
17+
18+
class DefaultMethodTest extends ClearAfterClass {
19+
ClearAfterClass.stateToClear = DirectCompileTest
20+
val compiler = DirectCompileTest.compiler
21+
22+
@Test
23+
def defaultMethodsViaGenBCode(): Unit = {
24+
import compiler._
25+
val code = "package pack { trait T { def foo: Int }}"
26+
object makeFooDefaultMethod extends Transformer {
27+
val Foo = TermName("foo")
28+
/** Transforms a single tree. */
29+
override def transform(tree: compiler.Tree): compiler.Tree = tree match {
30+
case dd @ DefDef(_, Foo, _, _, _, _) =>
31+
dd.symbol.setFlag(reflect.internal.Flags.DEFAULTMETHOD)
32+
copyDefDef(dd)(rhs = Literal(Constant(1)).setType(definitions.IntTpe))
33+
case _ => super.transform(tree)
34+
}
35+
}
36+
val asmClasses: List[ClassNode] = readAsmClasses(compileTransformed(compiler)(code, Nil, makeFooDefaultMethod.transform(_)))
37+
val foo = asmClasses.head.methods.iterator.asScala.toList.last
38+
assertTrue("default method should not be abstract", (foo.access & Opcodes.ACC_ABSTRACT) == 0)
39+
assertTrue("default method body emitted", foo.instructions.size() > 0)
40+
}
41+
42+
43+
}

0 commit comments

Comments
 (0)