|
| 1 | +package dotty.tools.backend.sjs |
| 2 | + |
| 3 | +import org.scalajs.ir |
| 4 | +import org.scalajs.ir.{Position, Trees => js, Types => jstpe} |
| 5 | +import org.scalajs.ir.Names._ |
| 6 | +import org.scalajs.ir.OriginalName.NoOriginalName |
| 7 | + |
| 8 | +import JSCodeGen.UndefinedParam |
| 9 | + |
| 10 | +object JSConstructorGen { |
| 11 | + |
| 12 | + /** Builds one JS constructor out of several "init" methods and their |
| 13 | + * dispatcher. |
| 14 | + * |
| 15 | + * This method and the rest of this file are copied verbatim from `GenJSCode` |
| 16 | + * for scalac, since there is no dependency on the compiler trees/symbols/etc. |
| 17 | + * We are only manipulating IR trees and types. |
| 18 | + * |
| 19 | + * The only difference is the two parameters `overloadIdent` and `reportError`, |
| 20 | + * which are added so that this entire file can be even more isolated. |
| 21 | + */ |
| 22 | + def buildJSConstructorDef(dispatch: js.JSMethodDef, ctors: List[js.MethodDef], |
| 23 | + overloadIdent: js.LocalIdent)( |
| 24 | + reportError: String => Unit)( |
| 25 | + implicit pos: Position): js.JSMethodDef = { |
| 26 | + |
| 27 | + val js.JSMethodDef(_, dispatchName, dispatchArgs, dispatchResolution) = |
| 28 | + dispatch |
| 29 | + |
| 30 | + val jsConstructorBuilder = mkJSConstructorBuilder(ctors, reportError) |
| 31 | + |
| 32 | + // Section containing the overload resolution and casts of parameters |
| 33 | + val overloadSelection = mkOverloadSelection(jsConstructorBuilder, |
| 34 | + overloadIdent, dispatchResolution) |
| 35 | + |
| 36 | + /* Section containing all the code executed before the call to `this` |
| 37 | + * for every secondary constructor. |
| 38 | + */ |
| 39 | + val prePrimaryCtorBody = |
| 40 | + jsConstructorBuilder.mkPrePrimaryCtorBody(overloadIdent) |
| 41 | + |
| 42 | + val primaryCtorBody = jsConstructorBuilder.primaryCtorBody |
| 43 | + |
| 44 | + /* Section containing all the code executed after the call to this for |
| 45 | + * every secondary constructor. |
| 46 | + */ |
| 47 | + val postPrimaryCtorBody = |
| 48 | + jsConstructorBuilder.mkPostPrimaryCtorBody(overloadIdent) |
| 49 | + |
| 50 | + val newBody = js.Block(overloadSelection ::: prePrimaryCtorBody :: |
| 51 | + primaryCtorBody :: postPrimaryCtorBody :: js.Undefined() :: Nil) |
| 52 | + |
| 53 | + js.JSMethodDef(js.MemberFlags.empty, dispatchName, dispatchArgs, newBody)( |
| 54 | + dispatch.optimizerHints, None) |
| 55 | + } |
| 56 | + |
| 57 | + private class ConstructorTree(val overrideNum: Int, val method: js.MethodDef, |
| 58 | + val subConstructors: List[ConstructorTree]) { |
| 59 | + |
| 60 | + lazy val overrideNumBounds: (Int, Int) = |
| 61 | + if (subConstructors.isEmpty) (overrideNum, overrideNum) |
| 62 | + else (subConstructors.head.overrideNumBounds._1, overrideNum) |
| 63 | + |
| 64 | + def get(methodName: MethodName): Option[ConstructorTree] = { |
| 65 | + if (methodName == this.method.methodName) { |
| 66 | + Some(this) |
| 67 | + } else { |
| 68 | + subConstructors.iterator.map(_.get(methodName)).collectFirst { |
| 69 | + case Some(node) => node |
| 70 | + } |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + def getParamRefs(implicit pos: Position): List[js.VarRef] = |
| 75 | + method.args.map(_.ref) |
| 76 | + |
| 77 | + def getAllParamDefsAsVars(implicit pos: Position): List[js.VarDef] = { |
| 78 | + val localDefs = method.args.map { pDef => |
| 79 | + js.VarDef(pDef.name, pDef.originalName, pDef.ptpe, mutable = true, |
| 80 | + jstpe.zeroOf(pDef.ptpe)) |
| 81 | + } |
| 82 | + localDefs ++ subConstructors.flatMap(_.getAllParamDefsAsVars) |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + private class JSConstructorBuilder(root: ConstructorTree, reportError: String => Unit) { |
| 87 | + |
| 88 | + def primaryCtorBody: js.Tree = root.method.body.getOrElse( |
| 89 | + throw new AssertionError("Found abstract constructor")) |
| 90 | + |
| 91 | + def hasSubConstructors: Boolean = root.subConstructors.nonEmpty |
| 92 | + |
| 93 | + def getOverrideNum(methodName: MethodName): Int = |
| 94 | + root.get(methodName).fold(-1)(_.overrideNum) |
| 95 | + |
| 96 | + def getParamRefsFor(methodName: MethodName)(implicit pos: Position): List[js.VarRef] = |
| 97 | + root.get(methodName).fold(List.empty[js.VarRef])(_.getParamRefs) |
| 98 | + |
| 99 | + def getAllParamDefsAsVars(implicit pos: Position): List[js.VarDef] = |
| 100 | + root.getAllParamDefsAsVars |
| 101 | + |
| 102 | + def mkPrePrimaryCtorBody(overrideNumIdent: js.LocalIdent)( |
| 103 | + implicit pos: Position): js.Tree = { |
| 104 | + val overrideNumRef = js.VarRef(overrideNumIdent)(jstpe.IntType) |
| 105 | + mkSubPreCalls(root, overrideNumRef) |
| 106 | + } |
| 107 | + |
| 108 | + def mkPostPrimaryCtorBody(overrideNumIdent: js.LocalIdent)( |
| 109 | + implicit pos: Position): js.Tree = { |
| 110 | + val overrideNumRef = js.VarRef(overrideNumIdent)(jstpe.IntType) |
| 111 | + js.Block(mkSubPostCalls(root, overrideNumRef)) |
| 112 | + } |
| 113 | + |
| 114 | + private def mkSubPreCalls(constructorTree: ConstructorTree, |
| 115 | + overrideNumRef: js.VarRef)(implicit pos: Position): js.Tree = { |
| 116 | + val overrideNumss = constructorTree.subConstructors.map(_.overrideNumBounds) |
| 117 | + val paramRefs = constructorTree.getParamRefs |
| 118 | + val bodies = constructorTree.subConstructors.map { constructorTree => |
| 119 | + mkPrePrimaryCtorBodyOnSndCtr(constructorTree, overrideNumRef, paramRefs) |
| 120 | + } |
| 121 | + overrideNumss.zip(bodies).foldRight[js.Tree](js.Skip()) { |
| 122 | + case ((numBounds, body), acc) => |
| 123 | + val cond = mkOverrideNumsCond(overrideNumRef, numBounds) |
| 124 | + js.If(cond, body, acc)(jstpe.BooleanType) |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + private def mkPrePrimaryCtorBodyOnSndCtr(constructorTree: ConstructorTree, |
| 129 | + overrideNumRef: js.VarRef, outputParams: List[js.VarRef])( |
| 130 | + implicit pos: Position): js.Tree = { |
| 131 | + val subCalls = |
| 132 | + mkSubPreCalls(constructorTree, overrideNumRef) |
| 133 | + |
| 134 | + val preSuperCall = { |
| 135 | + def checkForUndefinedParams(args: List[js.Tree]): List[js.Tree] = { |
| 136 | + def isUndefinedParam(tree: js.Tree): Boolean = tree match { |
| 137 | + case js.Transient(UndefinedParam) => true |
| 138 | + case _ => false |
| 139 | + } |
| 140 | + |
| 141 | + if (!args.exists(isUndefinedParam)) { |
| 142 | + args |
| 143 | + } else { |
| 144 | + /* If we find an undefined param here, we're in trouble, because |
| 145 | + * the handling of a default param for the target constructor has |
| 146 | + * already been done during overload resolution. If we store an |
| 147 | + * `undefined` now, it will fall through without being properly |
| 148 | + * processed. |
| 149 | + * |
| 150 | + * Since this seems very tricky to deal with, and a pretty rare |
| 151 | + * use case (with a workaround), we emit an "implementation |
| 152 | + * restriction" error. |
| 153 | + */ |
| 154 | + reportError( |
| 155 | + "Implementation restriction: in a JS class, a secondary " + |
| 156 | + "constructor calling another constructor with default " + |
| 157 | + "parameters must provide the values of all parameters.") |
| 158 | + |
| 159 | + /* Replace undefined params by undefined to prevent subsequent |
| 160 | + * compiler crashes. |
| 161 | + */ |
| 162 | + args.map { arg => |
| 163 | + if (isUndefinedParam(arg)) |
| 164 | + js.Undefined()(arg.pos) |
| 165 | + else |
| 166 | + arg |
| 167 | + } |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + constructorTree.method.body.get match { |
| 172 | + case js.Block(stats) => |
| 173 | + val beforeSuperCall = stats.takeWhile { |
| 174 | + case js.ApplyStatic(_, _, mtd, _) => !mtd.name.isConstructor |
| 175 | + case _ => true |
| 176 | + } |
| 177 | + val superCallParams = stats.collectFirst { |
| 178 | + case js.ApplyStatic(_, _, mtd, js.This() :: args) |
| 179 | + if mtd.name.isConstructor => |
| 180 | + val checkedArgs = checkForUndefinedParams(args) |
| 181 | + zipMap(outputParams, checkedArgs)(js.Assign(_, _)) |
| 182 | + }.getOrElse(Nil) |
| 183 | + |
| 184 | + beforeSuperCall ::: superCallParams |
| 185 | + |
| 186 | + case js.ApplyStatic(_, _, mtd, js.This() :: args) |
| 187 | + if mtd.name.isConstructor => |
| 188 | + val checkedArgs = checkForUndefinedParams(args) |
| 189 | + zipMap(outputParams, checkedArgs)(js.Assign(_, _)) |
| 190 | + |
| 191 | + case _ => Nil |
| 192 | + } |
| 193 | + } |
| 194 | + |
| 195 | + js.Block(subCalls :: preSuperCall) |
| 196 | + } |
| 197 | + |
| 198 | + private def mkSubPostCalls(constructorTree: ConstructorTree, |
| 199 | + overrideNumRef: js.VarRef)(implicit pos: Position): js.Tree = { |
| 200 | + val overrideNumss = constructorTree.subConstructors.map(_.overrideNumBounds) |
| 201 | + val bodies = constructorTree.subConstructors.map { ct => |
| 202 | + mkPostPrimaryCtorBodyOnSndCtr(ct, overrideNumRef) |
| 203 | + } |
| 204 | + overrideNumss.zip(bodies).foldRight[js.Tree](js.Skip()) { |
| 205 | + case ((numBounds, js.Skip()), acc) => acc |
| 206 | + |
| 207 | + case ((numBounds, body), acc) => |
| 208 | + val cond = mkOverrideNumsCond(overrideNumRef, numBounds) |
| 209 | + js.If(cond, body, acc)(jstpe.BooleanType) |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + private def mkPostPrimaryCtorBodyOnSndCtr(constructorTree: ConstructorTree, |
| 214 | + overrideNumRef: js.VarRef)(implicit pos: Position): js.Tree = { |
| 215 | + val postSuperCall = { |
| 216 | + constructorTree.method.body.get match { |
| 217 | + case js.Block(stats) => |
| 218 | + stats.dropWhile { |
| 219 | + case js.ApplyStatic(_, _, mtd, _) => !mtd.name.isConstructor |
| 220 | + case _ => true |
| 221 | + }.tail |
| 222 | + |
| 223 | + case _ => Nil |
| 224 | + } |
| 225 | + } |
| 226 | + js.Block(postSuperCall :+ mkSubPostCalls(constructorTree, overrideNumRef)) |
| 227 | + } |
| 228 | + |
| 229 | + private def mkOverrideNumsCond(numRef: js.VarRef, |
| 230 | + numBounds: (Int, Int))(implicit pos: Position) = numBounds match { |
| 231 | + case (lo, hi) if lo == hi => |
| 232 | + js.BinaryOp(js.BinaryOp.Int_==, js.IntLiteral(lo), numRef) |
| 233 | + |
| 234 | + case (lo, hi) if lo == hi - 1 => |
| 235 | + val lhs = js.BinaryOp(js.BinaryOp.Int_==, numRef, js.IntLiteral(lo)) |
| 236 | + val rhs = js.BinaryOp(js.BinaryOp.Int_==, numRef, js.IntLiteral(hi)) |
| 237 | + js.If(lhs, js.BooleanLiteral(true), rhs)(jstpe.BooleanType) |
| 238 | + |
| 239 | + case (lo, hi) => |
| 240 | + val lhs = js.BinaryOp(js.BinaryOp.Int_<=, js.IntLiteral(lo), numRef) |
| 241 | + val rhs = js.BinaryOp(js.BinaryOp.Int_<=, numRef, js.IntLiteral(hi)) |
| 242 | + js.BinaryOp(js.BinaryOp.Boolean_&, lhs, rhs) |
| 243 | + js.If(lhs, rhs, js.BooleanLiteral(false))(jstpe.BooleanType) |
| 244 | + } |
| 245 | + } |
| 246 | + |
| 247 | + private def zipMap[T, U, V](xs: List[T], ys: List[U])( |
| 248 | + f: (T, U) => V): List[V] = { |
| 249 | + for ((x, y) <- xs zip ys) yield f(x, y) |
| 250 | + } |
| 251 | + |
| 252 | + /** mkOverloadSelection return a list of `stats` with that starts with: |
| 253 | + * 1) The definition for the local variable that will hold the overload |
| 254 | + * resolution number. |
| 255 | + * 2) The definitions of all local variables that are used as parameters |
| 256 | + * in all the constructors. |
| 257 | + * 3) The overload resolution match/if statements. For each overload the |
| 258 | + * overload number is assigned and the parameters are cast and assigned |
| 259 | + * to their corresponding variables. |
| 260 | + */ |
| 261 | + private def mkOverloadSelection(jsConstructorBuilder: JSConstructorBuilder, |
| 262 | + overloadIdent: js.LocalIdent, dispatchResolution: js.Tree)( |
| 263 | + implicit pos: Position): List[js.Tree] = { |
| 264 | + |
| 265 | + def deconstructApplyCtor(body: js.Tree): (List[js.Tree], MethodName, List[js.Tree]) = { |
| 266 | + val (prepStats, applyCtor) = (body: @unchecked) match { |
| 267 | + case applyCtor: js.ApplyStatic => |
| 268 | + (Nil, applyCtor) |
| 269 | + case js.Block(prepStats :+ (applyCtor: js.ApplyStatic)) => |
| 270 | + (prepStats, applyCtor) |
| 271 | + } |
| 272 | + val js.ApplyStatic(_, _, js.MethodIdent(ctorName), js.This() :: ctorArgs) = |
| 273 | + applyCtor |
| 274 | + assert(ctorName.isConstructor, |
| 275 | + s"unexpected super constructor call to non-constructor $ctorName at ${applyCtor.pos}") |
| 276 | + (prepStats, ctorName, ctorArgs) |
| 277 | + } |
| 278 | + |
| 279 | + if (!jsConstructorBuilder.hasSubConstructors) { |
| 280 | + val (prepStats, ctorName, ctorArgs) = |
| 281 | + deconstructApplyCtor(dispatchResolution) |
| 282 | + |
| 283 | + val refs = jsConstructorBuilder.getParamRefsFor(ctorName) |
| 284 | + assert(refs.size == ctorArgs.size, s"at $pos") |
| 285 | + val assignCtorParams = zipMap(refs, ctorArgs) { (ref, ctorArg) => |
| 286 | + js.VarDef(ref.ident, NoOriginalName, ref.tpe, mutable = false, ctorArg) |
| 287 | + } |
| 288 | + |
| 289 | + prepStats ::: assignCtorParams |
| 290 | + } else { |
| 291 | + val overloadRef = js.VarRef(overloadIdent)(jstpe.IntType) |
| 292 | + |
| 293 | + /* transformDispatch takes the body of the method generated by |
| 294 | + * `genJSConstructorDispatch` and transform it recursively. |
| 295 | + */ |
| 296 | + def transformDispatch(tree: js.Tree): js.Tree = tree match { |
| 297 | + // Parameter count resolution |
| 298 | + case js.Match(selector, cases, default) => |
| 299 | + val newCases = cases.map { |
| 300 | + case (literals, body) => (literals, transformDispatch(body)) |
| 301 | + } |
| 302 | + val newDefault = transformDispatch(default) |
| 303 | + js.Match(selector, newCases, newDefault)(tree.tpe) |
| 304 | + |
| 305 | + // Parameter type resolution |
| 306 | + case js.If(cond, thenp, elsep) => |
| 307 | + js.If(cond, transformDispatch(thenp), |
| 308 | + transformDispatch(elsep))(tree.tpe) |
| 309 | + |
| 310 | + // Throw(StringLiteral(No matching overload)) |
| 311 | + case tree: js.Throw => |
| 312 | + tree |
| 313 | + |
| 314 | + // Overload resolution done, apply the constructor |
| 315 | + case _ => |
| 316 | + val (prepStats, ctorName, ctorArgs) = deconstructApplyCtor(tree) |
| 317 | + |
| 318 | + val num = jsConstructorBuilder.getOverrideNum(ctorName) |
| 319 | + val overloadAssign = js.Assign(overloadRef, js.IntLiteral(num)) |
| 320 | + |
| 321 | + val refs = jsConstructorBuilder.getParamRefsFor(ctorName) |
| 322 | + assert(refs.size == ctorArgs.size, s"at $pos") |
| 323 | + val assignCtorParams = zipMap(refs, ctorArgs)(js.Assign(_, _)) |
| 324 | + |
| 325 | + js.Block(overloadAssign :: prepStats ::: assignCtorParams) |
| 326 | + } |
| 327 | + |
| 328 | + val newDispatchResolution = transformDispatch(dispatchResolution) |
| 329 | + val allParamDefsAsVars = jsConstructorBuilder.getAllParamDefsAsVars |
| 330 | + val overrideNumDef = js.VarDef(overloadIdent, NoOriginalName, |
| 331 | + jstpe.IntType, mutable = true, js.IntLiteral(0)) |
| 332 | + |
| 333 | + overrideNumDef :: allParamDefsAsVars ::: newDispatchResolution :: Nil |
| 334 | + } |
| 335 | + } |
| 336 | + |
| 337 | + private def mkJSConstructorBuilder(ctors: List[js.MethodDef], reportError: String => Unit)( |
| 338 | + implicit pos: Position): JSConstructorBuilder = { |
| 339 | + def findCtorForwarderCall(tree: js.Tree): MethodName = (tree: @unchecked) match { |
| 340 | + case js.ApplyStatic(_, _, method, js.This() :: _) |
| 341 | + if method.name.isConstructor => |
| 342 | + method.name |
| 343 | + |
| 344 | + case js.Block(stats) => |
| 345 | + stats.collectFirst { |
| 346 | + case js.ApplyStatic(_, _, method, js.This() :: _) |
| 347 | + if method.name.isConstructor => |
| 348 | + method.name |
| 349 | + }.get |
| 350 | + } |
| 351 | + |
| 352 | + val (primaryCtor :: Nil, secondaryCtors) = ctors.partition { |
| 353 | + _.body.get match { |
| 354 | + case js.Block(stats) => |
| 355 | + stats.exists(_.isInstanceOf[js.JSSuperConstructorCall]) |
| 356 | + |
| 357 | + case _: js.JSSuperConstructorCall => true |
| 358 | + case _ => false |
| 359 | + } |
| 360 | + } |
| 361 | + |
| 362 | + val ctorToChildren = secondaryCtors.map { ctor => |
| 363 | + findCtorForwarderCall(ctor.body.get) -> ctor |
| 364 | + }.groupBy(_._1).map(kv => kv._1 -> kv._2.map(_._2)).withDefaultValue(Nil) |
| 365 | + |
| 366 | + var overrideNum = -1 |
| 367 | + def mkConstructorTree(method: js.MethodDef): ConstructorTree = { |
| 368 | + val subCtrTrees = ctorToChildren(method.methodName).map(mkConstructorTree) |
| 369 | + overrideNum += 1 |
| 370 | + new ConstructorTree(overrideNum, method, subCtrTrees) |
| 371 | + } |
| 372 | + |
| 373 | + new JSConstructorBuilder(mkConstructorTree(primaryCtor), reportError: String => Unit) |
| 374 | + } |
| 375 | + |
| 376 | +} |
0 commit comments