summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@typesafe.com>2016-03-17 11:56:14 -0700
committerAdriaan Moors <adriaan.moors@typesafe.com>2016-03-26 22:54:10 -0700
commitf922f367d58b3ba6bbb4cb0864ce82c5cd6f7966 (patch)
tree18a9cf588cd9e6dbe0a3815258f4dede2af3c772
parent040c0434d456dd75a174147d8a0c4cab37266ba6 (diff)
downloadscala-f922f367d58b3ba6bbb4cb0864ce82c5cd6f7966.tar.gz
scala-f922f367d58b3ba6bbb4cb0864ce82c5cd6f7966.tar.bz2
scala-f922f367d58b3ba6bbb4cb0864ce82c5cd6f7966.zip
Additional SAM restrictions identified by Jason
Also test roundtripping serialization of a lambda that targets a SAM that's not FunctionN (it should make no difference).
-rw-r--r--spec/06-expressions.md7
-rw-r--r--src/reflect/scala/reflect/internal/Definitions.scala25
-rw-r--r--test/files/neg/sammy_error_exist_no_crash.scala4
-rw-r--r--test/files/neg/sammy_restrictions.check55
-rw-r--r--test/files/neg/sammy_restrictions.scala63
-rw-r--r--test/files/pos/sammy_implicit.scala3
-rw-r--r--test/files/pos/sammy_overload.scala27
-rw-r--r--test/files/pos/sammy_poly.scala12
-rw-r--r--test/files/pos/sammy_scope.scala4
-rw-r--r--test/files/run/lambda-serialization.scala14
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala2
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala6
12 files changed, 139 insertions, 83 deletions
diff --git a/spec/06-expressions.md b/spec/06-expressions.md
index f69c75bb96..2b93842a25 100644
--- a/spec/06-expressions.md
+++ b/spec/06-expressions.md
@@ -1365,8 +1365,11 @@ An expression `(p1, ..., pN) => body` of function type `(T1, ..., TN) => T` is s
It follows that:
- the type `S` must have an accessible, no-argument, constructor;
- - the class of `S` must not be `@specialized`;
- - the class of `S` must not be nested or local (it must not capture its environment).
+ - the class of `S` must not be nested or local (it must not capture its environment, as that precludes a zero-argument constructor).
+
+Additionally (the following are implementation restrictions):
+ - `S`'s [erases](03-types.html#type-erasure) to a trait (this allows for a more efficient encoding when the JVM is the underlying platform);
+ - the class of `S` must not be `@specialized`.
### Method Conversions
diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala
index 2fa39bc453..ef9a76f9c4 100644
--- a/src/reflect/scala/reflect/internal/Definitions.scala
+++ b/src/reflect/scala/reflect/internal/Definitions.scala
@@ -844,13 +844,26 @@ trait Definitions extends api.StandardDefinitions {
* has a public no-arg primary constructor.
*/
def samOf(tp: Type): Symbol = {
- // if tp has a constructor, it must be public and must not take any arguments
- // (not even an implicit argument list -- to keep it simple for now)
- val tpSym = tp.typeSymbol
- val ctor = tpSym.primaryConstructor
- val ctorOk = !ctor.exists || (!ctor.isOverloaded && ctor.isPublic && ctor.info.params.isEmpty && ctor.info.paramSectionCount <= 1)
+ // look at erased type because we (only) care about what ends up in bytecode
+ // (e.g., an alias type or intersection type is fine as long as the intersection dominator compiles to an interface)
+ val tpSym = erasure.javaErasure(tp).typeSymbol
+
+ if (tpSym.exists
+ // We use Java's MetaLambdaFactory, which requires an interface for the sam's owner
+ // (TODO: Can't use isInterface, yet, as it hasn't been updated for the new trait encoding)
+ && (tpSym.isJavaInterface || tpSym.isTrait)
+ // explicit outer precludes no-arg ctor
+ && tpSym.isStatic
+ // impl restriction -- we currently use the boxed apply, so not really useful to allow specialized sam types (https://github.com/scala/scala/pull/4971#issuecomment-198119167)
+ && !tpSym.isSpecialized) {
+
+ // this does not apply yet, since traits don't have constructors during type checking
+ // if tp has a constructor, it must be public and must not take any arguments
+ // (not even an implicit argument list -- to keep it simple for now)
+ // && { val ctor = tpSym.primaryConstructor
+ // !ctor.exists || (!ctor.isOverloaded && ctor.isPublic && ctor.info.params.isEmpty && ctor.info.paramSectionCount <= 1)
+ // }
- if (tpSym.exists && ctorOk) {
// find the single abstract member, if there is one
// don't go out requiring DEFERRED members, as you will get them even if there's a concrete override:
// scala> abstract class X { def m: Int }
diff --git a/test/files/neg/sammy_error_exist_no_crash.scala b/test/files/neg/sammy_error_exist_no_crash.scala
index da7e47206f..667b4db763 100644
--- a/test/files/neg/sammy_error_exist_no_crash.scala
+++ b/test/files/neg/sammy_error_exist_no_crash.scala
@@ -1,6 +1,6 @@
-abstract class F[T] { def apply(s: T): Int }
+trait F[T] { def apply(s: T): Int }
object NeedsNiceError {
def bar(x: F[_ >: String]) = ???
bar(_.parseInt)
-} \ No newline at end of file
+}
diff --git a/test/files/neg/sammy_restrictions.check b/test/files/neg/sammy_restrictions.check
index 0276f3a067..5fd2c858c2 100644
--- a/test/files/neg/sammy_restrictions.check
+++ b/test/files/neg/sammy_restrictions.check
@@ -1,46 +1,37 @@
-sammy_restrictions.scala:31: error: type mismatch;
+sammy_restrictions.scala:37: error: type mismatch;
found : () => Int
required: NoAbstract
- (() => 0) : NoAbstract
+ (() => 0) : NoAbstract // error expected
^
-sammy_restrictions.scala:32: error: type mismatch;
- found : Int => Int
- required: TwoAbstract
- ((x: Int) => 0): TwoAbstract
- ^
-sammy_restrictions.scala:35: error: type mismatch;
- found : Int => Int
- required: NoEmptyConstructor
- ((x: Int) => 0): NoEmptyConstructor
- ^
-sammy_restrictions.scala:37: error: type mismatch;
- found : Int => Int
- required: OneEmptySecondaryConstructor
- ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call.
- ^
sammy_restrictions.scala:38: error: type mismatch;
found : Int => Int
- required: MultipleConstructorLists
- ((x: Int) => 0): MultipleConstructorLists
+ required: TwoAbstract
+ ((x: Int) => 0): TwoAbstract // error expected
^
-sammy_restrictions.scala:39: error: type mismatch;
+sammy_restrictions.scala:41: error: type mismatch;
found : Int => Int
required: MultipleMethodLists
- ((x: Int) => 0): MultipleMethodLists
+ ((x: Int) => 0): MultipleMethodLists // error expected
^
-sammy_restrictions.scala:40: error: type mismatch;
- found : Int => Int
- required: ImplicitConstructorParam
- ((x: Int) => 0): ImplicitConstructorParam
- ^
-sammy_restrictions.scala:41: error: type mismatch;
+sammy_restrictions.scala:42: error: type mismatch;
found : Int => Int
required: ImplicitMethodParam
- ((x: Int) => 0): ImplicitMethodParam
+ ((x: Int) => 0): ImplicitMethodParam // error expected
^
-sammy_restrictions.scala:44: error: type mismatch;
+sammy_restrictions.scala:45: error: type mismatch;
found : Int => Int
required: PolyMethod
- ((x: Int) => 0): PolyMethod
- ^
-9 errors found
+ ((x: Int) => 0): PolyMethod // error expected
+ ^
+sammy_restrictions.scala:47: error: missing parameter type
+ (x => x + 1): NotAnInterface[Int, Int] // error expected (not an interface)
+ ^
+sammy_restrictions.scala:48: error: type mismatch;
+ found : String => Int
+ required: A[Object,Int]
+ ((x: String) => 1): A[Object, Int] // error expected (type mismatch)
+ ^
+sammy_restrictions.scala:51: error: missing parameter type
+ n.app(1)(x => List(x)) // error expected: n.F is not a SAM type (it does not have a no-arg ctor since it has an outer pointer)
+ ^
+8 errors found
diff --git a/test/files/neg/sammy_restrictions.scala b/test/files/neg/sammy_restrictions.scala
index 101342ad0b..ed8cf35aa4 100644
--- a/test/files/neg/sammy_restrictions.scala
+++ b/test/files/neg/sammy_restrictions.scala
@@ -1,45 +1,52 @@
-abstract class NoAbstract
+trait NoAbstract
-abstract class TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int }
+trait TwoAbstract { def ap(a: Int): Int; def pa(a: Int): Int }
-abstract class Base // check that the super class constructor isn't considered.
-abstract class NoEmptyConstructor(a: Int) extends Base { def this(a: String) = this(0); def ap(a: Int): Int }
+trait Base // check that the super class constructor isn't considered.
-abstract class OneEmptyConstructor() { def this(a: Int) = this(); def ap(a: Int): Int }
+trait MultipleMethodLists { def ap(a: Int)(): Int }
-abstract class OneEmptySecondaryConstructor(a: Int) { def this() = this(0); def ap(a: Int): Int }
+trait ImplicitMethodParam { def ap(a: Int)(implicit b: String): Int }
-abstract class MultipleConstructorLists()() { def ap(a: Int): Int }
+trait PolyClass[T] { def ap(a: T): T }
-abstract class MultipleMethodLists()() { def ap(a: Int)(): Int }
+trait PolyMethod { def ap[T](a: T): T }
-abstract class ImplicitConstructorParam()(implicit a: String) { def ap(a: Int): Int }
+trait OneAbstract { def ap(a: Int): Any }
+trait DerivedOneAbstract extends OneAbstract
-abstract class ImplicitMethodParam() { def ap(a: Int)(implicit b: String): Int }
+// restrictions
-abstract class PolyClass[T] { def ap(a: T): T }
+// must be an interface
+abstract class NotAnInterface[T, R]{ def apply(x: T): R }
-abstract class PolyMethod { def ap[T](a: T): T }
+trait A[T, R]{ def apply(x: T): R }
+
+// must not capture
+class Nested {
+ trait F[T, U] { def apply(x: T): U }
+
+ def app[T, U](x: T)(f: F[T, U]): U = f(x)
+}
-abstract class OneAbstract { def ap(a: Int): Any }
-abstract class DerivedOneAbstract extends OneAbstract
object Test {
implicit val s: String = ""
type NonClassType = DerivedOneAbstract with OneAbstract
- (() => 0) : NoAbstract
- ((x: Int) => 0): TwoAbstract
- ((x: Int) => 0): DerivedOneAbstract // okay
- ((x: Int) => 0): NonClassType // okay -- we also allow type aliases in instantiation expressions, if they resolve to a class type
- ((x: Int) => 0): NoEmptyConstructor
- ((x: Int) => 0): OneEmptyConstructor // okay
- ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call.
- ((x: Int) => 0): MultipleConstructorLists
- ((x: Int) => 0): MultipleMethodLists
- ((x: Int) => 0): ImplicitConstructorParam
- ((x: Int) => 0): ImplicitMethodParam
-
- ((x: Int) => 0): PolyClass[Int] // okay
- ((x: Int) => 0): PolyMethod
+ (() => 0) : NoAbstract // error expected
+ ((x: Int) => 0): TwoAbstract // error expected
+ ((x: Int) => 0): DerivedOneAbstract
+ ((x: Int) => 0): NonClassType
+ ((x: Int) => 0): MultipleMethodLists // error expected
+ ((x: Int) => 0): ImplicitMethodParam // error expected
+
+ ((x: Int) => 0): PolyClass[Int]
+ ((x: Int) => 0): PolyMethod // error expected
+
+ (x => x + 1): NotAnInterface[Int, Int] // error expected (not an interface)
+ ((x: String) => 1): A[Object, Int] // error expected (type mismatch)
+
+ val n = new Nested
+ n.app(1)(x => List(x)) // error expected: n.F is not a SAM type (it does not have a no-arg ctor since it has an outer pointer)
}
diff --git a/test/files/pos/sammy_implicit.scala b/test/files/pos/sammy_implicit.scala
index e4b82df4cc..ab63fc729e 100644
--- a/test/files/pos/sammy_implicit.scala
+++ b/test/files/pos/sammy_implicit.scala
@@ -1,5 +1,6 @@
+trait Fun[A, B] { def apply(a: A): B }
+
abstract class SamImplicitConvert {
- trait Fun[A, B] { def apply(a: A): B }
class Lst[T]
abstract class Str { def getBytes: Array[Int] }
def flatMap[B](f: Fun[Str, Lst[B]]): List[B] = ???
diff --git a/test/files/pos/sammy_overload.scala b/test/files/pos/sammy_overload.scala
index 5472248f4d..6a3c88ec55 100644
--- a/test/files/pos/sammy_overload.scala
+++ b/test/files/pos/sammy_overload.scala
@@ -6,4 +6,29 @@ object Test {
def foo(x: String): Unit = ???
def foo(): Unit = ???
val f: Consumer[_ >: String] = foo
-} \ No newline at end of file
+}
+
+trait A[A, B] { def apply(a: A): B }
+
+class ArityDisambiguate {
+ object O {
+ def m(a: A[Int, Int]) = 0
+ def m(f: (Int, Int) => Int) = 1
+ }
+
+ O.m(x => x) // ok
+ O.m((x, y) => x) // ok
+}
+
+class InteractionWithImplicits {
+ object O {
+ class Ev
+ implicit object E1 extends Ev
+ implicit object E2 extends Ev
+ def m(a: A[Int, Int])(implicit ol: E1.type) = 0
+ def m(a: A[String, Int])(implicit ol: E2.type) = 1
+ }
+
+ O.m((x: Int) => 1) // ok
+ O.m((x: String) => 1) // ok
+}
diff --git a/test/files/pos/sammy_poly.scala b/test/files/pos/sammy_poly.scala
index 75ee36f654..ba10baea49 100644
--- a/test/files/pos/sammy_poly.scala
+++ b/test/files/pos/sammy_poly.scala
@@ -1,8 +1,12 @@
// test synthesizeSAMFunction where the sam type is not fully defined
-class T {
- trait F[T, U] { def apply(x: T): U }
-// type F[T, U] = T => U
- // NOTE: the f(x) desugaring for now assumes the single abstract method is called 'apply'
+trait F[T, R]{ def apply(x: T): R }
+
+class PolySammy {
+ (x => x + 1): F[Int, Int]
+ ((x: Int) => x + 1): F[Int, Int]
+ ((x: String) => 1): F[String, Int]
+ ((x: Object) => 1): F[String, Int]
+
def app[T, U](x: T)(f: F[T, U]): U = f(x)
app(1)(x => List(x))
}
diff --git a/test/files/pos/sammy_scope.scala b/test/files/pos/sammy_scope.scala
index 8f1fe7058e..9d35501a47 100644
--- a/test/files/pos/sammy_scope.scala
+++ b/test/files/pos/sammy_scope.scala
@@ -1,8 +1,8 @@
// test synthesizeSAMFunction: scope hygiene
-abstract class SamFun[T1, R] { self =>
+trait SamFun[T1, R] { self =>
def apply(v1: T1): R
// this should type check, as the apply ref is equivalent to self.apply
// it shouldn't resolve to the sam's apply that's synthesized (that wouldn't type check, hence the pos test)
def compose[A](g: SamFun[A, T1]): SamFun[A, R] = { x => apply(g(x)) }
-} \ No newline at end of file
+}
diff --git a/test/files/run/lambda-serialization.scala b/test/files/run/lambda-serialization.scala
index 46b26d7c5e..0eee1193d7 100644
--- a/test/files/run/lambda-serialization.scala
+++ b/test/files/run/lambda-serialization.scala
@@ -1,8 +1,11 @@
import java.io.{ByteArrayInputStream, ObjectInputStream, ObjectOutputStream, ByteArrayOutputStream}
+trait IntToString { def apply(i: Int): String }
+
object Test {
def main(args: Array[String]): Unit = {
- roundTrip
+ roundTrip()
+ roundTripIndySam()
}
def roundTrip(): Unit = {
@@ -22,6 +25,15 @@ object Test {
assert(serializeDeserialize(serializeDeserialize(specializedLambda)).apply(42) == 2)
}
+ // lambda targeting a SAM, not a FunctionN (should behave the same way)
+ def roundTripIndySam(): Unit = {
+ val lambda: IntToString = (x: Int) => "yo!" * x
+ val reconstituted1 = serializeDeserialize(lambda).asInstanceOf[IntToString]
+ val reconstituted2 = serializeDeserialize(reconstituted1).asInstanceOf[IntToString]
+ assert(reconstituted1.apply(2) == "yo!yo!")
+ assert(reconstituted1.getClass == reconstituted2.getClass)
+ }
+
def serializeDeserialize[T <: AnyRef](obj: T) = {
val buffer = new ByteArrayOutputStream
val out = new ObjectOutputStream(buffer)
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
index 6e1ac3ba9f..b37b5efa7e 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
@@ -174,7 +174,7 @@ class CallGraphTest extends ClearAfterClass {
| def t2(i: Int, f: Int => Int, z: Int) = h(f) + i - z
| def t3(f: Int => Int) = h(x => f(x + 1))
|}
- |abstract class D {
+ |trait D {
| def iAmASam(x: Int): Int
| def selfSamCall = iAmASam(10)
|}
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 0ba0ecca4c..10ab006017 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
@@ -100,7 +100,7 @@ class ScalaInlineInfoTest extends ClearAfterClass {
@Test
def inlineInfoSam(): Unit = {
val code =
- """abstract class C {
+ """trait C { // expected to be seen as sam: g(I)I
| def f = 0
| def g(x: Int): Int
| val foo = "hi"
@@ -108,10 +108,10 @@ class ScalaInlineInfoTest extends ClearAfterClass {
|abstract class D {
| val biz: Int
|}
- |trait T {
+ |trait T { // expected to be seen as sam: h(Ljava/lang/String;)I
| def h(a: String): Int
|}
- |abstract class E extends T {
+ |trait E extends T { // expected to be seen as sam: h(Ljava/lang/String;)I
| def hihi(x: Int) = x
|}
|class F extends T {