summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@typesafe.com>2016-03-29 16:47:47 -0700
committerAdriaan Moors <adriaan.moors@typesafe.com>2016-03-29 23:08:40 -0700
commit63f017586f31de11bc6004dca7cea0c26ceb5ff5 (patch)
treea1fd4c7e1a879aeb19981d8d71f7823d0c5bfad6
parent0a3362b3ea5cd7355cd9ccc529783549a4cb5c5f (diff)
downloadscala-63f017586f31de11bc6004dca7cea0c26ceb5ff5.tar.gz
scala-63f017586f31de11bc6004dca7cea0c26ceb5ff5.tar.bz2
scala-63f017586f31de11bc6004dca7cea0c26ceb5ff5.zip
Better detection of types LMF cannot instantiate.
LambdaMetaFactory can only properly instantiate Java interfaces (with one abstract method, of course). A trait always compiles to an interface, but a subclass that can be instantiated may require mixing in further members, which LMF cannot do. (Nested traits, traits with fields,... do not qualify.) Traits that cannot be instantiated by LMF are still SAM targets, we simply created anonymous subclasses as before.
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala32
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala21
-rw-r--r--test/files/run/sammy_restrictions_LMF.scala42
-rw-r--r--test/files/run/sammy_specialization_restriction.scala34
4 files changed, 80 insertions, 49 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala
index 2df4265573..cdf3e18b5a 100644
--- a/src/compiler/scala/tools/nsc/transform/Erasure.scala
+++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala
@@ -1187,5 +1187,37 @@ abstract class Erasure extends AddInterfaces
bridge.resetFlag(BRIDGE)
}
+ /** Does this symbol compile to the underlying platform's notion of an interface,
+ * without requiring compiler magic before it can be instantiated?
+ *
+ * More specifically, we're interested in whether LambdaMetaFactory can instantiate this type,
+ * assuming it has a single abstract method. In other words, if we were to mix this
+ * trait into a class, it should not result in any compiler-generated members having to be
+ * implemented in ("mixed in to") this class (except for the SAM).
+ *
+ * Thus, the type must erase to a java interface, either by virtue of being defined as one,
+ * or by being a trait that:
+ * - is static (explicitouter or lambdalift may add disqualifying members)
+ * - extends only other traits that compile to pure interfaces (except for Any)
+ * - has no val/var members
+ *
+ * TODO: can we speed this up using the INTERFACE flag, or set it correctly by construction?
+ */
+ final def compilesToPureInterface(tpSym: Symbol): Boolean = {
+ def ok(sym: Symbol) =
+ sym.isJavaInterface ||
+ sym.isTrait &&
+ // Unless sym.isStatic, even if the constructor is zero-argument now, it may acquire arguments in explicit outer or lambdalift.
+ // This is an impl restriction to simplify the decision of whether to expand the SAM during uncurry
+ // (when we don't yet know whether it will receive an outer pointer in explicit outer or whether lambda lift will add proxies for captures).
+ // When we delay sam expansion until after explicit outer & lambda lift, we could decide there whether
+ // to expand sam at compile time or use LMF, and this implementation restriction could be lifted.
+ sym.isStatic &&
+ (sym.isInterface || sym.info.decls.forall(mem => mem.isMethod || mem.isType)) // TODO OPT: && {sym setFlag INTERFACE; true})
+
+ // we still need to check our ancestors even if the INTERFACE flag is set, as it doesn't take inheritance into account
+ ok(tpSym) && tpSym.ancestors.forall(sym => (sym eq AnyClass) || (sym eq ObjectClass) || ok(sym))
+ }
+
private class TypeRefAttachment(val tpe: TypeRef)
}
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index 9c4b125fc1..50e413fba2 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -79,21 +79,12 @@ abstract class UnCurry extends InfoTransform
// (TODO: Can't use isInterface, yet, as it hasn't been updated for the new trait encoding)
val canUseLambdaMetaFactory = inConstructorFlag == 0 && (fun.attachments.get[SAMFunction] match {
case Some(SAMFunction(userDefinedSamTp, sam)) =>
- val tpSym = erasure.javaErasure(userDefinedSamTp).typeSymbol // we only care about what ends up in the bytecode
- (
- // LMF only supports interfaces
- (tpSym.isJavaInterface || tpSym.isTrait)
- // Unless tpSym.isStatic, even if the constructor is zero-argument now, it may acquire arguments in explicit outer or lambdalift.
- // This is an impl restriction to simplify the decision of whether to expand the SAM during uncurry
- // (when we don't yet know whether it will receive an outer pointer in explicit outer or whether lambda lift will add proxies for captures).
- // When we delay sam expansion until after explicit outer & lambda lift, we could decide there whether
- // to expand sam at compile time or use LMF, and this implementation restriction could be lifted.
- && 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)
- // specialization and LMF are at odds, since LMF implements the single abstract method,
- // but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing
- && !specializeTypes.isSpecializedIn(sam, userDefinedSamTp)
- )
+ // LambdaMetaFactory cannot mix in trait members for us, or instantiate classes -- only pure interfaces need apply
+ erasure.compilesToPureInterface(erasure.javaErasure(userDefinedSamTp).typeSymbol) &&
+ // 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)
+ // specialization and LMF are at odds, since LMF implements the single abstract method,
+ // but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing
+ !specializeTypes.isSpecializedIn(sam, userDefinedSamTp)
case _ => true // our built-in FunctionN's are suitable for LambdaMetaFactory by construction
})
diff --git a/test/files/run/sammy_restrictions_LMF.scala b/test/files/run/sammy_restrictions_LMF.scala
new file mode 100644
index 0000000000..40bb234a72
--- /dev/null
+++ b/test/files/run/sammy_restrictions_LMF.scala
@@ -0,0 +1,42 @@
+trait T[@specialized A] { def apply(a: A): A }
+trait TInt extends T[Int]
+
+trait TWithVal { val x: Any = 1; def apply(x: Int): String }
+
+object Test extends App {
+ final val AnonFunClass = "$anonfun$"
+ final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this
+
+ private def LMF(f: Any): Unit = {
+ val className = f.getClass.toString
+ assert(!(className contains AnonFunClass), className)
+ assert((className contains LMFClass), className)
+ }
+
+ private def notLMF(f: Any): Unit = {
+ val className = f.getClass.toString
+ assert((className contains AnonFunClass), className)
+ assert(!(className contains LMFClass), className)
+ }
+
+ // Check that we expand the SAM of a type that is specialized.
+ // This is an implementation restriction -- the current specialization scheme is not
+ // amenable to using LambdaMetaFactory to spin up subclasses.
+ // Since the generic method is abstract, and the specialized ones are concrete,
+ // specialization is rendered moot because we cannot implement the specialized method
+ // with the lambda using LMF.
+
+ // not LMF if specialized at this type
+ notLMF((x => x): T[Int])
+ // not LMF if specialized at this type (via subclass)
+ notLMF((x => x): TInt)
+ // LMF ok if not specialized at this type
+ LMF((x => x): T[String])
+
+ // traits with a val member also cannot be instantiated by LMF
+ val fVal: TWithVal = (x => "a")
+ notLMF(fVal)
+ assert(fVal.x == 1)
+
+
+}
diff --git a/test/files/run/sammy_specialization_restriction.scala b/test/files/run/sammy_specialization_restriction.scala
deleted file mode 100644
index 4487bb3ad7..0000000000
--- a/test/files/run/sammy_specialization_restriction.scala
+++ /dev/null
@@ -1,34 +0,0 @@
-trait T[@specialized A] { def apply(a: A): A }
-trait TInt extends T[Int]
-
-// Check that we expand the SAM of a type that is specialized.
-// This is an implementation restriction -- the current specialization scheme is not
-// amenable to using LambdaMetaFactory to spin up subclasses.
-// Since the generic method is abstract, and the specialized ones are concrete,
-// specialization is rendered moot because we cannot implement the specialized method
-// with the lambda using LMF.
-object Test extends App {
- final val AnonFunClass = "$anonfun$"
- final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this
-
- def specializedSamPrecludesLMF() = {
- val className = ((x => x): T[Int]).getClass.toString
- assert((className contains AnonFunClass), className)
- assert(!(className contains LMFClass), className)
- }
-
- def specializedSamSubclassPrecludesLMF() = {
- val className = ((x => x): TInt).getClass.toString
- assert((className contains AnonFunClass), className)
- assert(!(className contains LMFClass), className)
- }
-
- def nonSpecializedSamUsesLMF() = {
- val className = ((x => x): T[String]).getClass.toString
- assert(!(className contains AnonFunClass), className)
- assert(className contains LMFClass, className)
- }
-
- specializedSamPrecludesLMF()
- nonSpecializedSamUsesLMF()
-}