diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Erasure.scala | 32 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/UnCurry.scala | 21 | ||||
-rw-r--r-- | test/files/run/sammy_restrictions_LMF.scala | 42 | ||||
-rw-r--r-- | test/files/run/sammy_specialization_restriction.scala | 34 |
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() -} |