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
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,9 @@ object Erasure {

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

Expand Down
53 changes: 45 additions & 8 deletions compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dotc
package transform

import core.*
import core.TypeErasure.erasure
import Scopes.newScope
import Contexts.*, Symbols.*, Types.*, Flags.*, Decorators.*, StdNames.*, Constants.*
import MegaPhase.*
Expand Down Expand Up @@ -69,14 +70,50 @@ class ExpandSAMs extends MiniPhase:
case _ => tp
val tpe1 = collectAndStripRefinements(tpe)
val Seq(samDenot) = tpe1.possibleSamMethods
cpy.Block(tree)(stats,
transformFollowingDeep:
AnonClass(List(tpe1),
List(samDenot.symbol.asTerm.name -> fn.symbol.asTerm),
refinements.toList,
adaptVarargs = true
)
)
val implSym = fn.symbol.asTerm

// Check if SAM param erases to array but impl param doesn't (see #23179).
// E.g. SAM expects Array[AnyRef] (erases to Array[Object]) but impl has
// Array[? >: AnyRef] (erases to Object). In this case we need to create
// a wrapper with the SAM signature that casts arguments before forwarding.
val samInfo = samDenot.info
val implInfo = implSym.info
val needsArrayAdaptation = (samInfo, implInfo) match
case (samMt: MethodType, implMt: MethodType) =>
samMt.paramInfos.lazyZip(implMt.paramInfos).exists { (samPt, implPt) =>
erasure(samPt).isInstanceOf[JavaArrayType] && !erasure(implPt).isInstanceOf[JavaArrayType]
}
case _ => false

if needsArrayAdaptation then
// Create a wrapper method with SAM's signature that casts args to impl's types
val wrapperSym = newSymbol(
implSym.owner, implSym.name.asTermName, Synthetic | Method,
samInfo, coord = implSym.span).entered.asTerm
val wrapperDef = DefDef(wrapperSym, paramss => {
val implMt = implInfo.stripPoly.asInstanceOf[MethodType]
val adaptedArgs = paramss.head.lazyZip(implMt.paramInfos).map { (arg, implPt) =>
if arg.tpe =:= implPt then arg else arg.cast(implPt)
}
ref(implSym).appliedToTermArgs(adaptedArgs)
})
cpy.Block(tree)(fn :: wrapperDef :: Nil,
transformFollowingDeep:
AnonClass(List(tpe1),
List(samDenot.symbol.asTerm.name -> wrapperSym),
refinements.toList,
adaptVarargs = true
)
)
else
cpy.Block(tree)(stats,
transformFollowingDeep:
AnonClass(List(tpe1),
List(samDenot.symbol.asTerm.name -> implSym),
refinements.toList,
adaptVarargs = true
)
)
}
case _ =>
tree
Expand Down
22 changes: 22 additions & 0 deletions tests/neg/i23179-reverse.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Test that reverse cases of #23179 are properly rejected by the type checker.
// The original issue was about SAM expecting Array[T] but impl having Array[? >: T].
// Due to array invariance, the reverse cases cannot occur.

object TestReverse {
trait A { def f(a: Array[? <: AnyRef]): Any }
def g(a: A) = a.f(Array.empty[AnyRef])

def test(): Unit = {
g((x: Array[AnyRef]) => x.headOption) // error: Array[AnyRef] is not a subtype of Array[? <: AnyRef]
}
}

object TestResult {
trait A { def f(a: Int): Array[AnyRef] }
def g(a: A) = a.f(1)

def test(): Unit = {
val arr: Array[? >: AnyRef] = Array("hello")
g((x: Int) => arr) // error: Array[? >: AnyRef] is not a subtype of Array[AnyRef]
}
}
31 changes: 31 additions & 0 deletions tests/run/i23179.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
object Test {
// Basic case: SAM expects Array[AnyRef], impl has Array[? >: AnyRef]
trait A { def f(a: Array[AnyRef]): Any }
def g(a: A) = a.f(Array.empty[AnyRef])

// Multiple array parameters
trait MultiParam { def f(a: Array[AnyRef], b: Array[AnyRef]): Any }
def gMulti(a: MultiParam) = a.f(Array.empty[AnyRef], Array.empty[AnyRef])

// Mixed parameters: only some need adaptation
trait MixedParam { def f(a: Array[AnyRef], b: Int): Any }
def gMixed(a: MixedParam) = a.f(Array.empty[AnyRef], 42)

// Generic return type
trait GenericReturn[T] { def f(a: Array[AnyRef]): T }
def gGeneric[T](a: GenericReturn[T]): T = a.f(Array.empty[AnyRef])

def main(args: Array[String]): Unit = {
// Basic case
g((x: Array[? >: AnyRef]) => x.headOption)

// Multiple array parameters - both need adaptation
gMulti((x: Array[? >: AnyRef], y: Array[? >: AnyRef]) => (x.headOption, y.headOption))

// Mixed - only first param needs adaptation
gMixed((x: Array[? >: AnyRef], n: Int) => (x.headOption, n))

// Generic return type
val result: Option[?] = gGeneric((x: Array[? >: AnyRef]) => x.headOption)
}
}
Loading