@@ -376,99 +376,131 @@ object Erasure {
376
376
*
377
377
* val f: Function1[Int, Any] = x => ...
378
378
*
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:
380
380
*
381
381
* def $anonfun(x: Int): Any = ...
382
382
* val f: Function1[Int, Any] = closure($anonfun)
383
383
*
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)
385
385
* of `Function1` after erasure is:
386
386
*
387
387
* def apply(x: Object): Object
388
388
*
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:
390
394
*
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)
397
397
*
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.
421
402
*
422
403
* See test cases lambda-*.scala and t8017/ for concrete examples.
423
404
*/
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
472
504
473
505
/** Eta expand given `tree` that has the given method type `mt`, so that
474
506
* it conforms to erased result type `pt`.
0 commit comments