Skip to content

Commit 4e90eba

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 7685e83 commit 4e90eba

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
@@ -565,54 +565,85 @@ object Erasure {
565565
super.typedDefDef(ddef1, sym)
566566
}
567567

568-
/** After erasure, we may have to replace the closure method by a bridge.
569-
* LambdaMetaFactory handles this automatically for most types, but we have
570-
* to deal with boxing and unboxing of value classes ourselves.
571-
*/
572568
override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context) = {
573569
val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol)
574570
var implClosure @ Closure(_, meth, _) = super.typedClosure(tree, pt)
575571
if (xxl) implClosure = cpy.Closure(implClosure)(tpt = TypeTree(defn.FunctionXXLType))
576572
implClosure.tpe match {
577573
case SAMType(sam) =>
578-
val implType = meth.tpe.widen
574+
val implType = meth.tpe.widen.asInstanceOf[MethodType]
579575

580-
val List(implParamTypes) = implType.paramInfoss
576+
val implParamTypes = implType.paramInfos
581577
val List(samParamTypes) = sam.info.paramInfoss
582578
val implResultType = implType.resultType
583579
val samResultType = sam.info.resultType
584580

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