8000 Fix #2164: Support default args constructors in js.Any classes. · renowncoder/scala-js@a8de5aa · GitHub
[go: up one dir, main page]

Skip to content

Commit a8de5aa

Browse files
committed
Fix scala-js#2164: Support default args constructors in js.Any classes.
Add explicit restriction: ScalaJSDefined and Scala classes that have a js.Native module cannot use constructors with default arguments.
1 parent 720bd08 commit a8de5aa

File tree

6 files changed

+271
-63
lines changed

6 files changed

+271
-63
lines changed

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

Lines changed: 82 additions & 42 deletions
+
} else if (isJSNativeCtorDefaultParam(sym)) {
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ abstract class GenJSCode extends plugins.PluginComponent
292292
"genClass() must be called only for normal classes: "+sym)
293293
assert(sym.superClass != NoSymbol, sym)
294294

295+
if (hasDefaultCtorArgsAndRawJSModule(sym)) {
296+
reporter.error(pos,
297+
"Implementation restriction: constructors of " +
298+
"Scala classes cannot have default parameters " +
299+
"if their companion module is JS native.")
300+
}
301+
295302
val classIdent = encodeClassFullNameIdent(sym)
296303
val isHijacked = isHijackedBoxedClass(sym)
297304

@@ -650,54 +657,51 @@ abstract class GenJSCode extends plugins.PluginComponent
650657
constructorTrees: List[DefDef]): js.Tree = {
651658
implicit val pos = classSym.pos
652659

653-
// Implementation restriction
654-
val syms = constructorTrees.map(_.symbol)
655-
val hasBadParam = enteringPhase(currentRun.uncurryPhase) {
656-
syms.exists(_.paramss.flatten.exists(p => p.hasDefault))
657-
}
658-
if (hasBadParam) {
660+
if (hasDefaultCtorArgsAndRawJSModule(classSym)) {
659661
reporter.error(pos,
660-
"Implementation restriction: the constructor of a " +
661-
"Scala.js-defined JS classes cannot have default parameters.")
662-
}
663-
664-
withNewLocalNameScope {
665-
val ctors: List[js.MethodDef] = constructorTrees.flatMap { tree =>
666-
genMethodWithCurrentLocalNameScope(tree)
667-
}
662+
"Implementation restriction: constructors of " +
663+
"Scala.js-defined JS classes cannot have default parameters " +
664+
"if their companion module is JS native.")
665+
js.EmptyTree
666+
} else {
667+
withNewLocalNameScope {
668+
val ctors: List[js.MethodDef] = constructorTrees.flatMap { tree =>
669+
genMethodWithCurrentLocalNameScope(tree)
670+
}
668671

669-
val dispatch =
670-
genJSConstructorExport(constructorTrees.map(_.symbol))
671-
val js.MethodDef(_, dispatchName, dispatchArgs, dispatchResultType,
672-
dispatchResolution) = dispatch
672+
val dispatch =
673+
genJSConstructorExport(constructorTrees.map(_.symbol))
674+
val js.MethodDef(_, dispatchName, dispatchArgs, dispatchResultType,
675+
dispatchResolution) = dispatch
673676

674-
val jsConstructorBuilder = mkJSConstructorBuilder(ctors)
677+
val jsConstructorBuilder = mkJSConstructorBuilder(ctors)
675678

676-
val overloadIdent = freshLocalIdent("overload")
679+
val overloadIdent = freshLocalIdent("overload")
677680

678-
// Section containing the overload resolution and casts of parameters
679-
val overloadSelection = mkOverloadSelection(jsConstructorBuilder,
681+
// Section containing the overload resolution and casts of parameters
682+
val overloadSelection = mkOverloadSelection(jsConstructorBuilder,
680683
overloadIdent, dispatchResolution)
681684

682-
/* Section containing all the code executed before the call to `this`
683-
* for every secondary constructor.
684-
*/
685-
val prePrimaryCtorBody =
686-
jsConstructorBuilder.mkPrePrimaryCtorBody(overloadIdent)
685+
/* Section containing all the code executed before the call to `this`
686+
* for every secondary constructor.
687+
*/
688+
val prePrimaryCtorBody =
689+
jsConstructorBuilder.mkPrePrimaryCtorBody(overloadIdent)
687690

688-
val primaryCtorBody = jsConstructorBuilder.primaryCtorBody
691+
val primaryCtorBody = jsConstructorBuilder.primaryCtorBody
689692

690-
/* Section containing all the code executed after the call to this for
691-
* every secondary constructor.
692-
*/
693-
val postPrimaryCtorBody =
694-
jsConstructorBuilder.mkPostPrimaryCtorBody(overloadIdent)
693+
/* Section containing all the code executed after the call to this for
694+
* every secondary constructor.
695+
*/
696+
val postPrimaryCtorBody =
697+
jsConstructorBuilder.mkPostPrimaryCtorBody(overloadIdent)
695698

696-
val newBody = js.Block(overloadSelection ::: prePrimaryCtorBody ::
697-
primaryCtorBody :: postPrimaryCtorBody :: Nil)
699+
val newBody = js.Block(overloadSelection ::: prePrimaryCtorBody ::
700+
primaryCtorBody :: postPrimaryCtorBody :: Nil)
698701

699-
js.MethodDef(static = false, dispatchName, dispatchArgs, jstpe.NoType,
700-
newBody)(dispatch.optimizerHints, None)
702+
js.MethodDef(static = false, dispatchName, dispatchArgs, jstpe.NoType,
703+
newBody)(dispatch.optimizerHints, None)
704+
}
701705
}
702706
}
703707

@@ -1066,7 +1070,7 @@ abstract class GenJSCode extends plugins.PluginComponent
10661070
Some(js.MethodDef(static = false, methodName,
10671071
jsParams, toIRType(sym.tpe.resultType), body)(
10681072
OptimizerHints.empty, None))
1069-
} else if (isRawJSCtorDefaultParam(sym)) {
1073
10701074
None
10711075
} else if (sym.isClassConstructor && isHijackedBoxedClass(sym.owner)) {
10721076
None
@@ -1776,8 +1780,12 @@ abstract class GenJSCode extends plugins.PluginComponent
17761780
val sym = fun.symbol
17771781

17781782
def isRawJSDefaultParam: Boolean = {
1779-
sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) &&
1780-
(isRawJSType(sym.owner.tpe) || isRawJSCtorDefaultParam(sym))
1783+
if (isCtorDefaultParam(sym)) {
1784+
isRawJSCtorDefaultParam(sym)
1785+
} else {
1786+
sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) &&
1787+
isRawJSType(sym.owner.tpe)
1788+
}
17811789
}
17821790

17831791
fun match {
@@ -4465,7 +4473,7 @@ abstract class GenJSCode extends plugins.PluginComponent
44654473

44664474
if (isGlobalScope) {
44674475
genLoadGlobal()
4468-
} else if (isRawJSType(sym.tpe) && !isScalaJSDefinedJSClass(sym)) {
4476+
} else if (isJSNativeClass(sym)) {
44694477
genPrimitiveJSModule(sym)
44704478
} else {
44714479
val moduleClassName = encodeClassFullName(sym)
@@ -4520,6 +4528,10 @@ abstract class GenJSCode extends plugins.PluginComponent
45204528
def isScalaJSDefinedJSClass(sym: Symbol): Boolean =
45214529
!sym.isTrait && sym.hasAnnotation(ScalaJSDefinedAnnotation)
45224530

4531+
/** Tests whether the given class is a JS native class. */
4532+
private def isJSNativeClass(sym: Symbol): Boolean =
4533+
isRawJSType(sym.tpe) && !isScalaJSDefinedJSClass(sym)
4534+
45234535
/** Tests whether the given member is exposed, i.e., whether it was
45244536
* originally a public or protected member of a Scala.js-defined JS class.
45254537
*/
@@ -4531,12 +4543,40 @@ abstract class GenJSCode extends plugins.PluginComponent
45314543
sym.isAnonymousClass && AllJSFunctionClasses.exists(sym isSubClass _)
45324544

45334545
private def isRawJSCtorDefaultParam(sym: Symbol) = {
4546+
isCtorDefaultParam(sym) &&
4547+
isRawJSType(patchedLinkedClassOfClass(sym.owner).tpe)
4548+
}
4549+
4550+
private def isJSNativeCtorDefaultParam(sym: Symbol) = {
4551+
isCtorDefaultParam(sym) &&
4552+
isJSNativeClass(patchedLinkedClassOfClass(sym.owner))
4553+
}
4554+
4555+
private def isCtorDefaultParam(sym: Symbol) = {
45344556
sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) &&
45354557
sym.owner.isModuleClass &&
4536-
isRawJSType(patchedLinkedClassOfClass(sym.owner).tpe) &&
45374558
nme.defaultGetterToMethod(sym.name) == nme.CONSTRUCTOR
45384559
}
45394560

4561+
private def hasDefaultCtorArgsAndRawJSModule(classSym: Symbol): Boolean = {
4562+
/* Get the companion module class.
4563+
* For inner classes the sym.owner.companionModule can be broken,
4564+
* therefore companionModule is fetched at uncurryPhase.
4565+
*/
4566+
val companionClass = enteringPhase(currentRun.uncurryPhase) {
4567+
classSym.companionModule
4568+
}.moduleClass
4569+
4570+
def hasDefaultParameters = {
4571+
val syms = classSym.info.members.filter(_.isClassConstructor)
4572+
enteringPhase(currentRun.uncurryPhase) {
4573+
syms.exists(_.paramss.iterator.flatten.exists(_.hasDefault))
4574+
}
4575+
}
4576+
4577+
isJSNativeClass(companionClass) && hasDefaultParameters
4578+
}
4579+
45404580
private def patchedLinkedClassOfClass(sym: Symbol): Symbol = {
45414581
/* Work around a bug of scalac with linkedClassOfClass where package
45424582
* objects are involved (the companion class would somehow exist twice

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,8 +621,18 @@ trait GenJSExports extends SubComponent { self: GenJSCode =>
621621
val verifiedOrDefault = if (param.hasFlag(Flags.DEFAULTPARAM)) {
622622
js.If(js.BinaryOp(js.BinaryOp.===, jsArg, js.Undefined()), {
623623
val trgSym = {
624-
if (sym.isClassConstructor) sym.owner.companionModule.moduleClass
625-
else sym.owner
624+
if (sym.isClassConstructor) {
625+
/* Get the companion module class.
626+
* For inner classes the sym.owner.companionModule can be broken,
627+
* therefore companionModule is fetched at uncurryPhase.
628+
*/
629+
val companionModule = enteringPhase(currentRun.namerPhase) {
630+
sym.owner.companionModule
631+
}
632+
companionModule.moduleClass
633+
} else {
634+
sym.owner
635+
}
626636
}
627637
val defaultGetter = trgSym.tpe.member(
628638
nme.defaultGetterName(sym.name, i+1))

compiler/src/test/scala/org/scalajs/core/compiler/test/JSInteropTest.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,4 +1125,29 @@ class JSInteropTest extends DirectTest with TestHelpers {
11251125
"""
11261126
}
11271127

1128+
@Test
1129+
def noDefaultConstructorArgsIfModuleIsJSNative: Unit = {
1130+
"""
1131+
@ScalaJSDefined
1132+
class A(x: Int = 1) extends js.Object
1133+
@js.native
1134+
object A extends js.Object
1135+
""" hasErrors
1136+
"""
1137+
|newSource1.scala:6: error: Implementation restriction: constructors of Scala.js-defined JS classes cannot have default parameters if their companion module is JS native.
1138+
| class A(x: Int = 1) extends js.Object
1139+
| ^
1140+
"""
1141+
1142+
"""
1143+
class A(x: Int = 1)
1144+
@js.native
1145+
object A extends js.Object
1146+
""" hasErrors
1147+
"""
1148+
|newSource1.scala:5: error: Implementation restriction: constructors of Scala classes cannot have default parameters if their companion module is JS native.
1149+
| class A(x: Int = 1)
1150+
| ^
1151+
"""
1152+
}
11281153
}

compiler/src/test/scala/org/scalajs/core/compiler/test/ScalaJSDefinedTest.scala

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -400,19 +400,6 @@ class ScalaJSDefinedTest extends DirectTest with TestHelpers {
400400
"""
401401
}
402402

403-
@Test
404-
def noDefault: Unit = {
405-
"""
406-
@ScalaJSDefined
407-
class A(x: Int, y: Int = 4) extends js.Object
408-
""" hasErrors
409-
"""
410-
|newSource1.scala:6: error: Implementation restriction: the constructor of a Scala.js-defined JS classes cannot have default parameters.
411-
| class A(x: Int, y: Int = 4) extends js.Object
412-
| ^
413-
"""
414-
}
415-
416403
@Test
417404
def noUseJsNative: Unit = {
418405
"""

test-suite/js/src/test/resources/ScalaJSDefinedTestNatives.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,14 @@
4040
};
4141
$g.ScalaJSDefinedTestNativeParentClassWithVarargs =
4242
ScalaJSDefinedTestNativeParentClassWithVarargs;
43+
44+
var ConstructorDefaultParam = function(foo) {
45+
$g.Object.call(this);
46+
if (foo == undefined) {
47+
this.foo = -1;
48+
} else {
49+
this.foo = foo;
50+
}
51+
};
52+
$g.ConstructorDefaultParam = ConstructorDefaultParam;
4353
}).call(this);

0 commit comments

Comments
 (0)
0