Skip to content

Commit 2401232

Browse files
bracevacWojciechMazur
authored andcommitted
Refine parameter adaptation logic for arrays (#23591)
Fixes #23179 Since JDK 9, argument checks for LambdaMetaFactory have become stricter. Which is to say that its JDK 8 version was too lax. We apply adaptation now in case the samType is an array, but the implType is not. [Cherry-picked e53d6fc]
1 parent 3a54a1a commit 2401232

File tree

4 files changed

+101
-9
lines changed

4 files changed

+101
-9
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,9 @@ object Erasure {
483483

484484
val paramAdaptationNeeded =
485485
implParamTypes.lazyZip(samParamTypes).exists((implType, samType) =>
486-
!sameClass(implType, samType) && !autoAdaptedParam(implType))
486+
!sameClass(implType, samType) && (!autoAdaptedParam(implType)
487+
// LambdaMetaFactory cannot auto-adapt between Object and Array types
488+
|| samType.isInstanceOf[JavaArrayType]))
487489
val resultAdaptationNeeded =
488490
!sameClass(implResultType, samResultType) && !autoAdaptedResult
489491

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

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dotc
33
package transform
44

55
import core.*
6+
import core.TypeErasure.erasure
67
import Scopes.newScope
78
import Contexts.*, Symbols.*, Types.*, Flags.*, Decorators.*, StdNames.*, Constants.*
89
import MegaPhase.*
@@ -69,14 +70,50 @@ class ExpandSAMs extends MiniPhase:
6970
case _ => tp
7071
val tpe1 = collectAndStripRefinements(tpe)
7172
val Seq(samDenot) = tpe1.possibleSamMethods
72-
cpy.Block(tree)(stats,
73-
transformFollowingDeep:
74-
AnonClass(List(tpe1),
75-
List(samDenot.symbol.asTerm.name -> fn.symbol.asTerm),
76-
refinements.toList,
77-
adaptVarargs = true
78-
)
79-
)
73+
val implSym = fn.symbol.asTerm
74+
75+
// Check if SAM param erases to array but impl param doesn't (see #23179).
76+
// E.g. SAM expects Array[AnyRef] (erases to Array[Object]) but impl has
77+
// Array[? >: AnyRef] (erases to Object). In this case we need to create
78+
// a wrapper with the SAM signature that casts arguments before forwarding.
79+
val samInfo = samDenot.info
80+
val implInfo = implSym.info
81+
val needsArrayAdaptation = (samInfo, implInfo) match
82+
case (samMt: MethodType, implMt: MethodType) =>
83+
samMt.paramInfos.lazyZip(implMt.paramInfos).exists { (samPt, implPt) =>
84+
erasure(samPt).isInstanceOf[JavaArrayType] && !erasure(implPt).isInstanceOf[JavaArrayType]
85+
}
86+
case _ => false
87+
88+
if needsArrayAdaptation then
89+
// Create a wrapper method with SAM's signature that casts args to impl's types
90+
val wrapperSym = newSymbol(
91+
implSym.owner, implSym.name.asTermName, Synthetic | Method,
92+
samInfo, coord = implSym.span).entered.asTerm
93+
val wrapperDef = DefDef(wrapperSym, paramss => {
94+
val implMt = implInfo.stripPoly.asInstanceOf[MethodType]
95+
val adaptedArgs = paramss.head.lazyZip(implMt.paramInfos).map { (arg, implPt) =>
96+
if arg.tpe =:= implPt then arg else arg.cast(implPt)
97+
}
98+
ref(implSym).appliedToTermArgs(adaptedArgs)
99+
})
100+
cpy.Block(tree)(fn :: wrapperDef :: Nil,
101+
transformFollowingDeep:
102+
AnonClass(List(tpe1),
103+
List(samDenot.symbol.asTerm.name -> wrapperSym),
104+
refinements.toList,
105+
adaptVarargs = true
106+
)
107+
)
108+
else
109+
cpy.Block(tree)(stats,
110+
transformFollowingDeep:
111+
AnonClass(List(tpe1),
112+
List(samDenot.symbol.asTerm.name -> implSym),
113+
refinements.toList,
114+
adaptVarargs = true
115+
)
116+
)
80117
}
81118
case _ =>
82119
tree

tests/neg/i23179-reverse.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Test that reverse cases of #23179 are properly rejected by the type checker.
2+
// The original issue was about SAM expecting Array[T] but impl having Array[? >: T].
3+
// Due to array invariance, the reverse cases cannot occur.
4+
5+
object TestReverse {
6+
trait A { def f(a: Array[? <: AnyRef]): Any }
7+
def g(a: A) = a.f(Array.empty[AnyRef])
8+
9+
def test(): Unit = {
10+
g((x: Array[AnyRef]) => x.headOption) // error: Array[AnyRef] is not a subtype of Array[? <: AnyRef]
11+
}
12+
}
13+
14+
object TestResult {
15+
trait A { def f(a: Int): Array[AnyRef] }
16+
def g(a: A) = a.f(1)
17+
18+
def test(): Unit = {
19+
val arr: Array[? >: AnyRef] = Array("hello")
20+
g((x: Int) => arr) // error: Array[? >: AnyRef] is not a subtype of Array[AnyRef]
21+
}
22+
}

tests/run/i23179.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
object Test {
2+
// Basic case: SAM expects Array[AnyRef], impl has Array[? >: AnyRef]
3+
trait A { def f(a: Array[AnyRef]): Any }
4+
def g(a: A) = a.f(Array.empty[AnyRef])
5+
6+
// Multiple array parameters
7+
trait MultiParam { def f(a: Array[AnyRef], b: Array[AnyRef]): Any }
8+
def gMulti(a: MultiParam) = a.f(Array.empty[AnyRef], Array.empty[AnyRef])
9+
10+
// Mixed parameters: only some need adaptation
11+
trait MixedParam { def f(a: Array[AnyRef], b: Int): Any }
12+
def gMixed(a: MixedParam) = a.f(Array.empty[AnyRef], 42)
13+
14+
// Generic return type
15+
trait GenericReturn[T] { def f(a: Array[AnyRef]): T }
16+
def gGeneric[T](a: GenericReturn[T]): T = a.f(Array.empty[AnyRef])
17+
18+
def main(args: Array[String]): Unit = {
19+
// Basic case
20+
g((x: Array[? >: AnyRef]) => x.headOption)
21+
22+
// Multiple array parameters - both need adaptation
23+
gMulti((x: Array[? >: AnyRef], y: Array[? >: AnyRef]) => (x.headOption, y.headOption))
24+
25+
// Mixed - only first param needs adaptation
26+
gMixed((x: Array[? >: AnyRef], n: Int) => (x.headOption, n))
27+
28+
// Generic return type
29+
val result: Option[?] = gGeneric((x: Array[? >: AnyRef]) => x.headOption)
30+
}
31+
}

0 commit comments

Comments
 (0)