Skip to content

Commit 9ff67d5

Browse files
committed
Fix scala-js#3953: Patch the types of ParamDefs to match their method signature.
In some rare cases that involve Higher Kinded Types and type aliases, scalac produces DefDef's whose params' types do not match the method type. This is filed upstream as scala/bug#11884 We work around the issue by patching a posteriori the types of `js.ParamDef`s and their `js.VarRef`s to match the type advertised by the method type. In the entire test suite, the only method that requires a patch is the `PartialFunction`'s `applyOrElse` in the newly added test case.
1 parent becac43 commit 9ff67d5

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1809,7 +1809,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
18091809
params.map(genParamDef(_))
18101810
}
18111811

1812-
if (isAbstractMethod(dd)) {
1812+
val jsMethodDef = if (isAbstractMethod(dd)) {
18131813
val body = if (scalaUsesImplClasses &&
18141814
sym.hasAnnotation(JavaDefaultMethodAnnotation)) {
18151815
/* For an interface method with @JavaDefaultMethod, make it a
@@ -1899,6 +1899,24 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
18991899

19001900
methodDefWithoutUselessVars
19011901
}
1902+
1903+
/* #3953 Patch the param defs to have the type advertised by the method's type.
1904+
* This works around https://github.com/scala/bug/issues/11884, whose fix
1905+
* upstream is blocked because it is not binary compatible. The fix here
1906+
* only affects the inside of the js.MethodDef, so it is binary compat.
1907+
*/
1908+
val paramTypeRewrites = jsParams.zip(sym.tpe.paramTypes.map(toIRType(_))).collect {
1909+
case (js.ParamDef(name, _, tpe, _), sigType) if tpe != sigType => name.name -> sigType
1910+
}
1911+
if (paramTypeRewrites.isEmpty) {
1912+
// Overwhelmingly common case: all the types match, so there is nothing to do
1913+
jsMethodDef
1914+
} else {
1915+
devWarning(
1916+
"Working around https://github.com/scala/bug/issues/11884 " +
1917+
s"for ${sym.fullName} at ${sym.pos}")
1918+
patchTypeOfParamDefs(jsMethodDef, paramTypeRewrites.toMap)
1919+
}
19021920
}
19031921
}
19041922

@@ -1958,6 +1976,42 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
19581976
newBody)(methodDef.optimizerHints, None)(methodDef.pos)
19591977
}
19601978

1979+
/** Patches the type of selected param defs in a [[js.MethodDef]].
1980+
*
1981+
* @param patches
1982+
* Map from local name to new type. For param defs not in the map, the
1983+
* type is untouched.
1984+
*/
1985+
private def patchTypeOfParamDefs(methodDef: js.MethodDef,
1986+
patches: Map[LocalName, jstpe.Type]): js.MethodDef = {
1987+
1988+
def newType(name: js.LocalIdent, oldType: jstpe.Type): jstpe.Type =
1989+
patches.getOrElse(name.name, oldType)
1990+
1991+
val js.MethodDef(flags, methodName, originalName, params, resultType, body) =
1992+
methodDef
1993+
val newParams = for {
1994+
p @ js.ParamDef(name, originalName, ptpe, mutable) <- params
1995+
} yield {
1996+
js.ParamDef(name, originalName, newType(name, ptpe), mutable)(p.pos)
1997+
}
1998+
val transformer = new ir.Transformers.Transformer {
1999+
override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match {
2000+
case tree @ js.VarRef(name) =>
2001+
js.VarRef(name)(newType(name, tree.tpe))(tree.pos)
2002+
case js.Closure(arrow, captureParams, params, restParam, body, captureValues) =>
2003+
js.Closure(arrow, captureParams, params, restParam, body,
2004+
captureValues.map(transformExpr))(tree.pos)
2005+
case _ =>
2006+
super.transform(tree, isStat)
2007+
}
2008+
}
2009+
val newBody = body.map(
2010+
b => transformer.transform(b, isStat = resultType == jstpe.NoType))
2011+
js.MethodDef(flags, methodName, originalName, newParams, resultType,
2012+
newBody)(methodDef.optimizerHints, None)(methodDef.pos)
2013+
}
2014+
19612015
/** Generates the JSNativeMemberDef of a JS native method. */
19622016
def genJSNativeMemberDef(tree: DefDef): js.JSNativeMemberDef = {
19632017
implicit val pos = tree.pos

test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,27 @@ class RegressionTest {
870870
assertEquals("lazily always", ex2.getMessage())
871871
}
872872

873+
@Test def paramDefWithWrongTypeWithHKTAndTypeAliases_Issue3953(): Unit = {
874+
assumeFalse("Scala/JVM 2.11.x produces wrong bytecode for this test",
875+
Platform.executingInJVM && Platform.scalaVersion.startsWith("2.11."))
876+
877+
import scala.language.higherKinds
878+
879+
sealed class StreamT[M[_]](val step: M[Step[StreamT[M]]])
880+
881+
sealed abstract class Step[S]
882+
883+
def mustMatch[A](actual: A)(f: PartialFunction[A, Boolean]): Boolean =
884+
f.applyOrElse(actual, (_: Any) => false)
885+
886+
type Id[A] = A
887+
888+
val result = mustMatch(new StreamT[Id](null).step) {
889+
case _ => true
890+
}
891+
assertTrue(result)
892+
}
893+
873894
}
874895

875896
object RegressionTest {

0 commit comments

Comments
 (0)