Skip to content

Commit cc1b798

Browse files
committed
Generalize bridge creation for closures in Erasure
Previously, we only created bridges for derived value classes because we assumed the default adaptation done by LambdaMetaFactory was good enough. But this is not the case when unboxing null: LMF will throw an NPE when Scala semantics require using the default value of the value class instead. We fix this by creating more bridges when needed, this is similar to the solution adopted in scalac. Also fixed a bug where adapted closures did not preserve Closure#tpt, leading to a runtime failure in lambda-sam-bridge.scala
1 parent 906a3d2 commit cc1b798

File tree

5 files changed

+102
-42
lines changed

5 files changed

+102
-42
lines changed

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -572,54 +572,85 @@ object Erasure {
572572
super.typedDefDef(ddef1, sym)
573573
}
574574

575-
/** After erasure, we may have to replace the closure method by a bridge.
576-
* LambdaMetaFactory handles this automatically for most types, but we have
577-
* to deal with boxing and unboxing of value classes ourselves.
578-
*/
579575
override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context) = {
580576
val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol)
581577
var implClosure @ Closure(_, meth, _) = super.typedClosure(tree, pt)
582578
if (xxl) implClosure = cpy.Closure(implClosure)(tpt = TypeTree(defn.FunctionXXLType))
583579
implClosure.tpe match {
584580
case SAMType(sam) =>
585-
val implType = meth.tpe.widen
581+
val implType = meth.tpe.widen.asInstanceOf[MethodType]
586582

587-
val List(implParamTypes) = implType.paramInfoss
583+
val implParamTypes = implType.paramInfos
588584
val List(samParamTypes) = sam.info.paramInfoss
589585
val implResultType = implType.resultType
590586
val samResultType = sam.info.resultType
591587

592-
// Given a value class V with an underlying type U, the following code:
593-
// val f: Function1[V, V] = x => ...
594-
// results in the creation of a closure and a method:
595-
// def $anonfun(v1: V): V = ...
596-
// val f: Function1[V, V] = closure($anonfun)
597-
// After [[Erasure]] this method will look like:
598-
// def $anonfun(v1: ErasedValueType(V, U)): ErasedValueType(V, U) = ...
599-
// And after [[ElimErasedValueType]] it will look like:
600-
// def $anonfun(v1: U): U = ...
601-
// This method does not implement the SAM of Function1[V, V] anymore and
602-
// needs to be replaced by a bridge:
603-
// def $anonfun$2(v1: V): V = new V($anonfun(v1.underlying))
604-
// val f: Function1 = closure($anonfun$2)
605-
// In general, a bridge is needed when the signature of the closure method after
606-
// Erasure contains an ErasedValueType but the corresponding type in the functional
607-
// interface is not an ErasedValueType.
608-
val bridgeNeeded =
609-
(implResultType :: implParamTypes, samResultType :: samParamTypes).zipped.exists(
610-
(implType, samType) => implType.isErasedValueType && !samType.isErasedValueType
611-
)
612-
613-
if (bridgeNeeded) {
614-
val bridge = ctx.newSymbol(ctx.owner, nme.ANON_FUN, Flags.Synthetic | Flags.Method, sam.info)
615-
val bridgeCtx = ctx.withOwner(bridge)
616-
Closure(bridge, bridgeParamss => {
617-
implicit val ctx = bridgeCtx
618-
619-
val List(bridgeParams) = bridgeParamss
620-
val rhs = Apply(meth, (bridgeParams, implParamTypes).zipped.map(adapt(_, _)))
621-
adapt(rhs, sam.info.resultType)
622-
})
588+
// The following code:
589+
//
590+
// val f: Function1[Int, Any] = x => ...
591+
//
592+
// results in the creation of a closure and a method in the typer:
593+
//
594+
// def $anonfun(x: Int): Any = ...
595+
// val f: Function1[Int, Any] = closure($anonfun)
596+
//
597+
// Notice that `$anonfun` takes a primitive as argument, but the single abstract method
598+
// of `Function1` after erasure is:
599+
//
600+
// def apply(x: Object): Object
601+
//
602+
// which takes a reference as argument. Hence, some form of adaptation is required.
603+
//
604+
// If we do nothing, the LambdaMetaFactory bootstrap method will
605+
// automatically do the adaptation. Unfortunately, the result does not
606+
// implement the expected Scala semantics: null should be "unboxed" to
607+
// the default value of the value class, but LMF will throw a
608+
// NullPointerException instead. LMF is also not capable of doing
609+
// adaptation for derived value classes.
610+
//
611+
// Thus, we need to replace the closure method by a bridge method that
612+
// forwards to the original closure method with appropriate
613+
// boxing/unboxing. For our example above, this would be:
614+
//
615+
// def $anonfun1(x: Object): Object = $anonfun(BoxesRunTime.unboxToInt(x))
616+
// val f: Function1 = closure($anonfun1)
617+
//
618+
// In general, a bridge is needed when, after Erasure:
619+
// - one of the parameter type of the closure method is a non-reference type,
620+
// and the corresponding type in the SAM is a reference type
621+
// - or the result type of the closure method is an erased value type
622+
// and the result type in the SAM isn't
623+
// However, the following exception exists: If the SAM is replaced by
624+
// JFunction*mc* in [[FunctionalInterfaces]], no bridge is needed: the
625+
// SAM contains default methods to handle adaptation
626+
//
627+
// See test cases lambda-*.scala and t8017/ for concrete examples.
628+
629+
def isReferenceType(tp: Type) = !tp.isPrimitiveValueType && !tp.isErasedValueType
630+
631+
if (!defn.isSpecializableFunction(implClosure.tpe.widen.classSymbol.asClass, implParamTypes, implResultType)) {
632+
val paramAdaptationNeeded =
633+
(implParamTypes, samParamTypes).zipped.exists((implType, samType) =>
634+
!isReferenceType(implType) && isReferenceType(samType))
635+
val resultAdaptationNeeded =
636+
implResultType.isErasedValueType && !samResultType.isErasedValueType
637+
638+
if (paramAdaptationNeeded || resultAdaptationNeeded) {
639+
val bridgeType =
640+
if (paramAdaptationNeeded) {
641+
if (resultAdaptationNeeded) sam.info
642+
else implType.derivedLambdaType(paramInfos = samParamTypes)
643+
} else implType.derivedLambdaType(resType = samResultType)
644+
val bridge = ctx.newSymbol(ctx.owner, nme.ANON_FUN, Flags.Synthetic | Flags.Method, bridgeType)
645+
val bridgeCtx = ctx.withOwner(bridge)
646+
Closure(bridge, bridgeParamss => {
647+
implicit val ctx = bridgeCtx
648+
649+
val List(bridgeParams) = bridgeParamss
650+
val rhs = Apply(meth, (bridgeParams, implParamTypes).zipped.map(adapt(_, _)))
651+
adapt(rhs, bridgeType.resultType)
652+
}, targetType = implClosure.tpt.tpe)
653+
} else implClosure
623654
} else implClosure
624655
case _ =>
625656
implClosure

tests/run/lambda-null.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ generic Function3: 0 0 0
66
# Generic calls
77
specialized Function1: 0
88
generic Function1: null
9+
unspecialized Function3: 0 0 0
910
generic Function3: null null null

tests/run/lambda-null.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ object Test {
4949
// implement the only abstract method in Function3: `apply` which has
5050
// type (Object, Object, Object): Object
5151
//
52-
// FIXME: This means that LambdaMetaFactory will generate a bridge from
53-
// `apply` to the closure method. this work most of the time, but LMF does
54-
// not unbox null to 0 as required by Scala semantics, instead it throws an NPE.
52+
// Erasure will replace the closure method by a bridge method with the
53+
// correct type that forwards to the original closure method with
54+
// appropriate boxing/unboxing.
5555
val if3_unspecialized: (Int, Int, Int) => Int = (x, y, z) => {
5656
println("unspecialized Function3: " + x + " " + y + " " + z)
5757
x + y + z
@@ -74,8 +74,7 @@ object Test {
7474
println("# Generic calls")
7575
assert(genericCall1(if1_specialized) == 0)
7676
assert(genericCall1(if1_generic) == 0)
77-
// FIXME: throws NullPointerException, see above
78-
// assert(genericCall3(if3_unspecialized) == 0)
77+
assert(genericCall3(if3_unspecialized) == 0)
7978
assert(genericCall3(if3_generic) == 0)
8079
}
8180
}

tests/run/lambda-sam-bridge.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class Meter(val x: Double) extends AnyVal
2+
3+
trait MySam[T] {
4+
def app(m: T): Any
5+
}
6+
7+
object Test {
8+
val sam: MySam[Meter] = (x: Meter) => (x: Any)
9+
10+
def main(args: Array[String]): Unit = {
11+
sam.app(new Meter(1.0))
12+
}
13+
}
14+
15+

tests/run/lambda-unit.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
trait SAMUnit {
2+
def foo(a: Object): Unit
3+
}
4+
5+
object Test {
6+
val fun: Object => Unit = a => assert(a == "")
7+
val sam: SAMUnit = a => assert(a == "")
8+
9+
def main(args: Array[String]): Unit = {
10+
fun("")
11+
(fun: Object => Any)("")
12+
sam.foo("")
13+
}
14+
}

0 commit comments

Comments
 (0)