10000 Fix #1811: Add support for secondary constructors in JS classes · scala-js/scala-js@4050518 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4050518

Browse files
committed
Fix #1811: Add support for secondary constructors in JS classes
1 parent 8d5fb03 commit 4050518

File tree

5 files changed

+474
-46
lines changed

5 files changed

+474
-46
lines changed

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

Lines changed: 315 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -682,16 +682,304 @@ abstract class GenJSCode extends plugins.PluginComponent
682682
"repeated parameters.")
683683
}
684684

685-
// Implementation restriction
686-
for (tree <- secondaryCtorTrees) {
687-
reporter.error(tree.pos,
688-
"Implementation restriction: Scala.js-defined JS classes cannot " +
689-
"have secondary constructors")
685+
withNewLocalNameScope {
686+
val infoBuilder = new MethodInfoBuilder()
687+
infoBuilder.setEncodedName("constructor")
688+
infoBuilder.setIsExported(true)
689+
infoBuilder.setIsStatic(false)
690+
691+
val infoBuilderOpt = Some(infoBuilder)
692+
693+
val primaryCtor =
694+
genMethodWithInfoBuilder(primaryCtorTree, infoBuilderOpt).get._1
695+
val secondaryCtors = secondaryCtorTrees.map { tree =>
696+
genMethodWithInfoBuilder(tree, infoBuilderOpt).get._1
697+
}
698+
699+
val dispatch =
700+
genJSConstructorExport(constructorTrees.map(_.symbol), infoBuilder)
701+
702+
val isConstructor =
703+
secondaryCtors.map(_.name.name).toSet + primaryCtor.name.name
704+
705+
val constructorGraph =
706+
mkConstructorGraph(primaryCtor, secondaryCtors, isConstructor)
707+
708+
val overloadIdent = js.Ident("overload")
709+
710+
val js.MethodDef(_, dispatchName, dispatchArgs, dispatchResultType,
711+
dispatchResolution) = dispatch
712+
713+
val overloadSelection = mkOverloadSelection(constructorGraph,
714+
overloadIdent, dispatchResolution, isConstructor)
715+
716+
val preSuperCall =
717+
constructorGraph.mkPreSuperCall(overloadIdent, dispatch, isConstructor)
718+
719+
val superCall = primaryCtor.body
720+
721+
val postSupperCall =
722+
constructorGraph.mkPostSuperCall(overloadIdent, isConstructor)
723+
724+
val newBody = js.Block(overloadSelection :::
725+
preSuperCall :: superCall :: postSupperCall :: Nil)
726+
727+
val newConstructor = js.MethodDef(static = false, dispatchName, dispatchArgs,
728+
jstpe.NoType, newBody)(dispatch.optimizerHints, dispatch.hash)
729+
730+
// Boldly get rid of everything in infoBuilder, because it's broken
731+
currentClassInfoBuilder.addMethod(
732+
ir.Infos.generateMethodInfo(newConstructor))
733+
734+
newConstructor
735+
}
736+
}
737+
738+
private class ConstructorGraph(overrideNum: Int, method: js.MethodDef,
739+
subConstructors: List[ConstructorGraph])(implicit pos: Position) {
740+
741+
def methodName: String = method.name.name
742+
743+
def hasSubConstructors: Boolean = subConstructors.nonEmpty
744+
745+
lazy val overrideNumBounds: (Int, Int) =
746+
if (subConstructors.isEmpty) (overrideNum, overrideNum)
747+
else (subConstructors.last.overrideNumBounds._1, overrideNum)
748+
749+
def get(methodName: String): Option[ConstructorGraph] = {
750+
if (methodName == this.methodName) {
751+
Some(this)
752+
} else {
753+
subConstructors.iterator.map(_.get(methodName)).collectFirst {
754+
case Some(node) => node
755+
}
756+
}
690757
}
691758

692-
genMethod(primaryCtorTree).get
759+
def getOverrideNum(methodName: String): Int =
760+
get(methodName).fold(-1)(_.getOverrideNum)
761+
762+
private[ConstructorGraph] def getOverrideNum: Int = overrideNum
763+
764+
def getParamRefs(methodName: String): List[js.VarRef] =
765+
get(methodName).fold(List.empty[js.VarRef])(_.getParamRefs)
766+
767+
private[ConstructorGraph] def getParamRefs: List[js.VarRef] =
768+
method.args.map(_.ref)
769+
770+
def mkPreSuperCall(overrideNumIdent: js.Ident, dispatch: js.MethodDef,
771+
isConstructor: Set[String])(implicit pos: Position): js.Tree = {
772+
val overrideNumRef = js.VarRef(overrideNumIdent)(jstpe.IntType)
773+
mkSubPreCalls(overrideNumRef, isConstructor)
774+
}
775+
776+
def mkPostSuperCall(overrideNumIdent: js.Ident,
777+
isConstructor: Set[String])(implicit pos: Position): js.Tree = {
778+
val overrideNumRef = js.VarRef(overrideNumIdent)(jstpe.IntType)
779+
js.Block(mkSubPostCalls(overrideNumRef, isConstructor))
780+
}
781+
782+
def getAllParamDefsAsVars: List[js.VarDef] = {
783+
val localDefs = method.args.map { pDef =>
784+
js.VarDef(pDef.name, pDef.ptpe, true, jstpe.zeroOf(pDef.ptpe))
785+
}
786+
localDefs ++ subConstructors.flatMap(_.getAllParamDefsAsVars)
787+
}
788+
789+
private[ConstructorGraph] def mkSubPreCalls(overrideNumRef: js.VarRef,
790+
isConstructor: Set[String])(implicit pos: Position) = {
791+
val overrideNumss = subConstructors.map(_.overrideNumBounds)
792+
val paramRefs = getParamRefs
793+
val bodies = subConstructors.map { cg =>
794+
cg.mkPreSuperCallOnSndCtr(overrideNumRef, paramRefs, isConstructor)
795+
}
796+
overrideNumss.zip(bodies).foldRight[js.Tree](js.Skip()) {
797+
case ((numBounds, body), acc) =>
798+
val cond = mkOverrideNumsCond(overrideNumRef, numBounds)
799+
js.If(cond, body, acc)(jstpe.BooleanType)
800+
}
801+
}
802+
803+
private[ConstructorGraph] def mkPreSuperCallOnSndCtr(
804+
overrideNumRef: js.VarRef, outputParams: List[js.VarRef],
805+
isConstructor: Set[String])(implicit pos: Position): js.Tree = {
806+
val subCalls =
807+
mkSubPreCalls(overrideNumRef, isConstructor)
808+
809+
val preSuperCall = {
810+
method.body match {
811+
case js.Block(stats) =>
812+
val beforeSuperCall = stats.takeWhile {
813+
case js.ApplyStatic(_, method, _) if isConstructor(method.name) => false
814+
815+
case _ => true
816+
}
817+
val superCallParams = stats.collectFirst {
818+
case js.ApplyStatic(cls, method, args) if isConstructor(methodName) =>
819+
zipMap(outputParams, args.tail) { (ref, tree) =>
820+
js.Assign(ref, tree)
821+
}
822+
}.getOrElse(Nil)
823+
824+
beforeSuperCall ::: superCallParams
825+
826+
case js.ApplyStatic(cls, method, args) =>
827+
zipMap(outputParams, args.tail) { (ref, tree) =>
828+
js.Assign(ref, tree)
829+
}
830+
831+
case _ => Nil
832+
}
833+
}
834+
835+
js.Block(subCalls :: preSuperCall)
836+
}
837+
838+
private[ConstructorGraph] def mkSubPostCalls(overrideNumRef: js.VarRef,
839+
isConstructor: Set[String])(implicit pos: Position): js.Tree = {
840+
val overrideNumss = subConstructors.map(_.overrideNumBounds)
841+
val bodies = subConstructors.map { cg =>
842+
cg.mkPostSuperCallOnSndCtr(overrideNumRef, isConstructor)
843+
}
844+
overrideNumss.zip(bodies).foldRight[js.Tree](js.Skip()) {
845+
case ((numBounds, js.Skip()), acc) => acc
846+
847+
case ((numBounds, body), acc) =>
848+
val cond = mkOverrideNumsCond(overrideNumRef, numBounds)
849+
js.If(cond, body, acc)(jstpe.BooleanType)
850+
}
851+
}
852+
853+
private[ConstructorGraph] def mkPostSuperCallOnSndCtr(overrideNumRef: js.VarRef,
854+
isConstructor: Set[String])(implicit pos: Position): js.Tree = {
855+
val postSuperCall = {
856+
method.body match {
857+
case js.Block(stats) =>
858+
stats.dropWhile {
859+
case js.ApplyStatic(_, mtd, _) if isConstructor(mtd.name) => false
860+
case _ => true
861+
}.tail
862+
863+
case _ => Nil
864+
}
865+
}
866+
js.Block(postSuperCall :+ mkSubPostCalls(overrideNumRef, isConstructor))
867+
}
868+
869+
private def mkOverrideNumsCond(numRef: js.VarRef,
870+
numBounds: (Int, Int)) = numBounds match {
871+
case (lo, hi) if lo == hi =>
872+
js.BinaryOp(js.BinaryOp.===, js.IntLiteral(lo), numRef)
873+
874+
case (lo, hi) if lo == hi - 1 =>
875+
val lhs = js.BinaryOp(js.BinaryOp.===, numRef, js.IntLiteral(lo))
876+
val rhs = js.BinaryOp(js.BinaryOp.===, numRef, js.IntLiteral(hi))
877+
js.If(lhs, js.BooleanLiteral(true), rhs)(jstpe.BooleanType)
878+
879+
case (lo, hi) =>
880+
val lhs = js.BinaryOp(js.BinaryOp.Num_<=, js.IntLiteral(lo), numRef)
881+
val rhs = js.BinaryOp(js.BinaryOp.Num_<=, numRef, js.IntLiteral(hi))
882+
js.BinaryOp(js.BinaryOp.Boolean_&, lhs, rhs)
883+
js.If(lhs, rhs, js.BooleanLiteral(false))(jstpe.BooleanType)
884+
}
885+
}
886+
887+
private def zipMap[T, U, V](xs: List[T], ys: List[U])(
888+
f: (T, U) => V): List[V] = {
889+
if (xs.nonEmpty && ys.nonEmpty)
890+
f(xs.head, ys.head) :: zipMap(xs.tail, ys.tail)(f)
891+
else
892+
Nil
693893
}
694894

895+
private def mkOverloadSelection(constructorGraph: ConstructorGraph,
896+
overloadIdent: js.Ident, dispatchResolution: js.Tree,
897+
isConstructor: Set[String])(implicit pos: Position): List[js.Tree] = {
898+
if (!constructorGraph.hasSubConstructors) {
899+
dispatchResolution match {
900+
case js.Block(stats) =>
901+
val js.ApplyStatic(_, method, _) = stats.last
902+
val refs = constructorGraph.getParamRefs(method.name)
903+
val rhss = stats.collect { case js.VarDef(_, _, _, rhs) => rhs }
904+
zipMap(refs, rhss) { (ref, rhs) =>
905+
js.VarDef(ref.ident, ref.tpe, false, rhs)
906+
}
907+
908+
case js.ApplyStatic(cls, method, args) if isConstructor(method.name) =>
909+
// Calls the constructor with no arguments
910+
Nil
911+
912+
case tree => List(tree)
913+
}
914+
} else {
915+
val overloadRef = js.VarRef(overloadIdent)(jstpe.IntType)
916+
def assignOverloadNumbers(tree: js.Tree): js.Tree = tree match {
917+
case js.Block(stats) =>
918+
val js.ApplyStatic(_, method, _) = stats.last
919+
val num = constructorGraph.getOverrideNum(method.name)
920+
val refs = overloadRef :: constructorGraph.getParamRefs(method.name)
921+
val rhss = js.IntLiteral(num) ::
922+
stats.collect { case js.VarDef(_, _, _, rhs) => rhs }
923+
val newStats = zipMap(refs, rhss)((ref, rhs) => js.Assign(ref, rhs))
924+
js.Block(newStats)
925+
926+
case js.ApplyStatic(cls, method, args) if isConstructor(method.name) =>
927+
// Calls the constructor with no arguments
928+
js.Assign(overloadRef,
929+
js.IntLiteral(constructorGraph.getOverrideNum(method.name)))
930+
931+
case js.Match(selector, cases, default) =>
932+
val newCases = cases.map {
933+
case (literals, body) => (literals, assignOverloadNumbers(body))
934+
}
935+
js.Match(selector, newCases, default)(tree.tpe)
936+
937+
case js.If(cond, thenp, elsep) =>
938+
js.If(cond, assignOverloadNumbers(thenp),
939+
assignOverloadNumbers(elsep))(tree.tpe)
940+
941+
case _ => tree
942+
}
943+
944+
val newDispatchResolution = assignOverloadNumbers(dispatchResolution)
945+
val allParamDefsAsVars = constructorGraph.getAllParamDefsAsVars
946+
val overrideNumDef =
947+
js.VarDef(overloadIdent, jstpe.IntType, true, js.IntLiteral(0))
948+
949+
overrideNumDef :: allParamDefsAsVars ::: newDispatchResolution :: Nil
950+
}
951+
}
952+
953+
private def mkConstructorGraph(primaryCtor: js.MethodDef,
954+
secondaryCtors: List[js.MethodDef], isConstructor: Set[String])(
955+
implicit pos: Position): ConstructorGraph = {
956+
def superCall(tree: js.Tree): Option[String] = tree match {
957+
case js.Block(stats) =>
958+
stats.map(superCall).collectFirst {
959+
case Some(name) => name
960+
}
961+
962+
case js.ApplyStatic(cls, method, args) if isConstructor(method.name) =>
963+
Some(method.name)
964+
965+
case _ => None
966+
}
967+
968+
var overrideNum = -1
969+
def mkConstructorGraph(method: js.MethodDef): ConstructorGraph = {
970+
val methodName = method.name.name
971+
val subCtrGraphs = secondaryCtors.collect {
972+
case subCtr if superCall(subCtr.body).exists(_ == methodName) =>
973+
mkConstructorGraph(subCtr)
974+
}
975+
overrideNum += 1
976+
new ConstructorGraph(overrideNum, method, subCtrGraphs)
977+
}
978+
979+
mkConstructorGraph(primaryCtor)
980+
}
981+
982+
695983
// Generate a method -------------------------------------------------------
696984

697985
def genMethod(dd: DefDef): Option[js.MethodDef] = withNewLocalNameScope {
@@ -720,8 +1008,14 @@ abstract class GenJSCode extends plugins.PluginComponent
7201008
*
7211009
* Other (normal) methods are emitted with `genMethodBody()`.
7221010
*/
1011+
7231012
def genMethodWithInfoBuilder(
7241013
dd: DefDef): Option[(js.MethodDef, MethodInfoBuilder)] = {
1014+
genMethodWithInfoBuilder(dd, None)
1015+
}
1016+
1017+
def genMethodWithInfoBuilder(dd: DefDef,
1018+
infoBuilderOpt: Option[MethodInfoBuilder]): Option[(js.MethodDef, MethodInfoBuilder)] = {
7251019

7261020
implicit val pos = dd.pos
7271021
val DefDef(mods, name, _, vparamss, _, rhs) = dd
@@ -742,14 +1036,16 @@ abstract class GenJSCode extends plugins.PluginComponent
7421036
val isJSClassConstructor =
7431037
sym.isClassConstructor && isScalaJSDefinedJSClass(currentClassSym)
7441038

745-
val methodName: js.PropertyName =
746-
if (isJSClassConstructor) js.StringLiteral("constructor")
747-
else encodeMethodSym(sym)
1039+
val methodName: js.PropertyName = encodeMethodSym(sym)
7481040

749-
def createInfoBuilder() = {
750-
new MethodInfoBuilder()
751-
.setEncodedName(methodName.name)
752-
.setIsStatic(sym.owner.isImplClass)
1041+
def getInfoBuilder() = {
1042+
if (infoBuilderOpt.isDefined) {
1043+
infoBuilderOpt.get
1044+
} else {
1045+
new MethodInfoBuilder()
1046+
.setEncodedName(methodName.name)
1047+
.setIsStatic(sym.owner.isImplClass)
1048+
}
7531049
}
7541050

7551051
def jsParams = for (param <- params) yield {
@@ -761,7 +1057,7 @@ abstract class GenJSCode extends plugins.PluginComponent
7611057
if (scalaPrimitives.isPrimitive(sym)) {
7621058
None
7631059
} else if (sym.isDeferred || sym.owner.isInterface) {
764-
val infoBuilder = createInfoBuilder().setIsAbstract(true)
1060+
val infoBuilder = getInfoBuilder().setIsAbstract(true)
7651061
Some((
7661062
js.MethodDef(static = false, methodName,
7671063
jsParams, currentClassType, js.EmptyTree)(
@@ -775,7 +1071,7 @@ abstract class GenJSCode extends plugins.PluginComponent
7751071
None
7761072
} else {
7771073
withScopedVars(
778-
currentMethodInfoBuilder := createInfoBuilder(),
1074+
currentMethodInfoBuilder := getInfoBuilder(),
7791075
mutableLocalVars := mutable.Set.empty,
7801076
mutatedLocalVars := mutable.Set.empty
7811077
) {
@@ -803,14 +1099,12 @@ abstract class GenJSCode extends plugins.PluginComponent
8031099

8041100
val methodDef = {
8051101
if (isJSClassConstructor) {
806-
currentMethodInfoBuilder.setIsExported(true)
8071102
val body0 = genStat(rhs)
808-
val body1 = moveAllStatementsAfterSuperConstructorCall(body0)
809-
val (patchedParams, patchedBody) =
810-
patchFunBodyWithBoxes(sym, jsParams, body1)
1103+
val body1 =
1104+
if (!sym.isPrimaryConstructor) body0
1105+
else moveAllStatementsAfterSuperConstructorCall(body0)
8111106
js.MethodDef(static = false, methodName,
812-
patchedParams, jstpe.NoType, patchedBody)(
813-
optimizerHints, None)
1107+
jsParams, jstpe.NoType, body1)(optimizerHints, None)
8141108
} else if (sym.isClassConstructor) {
8151109
js.MethodDef(static = false, methodName,
8161110
jsParams, currentClassType,

0 commit comments

Comments
 (0)
0