Skip to content

Commit a2354a8

Browse files
authored
Merge pull request #8519 from dotty-staging/adapt-shortcut-closure
Fix #8501: Run closure adaptation in Erasure.etaExpand
2 parents 52007b7 + d3c252d commit a2354a8

File tree

1 file changed

+108
-98
lines changed

1 file changed

+108
-98
lines changed

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

Lines changed: 108 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class Erasure extends Phase with DenotTransformer {
101101

102102
def run(implicit ctx: Context): Unit = {
103103
val unit = ctx.compilationUnit
104-
unit.tpdTree = eraser.typedExpr(unit.tpdTree)(ctx.fresh.setPhase(this.next))
104+
unit.tpdTree = eraser.typedExpr(unit.tpdTree)(ctx.fresh.setTyper(eraser).setPhase(this.next))
105105
}
106106

107107
override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = {
@@ -354,6 +354,106 @@ object Erasure {
354354
cast(tree, pt)
355355
end adaptToType
356356

357+
358+
/** The following code:
359+
*
360+
* val f: Function1[Int, Any] = x => ...
361+
*
362+
* results in the creation of a closure and a method in the typer:
363+
*
364+
* def $anonfun(x: Int): Any = ...
365+
* val f: Function1[Int, Any] = closure($anonfun)
366+
*
367+
* Notice that `$anonfun` takes a primitive as argument, but the single abstract method
368+
* of `Function1` after erasure is:
369+
*
370+
* def apply(x: Object): Object
371+
*
372+
* which takes a reference as argument. Hence, some form of adaptation is required.
373+
*
374+
* If we do nothing, the LambdaMetaFactory bootstrap method will
375+
* automatically do the adaptation. Unfortunately, the result does not
376+
* implement the expected Scala semantics: null should be "unboxed" to
377+
* the default value of the value class, but LMF will throw a
378+
* NullPointerException instead. LMF is also not capable of doing
379+
* adaptation for derived value classes.
380+
*
381+
* Thus, we need to replace the closure method by a bridge method that
382+
* forwards to the original closure method with appropriate
383+
* boxing/unboxing. For our example above, this would be:
384+
*
385+
* def $anonfun1(x: Object): Object = $anonfun(BoxesRunTime.unboxToInt(x))
386+
* val f: Function1 = closure($anonfun1)
387+
*
388+
* In general a bridge is needed when, after Erasure, one of the
389+
* parameter type or the result type of the closure method has a
390+
* different type, and we cannot rely on auto-adaptation.
391+
*
392+
* Auto-adaptation works in the following cases:
393+
* - If the SAM is replaced by JFunction*mc* in
394+
* [[FunctionalInterfaces]], no bridge is needed: the SAM contains
395+
* default methods to handle adaptation.
396+
* - If a result type of the closure method is a primitive value type
397+
* different from Unit, we can rely on the auto-adaptation done by
398+
* LMF (because it only needs to box, not unbox, so no special
399+
* handling of null is required).
400+
* - If the SAM is replaced by JProcedure* in
401+
* [[DottyBackendInterface]] (this only happens when no explicit SAM
402+
* type is given), no bridge is needed to box a Unit result type:
403+
* the SAM contains a default method to handle that.
404+
*
405+
* See test cases lambda-*.scala and t8017/ for concrete examples.
406+
*/
407+
def adaptClosure(tree: tpd.Closure)(using ctx: Context): Tree = {
408+
val implClosure @ Closure(_, meth, _) = tree
409+
410+
implClosure.tpe match {
411+
case SAMType(sam) =>
412+
val implType = meth.tpe.widen.asInstanceOf[MethodType]
413+
414+
val implParamTypes = implType.paramInfos
415+
val List(samParamTypes) = sam.paramInfoss
416+
val implResultType = implType.resultType
417+
val samResultType = sam.resultType
418+
419+
if (!defn.isSpecializableFunction(implClosure.tpe.widen.classSymbol.asClass, implParamTypes, implResultType)) {
420+
def autoAdaptedParam(tp: Type) = !tp.isErasedValueType && !tp.isPrimitiveValueType
421+
val explicitSAMType = implClosure.tpt.tpe.exists
422+
def autoAdaptedResult(tp: Type) = !tp.isErasedValueType &&
423+
(!explicitSAMType || tp.typeSymbol != defn.UnitClass)
424+
def sameSymbol(tp1: Type, tp2: Type) = tp1.typeSymbol == tp2.typeSymbol
425+
426+
val paramAdaptationNeeded =
427+
implParamTypes.lazyZip(samParamTypes).exists((implType, samType) =>
428+
!sameSymbol(implType, samType) && !autoAdaptedParam(implType))
429+
val resultAdaptationNeeded =
430+
!sameSymbol(implResultType, samResultType) && !autoAdaptedResult(implResultType)
431+
432+
if (paramAdaptationNeeded || resultAdaptationNeeded) {
433+
val bridgeType =
434+
if (paramAdaptationNeeded)
435+
if (resultAdaptationNeeded) sam
436+
else implType.derivedLambdaType(paramInfos = samParamTypes)
437+
else implType.derivedLambdaType(resType = samResultType)
438+
val bridge = ctx.newSymbol(ctx.owner, AdaptedClosureName(meth.symbol.name.asTermName), Flags.Synthetic | Flags.Method, bridgeType)
439+
val bridgeCtx = ctx.withOwner(bridge)
440+
Closure(bridge, bridgeParamss => {
441+
implicit val ctx = bridgeCtx
442+
443+
val List(bridgeParams) = bridgeParamss
444+
assert(ctx.typer.isInstanceOf[Erasure.Typer])
445+
val rhs = Apply(meth, bridgeParams.lazyZip(implParamTypes).map(ctx.typer.adapt(_, _)))
446+
ctx.typer.adapt(rhs, bridgeType.resultType)
447+
}, targetType = implClosure.tpt.tpe)
448+
}
449+
else implClosure
450+
}
451+
else implClosure
452+
case _ =>
453+
implClosure
454+
}
455+
}
456+
357457
/** Eta expand given `tree` that has the given method type `mt`, so that
358458
* it conforms to erased result type `pt`.
359459
* To do this correctly, we have to look at the tree's original pre-erasure
@@ -392,14 +492,16 @@ object Erasure {
392492
case refs1 => refs1
393493
abstracted(args ::: expandedRefs, resTpe, anonFun.info.finalResultType)(
394494
using ctx.withOwner(anonFun))
395-
Closure(anonFun, lambdaBody)
495+
496+
val unadapted = Closure(anonFun, lambdaBody)
497+
cpy.Block(unadapted)(unadapted.stats, adaptClosure(unadapted.expr.asInstanceOf[Closure]))
396498
catch case ex: MatchError =>
397499
println(i"error while abstracting tree = $tree | mt = $mt | args = $args%, % | tp = $tp | pt = $pt")
398500
throw ex
399501
else
400502
assert(args.length == targetLength, i"wrong # args tree = $tree | args = $args%, % | mt = $mt | tree type = ${tree.tpe}")
401503
val app = untpd.cpy.Apply(tree1)(tree1, args)
402-
assert(ctx.typer.isInstanceOf[Typer])
504+
assert(ctx.typer.isInstanceOf[Erasure.Typer])
403505
ctx.typer.typed(app, pt)
404506
.changeOwnerAfter(origOwner, ctx.owner, ctx.erasurePhase.asInstanceOf[Erasure])
405507

@@ -765,7 +867,7 @@ object Erasure {
765867
// accessor that's produced with an `enteredAfter` in ExplicitOuter, so
766868
// `tranformInfo` of the constructor in erasure yields a method type without
767869
// an outer parameter. We fix this problem by adding the missing outer
768-
// parameter here.
870+
// parameter here.
769871
constr.copySymDenotation(
770872
info = outer.addParam(constr.owner.asClass, constr.info)
771873
).installAfter(erasurePhase)
@@ -774,101 +876,9 @@ object Erasure {
774876

775877
override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = {
776878
val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol)
777-
var implClosure @ Closure(_, meth, _) = super.typedClosure(tree, pt)
879+
var implClosure = super.typedClosure(tree, pt).asInstanceOf[Closure]
778880
if (xxl) implClosure = cpy.Closure(implClosure)(tpt = TypeTree(defn.FunctionXXLClass.typeRef))
779-
implClosure.tpe match {
780-
case SAMType(sam) =>
781-
val implType = meth.tpe.widen.asInstanceOf[MethodType]
782-
783-
val implParamTypes = implType.paramInfos
784-
val List(samParamTypes) = sam.paramInfoss
785-
val implResultType = implType.resultType
786-
val samResultType = sam.resultType
787-
788-
// The following code:
789-
//
790-
// val f: Function1[Int, Any] = x => ...
791-
//
792-
// results in the creation of a closure and a method in the typer:
793-
//
794-
// def $anonfun(x: Int): Any = ...
795-
// val f: Function1[Int, Any] = closure($anonfun)
796-
//
797-
// Notice that `$anonfun` takes a primitive as argument, but the single abstract method
798-
// of `Function1` after erasure is:
799-
//
800-
// def apply(x: Object): Object
801-
//
802-
// which takes a reference as argument. Hence, some form of adaptation is required.
803-
//
804-
// If we do nothing, the LambdaMetaFactory bootstrap method will
805-
// automatically do the adaptation. Unfortunately, the result does not
806-
// implement the expected Scala semantics: null should be "unboxed" to
807-
// the default value of the value class, but LMF will throw a
808-
// NullPointerException instead. LMF is also not capable of doing
809-
// adaptation for derived value classes.
810-
//
811-
// Thus, we need to replace the closure method by a bridge method that
812-
// forwards to the original closure method with appropriate
813-
// boxing/unboxing. For our example above, this would be:
814-
//
815-
// def $anonfun1(x: Object): Object = $anonfun(BoxesRunTime.unboxToInt(x))
816-
// val f: Function1 = closure($anonfun1)
817-
//
818-
// In general a bridge is needed when, after Erasure, one of the
819-
// parameter type or the result type of the closure method has a
820-
// different type, and we cannot rely on auto-adaptation.
821-
//
822-
// Auto-adaptation works in the following cases:
823-
// - If the SAM is replaced by JFunction*mc* in
824-
// [[FunctionalInterfaces]], no bridge is needed: the SAM contains
825-
// default methods to handle adaptation.
826-
// - If a result type of the closure method is a primitive value type
827-
// different from Unit, we can rely on the auto-adaptation done by
828-
// LMF (because it only needs to box, not unbox, so no special
829-
// handling of null is required).
830-
// - If the SAM is replaced by JProcedure* in
831-
// [[DottyBackendInterface]] (this only happens when no explicit SAM
832-
// type is given), no bridge is needed to box a Unit result type:
833-
// the SAM contains a default method to handle that.
834-
//
835-
// See test cases lambda-*.scala and t8017/ for concrete examples.
836-
837-
if (!defn.isSpecializableFunction(implClosure.tpe.widen.classSymbol.asClass, implParamTypes, implResultType)) {
838-
def autoAdaptedParam(tp: Type) = !tp.isErasedValueType && !tp.isPrimitiveValueType
839-
val explicitSAMType = implClosure.tpt.tpe.exists
840-
def autoAdaptedResult(tp: Type) = !tp.isErasedValueType &&
841-
(!explicitSAMType || tp.typeSymbol != defn.UnitClass)
842-
def sameSymbol(tp1: Type, tp2: Type) = tp1.typeSymbol == tp2.typeSymbol
843-
844-
val paramAdaptationNeeded =
845-
implParamTypes.lazyZip(samParamTypes).exists((implType, samType) =>
846-
!sameSymbol(implType, samType) && !autoAdaptedParam(implType))
847-
val resultAdaptationNeeded =
848-
!sameSymbol(implResultType, samResultType) && !autoAdaptedResult(implResultType)
849-
850-
if (paramAdaptationNeeded || resultAdaptationNeeded) {
851-
val bridgeType =
852-
if (paramAdaptationNeeded)
853-
if (resultAdaptationNeeded) sam
854-
else implType.derivedLambdaType(paramInfos = samParamTypes)
855-
else implType.derivedLambdaType(resType = samResultType)
856-
val bridge = ctx.newSymbol(ctx.owner, AdaptedClosureName(meth.symbol.name.asTermName), Flags.Synthetic | Flags.Method, bridgeType)
857-
val bridgeCtx = ctx.withOwner(bridge)
858-
Closure(bridge, bridgeParamss => {
859-
implicit val ctx = bridgeCtx
860-
861-
val List(bridgeParams) = bridgeParamss
862-
val rhs = Apply(meth, bridgeParams.lazyZip(implParamTypes).map(adapt(_, _)))
863-
adapt(rhs, bridgeType.resultType)
864-
}, targetType = implClosure.tpt.tpe)
865-
}
866-
else implClosure
867-
}
868-
else implClosure
869-
case _ =>
870-
implClosure
871-
}
881+
adaptClosure(implClosure)
872882
}
873883

874884
override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(implicit ctx: Context): Tree =

0 commit comments

Comments
 (0)