Skip to content

Commit c6be3bc

Browse files
committed
Fix #23179 for Scala.js and add regression tests
- Guard the Erasure.scala array adaptation check with !scalajs.value since Scala.js custom SAMs are handled differently (via ExpandSAMs) - Add array type adaptation in tpd.AnonClass for Scala.js, which handles custom SAM closures that get expanded to anonymous classes before erasure - Add neg test documenting that reverse cases (impl has Array, SAM erases to Object) are rejected by the type checker due to array invariance
1 parent 9e16c38 commit c6be3bc

File tree

3 files changed

+60
-7
lines changed

3 files changed

+60
-7
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -392,13 +392,43 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
392392
termForwarders: List[(TermName, TermSymbol)],
393393
typeMembers: List[(TypeName, TypeBounds)],
394394
adaptVarargs: Boolean)(using Context): Block = {
395+
import core.TypeErasure.erasure
395396
AnonClass(termForwarders.head._2.owner, parents, termForwarders.map(_._2.span).reduceLeft(_ `union` _), adaptVarargs) { cls =>
396397
def forwarder(name: TermName, fn: TermSymbol) = {
397-
val fwdMeth = fn.copy(cls, name, Synthetic | Method | Final).entered.asTerm
398-
for overridden <- fwdMeth.allOverriddenSymbols do
399-
if overridden.is(Extension) then fwdMeth.setFlag(Extension)
400-
if !overridden.is(Deferred) then fwdMeth.setFlag(Override)
401-
DefDef(fwdMeth, ref(fn).appliedToArgss(_))
398+
// For Scala.js, check if parameter types will erase to different class symbols (see #23179).
399+
// This happens e.g. when SAM expects Array[T] but impl has a wildcard that erases to Object.
400+
// We need to use SAM's type and add casts in this case.
401+
// On JVM, this is handled in Erasure.adaptClosure instead.
402+
val needsAdaptation = ctx.settings.scalajs.value && {
403+
val samInfo = parents.head.memberInfo(parents.head.member(name).symbol)
404+
val implInfo = fn.info
405+
(samInfo, implInfo) match
406+
case (samMt: MethodType, implMt: MethodType) =>
407+
samMt.paramInfos.lazyZip(implMt.paramInfos).exists((samPt, implPt) =>
408+
erasure(samPt).classSymbol != erasure(implPt).classSymbol)
409+
case _ => false
410+
}
411+
if needsAdaptation then
412+
val samInfo = parents.head.memberInfo(parents.head.member(name).symbol)
413+
val implInfo = fn.info
414+
val fwdMeth = fn.copy(cls, name, Synthetic | Method | Final, info = samInfo).entered.asTerm
415+
for overridden <- fwdMeth.allOverriddenSymbols do
416+
if overridden.is(Extension) then fwdMeth.setFlag(Extension)
417+
if !overridden.is(Deferred) then fwdMeth.setFlag(Override)
418+
val implType = implInfo.asInstanceOf[MethodType]
419+
DefDef(fwdMeth, paramss =>
420+
val adaptedParams = paramss.head.lazyZip(implType.paramInfos).map { (param, implParamType) =>
421+
if param.tpe =:= implParamType then param
422+
else param.cast(implParamType)
423+
}
424+
ref(fn).appliedToTermArgs(adaptedParams)
425+
)
426+
else
427+
val fwdMeth = fn.copy(cls, name, Synthetic | Method | Final).entered.asTerm
428+
for overridden <- fwdMeth.allOverriddenSymbols do
429+
if overridden.is(Extension) then fwdMeth.setFlag(Extension)
430+
if !overridden.is(Deferred) then fwdMeth.setFlag(Override)
431+
DefDef(fwdMeth, ref(fn).appliedToArgss(_))
402432
}
403433
termForwarders.map((name, sym) => forwarder(name, sym)) ++
404434
typeMembers.map((name, info) => TypeDef(newSymbol(cls, name, Synthetic, info).entered))

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,11 +484,12 @@ object Erasure {
484484
val paramAdaptationNeeded =
485485
implParamTypes.lazyZip(samParamTypes).exists: (implType, samType) =>
486486
!sameClass(implType, samType) && !autoAdaptedParam(implType)
487-
|| (samType, implType).match {
487+
// For Scala.js, array adaptation is handled in tpd.AnonClass (see #23179)
488+
|| !ctx.settings.scalajs.value && ((samType, implType).match {
488489
case (defn.ArrayOf(_), defn.ArrayOf(_)) => false
489490
case (defn.ArrayOf(_), _) => true // see #23179
490491
case _ => false
491-
}
492+
})
492493
val resultAdaptationNeeded =
493494
!sameClass(implResultType, samResultType) && !autoAdaptedResult
494495

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+
}

0 commit comments

Comments
 (0)