From f922f367d58b3ba6bbb4cb0864ce82c5cd6f7966 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 17 Mar 2016 11:56:14 -0700 Subject: 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). --- spec/06-expressions.md | 7 ++- .../scala/reflect/internal/Definitions.scala | 25 ++++++--- test/files/neg/sammy_error_exist_no_crash.scala | 4 +- test/files/neg/sammy_restrictions.check | 55 ++++++++----------- test/files/neg/sammy_restrictions.scala | 63 ++++++++++++---------- test/files/pos/sammy_implicit.scala | 3 +- test/files/pos/sammy_overload.scala | 27 +++++++++- test/files/pos/sammy_poly.scala | 12 +++-- test/files/pos/sammy_scope.scala | 4 +- test/files/run/lambda-serialization.scala | 14 ++++- .../tools/nsc/backend/jvm/opt/CallGraphTest.scala | 2 +- .../nsc/backend/jvm/opt/ScalaInlineInfoTest.scala | 6 +-- 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 { -- cgit v1.2.3