Skip to content

Commit 63f0175

Browse files
committed
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.
1 parent 0a3362b commit 63f0175

File tree

4 files changed

+80
-49
lines changed

4 files changed

+80
-49
lines changed

src/compiler/scala/tools/nsc/transform/Erasure.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,5 +1187,37 @@ abstract class Erasure extends AddInterfaces
11871187
bridge.resetFlag(BRIDGE)
11881188
}
11891189

1190+
/** Does this symbol compile to the underlying platform's notion of an interface,
1191+
* without requiring compiler magic before it can be instantiated?
1192+
*
1193+
* More specifically, we're interested in whether LambdaMetaFactory can instantiate this type,
1194+
* assuming it has a single abstract method. In other words, if we were to mix this
1195+
* trait into a class, it should not result in any compiler-generated members having to be
1196+
* implemented in ("mixed in to") this class (except for the SAM).
1197+
*
1198+
* Thus, the type must erase to a java interface, either by virtue of being defined as one,
1199+
* or by being a trait that:
1200+
* - is static (explicitouter or lambdalift may add disqualifying members)
1201+
* - extends only other traits that compile to pure interfaces (except for Any)
1202+
* - has no val/var members
1203+
*
1204+
* TODO: can we speed this up using the INTERFACE flag, or set it correctly by construction?
1205+
*/
1206+
final def compilesToPureInterface(tpSym: Symbol): Boolean = {
1207+
def ok(sym: Symbol) =
1208+
sym.isJavaInterface ||
1209+
sym.isTrait &&
1210+
// Unless sym.isStatic, even if the constructor is zero-argument now, it may acquire arguments in explicit outer or lambdalift.
1211+
// This is an impl restriction to simplify the decision of whether to expand the SAM during uncurry
1212+
// (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).
1213+
// When we delay sam expansion until after explicit outer & lambda lift, we could decide there whether
1214+
// to expand sam at compile time or use LMF, and this implementation restriction could be lifted.
1215+
sym.isStatic &&
1216+
(sym.isInterface || sym.info.decls.forall(mem => mem.isMethod || mem.isType)) // TODO OPT: && {sym setFlag INTERFACE; true})
1217+
1218+
// we still need to check our ancestors even if the INTERFACE flag is set, as it doesn't take inheritance into account
1219+
ok(tpSym) && tpSym.ancestors.forall(sym => (sym eq AnyClass) || (sym eq ObjectClass) || ok(sym))
1220+
}
1221+
11901222
private class TypeRefAttachment(val tpe: TypeRef)
11911223
}

src/compiler/scala/tools/nsc/transform/UnCurry.scala

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,21 +79,12 @@ abstract class UnCurry extends InfoTransform
7979
// (TODO: Can't use isInterface, yet, as it hasn't been updated for the new trait encoding)
8080
val canUseLambdaMetaFactory = inConstructorFlag == 0 && (fun.attachments.get[SAMFunction] match {
8181
case Some(SAMFunction(userDefinedSamTp, sam)) =>
82-
val tpSym = erasure.javaErasure(userDefinedSamTp).typeSymbol // we only care about what ends up in the bytecode
83-
(
84-
// LMF only supports interfaces
85-
(tpSym.isJavaInterface || tpSym.isTrait)
86-
// Unless tpSym.isStatic, even if the constructor is zero-argument now, it may acquire arguments in explicit outer or lambdalift.
87-
// This is an impl restriction to simplify the decision of whether to expand the SAM during uncurry
88-
// (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).
89-
// When we delay sam expansion until after explicit outer & lambda lift, we could decide there whether
90-
// to expand sam at compile time or use LMF, and this implementation restriction could be lifted.
91-
&& tpSym.isStatic
92-
// 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)
93-
// specialization and LMF are at odds, since LMF implements the single abstract method,
94-
// but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing
95-
&& !specializeTypes.isSpecializedIn(sam, userDefinedSamTp)
96-
)
82+
// LambdaMetaFactory cannot mix in trait members for us, or instantiate classes -- only pure interfaces need apply
83+
erasure.compilesToPureInterface(erasure.javaErasure(userDefinedSamTp).typeSymbol) &&
84+
// 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)
85+
// specialization and LMF are at odds, since LMF implements the single abstract method,
86+
// but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing
87+
!specializeTypes.isSpecializedIn(sam, userDefinedSamTp)
9788

9889
case _ => true // our built-in FunctionN's are suitable for LambdaMetaFactory by construction
9990
})
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
trait T[@specialized A] { def apply(a: A): A }
2+
trait TInt extends T[Int]
3+
4+
trait TWithVal { val x: Any = 1; def apply(x: Int): String }
5+
6+
object Test extends App {
7+
final val AnonFunClass = "$anonfun$"
8+
final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this
9+
10+
private def LMF(f: Any): Unit = {
11+
val className = f.getClass.toString
12+
assert(!(className contains AnonFunClass), className)
13+
assert((className contains LMFClass), className)
14+
}
15+
16+
private def notLMF(f: Any): Unit = {
17+
val className = f.getClass.toString
18+
assert((className contains AnonFunClass), className)
19+
assert(!(className contains LMFClass), className)
20+
}
21+
22+
// Check that we expand the SAM of a type that is specialized.
23+
// This is an implementation restriction -- the current specialization scheme is not
24+
// amenable to using LambdaMetaFactory to spin up subclasses.
25+
// Since the generic method is abstract, and the specialized ones are concrete,
26+
// specialization is rendered moot because we cannot implement the specialized method
27+
// with the lambda using LMF.
28+
29+
// not LMF if specialized at this type
30+
notLMF((x => x): T[Int])
31+
// not LMF if specialized at this type (via subclass)
32+
notLMF((x => x): TInt)
33+
// LMF ok if not specialized at this type
34+
LMF((x => x): T[String])
35+
36+
// traits with a val member also cannot be instantiated by LMF
37+
val fVal: TWithVal = (x => "a")
38+
notLMF(fVal)
39+
assert(fVal.x == 1)
40+
41+
42+
}

test/files/run/sammy_specialization_restriction.scala

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)