8000 Refactoring: Isolate handling of javalib methods with special bodies. · scala-js/scala-js@5c31843 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5c31843

Browse files
committed
Refactoring: Isolate handling of javalib methods with special bodies.
A number of methods from the javalib are special-cased by the compiler, which replaces their body with a dedicated `UnaryOp` or `BinaryOp`. This commit refactors that handling to isolate it better from the handling of regular methods. We also make it a bit more flexible, so that we can more easily add further such methods in the future.
1 parent 493130e commit 5c31843

File tree

1 file changed

+106
-73
lines changed

1 file changed

+106
-73
lines changed

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

Lines changed: 106 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2298,50 +2298,17 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
22982298
isJSFunctionDef(currentClassSym)) {
22992299
val flags = js.MemberFlags.empty.withNamespace(namespace)
23002300
val body = {
2301-
def genAsUnaryOp(op: js.UnaryOp.Code): js.Tree =
2302-
js.UnaryOp(op, genThis())
2303-
def genAsBinaryOp(op: js.BinaryOp.Code): js.Tree =
2304-
js.BinaryOp(op, genThis(), jsParams.head.ref)
2305-
def genAsBinaryOpRhsNotNull(op: js.BinaryOp.Code): js.Tree =
2306-
js.BinaryOp(op, genThis(), js.UnaryOp(js.UnaryOp.CheckNotNull, jsParams.head.ref))
2307-
2308-
if (currentClassSym.get == HackedStringClass) {
2309-
/* Hijack the bodies of String.length and String.charAt and replace
2310-
* them with String_length and String_charAt operations, respectively.
2311-
*/
2312-
methodName.name match {
2313-
case `lengthMethodName` => genAsUnaryOp(js.UnaryOp.String_length)
2314-
case `charAtMethodName` => genAsBinaryOp(js.BinaryOp.String_charAt)
2315-
case _ => genBody()
2316-
}
2317-
} else if (currentClassSym.get == ClassClass) {
2318-
// Similar, for the Class_x operations
2319-
methodName.name match {
2320-
case `getNameMethodName` => genAsUnaryOp(js.UnaryOp.Class_name)
2321-
case `isPrimitiveMethodName` => genAsUnaryOp(js.UnaryOp.Class_isPrimitive)
2322-
case `isInterfaceMethodName` => genAsUnaryOp(js.UnaryOp.Class_isInterface)
2323-
case `isArrayMethodName` => genAsUnaryOp(js.UnaryOp.Class_isArray)
2324-
case `getComponentTypeMethodName` => genAsUnaryOp(js.UnaryOp.Class_componentType)
2325-
case `getSuperclassMethodName` => genAsUnaryOp(js.UnaryOp.Class_superClass)
2326-
2327-
case `isInstanceMethodName` => genAsBinaryOp(js.BinaryOp.Class_isInstance)
2328-
case `isAssignableFromMethodName` => genAsBinaryOpRhsNotNull(js.BinaryOp.Class_isAssignableFrom)
2329-
case `castMethodName` => genAsBinaryOp(js.BinaryOp.Class_cast)
2330-
2331-
case _ => genBody()
2332-
}
2333-
} else if (currentClassSym.get == JavaLangReflectArrayModClass) {
2334-
methodName.name match {
2335-
case `arrayNewInstanceMethodName` =>
2336-
val List(jlClassParam, lengthParam) = jsParams
2337-
js.BinaryOp(js.BinaryOp.Class_newArray,
2338-
js.UnaryOp(js.UnaryOp.CheckNotNull, jlClassParam.ref),
2339-
lengthParam.ref)
2340-
case _ =>
2301+
val classOwner = currentClassSym.owner
2302+
if (classOwner != JavaLangPackageClass && classOwner.owner != JavaLangPackageClass) {
2303+
// Fast path; it cannot be any of the special methods of the javalib
2304+
genBody()
2305+
} else {
2306+
JavalibMethodsWithOpBody.get((encodeClassName(currentClassSym), methodName.name)) match {
2307+
case None =>
23412308
genBody()
2309+
case Some(javalibOpBody) =>
2310+
javalibOpBody.generate(genThis(), jsParams.map(_.ref))
23422311
}
2343-
} else {
2344-
genBody()
23452312
}
23462313
}
23472314
js.MethodDef(flags, methodName, originalName, jsParams, resultIRType,
@@ -7379,37 +7346,6 @@ private object GenJSCode {
73797346
private val ObjectArgConstructorName =
73807347
MethodName.constructor(List(jswkn.ObjectRef))
73817348

7382-
private val lengthMethodName =
7383-
MethodName("length", Nil, jstpe.IntRef)
7384-
private val charAtMethodName =
7385-
MethodName("charAt", List(jstpe.IntRef), jstpe.CharRef)
7386-
7387-
private val getNameMethodName =
7388-
MethodName("getName", Nil, jstpe.ClassRef(jswkn.BoxedStringClass))
7389-
private val isPrimitiveMethodName =
7390-
MethodName("isPrimitive", Nil, jstpe.BooleanRef)
7391-
private val isInterfaceMethodName =
7392-
MethodName("isInterface", Nil, jstpe.BooleanRef)
7393-
private val isArrayMethodName =
7394-
MethodName("isArray", Nil, jstpe.BooleanRef)
7395-
private val getComponentTypeMethodName =
7396-
MethodName("getComponentType", Nil, jstpe.ClassRef(jswkn.ClassClass))
7397-
private val getSuperclassMethodName =
7398-
MethodName("getSuperclass", Nil, jstpe.ClassRef(jswkn.ClassClass))
7399-
7400-
private val isInstanceMethodName =
7401-
MethodName("isInstance", List(jstpe.ClassRef 6D4E (jswkn.ObjectClass)), jstpe.BooleanRef)
7402-
private val isAssignableFromMethodName =
7403-
MethodName("isAssignableFrom", List(jstpe.ClassRef(jswkn.ClassClass)), jstpe.BooleanRef)
7404-
private val castMethodName =
7405-
MethodName("cast", List(jstpe.ClassRef(jswkn.ObjectClass)), jstpe.ClassRef(jswkn.ObjectClass))
7406-
7407-
private val arrayNewInstanceMethodName = {
7408-
MethodName("newInstance",
7409-
List(jstpe.ClassRef(jswkn.ClassClass), jstpe.IntRef),
7410-
jstpe.ClassRef(jswkn.ObjectClass))
7411-
}
7412-
74137349
private val thisOriginalName = OriginalName("this")
74147350

74157351
private object BlockOrAlone {
@@ -7425,4 +7361,101 @@ private object GenJSCode {
74257361
case _ => Some((tree, Nil))
74267362
}
74277363
}
7364+
7365+
private abstract class JavalibOpBody {
7366+
/** Generates the body of this special method, given references to the receiver and parameters. */
7367+
def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree
7368+
}
7369+
7370+
private object JavalibOpBody {
7371+
private def checkNotNullIf(arg: js.Tree, checkNulls: Boolean)(implicit pos: ir.Position): js.Tree =
7372+
if (checkNulls && arg.tpe.isNullable) js.UnaryOp(js.UnaryOp.CheckNotNull, arg)
7373+
else arg
7374+
7375+
/* These are case classes for convenience (for the apply method).
7376+
* They are not intended for pattern matching.
7377+
*/
7378+
7379+
/** UnaryOp applying to the `this` parameter. */
7380+
final case class ThisUnaryOp(op: js.UnaryOp.Code) extends JavalibOpBody {
7381+
def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree = {
7382+
assert(args.isEmpty)
7383+
js.UnaryOp(op, receiver)
7384+
}
7385+
}
7386+
7387+
/** BinaryOp applying to the `this` parameter and the regular parameter. */
7388+
final case class ThisBinaryOp(op: js.BinaryOp.Code, checkNulls: Boolean = false) extends JavalibOpBody {
7389+
def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree = {
7390+
val List(rhs) = args: @unchecked
7391+
js.BinaryOp(op, receiver, checkNotNullIf(rhs, checkNulls))
7392+
}
7393+
}
7394+
7395+
/** UnaryOp applying to the only regular parameter (`this` is ignored). */
7396+
final case class ArgUnaryOp(op: js.UnaryOp.Code, checkNulls: Boolean = false) extends JavalibOpBody {
7397+
def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree = {
7398+
val List(arg) = args: @unchecked
7399+
js.UnaryOp(op, checkNotNullIf(arg, checkNulls))
7400+
}
7401+
}
7402+
7403+
/** BinaryOp applying to the two regular paramters (`this` is ignored). */
7404+
final case class ArgBinaryOp(op: js.BinaryOp.Code, checkNulls: Boolean = false) extends JavalibOpBody {
7405+
def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree = {
7406+
val List(lhs, rhs) = args: @unchecked
7407+
js.BinaryOp(op, checkNotNullIf(lhs, checkNulls), checkNotNullIf(rhs, checkNulls))
7408+
}
7409+
}
7410+
}
7411+
7412+
/** Methods of the javalib whose body must be replaced by a dedicated
7413+
* UnaryOp or BinaryOp.
7414+
*
7415+
* We use IR encoded names to identify them, rather than scalac Symbols.
7416+
* There is no fundamental reason for that. It makes it easier to define
7417+
* this map in a declarative way, especially when overloaded methods are
7418+
* concerned (Array.newInstance). It also allows to define it independently
7419+
* of the Global instance, but that is marginal.
7420+
*/
7421+
private lazy val JavalibMethodsWithOpBody: Map[(ClassName, MethodName), JavalibOpBody] = {
7422+
import JavalibOpBody._
7423+
import js.{UnaryOp => unop, BinaryOp => binop}
7424+
import jstpe.{BooleanRef => Z, CharRef => C, IntRef => I}
7425+
import MethodName.{apply => m}
7426+
7427+
val O = jswkn.ObjectRef
7428+
val CC = jstpe.ClassRef(jswkn.ClassClass)
7429+
val T = jstpe.ClassRef(jswkn.BoxedStringClass)
7430+
7431+
val byClass: Map[ClassName, Map[MethodName, JavalibOpBody]] = Map(
7432+
jswkn.BoxedStringClass -> Map(
7433+
m("length", Nil, I) -> ThisUnaryOp(unop.String_length),
7434+
m("charAt", List(I), C) -> ThisBinaryOp(binop.String_charAt)
7435+
),
7436+
jswkn.ClassClass -> Map(
7437+
// Unary operators
7438+
m("getName", Nil, T) -> ThisUnaryOp(unop.Class_name),
7439+
m("isPrimitive", Nil, Z) -> ThisUnaryOp(unop.Class_isPrimitive),
7440+
m("isInterface", Nil, Z) -> ThisUnaryOp(unop.Class_isInterface),
7441+
m("isArray", Nil, Z) -> ThisUnaryOp(unop.Class_isArray),
7442+
m("getComponentType", Nil, CC) -> ThisUnaryOp(unop.Class_componentType),
7443+
m("getSuperclass", Nil, CC) -> ThisUnaryOp(unop.Class_superClass),
7444+
// Binary operators
7445+
m("isInstance", List(O), Z) -> ThisBinaryOp(binop.Class_isInstance),
7446+
m("isAssignableFrom", List(CC), Z) -> ThisBinaryOp(binop.Class_isAssignableFrom, checkNulls = true),
7447+
m("cast", List(O), O) -> ThisBinaryOp(binop.Class_cast)
7448+
),
7449+
ClassName("java.lang.reflect.Array$") -> Map(
7450+
m("newInstance", List(CC, I), O) -> ArgBinaryOp(binop.Class_newArray, checkNulls = true)
7451+
)
7452+
)
7453+
7454+
for {
7455+
(cls, methods) <- byClass
7456+
(methodName, body) <- methods
7457+
} yield {
7458+
(cls, methodName) -> body
7459+
}
7460+
}
74287461
}

0 commit comments

Comments
 (0)
0