summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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()
-}