Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/config/JavaPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ class JavaPlatform extends Platform {
cls.superClass == defn.ObjectClass &&
cls.directlyInheritedTraits.forall(_.is(NoInits)) &&
!ExplicitOuter.needsOuterIfReferenced(cls) &&
cls.typeRef.fields.isEmpty // Superaccessors already show up as abstract methods here, so no test necessary
// Superaccessors already show up as abstract methods here, so no test necessary
cls.typeRef.fields.isEmpty &&
// Check if the SAM can be implemented via LambdaMetaFactory
TypeErasure.samExpansionNotNeeded(cls)

/** We could get away with excluding BoxedBooleanClass for the
* purpose of equality testing since it need not compare equal
Expand Down
99 changes: 97 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ end SourceLanguage
* only for isInstanceOf, asInstanceOf: PolyType, TypeParamRef, TypeBounds
*
*/
object TypeErasure {
object TypeErasure:

private def erasureDependsOnArgs(sym: Symbol)(using Context) =
sym == defn.ArrayClass || sym == defn.PairClass || sym.isDerivedValueClass
Expand Down Expand Up @@ -586,7 +586,102 @@ object TypeErasure {
defn.FunctionType(n = info.nonErasedParamCount)
}
erasure(functionType(applyInfo))
}

/** Check if LambdaMetaFactory can handle signature adaptation between two method types.
*
* LMF has limitations on what type adaptations it can perform automatically.
* This method checks whether manual bridging is needed for params and/or result.
*
* The adaptation rules are:
* - For parameters: primitives and value classes cannot be auto-adapted by LMF
* because the Scala spec requires null to be "unboxed" to the default value,
* but LMF throws `NullPointerException` instead.
* - For results: value classes and Unit cannot be auto-adapted by LMF.
* Non-Unit primitives can be auto-adapted since LMF only needs to box (not unbox).
* - LMF cannot auto-adapt between Object and Array types.
*
* @param implParamTypes Parameter types of the implementation method
* @param implResultType Result type of the implementation method
* @param samParamTypes Parameter types of the SAM method
* @param samResultType Result type of the SAM method
*
* @return (paramNeeded, resultNeeded) indicating what needs bridging
*/
def additionalAdaptationNeeded(
implParamTypes: List[Type],
implResultType: Type,
samParamTypes: List[Type],
samResultType: Type
)(using Context): (paramNeeded: Boolean, resultNeeded: Boolean) =
def sameClass(tp1: Type, tp2: Type) = tp1.classSymbol == tp2.classSymbol

/** Can the implementation parameter type `tp` be auto-adapted to a different
* parameter type in the SAM?
*
* For derived value classes, we always need to do the bridging manually.
* For primitives, we cannot rely on auto-adaptation on the JVM because
* the Scala spec requires null to be "unboxed" to the default value of
* the value class, but the adaptation performed by LambdaMetaFactory
* will throw a `NullPointerException` instead.
*/
def autoAdaptedParam(tp: Type) = !tp.isErasedValueType && !tp.isPrimitiveValueType

/** Can the implementation result type be auto-adapted to a different result
* type in the SAM?
*
* For derived value classes, it's the same story as for parameters.
* For non-Unit primitives, we can actually rely on the `LambdaMetaFactory`
* adaptation, because it only needs to box, not unbox, so no special
* handling of null is required.
*/
def autoAdaptedResult(tp: Type) =
!tp.isErasedValueType && !(tp.classSymbol eq defn.UnitClass)

val paramAdaptationNeeded =
implParamTypes.lazyZip(samParamTypes).exists((implType, samType) =>
!sameClass(implType, samType) && (!autoAdaptedParam(implType)
// LambdaMetaFactory cannot auto-adapt between Object and Array types
|| samType.isInstanceOf[JavaArrayType]))

val resultAdaptationNeeded =
!sameClass(implResultType, samResultType) && !autoAdaptedResult(implResultType)

(paramAdaptationNeeded, resultAdaptationNeeded)
end additionalAdaptationNeeded

/** Check if LambdaMetaFactory can handle the SAM method's required signature adaptation.
*
* When a SAM method overrides other methods, the erased signatures must be compatible
* to be qualifies as a valid functional interface on JVM.
* This method returns true if all overridden methods have compatible erased signatures
* that LMF can auto-adapt (or don't need adaptation).
*
* When this returns true, the SAM class does not need to be expanded.
*
* @param cls The SAM class to check
* @return true if LMF can handle the required adaptation
*/
def samExpansionNotNeeded(cls: ClassSymbol)(using Context): Boolean = cls.typeRef.possibleSamMethods match
case Seq(samMeth) =>
val samMethSym = samMeth.symbol
val erasedSamInfo = transformInfo(samMethSym, samMeth.info)

val (erasedSamParamTypes, erasedSamResultType) = erasedSamInfo match
case mt: MethodType => (mt.paramInfos, mt.resultType)
case _ => return false

samMethSym.allOverriddenSymbols.forall { overridden =>
val erasedOverriddenInfo = transformInfo(overridden, overridden.info)
erasedOverriddenInfo match
case mt: MethodType =>
val (paramNeeded, resultNeeded) =
additionalAdaptationNeeded(erasedSamParamTypes, erasedSamResultType, mt.paramInfos, mt.resultType)
!(paramNeeded || resultNeeded)
case _ => true
}
case _ => false
end samExpansionNotNeeded
end TypeErasure

import TypeErasure.*

Expand Down
38 changes: 3 additions & 35 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -453,41 +453,9 @@ object Erasure {
val samParamTypes = sam.paramInfos
val samResultType = sam.resultType

/** Can the implementation parameter type `tp` be auto-adapted to a different
* parameter type in the SAM?
*
* For derived value classes, we always need to do the bridging manually.
* For primitives, we cannot rely on auto-adaptation on the JVM because
* the Scala spec requires null to be "unboxed" to the default value of
* the value class, but the adaptation performed by LambdaMetaFactory
* will throw a `NullPointerException` instead. See `lambda-null.scala`
* for test cases.
*
* @see [LambdaMetaFactory](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/invoke/LambdaMetafactory.html)
*/
def autoAdaptedParam(tp: Type) =
!tp.isErasedValueType && !tp.isPrimitiveValueType

/** Can the implementation result type be auto-adapted to a different result
* type in the SAM?
*
* For derived value classes, it's the same story as for parameters.
* For non-Unit primitives, we can actually rely on the `LambdaMetaFactory`
* adaptation, because it only needs to box, not unbox, so no special
* handling of null is required.
*/
def autoAdaptedResult =
!implResultType.isErasedValueType && !implReturnsUnit

def sameClass(tp1: Type, tp2: Type) = tp1.classSymbol == tp2.classSymbol

val paramAdaptationNeeded =
implParamTypes.lazyZip(samParamTypes).exists((implType, samType) =>
!sameClass(implType, samType) && (!autoAdaptedParam(implType)
// LambdaMetaFactory cannot auto-adapt between Object and Array types
|| samType.isInstanceOf[JavaArrayType]))
val resultAdaptationNeeded =
!sameClass(implResultType, samResultType) && !autoAdaptedResult
// Check if bridging is needed using the common function from TypeErasure
val (paramAdaptationNeeded, resultAdaptationNeeded) =
additionalAdaptationNeeded(implParamTypes, implResultType, samParamTypes, samResultType)

if paramAdaptationNeeded || resultAdaptationNeeded then
// Instead of instantiating `scala.FunctionN`, see if we can instantiate
Expand Down
45 changes: 45 additions & 0 deletions tests/run/i24573.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
1
2
3
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
31
32
33
34
41
42
43
44
45
46
51
52
53
55
56
57
61
62
63
64
65
71
72
75
76
81
82
Loading
Loading