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

tests/neg/i7359-e.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-e.scala:3:6 --------------------------------------------------------------------
2+
3 | def wait(a: Long, n: Int): Unit // error
3+
| ^
4+
| Traits cannot redefine final method wait from class AnyRef.

tests/neg/i7359-e.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, n: Int): Unit // error

tests/neg/i7359-f.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E120] Duplicate Symbol Error: tests/neg/i7359-f.scala:1:6 ----------------------------------------------------------
2+
1 |trait SAMTrait // error
3+
| ^
4+
| Name clash between inherited members:
5+
| def equals: [T >: Boolean <: Boolean](obj: Any): T in trait SAMTrait at line 3 and
6+
| def equals(x$0: Any): Boolean in class Any
7+
| have the same type after erasure.

tests/neg/i7359-f.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait SAMTrait // error
2+
def first(): String
3+
def equals[T >: Boolean <: Boolean](obj: Any): T

tests/neg/i7359-g.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- [E007] Type Mismatch Error: tests/neg/i7359-g.scala:5:25 ------------------------------------------------------------
2+
5 |val m : SAMTrait = () => "Hello" // error
3+
| ^^^^^^^
4+
| Found: () => String
5+
| Required: SAMTrait

tests/neg/i7359-g.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
trait SAMTrait
2+
def first(): String
3+
def equals(obj: Int): Boolean
4+
5+
val m : SAMTrait = () => "Hello" // error

tests/neg/i7359.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.scala:3:6 ----------------------------------------------------------------------
2+
3 | def notify(): Unit // error
3+
| ^
4+
| Traits cannot redefine final method notify from class AnyRef.

tests/neg/i7359.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 notify(): Unit // error

tests/pos/i7359.scala

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package test
2+
3+
trait ObjectInterface
4+
def equals(obj: Any): Boolean
5+
def hashCode(): Int
6+
def toString(): String
7+
8+
trait SAMPlain
9+
def first(): String
10+
11+
trait SAMPlainWithOverriddenObjectMethods
12+
def first(): String
13+
def equals(obj: Any): Boolean
14+
def hashCode(): Int
15+
def toString(): String
16+
17+
trait SAMPlainWithExtends extends ObjectInterface
18+
def first(): String
19+
20+
trait SAMPlainWithExtendsAndOverride extends ObjectInterface
21+
def first(): String
22+
override def equals(obj: Any): Boolean
23+
override def hashCode(): Int
24+
override def toString(): String
25+
26+
trait SAMPlainCovariantOut[+O]
27+
def first(): O
28+
29+
trait SAMCovariantOut[+O]
30+
def first(): O
31+
def equals(obj: Any): Boolean
32+
def hashCode(): Int
33+
def toString(): String
34+
35+
trait SAMCovariantOutExtends[+O] extends ObjectInterface
36+
def first(): O
37+
38+
trait SAMCovariantOutExtendsAndOverride[+O] extends ObjectInterface
39+
def first(): O
40+
override def equals(obj: Any): Boolean
41+
override def hashCode(): Int
42+
override def toString(): String
43+
44+
trait SAMPlainContravariantIn[-I]
45+
def first(in: I): Unit
46+
47+
trait SAMContravariantIn[-I]
48+
def first(in: I): Unit
49+
def equals(obj: Any): Boolean
50+
def hashCode(): Int
51+
def toString(): String
52+
53+
trait SAMContravariantInExtends[-I] extends ObjectInterface
54+
def first(in: I): Unit
55+
56+
trait SAMContravariantInExtendsAndOverride[-I] extends ObjectInterface
57+
def first(in: I): Unit
58+
override def equals(obj: Any): Boolean
59+
override def hashCode(): Int
60+
override def toString(): String
61+
62+
trait SAMPlainInvariant[T]
63+
def first(in: T): T
64+
65+
trait SAMInvariant[T]
66+
def first(in: T): T
67+
def equals(obj: Any): Boolean
68+
def hashCode(): Int
69+
def toString(): String
70+
71+
trait SAMInvariantExtends[T] extends ObjectInterface
72+
def first(in: T): T
73+
74+
trait SAMInvariantExtendsAndOverride[T] extends ObjectInterface
75+
def first(in: T): T
76+
override def equals(obj: Any): Boolean
77+
override def hashCode(): Int
78+
override def toString(): String
79+
80+
trait SAMPlainInOut[-I, +O]
81+
def first(in: I): O
82+
83+
trait SAMInOut[-I, +O]
84+
def first(in: I): O
85+
def equals(obj: Any): Boolean
86+
def hashCode(): Int
87+
def toString(): String
88+
89+
trait SAMInOutExtends[-I, +O] extends ObjectInterface
90+
def first(in: I): O
91+
92+
trait SAMInOutExtendsAndOverride[-I, +O] extends ObjectInterface
93+
def first(in: I): O
94+
override def equals(obj: Any): Boolean
95+
override def hashCode(): Int
96+
override def toString(): String
97+
98+
type CustomString = String
99+
type CustomBoolean = Boolean
100+
type CustomInt = Int
101+
102+
trait SAMWithCustomAliases
103+
def first(): String
104+
def equals(obj: Any): CustomBoolean
105+
def hashCode(): CustomInt
106+
def toString(): CustomString
107+
108+
object Main
109+
def main(args: Array[String]) =
110+
val samPlain : SAMPlain = () => "Hello, World!"
111+
val samPlainWithOverriddenObjectMethods: SAMPlainWithOverriddenObjectMethods = () => "Hello, World!"
112+
val samPlainWithExtends : SAMPlainWithExtends = () => "Hello, World!"
113+
val samPlainWithExtendsAndOverride : SAMPlainWithExtendsAndOverride = () => "Hello, World!"
114+
115+
val samPlainCovariantOut : SAMPlainCovariantOut[_] = () => "Hello, World!"
116+
val samCovariantOut : SAMCovariantOut[_] = () => "Hello, World!"
117+
val samCovariantOutExtends : SAMCovariantOutExtends[_] = () => "Hello, World!"
118+
val samCovariantOutExtendsAndOverride : SAMCovariantOutExtendsAndOverride[_] = () => "Hello, World!"
119+
120+
val samPlainContravariantIn : SAMPlainContravariantIn[Int] = (x: Int) => ()
121+
val samContravariantIn : SAMContravariantIn[Int] = (x: Int) => ()
122+
val samContravariantInExtends : SAMContravariantInExtends[Int] = (x: Int) => ()
123+
val samContravariantInExtendsAndOverride : SAMContravariantInExtendsAndOverride[Int] = (x: Int) => ()
124+
125+
val samPlainInvariant : SAMPlainInvariant[String] = (x: String) => x
126+
val samInvariant : SAMInvariant[String] = (x: String) => x
127+
val samInvariantExtends : SAMInvariantExtends[String] = (x: String) => x
128+
val samInvariantExtendsAndOverride : SAMInvariantExtendsAndOverride[String] = (x: String) => x
129+
130+
val samPlainInOut : SAMPlainInOut[Int, String] = (x: Int) => x.toString
131+
val samInOut : SAMInOut[Int, String] = (x: Int) => x.toString
132+
val samInOutExtends : SAMInOutExtends[Int, String] = (x: Int) => x.toString
133+
val samInOutExtendsAndOverride : SAMInOutExtendsAndOverride[Int, String] = (x: Int) => x.toString
134+
135+
val samWithCustomAliases : SAMWithCustomAliases = () => "Hello, World!"

tests/run/i7359.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
o
2+
o
3+
()
4+
i
5+
10
6+
o

tests/run/i7359.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
trait ObjectInterface
2+
def equals(obj: Any): Boolean
3+
def hashCode(): Int
4+
def toString(): String
5+
6+
trait SAMPlainWithExtends extends ObjectInterface
7+
def first(): String
8+
9+
trait SAMCovariantOutExtends[+O] extends ObjectInterface
10+
def first(): O
11+
12+
trait SAMContravariantInExtends[-I] extends ObjectInterface
13+
def first(in: I): Unit
14+
15+
trait SAMInvariantExtends[T] extends ObjectInterface
16+
def first(in: T): T
17+
18+
trait SAMInOutExtends[-I, +O] extends ObjectInterface
19+
def first(in: I): O
20+
21+
type CustomString = String
22+
type CustomBoolean = Boolean
23+
type CustomInt = Int
24+
25+
trait SAMWithCustomAliases
26+
def first(): String
27+
def equals(obj: Any): CustomBoolean
28+
def hashCode(): CustomInt
29+
def toString(): CustomString
30+
31+
object Test
32+
33+
def main(args: Array[String]): Unit =
34+
val samPlainWithExtends : SAMPlainWithExtends = () => "o"
35+
val samCovariantOutExtends : SAMCovariantOutExtends[_] = () => "o"
36+
val samContravariantInExtends : SAMContravariantInExtends[Int] = (x: Int) => ()
37+
val samInvariantExtends : SAMInvariantExtends[String] = (x: String) => x
38+
val samInOutExtends : SAMInOutExtends[Int, String] = (x: Int) => x.toString
39+
val samWithCustomAliases : SAMWithCustomAliases = () => "o"
40+
41+
println(samPlainWithExtends.first())
42+
println(samCovariantOutExtends.first())
43+
println(samContravariantInExtends.first(10))
44+
println(samInvariantExtends.first("i"))
45+
println(samInOutExtends.first(10))
46+
println(samWithCustomAliases.first())

0 commit comments

Comments
 (0)