@@ -376,99 +376,131 @@ object Erasure {
376376 *
377377 * val f: Function1[Int, Any] = x => ...
378378 *
379- * results in the creation of a closure and a method in the typer:
379+ * results in the creation of a closure and an implementation method in the typer:
380380 *
381381 * def $anonfun(x: Int): Any = ...
382382 * val f: Function1[Int, Any] = closure($anonfun)
383383 *
384- * Notice that `$anonfun` takes a primitive as argument, but the single abstract method
384+ * Notice that `$anonfun` takes a primitive as argument, but the SAM (Single Abstract Method)
385385 * of `Function1` after erasure is:
386386 *
387387 * def apply(x: Object): Object
388388 *
389- * which takes a reference as argument. Hence, some form of adaptation is required.
389+ * which takes a reference as argument. Hence, some form of adaptation is
390+ * required. The most reliable way to do this adaptation is to replace the
391+ * closure implementation method by a bridge method that forwards to the
392+ * original method with appropriate boxing/unboxing. For our example above,
393+ * this would be:
390394 *
391- * If we do nothing, the LambdaMetaFactory bootstrap method will
392- * automatically do the adaptation. Unfortunately, the result does not
393- * implement the expected Scala semantics: null should be "unboxed" to
394- * the default value of the value class, but LMF will throw a
395- * NullPointerException instead. LMF is also not capable of doing
396- * adaptation for derived value classes.
395+ * def $anonfun$adapted(x: Object): Object = $anonfun(BoxesRunTime.unboxToInt(x))
396+ * val f: Function1 = closure($anonfun$adapted)
397397 *
398- * Thus, we need to replace the closure method by a bridge method that
399- * forwards to the original closure method with appropriate
400- * boxing/unboxing. For our example above, this would be:
401- *
402- * def $anonfun1(x: Object): Object = $anonfun(BoxesRunTime.unboxToInt(x))
403- * val f: Function1 = closure($anonfun1)
404- *
405- * In general a bridge is needed when, after Erasure, one of the
406- * parameter type or the result type of the closure method has a
407- * different type, and we cannot rely on auto-adaptation.
408- *
409- * Auto-adaptation works in the following cases:
410- * - If the SAM is replaced by JFunction*mc* in
411- * [[FunctionalInterfaces ]], no bridge is needed: the SAM contains
412- * default methods to handle adaptation.
413- * - If a result type of the closure method is a primitive value type
414- * different from Unit, we can rely on the auto-adaptation done by
415- * LMF (because it only needs to box, not unbox, so no special
416- * handling of null is required).
417- * - If the SAM is replaced by JProcedure* in
418- * [[DottyBackendInterface ]] (this only happens when no explicit SAM
419- * type is given), no bridge is needed to box a Unit result type:
420- * the SAM contains a default method to handle that.
398+ * But in some situations we can avoid generating this bridge, either
399+ * because the runtime can perform auto-adaptation, or because we can
400+ * replace the closure functional interface by a specialized sub-interface,
401+ * see comments in this method for details.
421402 *
422403 * See test cases lambda-*.scala and t8017/ for concrete examples.
423404 */
424- def adaptClosure (tree : tpd.Closure )(using Context ): Tree = {
425- val implClosure @ Closure (_, meth, _) = tree
426-
427- implClosure.tpe match {
428- case SAMType (sam) =>
429- val implType = meth.tpe.widen.asInstanceOf [MethodType ]
430-
431- val implParamTypes = implType.paramInfos
432- val List (samParamTypes) = sam.paramInfoss
433- val implResultType = implType.resultType
434- val samResultType = sam.resultType
435-
436- if (! defn.isSpecializableFunction(implClosure.tpe.classSymbol.asClass, implParamTypes, implResultType)) {
437- def autoAdaptedParam (tp : Type ) = ! tp.isErasedValueType && ! tp.isPrimitiveValueType
438- val explicitSAMType = implClosure.tpt.tpe.exists
439- def autoAdaptedResult (tp : Type ) = ! tp.isErasedValueType &&
440- (! explicitSAMType || tp.typeSymbol != defn.UnitClass )
441- def sameSymbol (tp1 : Type , tp2 : Type ) = tp1.typeSymbol == tp2.typeSymbol
442-
443- val paramAdaptationNeeded =
444- implParamTypes.lazyZip(samParamTypes).exists((implType, samType) =>
445- ! sameSymbol(implType, samType) && ! autoAdaptedParam(implType))
446- val resultAdaptationNeeded =
447- ! sameSymbol(implResultType, samResultType) && ! autoAdaptedResult(implResultType)
448-
449- if (paramAdaptationNeeded || resultAdaptationNeeded) {
450- val bridgeType =
451- if (paramAdaptationNeeded)
452- if (resultAdaptationNeeded) sam
453- else implType.derivedLambdaType(paramInfos = samParamTypes)
454- else implType.derivedLambdaType(resType = samResultType)
455- val bridge = newSymbol(ctx.owner, AdaptedClosureName (meth.symbol.name.asTermName), Flags .Synthetic | Flags .Method , bridgeType)
456- Closure (bridge, bridgeParamss =>
457- inContext(ctx.withOwner(bridge)) {
458- val List (bridgeParams) = bridgeParamss
459- assert(ctx.typer.isInstanceOf [Erasure .Typer ])
460- val rhs = Apply (meth, bridgeParams.lazyZip(implParamTypes).map(ctx.typer.adapt(_, _)))
461- ctx.typer.adapt(rhs, bridgeType.resultType)
462- },
463- targetType = implClosure.tpt.tpe)
464- }
465- else implClosure
466- }
467- else implClosure
468- case _ =>
469- implClosure
470- }
471- }
405+ def adaptClosure (tree : tpd.Closure )(using Context ): Tree =
406+ val Closure (env, meth, tpt) = tree
407+ assert(env.isEmpty, tree)
408+
409+ // The type of the lambda expression
410+ val lambdaType = tree.tpe
411+ // The interface containing the SAM that this closure should implement
412+ val functionalInterface = tpt.tpe
413+ // A lack of an explicit functional interface means we're implementing a scala.FunctionN
414+ val isFunction = ! functionalInterface.exists
415+ // The actual type of the implementation method
416+ val implType = meth.tpe.widen.asInstanceOf [MethodType ]
417+ val implParamTypes = implType.paramInfos
418+ val implResultType = implType.resultType
419+ val implReturnsUnit = implResultType.classSymbol eq defn.UnitClass
420+ // The SAM that this closure should implement
421+ val SAMType (sam) = lambdaType : @ unchecked
422+ val samParamTypes = sam.paramInfos
423+ val samResultType = sam.resultType
424+
425+ /** Can the implementation parameter type `tp` be auto-adapted to a different
426+ * parameter type in the SAM?
427+ *
428+ * For derived value classes, we always need to do the bridging manually.
429+ * For primitives, we cannot rely on auto-adaptation on the JVM because
430+ * the Scala spec requires null to be "unboxed" to the default value of
431+ * the value class, but the adaptation performed by LambdaMetaFactory
432+ * will throw a `NullPointerException` instead. See `lambda-null.scala`
433+ * for test cases.
434+ *
435+ * @see [LambdaMetaFactory](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html)
436+ */
437+ def autoAdaptedParam (tp : Type ) =
438+ ! tp.isErasedValueType && ! tp.isPrimitiveValueType
439+
440+ /** Can the implementation result type be auto-adapted to a different result
441+ * type in the SAM?
442+ *
443+ * For derived value classes, it's the same story as for parameters.
444+ * For non-Unit primitives, we can actually rely on the `LambdaMetaFactory`
445+ * adaptation, because it only needs to box, not unbox, so no special
446+ * handling of null is required.
447+ */
448+ def autoAdaptedResult =
449+ ! implResultType.isErasedValueType && ! implReturnsUnit
450+
451+ def sameClass (tp1 : Type , tp2 : Type ) = tp1.classSymbol == tp2.classSymbol
452+
453+ val paramAdaptationNeeded =
454+ implParamTypes.lazyZip(samParamTypes).exists((implType, samType) =>
455+ ! sameClass(implType, samType) && ! autoAdaptedParam(implType))
456+ val resultAdaptationNeeded =
457+ ! sameClass(implResultType, samResultType) && ! autoAdaptedResult
458+
459+ if paramAdaptationNeeded || resultAdaptationNeeded then
460+ // Instead of instantiating `scala.FunctionN`, see if we can instantiate
461+ // a specialized sub-interface where the SAM type matches the
462+ // implementation method type, thus avoiding the need for bridging.
463+ // This optimization is skipped when using Scala.js because its backend
464+ // does not support closures using custom functional interfaces.
465+ if isFunction && ! ctx.settings.scalajs.value then
466+ val arity = implParamTypes.length
467+ val specializedFunctionalInterface =
468+ if defn.isSpecializableFunctionSAM(implParamTypes, implResultType) then
469+ // Using these subclasses is critical to avoid boxing since their
470+ // SAM is a specialized method `apply$mc*$sp` whose default
471+ // implementation in FunctionN boxes.
472+ tpnme.JFunctionPrefix (arity).specializedFunction(implResultType, implParamTypes)
473+ else if ! paramAdaptationNeeded && implReturnsUnit then
474+ // Here, there is no actual boxing to avoid so we could get by
475+ // without JProcedureN, but Unit-returning functions are very
476+ // common so it seems worth it to not generate bridges for them.
477+ tpnme.JProcedure (arity)
478+ else
479+ EmptyTypeName
480+ if ! specializedFunctionalInterface.isEmpty then
481+ return cpy.Closure (tree)(tpt = TypeTree (requiredClass(specializedFunctionalInterface).typeRef))
482+
483+ // Otherwise, generate a new closure implemented with a bridge.
484+ val bridgeType =
485+ if paramAdaptationNeeded then
486+ if resultAdaptationNeeded then
487+ sam
488+ else
489+ implType.derivedLambdaType(paramInfos = samParamTypes)
490+ else
491+ implType.derivedLambdaType(resType = samResultType)
492+ val bridge = newSymbol(ctx.owner, AdaptedClosureName (meth.symbol.name.asTermName), Flags .Synthetic | Flags .Method , bridgeType)
493+ Closure (bridge, bridgeParamss =>
494+ inContext(ctx.withOwner(bridge)) {
495+ val List (bridgeParams) = bridgeParamss
496+ assert(ctx.typer.isInstanceOf [Erasure .Typer ])
497+ val rhs = Apply (meth, bridgeParams.lazyZip(implParamTypes).map(ctx.typer.adapt(_, _)))
498+ ctx.typer.adapt(rhs, bridgeType.resultType)
499+ },
500+ targetType = functionalInterface).withSpan(tree.span)
501+ else
502+ tree
503+ end adaptClosure
472504
473505 /** Eta expand given `tree` that has the given method type `mt`, so that
474506 * it conforms to erased result type `pt`.
0 commit comments