summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@typesafe.com>2016-03-29 00:21:07 -0700
committerAdriaan Moors <adriaan.moors@typesafe.com>2016-03-29 12:34:19 -0700
commit0a3362b3ea5cd7355cd9ccc529783549a4cb5c5f (patch)
treecd1c7a3892bfbd657870c619440c047334a1373f
parent3ae39036771acb107cbb4a37fe6113c243d89acc (diff)
downloadscala-0a3362b3ea5cd7355cd9ccc529783549a4cb5c5f.tar.gz
scala-0a3362b3ea5cd7355cd9ccc529783549a4cb5c5f.tar.bz2
scala-0a3362b3ea5cd7355cd9ccc529783549a4cb5c5f.zip
Specialization precludes use of LambdaMetaFactory for SAM
When a SAM type is specialized (i.e., a specialized type parameter receives a specialized type argument), do not use LambdaMetaFactory (expand during Uncurry instead). 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.
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala13
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala8
-rw-r--r--test/files/run/sammy_specialization_restriction.scala34
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala24
5 files changed, 78 insertions, 7 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala
index 1e479d3f63..636fb08b89 100644
--- a/src/compiler/scala/tools/nsc/transform/Constructors.scala
+++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala
@@ -501,8 +501,6 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
!sym.isSetter
)
- private def possiblySpecialized(s: Symbol) = specializeTypes.specializedTypeVars(s).nonEmpty
-
/*
* whether `sym` denotes a param-accessor (ie a field) that fulfills all of:
* (a) has stationary value, ie the same value provided via the corresponding ctor-arg; and
@@ -511,7 +509,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* (b.2) the constructor in the specialized (sub-)class.
* (c) isn't part of a DelayedInit subclass.
*/
- private def canBeSupplanted(sym: Symbol) = !isDelayedInitSubclass && isStationaryParamRef(sym) && !possiblySpecialized(sym)
+ private def canBeSupplanted(sym: Symbol) = !isDelayedInitSubclass && isStationaryParamRef(sym) && !specializeTypes.possiblySpecialized(sym)
override def transform(tree: Tree): Tree = tree match {
case Apply(Select(This(_), _), List()) =>
@@ -531,7 +529,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos
case Select(_, _) if guardSpecializedFieldInit => // reasoning behind this guard in the docu of `usesSpecializedField`
- if (possiblySpecialized(tree.symbol)) {
+ if (specializeTypes.possiblySpecialized(tree.symbol)) {
usesSpecializedField = true
}
super.transform(tree)
diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
index 998f0b22cb..0050d08f1b 100644
--- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
+++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
@@ -285,6 +285,19 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
for ((tvar, tpe) <- sym.info.typeParams.zip(args) if !tvar.isSpecialized || !isPrimitiveValueType(tpe))
yield tpe
+ /** Is `member` potentially affected by specialization? This is a gross overapproximation,
+ * but it should be okay for use outside of specialization.
+ */
+ def possiblySpecialized(sym: Symbol) = specializedTypeVars(sym).nonEmpty
+
+ /** Refines possiblySpecialized taking into account the instantiation of the specialized type variables at `site` */
+ def isSpecializedIn(sym: Symbol, site: Type) =
+ specializedTypeVars(sym) exists { tvar =>
+ val concretes = concreteTypes(tvar)
+ (concretes contains AnyRefClass) || (concretes contains site.memberType(tvar))
+ }
+
+
val specializedType = new TypeMap {
override def apply(tp: Type): Type = tp match {
case TypeRef(pre, sym, args) if args.nonEmpty =>
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index afa0fc92ff..9c4b125fc1 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -77,8 +77,8 @@ abstract class UnCurry extends InfoTransform
// We use Java's LambdaMetaFactory (LMF), which requires an interface for the sam's owner
private def mustExpandFunction(fun: Function) = forceExpandFunction || {
// (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].map(_.samTp) match {
- case Some(userDefinedSamTp) =>
+ 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
@@ -90,7 +90,9 @@ abstract class UnCurry extends InfoTransform
// 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)
- && !tpSym.isSpecialized
+ // 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_specialization_restriction.scala b/test/files/run/sammy_specialization_restriction.scala
new file mode 100644
index 0000000000..4487bb3ad7
--- /dev/null
+++ b/test/files/run/sammy_specialization_restriction.scala
@@ -0,0 +1,34 @@
+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()
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala
index f7218c576c..b9e45a7dc9 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala
@@ -71,6 +71,23 @@ class IndySammyTest extends ClearAfterClass {
)
}
+// def testSpecial(lam: String, lamTp: String, arg: String)(allowMessage: StoreReporter#Info => Boolean = _ => false) = {
+// val cls = compile("trait Special[@specialized A] { def apply(a: A): A}" )
+// val methodNodes = compileMethods(compiler)(s"def lam : $lamTp = $lam" +";"+ appDef(arg), allowMessage)
+//
+// val anonfun = methodNodes.filter(_.name contains "$anonfun$").map(convertMethod)
+// val lamInsn = methodNodes.find(_.name == "lam").map(instructionsFromMethod).get.dropNonOp
+// val applyInvoke = methodNodes.find(_.name == "app").map(convertMethod).get
+//
+// assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn)
+// assertSameCode(anonfun, lamBody)
+// assertSameCode(applyInvoke, List(
+// VarOp(ALOAD, 0),
+// Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List(
+// Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret)
+// )
+// }
+
// x => x : VC => VC applied to VC(1)
@Test
def testVC_VC_VC =
@@ -127,6 +144,13 @@ class IndySammyTest extends ClearAfterClass {
List(Op(ICONST_1), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "boxToInteger", "(I)Ljava/lang/Integer;", false)),
Op(IRETURN))()
+ // TODO
+ // x => x : Special[Int] applied to 1
+// @Test
+// def testSpecial_Int_1 =
+// testSpecial("x => x", "Special[Int]", "1")()
+
+
// Tests ThisReferringMethodsTraverser
@Test
def testStaticIfNoThisReference: Unit = {