Skip to content

Commit 1de54d7

Browse files
committed
Fix scala-js#4684: Handle Unit parameters with default values in JS types.
Both at call site (for native and non-native JS types) and at definition sites (for non-native JS types).
1 parent fc0f981 commit 1de54d7

File tree

4 files changed

+41
-3
lines changed

4 files changed

+41
-3
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3857,8 +3857,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
38573857
val Block(stats, expr) = tree
38583858

38593859
val genStatsAndExpr = if (!stats.exists(isCaseLabelDef(_))) {
3860-
// fast path
3861-
stats.map(genStat(_)) :+ genStatOrExpr(expr, isStat)
3860+
// #4684 Collapse { <undefined-param>; BoxedUnit } to <undefined-param>
3861+
val genStatsAndExpr0 = stats.map(genStat(_)) :+ genStatOrExpr(expr, isStat)
3862+
genStatsAndExpr0 match {
3863+
case (undefParam @ js.Transient(UndefinedParam)) :: js.Undefined() :: Nil =>
3864+
undefParam :: Nil
3865+
case _ =>
3866+
genStatsAndExpr0
3867+
}
38623868
} else {
38633869
genBlockWithCaseLabelDefs(stats :+ expr, isStat)
38643870
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
780780
// Pass previous arguments to defaultGetter
781781
val defaultGetterArgs = previousArgsValues(defaultGetter.tpe.params.size)
782782

783-
if (isJSType(trgSym)) {
783+
val callGetter = if (isJSType(trgSym)) {
784784
if (isNonNativeJSClass(defaultGetter.owner)) {
785785
if (defaultGetter.hasAnnotation(JSOptionalAnnotation))
786786
js.Undefined()
@@ -802,6 +802,12 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
802802
} else {
803803
genApplyMethod(trgTree, defaultGetter, defaultGetterArgs)
804804
}
805+
806+
// #4684 If the getter returns void, we must "box" it by returning undefined
807+
if (callGetter.tpe == jstpe.NoType)
808+
js.Block(callGetter, js.Undefined())
809+
else
810+
callGetter
805811
}
806812

807813
/** Generate the final forwarding call to the exported method. */

test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InteroperabilityTest.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,10 @@ class InteroperabilityTest {
543543

544544
assertJSArrayEquals[Any](js.Array(js.undefined, 1, 2, 3, 4), obj.multi()(1, 2, 3, 4)())
545545
assertJSArrayEquals[Any](js.Array(2, 5), obj.multi(2)()(5))
546+
547+
// #4684 Default params with Unit type
548+
assertJSArrayEquals[Any](js.Array(()), obj.unitParam(()))
549+
assertJSArrayEquals[Any](js.Array((), ()), obj.unitParam((), ()))
546550
}
547551

548552
@Test def defaultParametersForConstructors_Issue791(): Unit = {
@@ -793,6 +797,8 @@ object InteroperabilityTest {
793797
def named(x: Int = 1, y: Int = 1, z: Int = 1): js.Array[Any] = js.native
794798
@JSName("fun")
795799
def multi(x: Int = 1)(ys: Int*)(z: Int = 1): js.Array[Any] = js.native
800+
@JSName("fun")
801+
def unitParam(x: Unit, y: Unit = ()): js.Array[Any] = js.native
796802
}
797803

798804
@js.native

test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTest.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,15 +1055,21 @@ class NonNativeJSTypeTest {
10551055

10561056
@Test def defaultParameters(): Unit = {
10571057
class DefaultParameters extends js.Object {
1058+
var sideEffectCounter: Int = 0
1059+
10581060
def bar(x: Int, y: Int = 1): Int = x + y
10591061
def dependent(x: Int)(y: Int = x + 1): Int = x + y
1062+
def unitParam(x: Unit, y: Unit = { sideEffectCounter += 1; () }): Int = sideEffectCounter
10601063

10611064
def foobar(x: Int): Int = bar(x)
10621065
}
10631066

10641067
object DefaultParametersMod extends js.Object {
1068+
var sideEffectCounter: Int = 0
1069+
10651070
def bar(x: Int, y: Int = 1): Int = x + y
10661071
def dependent(x: Int)(y: Int = x + 1): Int = x + y
1072+
def unitParam(x: Unit, y: Unit = { sideEffectCounter += 1; () }): Int = sideEffectCounter
10671073

10681074
def foobar(x: Int): Int = bar(x)
10691075
}
@@ -1075,12 +1081,26 @@ class NonNativeJSTypeTest {
10751081
assertEquals(9, foo.dependent(4)(5))
10761082
assertEquals(17, foo.dependent(8)())
10771083

1084+
// #4684 Default params with Unit type
1085+
assertEquals(0, foo.sideEffectCounter)
1086+
assertEquals(1, foo.unitParam(()))
1087+
assertEquals(1, foo.sideEffectCounter)
1088+
assertEquals(2, foo.unitParam((), ())) // an actual undefined param counts as not provided
1089+
assertEquals(2, foo.sideEffectCounter)
1090+
10781091
assertEquals(9, DefaultParametersMod.bar(4, 5))
10791092
assertEquals(5, DefaultParametersMod.bar(4))
10801093
assertEquals(4, DefaultParametersMod.foobar(3))
10811094
assertEquals(9, DefaultParametersMod.dependent(4)(5))
10821095
assertEquals(17, DefaultParametersMod.dependent(8)())
10831096

1097+
// #4684 Default params with Unit type
1098+
assertEquals(0, DefaultParametersMod.sideEffectCounter)
1099+
assertEquals(1, DefaultParametersMod.unitParam(()))
1100+
assertEquals(1, DefaultParametersMod.sideEffectCounter)
1101+
assertEquals(2, DefaultParametersMod.unitParam((), ())) // an actual undefined param counts as not provided
1102+
assertEquals(2, DefaultParametersMod.sideEffectCounter)
1103+
10841104
def testDyn(dyn: js.Dynamic): Unit = {
10851105
assertEquals(9, dyn.bar(4, 5))
10861106
assertEquals(5, dyn.bar(4))

0 commit comments

Comments
 (0)