Skip to content

Commit 3f45ce8

Browse files
committed
Extract common logic for checking the need of parameter and result bridging
1 parent eb46cea commit 3f45ce8

File tree

3 files changed

+74
-81
lines changed

3 files changed

+74
-81
lines changed

compiler/src/dotty/tools/dotc/config/JavaPlatform.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class JavaPlatform extends Platform {
5454
// Superaccessors already show up as abstract methods here, so no test necessary
5555
cls.typeRef.fields.isEmpty &&
5656
// Check if the SAM can be implemented via LambdaMetaFactory
57-
TypeErasure.samDoesNotNeedExpansion(cls)
57+
TypeErasure.samNotNeededExpansion(cls)
5858

5959
/** We could get away with excluding BoxedBooleanClass for the
6060
* purpose of equality testing since it need not compare equal

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 70 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -587,22 +587,81 @@ object TypeErasure:
587587
}
588588
erasure(functionType(applyInfo))
589589

590-
/** Check if LambdaMetaFactory can handle the SAM method's required signature adaptation.
590+
/** Check if LambdaMetaFactory can handle signature adaptation between two method types.
591591
*
592-
* When a SAM method overrides other methods, the erased signatures must be compatible
593-
* for LambdaMetaFactory to work. This method returns true if any overridden method
594-
* has an incompatible erased signature that LMF cannot auto-adapt.
592+
* LMF has limitations on what type adaptations it can perform automatically.
593+
* This method checks whether manual bridging is needed for params and/or result.
595594
*
596-
* The adaptation rules mirror those in `Erasure.Boxing.adaptClosure`:
595+
* The adaptation rules are:
597596
* - For parameters: primitives and value classes cannot be auto-adapted by LMF
598-
* - For results: value classes and Unit cannot be auto-adapted by LMF
597+
* because the Scala spec requires null to be "unboxed" to the default value,
598+
* but LMF throws `NullPointerException` instead.
599+
* - For results: value classes and Unit cannot be auto-adapted by LMF.
600+
* Non-Unit primitives can be auto-adapted since LMF only needs to box (not unbox).
601+
* - LMF cannot auto-adapt between Object and Array types.
602+
*
603+
* @param implParamTypes Parameter types of the implementation method
604+
* @param implResultType Result type of the implementation method
605+
* @param samParamTypes Parameter types of the SAM method
606+
* @param samResultType Result type of the SAM method
607+
*
608+
* @return (paramNeeded, resultNeeded) indicating what needs bridging
609+
*/
610+
def additionalAdaptationNeeded(
611+
implParamTypes: List[Type],
612+
implResultType: Type,
613+
samParamTypes: List[Type],
614+
samResultType: Type
615+
)(using Context): (paramNeeded: Boolean, resultNeeded: Boolean) =
616+
def sameClass(tp1: Type, tp2: Type) = tp1.classSymbol == tp2.classSymbol
617+
618+
/** Can the implementation parameter type `tp` be auto-adapted to a different
619+
* parameter type in the SAM?
620+
*
621+
* For derived value classes, we always need to do the bridging manually.
622+
* For primitives, we cannot rely on auto-adaptation on the JVM because
623+
* the Scala spec requires null to be "unboxed" to the default value of
624+
* the value class, but the adaptation performed by LambdaMetaFactory
625+
* will throw a `NullPointerException` instead.
626+
*/
627+
def autoAdaptedParam(tp: Type) = !tp.isErasedValueType && !tp.isPrimitiveValueType
628+
629+
/** Can the implementation result type be auto-adapted to a different result
630+
* type in the SAM?
631+
*
632+
* For derived value classes, it's the same story as for parameters.
633+
* For non-Unit primitives, we can actually rely on the `LambdaMetaFactory`
634+
* adaptation, because it only needs to box, not unbox, so no special
635+
* handling of null is required.
636+
*/
637+
def autoAdaptedResult(tp: Type) =
638+
!tp.isErasedValueType && !(tp.classSymbol eq defn.UnitClass)
639+
640+
val paramAdaptationNeeded =
641+
implParamTypes.lazyZip(samParamTypes).exists((implType, samType) =>
642+
!sameClass(implType, samType) && (!autoAdaptedParam(implType)
643+
// LambdaMetaFactory cannot auto-adapt between Object and Array types
644+
|| samType.isInstanceOf[JavaArrayType]))
645+
646+
val resultAdaptationNeeded =
647+
!sameClass(implResultType, samResultType) && !autoAdaptedResult(implResultType)
648+
649+
(paramAdaptationNeeded, resultAdaptationNeeded)
650+
end additionalAdaptationNeeded
651+
652+
/** Check if LambdaMetaFactory can handle the SAM method's required signature adaptation.
653+
*
654+
* When a SAM method overrides other methods, the erased signatures must be compatible
655+
* to be qualifies as a valid functional interface on JVM.
656+
* This method returns true if all overridden methods have compatible erased signatures
657+
* that LMF can auto-adapt (or don't need adaptation).
599658
*
600659
* When this returns true, the SAM class does not need to be expanded.
601660
*
602661
* @param cls The SAM class to check
603662
* @return true if LMF can handle the required adaptation
604663
*/
605-
def samDoesNotNeedExpansion(cls: ClassSymbol)(using Context): Boolean = cls.typeRef.possibleSamMethods match
664+
def samNotNeededExpansion(cls: ClassSymbol)(using Context): Boolean = cls.typeRef.possibleSamMethods match
606665
case Seq(samMeth) =>
607666
val samMethSym = samMeth.symbol
608667
val erasedSamInfo = transformInfo(samMethSym, samMeth.info)
@@ -611,51 +670,17 @@ object TypeErasure:
611670
case mt: MethodType => (mt.paramInfos, mt.resultType)
612671
case _ => return false
613672

614-
def sameClass(tp1: Type, tp2: Type) = tp1.classSymbol == tp2.classSymbol
615-
616-
/** Can the implementation parameter type `tp` be auto-adapted to a different
617-
* parameter type in the SAM?
618-
*
619-
* For derived value classes, we always need to do the bridging manually.
620-
* For primitives, we cannot rely on auto-adaptation on the JVM because
621-
* the Scala spec requires null to be "unboxed" to the default value of
622-
* the value class, but the adaptation performed by LambdaMetaFactory
623-
* will throw a `NullPointerException` instead.
624-
*/
625-
def autoAdaptedParam(tp: Type) = !tp.isErasedValueType && !tp.isPrimitiveValueType
626-
627-
/** Can the implementation result type be auto-adapted to a different result
628-
* type in the SAM?
629-
*
630-
* For derived value classes, it's the same story as for parameters.
631-
* For non-Unit primitives, we can actually rely on the `LambdaMetaFactory`
632-
* adaptation, because it only needs to box, not unbox, so no special
633-
* handling of null is required.
634-
*/
635-
def autoAdaptedResult(implResultType: Type) =
636-
!implResultType.isErasedValueType && !(implResultType.classSymbol eq defn.UnitClass)
637-
638673
samMethSym.allOverriddenSymbols.forall { overridden =>
639674
val erasedOverriddenInfo = transformInfo(overridden, overridden.info)
640675
erasedOverriddenInfo match
641676
case mt: MethodType =>
642-
val overriddenParamTypes = mt.paramInfos
643-
val overriddenResultType = mt.resultType
644-
645-
val paramAdaptationNeeded =
646-
erasedSamParamTypes.lazyZip(overriddenParamTypes).exists((samType, overriddenType) =>
647-
!sameClass(samType, overriddenType) && (!autoAdaptedParam(samType)
648-
// LambdaMetaFactory cannot auto-adapt between Object and Array types
649-
|| overriddenType.isInstanceOf[JavaArrayType]))
650-
651-
val resultAdaptationNeeded =
652-
!sameClass(erasedSamResultType, overriddenResultType) && !autoAdaptedResult(erasedSamResultType)
653-
654-
!(paramAdaptationNeeded || resultAdaptationNeeded)
677+
val (paramNeeded, resultNeeded) =
678+
additionalAdaptationNeeded(erasedSamParamTypes, erasedSamResultType, mt.paramInfos, mt.resultType)
679+
!(paramNeeded || resultNeeded)
655680
case _ => true
656681
}
657682
case _ => false
658-
end samDoesNotNeedExpansion
683+
end samNotNeededExpansion
659684
end TypeErasure
660685

661686
import TypeErasure.*

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

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -453,41 +453,9 @@ object Erasure {
453453
val samParamTypes = sam.paramInfos
454454
val samResultType = sam.resultType
455455

456-
/** Can the implementation parameter type `tp` be auto-adapted to a different
457-
* parameter type in the SAM?
458-
*
459-
* For derived value classes, we always need to do the bridging manually.
460-
* For primitives, we cannot rely on auto-adaptation on the JVM because
461-
* the Scala spec requires null to be "unboxed" to the default value of
462-
* the value class, but the adaptation performed by LambdaMetaFactory
463-
* will throw a `NullPointerException` instead. See `lambda-null.scala`
464-
* for test cases.
465-
*
466-
* @see [LambdaMetaFactory](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/invoke/LambdaMetafactory.html)
467-
*/
468-
def autoAdaptedParam(tp: Type) =
469-
!tp.isErasedValueType && !tp.isPrimitiveValueType
470-
471-
/** Can the implementation result type be auto-adapted to a different result
472-
* type in the SAM?
473-
*
474-
* For derived value classes, it's the same story as for parameters.
475-
* For non-Unit primitives, we can actually rely on the `LambdaMetaFactory`
476-
* adaptation, because it only needs to box, not unbox, so no special
477-
* handling of null is required.
478-
*/
479-
def autoAdaptedResult =
480-
!implResultType.isErasedValueType && !implReturnsUnit
481-
482-
def sameClass(tp1: Type, tp2: Type) = tp1.classSymbol == tp2.classSymbol
483-
484-
val paramAdaptationNeeded =
485-
implParamTypes.lazyZip(samParamTypes).exists((implType, samType) =>
486-
!sameClass(implType, samType) && (!autoAdaptedParam(implType)
487-
// LambdaMetaFactory cannot auto-adapt between Object and Array types
488-
|| samType.isInstanceOf[JavaArrayType]))
489-
val resultAdaptationNeeded =
490-
!sameClass(implResultType, samResultType) && !autoAdaptedResult
456+
// Check if bridging is needed using the common function from TypeErasure
457+
val (paramAdaptationNeeded, resultAdaptationNeeded) =
458+
additionalAdaptationNeeded(implParamTypes, implResultType, samParamTypes, samResultType)
491459

492460
if paramAdaptationNeeded || resultAdaptationNeeded then
493461
// Instead of instantiating `scala.FunctionN`, see if we can instantiate

0 commit comments

Comments
 (0)