Skip to content

Commit 2992b74

Browse files
Backport "Fix #24573: Add stricter checks for platform SAM compatibility" to 3.8.0 (#24798)
Backports #24624 to the 3.8.0-RC4. PR submitted by the release tooling. [skip ci]
2 parents 66bfc9c + 6413f68 commit 2992b74

File tree

5 files changed

+339
-38
lines changed

5 files changed

+339
-38
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ class JavaPlatform extends Platform {
5050
cls.superClass == defn.ObjectClass &&
5151
cls.directlyInheritedTraits.forall(_.is(NoInits)) &&
5252
!ExplicitOuter.needsOuterIfReferenced(cls) &&
53-
cls.typeRef.fields.isEmpty // Superaccessors already show up as abstract methods here, so no test necessary
53+
// Superaccessors already show up as abstract methods here, so no test necessary
54+
cls.typeRef.fields.isEmpty &&
55+
// Check if the SAM can be implemented via LambdaMetaFactory
56+
TypeErasure.samExpansionNotNeeded(cls)
5457

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

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

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ end SourceLanguage
7474
* only for isInstanceOf, asInstanceOf: PolyType, TypeParamRef, TypeBounds
7575
*
7676
*/
77-
object TypeErasure {
77+
object TypeErasure:
7878

7979
private def erasureDependsOnArgs(sym: Symbol)(using Context) =
8080
sym == defn.ArrayClass || sym == defn.PairClass || sym.isDerivedValueClass
@@ -586,7 +586,102 @@ object TypeErasure {
586586
defn.FunctionType(n = info.nonErasedParamCount)
587587
}
588588
erasure(functionType(applyInfo))
589-
}
589+
590+
/** Check if LambdaMetaFactory can handle signature adaptation between two method types.
591+
*
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.
594+
*
595+
* The adaptation rules are:
596+
* - For parameters: primitives and value classes 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).
658+
*
659+
* When this returns true, the SAM class does not need to be expanded.
660+
*
661+
* @param cls The SAM class to check
662+
* @return true if LMF can handle the required adaptation
663+
*/
664+
def samExpansionNotNeeded(cls: ClassSymbol)(using Context): Boolean = cls.typeRef.possibleSamMethods match
665+
case Seq(samMeth) =>
666+
val samMethSym = samMeth.symbol
667+
val erasedSamInfo = transformInfo(samMethSym, samMeth.info)
668+
669+
val (erasedSamParamTypes, erasedSamResultType) = erasedSamInfo match
670+
case mt: MethodType => (mt.paramInfos, mt.resultType)
671+
case _ => return false
672+
673+
samMethSym.allOverriddenSymbols.forall { overridden =>
674+
val erasedOverriddenInfo = transformInfo(overridden, overridden.info)
675+
erasedOverriddenInfo match
676+
case mt: MethodType =>
677+
val (paramNeeded, resultNeeded) =
678+
additionalAdaptationNeeded(erasedSamParamTypes, erasedSamResultType, mt.paramInfos, mt.resultType)
679+
!(paramNeeded || resultNeeded)
680+
case _ => true
681+
}
682+
case _ => false
683+
end samExpansionNotNeeded
684+
end TypeErasure
590685

591686
import TypeErasure.*
592687

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/javase/8/docs/api/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

tests/run/i24573.check

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
1
2+
2
3+
3
4+
11
5+
12
6+
13
7+
14
8+
15
9+
16
10+
17
11+
18
12+
19
13+
20
14+
21
15+
22
16+
23
17+
24
18+
25
19+
31
20+
32
21+
33
22+
34
23+
41
24+
42
25+
43
26+
44
27+
45
28+
46
29+
51
30+
52
31+
53
32+
55
33+
56
34+
57
35+
61
36+
62
37+
63
38+
64
39+
65
40+
71
41+
72
42+
75
43+
76
44+
81
45+
82

0 commit comments

Comments
 (0)