Skip to content

Commit f183cc0

Browse files
committed
Closes #7359 - Processing SAM with overridden methods of Object fails
Dotty used to call the Types.abstractTermMembers method to find all the methods that might be used as candidates for SAM. But the method ignores the methods of java.lang.Object's when processing a list of a term's members and returns all the members that have no implementation. This used to cause the problem when there are overridden java.lang.Object methods in a trait. Such methods receive implementation by default so they cannot be considered as candidates for SAM methods. This problem prevents Dotty from recognising some traits as SAM. Now the Types.possibleSamMethods method is used in order to filter out all the overridden java.lang.Object methods that does not have an implementation yet. This patch applies additional predicate to Types.abstractTermMembers to filter out all the overridden java.lang.Object methods. Signed-off-by: Nikita Eshkeev <[email protected]>
1 parent 87ea7a6 commit f183cc0

21 files changed

+269
-5
lines changed

compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,8 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
846846
def addRemoteRemoteExceptionAnnotation: Unit = ()
847847

848848
def samMethod(): Symbol = ctx.atPhase(ctx.erasurePhase) {
849-
toDenot(sym).info.abstractTermMembers.toList match {
849+
val samMethods = toDenot(sym).info.possibleSamMethods.toList
850+
samMethods match {
850851
case x :: Nil => x.symbol
851852
case Nil => abort(s"${sym.show} is not a functional interface. It doesn't have abstract methods")
852853
case xs => abort(s"${sym.show} is not a functional interface. " +

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import java.lang.ref.WeakReference
3737
import scala.annotation.internal.sharable
3838
import scala.annotation.threadUnsafe
3939

40+
import dotty.tools.dotc.transform.SymUtils._
41+
4042
object Types {
4143

4244
@sharable private var nextId = 0
@@ -767,6 +769,26 @@ object Types {
767769
(name, buf) => buf ++= nonPrivateMember(name).altsWith(_.is(Deferred)))
768770
}
769771

772+
/**
773+
* Returns the set of methods that are abstract and do not overlap with any of
774+
* [[java.lang.Object]] methods.
775+
*
776+
* Conceptually, a SAM (functional interface) has exactly one abstract method.
777+
* If an interface declares an abstract method overriding one of the public
778+
* methods of [[java.lang.Object]], that also does not count toward the interface's
779+
* abstract method count.
780+
*
781+
* @see https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html
782+
*
783+
* @return the set of methods that are abstract and do not match any of [[java.lang.Object]]
784+
*
785+
*/
786+
final def possibleSamMethods(implicit ctx: Context): Seq[SingleDenotation] = {
787+
record("possibleSamMethods")
788+
abstractTermMembers
789+
.filterNot(m => m.symbol.matchingMember(defn.ObjectType).exists || m.symbol.isSuperAccessor)
790+
}
791+
770792
/** The set of abstract type members of this type. */
771793
final def abstractTypeMembers(implicit ctx: Context): Seq[SingleDenotation] = {
772794
record("abstractTypeMembers")
@@ -4369,8 +4391,7 @@ object Types {
43694391
}
43704392
def unapply(tp: Type)(implicit ctx: Context): Option[MethodType] =
43714393
if (isInstantiatable(tp)) {
4372-
val absMems = tp.abstractTermMembers
4373-
// println(s"absMems: ${absMems map (_.show) mkString ", "}")
4394+
val absMems = tp.possibleSamMethods
43744395
if (absMems.size == 1)
43754396
absMems.head.info match {
43764397
case mt: MethodType if !mt.isParamDependent &&

compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class ExpandSAMs extends MiniPhase {
4646
tree
4747
case tpe =>
4848
val tpe1 = checkRefinements(tpe, fn)
49-
val Seq(samDenot) = tpe1.abstractTermMembers.filter(!_.symbol.isSuperAccessor)
49+
val Seq(samDenot) = tpe1.possibleSamMethods
5050
cpy.Block(tree)(stats,
5151
AnonClass(tpe1 :: Nil, fn.symbol.asTerm :: Nil, samDenot.symbol.asTerm.name :: Nil))
5252
}

project/Build.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ object Build {
979979
++ (dir / "shared/src/test/scala/org/scalajs/testsuite/niobuffer" ** (("*.scala": FileFilter) -- "ByteBufferTest.scala")).get
980980
++ (dir / "shared/src/test/scala/org/scalajs/testsuite/niocharset" ** (("*.scala": FileFilter) -- "BaseCharsetTest.scala" -- "Latin1Test.scala" -- "USASCIITest.scala" -- "UTF16Test.scala" -- "UTF8Test.scala")).get
981981
++ (dir / "shared/src/test/scala/org/scalajs/testsuite/scalalib" ** (("*.scala": FileFilter) -- "ArrayBuilderTest.scala" -- "ClassTagTest.scala" -- "EnumerationTest.scala" -- "RangesTest.scala" -- "SymbolTest.scala")).get
982-
++ (dir / "shared/src/test/require-sam" ** (("*.scala": FileFilter) -- "SAMTest.scala")).get
982+
++ (dir / "shared/src/test/require-sam" ** "*.scala").get
983983
++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/compiler" ** (("*.scala": FileFilter) -- "DefaultMethodsTest.scala")).get
984984
++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/javalib/lang" ** "*.scala").get
985985
++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/javalib/util" ** (("*.scala": FileFilter) -- "CollectionsOnCopyOnWriteArrayListTestOnJDK8.scala")).get

tests/neg/i7359-b.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- [E105] Syntax Error: tests/neg/i7359-b.scala:3:6 --------------------------------------------------------------------
2+
3 | def notifyAll(): Unit // error
3+
| ^
4+
| Traits cannot redefine final method notifyAll from class AnyRef.

tests/neg/i7359-b.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait SAMTrait
2+
def first(): String
3+
def notifyAll(): Unit // error

tests/neg/i7359-c.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- [E105] Syntax Error: tests/neg/i7359-c.scala:3:6 --------------------------------------------------------------------
2+
3 | def wait(): Unit // error
3+
| ^
4+
| Traits cannot redefine final method wait from class AnyRef.

tests/neg/i7359-c.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait SAMTrait
2+
def first(): String
3+
def wait(): Unit // error

tests/neg/i7359-d.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- [E105] Syntax Error: tests/neg/i7359-d.scala:3:6 --------------------------------------------------------------------
2+
3 | def wait(a: Long): Unit // error
3+
| ^
4+
| Traits cannot redefine final method wait from class AnyRef.

tests/neg/i7359-d.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait SAMTrait
2+
def first(): String
3+
def wait(a: Long): Unit // error

0 commit comments

Comments
 (0)