Skip to content

Commit 93f209d

Browse files
committed
More efficient code for deciding if a mixin forwarder is needed (scala#5116)
Also adds a warning on junit test methods that compile as default methods.
1 parent 2c1d1ad commit 93f209d

File tree

8 files changed

+42
-16
lines changed

8 files changed

+42
-16
lines changed

src/compiler/scala/tools/nsc/settings/Warnings.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ trait Warnings {
2525
// currently considered too noisy for general use
2626
val warnUnusedImport = BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.")
2727

28+
val nowarnDefaultJunitMethods = BooleanSetting("-Ynowarn-default-junit-methods", "Don't warn when a JUnit @Test method is generated as a default method (not supported in JUnit 4).")
29+
2830
// Experimental lint warnings that are turned off, but which could be turned on programmatically.
2931
// They are not activated by -Xlint and can't be enabled on the command line because they are not
3032
// created using the standard factory methods.

src/compiler/scala/tools/nsc/transform/Mixin.scala

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package transform
88

99
import symtab._
1010
import Flags._
11+
import scala.annotation.tailrec
1112
import scala.collection.mutable
1213

1314
abstract class Mixin extends InfoTransform with ast.TreeDSL {
@@ -241,22 +242,16 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
241242
case NoSymbol =>
242243
val isMemberOfClazz = clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives.contains(member)
243244
if (isMemberOfClazz) {
244-
val competingMethods = clazz.baseClasses.iterator
245-
.filter(_ ne mixinClass)
246-
.map(member.overriddenSymbol)
247-
.filter(_.exists)
248-
.toList
249-
250-
// `member` is a concrete `method` defined in `mixinClass`, which is a base class of
245+
// `member` is a concrete method defined in `mixinClass`, which is a base class of
251246
// `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if:
252247
//
253-
// - A non-trait base class defines matching method. Example:
248+
// - A non-trait base class of `clazz` defines a matching method. Example:
254249
// class C {def f: Int}; trait T extends C {def f = 1}; class D extends T
255250
// Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would
256251
// resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual.
257252
//
258-
// - There exists another concrete, matching method in any of the base classes, and
259-
// the `mixinClass` does not itself extend that base class. In this case the
253+
// - There exists another concrete, matching method in a parent interface `p` of
254+
// `clazz`, and the `mixinClass` does not itself extend `p`. In this case the
260255
// forwarder is needed to disambiguate. Example:
261256
// trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2
262257
// In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves
@@ -265,13 +260,25 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
265260
// In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM
266261
// level.
267262

268-
lazy val mixinSuperInterfaces = mixinClass.ancestors.filter(_.isTraitOrInterface)
269-
val needsForwarder = competingMethods.exists(m => {
270-
!m.owner.isTraitOrInterface ||
271-
(!m.isDeferred && !mixinSuperInterfaces.contains(m.owner))
272-
})
273-
if (needsForwarder)
263+
@tailrec
264+
def existsCompetingMethod(baseClasses: List[Symbol]): Boolean = baseClasses match {
265+
case baseClass :: rest =>
266+
if (baseClass ne mixinClass) {
267+
val m = member.overriddenSymbol(baseClass)
268+
val isCompeting = m.exists && {
269+
!m.owner.isTraitOrInterface ||
270+
(!m.isDeferred && !mixinClass.isNonBottomSubClass(m.owner))
271+
}
272+
isCompeting || existsCompetingMethod(rest)
273+
} else existsCompetingMethod(rest)
274+
275+
case _ => false
276+
}
277+
278+
if (existsCompetingMethod(clazz.baseClasses))
274279
cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member
280+
else if (!settings.nowarnDefaultJunitMethods && JUnitTestClass.exists && member.hasAnnotation(JUnitTestClass))
281+
warning(member.pos, "JUnit tests in traits that are compiled as default methods are not executed by JUnit 4. JUnit 5 will fix this issue.")
275282
}
276283

277284
case _ =>

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,7 @@ trait Definitions extends api.StandardDefinitions {
11521152
lazy val ElidableMethodClass = requiredClass[scala.annotation.elidable]
11531153
lazy val ImplicitNotFoundClass = requiredClass[scala.annotation.implicitNotFound]
11541154
lazy val ImplicitAmbiguousClass = getClassIfDefined("scala.annotation.implicitAmbiguous")
1155+
lazy val JUnitTestClass = getClassIfDefined("org.junit.Test")
11551156
lazy val MigrationAnnotationClass = requiredClass[scala.annotation.migration]
11561157
lazy val ScalaStrictFPAttr = requiredClass[scala.annotation.strictfp]
11571158
lazy val SwitchClass = requiredClass[scala.annotation.switch]

src/reflect/scala/reflect/runtime/JavaUniverseForce.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
377377
definitions.ElidableMethodClass
378378
definitions.ImplicitNotFoundClass
379379
definitions.ImplicitAmbiguousClass
380+
definitions.JUnitTestClass
380381
definitions.MigrationAnnotationClass
381382
definitions.ScalaStrictFPAttr
382383
definitions.SwitchClass
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
C_1.scala:2: warning: JUnit tests in traits that are compiled as default methods are not executed by JUnit 4. JUnit 5 will fix this issue.
2+
@org.junit.Test def foo = 0
3+
^
4+
error: No warnings can be incurred under -Xfatal-warnings.
5+
one warning found
6+
one error found
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xfatal-warnings
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
trait T {
2+
@org.junit.Test def foo = 0
3+
}
4+
5+
class C extends T
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package org.junit;
2+
3+
public @interface Test { }

0 commit comments

Comments
 (0)