diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index b46c780480ff..a946ab46638f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index f76419f5198e..acd249966ec2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -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.* @@ -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 diff --git a/tests/neg/i23179-reverse.scala b/tests/neg/i23179-reverse.scala new file mode 100644 index 000000000000..f31a838fb114 --- /dev/null +++ b/tests/neg/i23179-reverse.scala @@ -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] + } +} diff --git a/tests/run/i23179.scala b/tests/run/i23179.scala new file mode 100644 index 000000000000..43e223b667f3 --- /dev/null +++ b/tests/run/i23179.scala @@ -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) + } +}