@@ -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