[JS IR BE] Support super calls for methods of Any
This commit is contained in:
@@ -126,7 +126,9 @@ class JsIntrinsics(
|
||||
val jsToJsType = defineToJsType() // creates name reference to KotlinType
|
||||
val jsCode = getInternalFunction("js") // js("<code>")
|
||||
val jsHashCode = getInternalFunction("hashCode")
|
||||
val jsGetObjectHashCode = getInternalFunction("getObjectHashCode")
|
||||
val jsToString = getInternalFunction("toString")
|
||||
val jsAnyToString = getInternalFunction("anyToString")
|
||||
val jsCompareTo = getInternalFunction("compareTo")
|
||||
val jsEquals = getInternalFunction("equals")
|
||||
|
||||
|
||||
+31
-18
@@ -207,22 +207,37 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
||||
{ call -> irCall(call, context.intrinsics.jsPropertySet.symbol, dispatchReceiverAsFirstArgument = true) }
|
||||
)
|
||||
|
||||
addWithPredicate(
|
||||
Name.identifier("hashCode"),
|
||||
{ call -> (call.superQualifier == null) && (call.symbol.owner.descriptor.isFakeOverriddenFromAny()) },
|
||||
{ call -> irCall(call, intrinsics.jsHashCode, dispatchReceiverAsFirstArgument = true) }
|
||||
)
|
||||
|
||||
addWithPredicate(
|
||||
Name.identifier("toString"), ::shouldReplaceToStringWithRuntimeCall,
|
||||
{ call -> irCall(call, intrinsics.jsToString, dispatchReceiverAsFirstArgument = true) }
|
||||
)
|
||||
|
||||
addWithPredicate(
|
||||
Name.identifier("compareTo"), ::shouldReplaceCompareToWithRuntimeCall,
|
||||
{ call -> irCall(call, intrinsics.jsCompareTo, dispatchReceiverAsFirstArgument = true) }
|
||||
)
|
||||
|
||||
put(Name.identifier("toString")) { call ->
|
||||
if (shouldReplaceToStringWithRuntimeCall(call)) {
|
||||
if (call.isSuperToAny()) {
|
||||
irCall(call, intrinsics.jsAnyToString, dispatchReceiverAsFirstArgument = true)
|
||||
} else {
|
||||
irCall(call, intrinsics.jsToString, dispatchReceiverAsFirstArgument = true)
|
||||
}
|
||||
} else {
|
||||
call
|
||||
}
|
||||
}
|
||||
|
||||
put(Name.identifier("hashCode")) { call ->
|
||||
if (call.symbol.owner.descriptor.isFakeOverriddenFromAny()) {
|
||||
if (call.isSuperToAny()) {
|
||||
irCall(call, intrinsics.jsGetObjectHashCode, dispatchReceiverAsFirstArgument = true)
|
||||
} else {
|
||||
irCall(call, intrinsics.jsHashCode, dispatchReceiverAsFirstArgument = true)
|
||||
}
|
||||
} else {
|
||||
call
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
put(Name.identifier("equals"), ::transformEqualsMethodCall)
|
||||
}
|
||||
|
||||
@@ -464,7 +479,6 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
||||
}
|
||||
|
||||
private fun transformEqualsMethodCall(call: IrCall): IrExpression {
|
||||
if (call.superQualifier != null) return call
|
||||
val symbol = call.symbol
|
||||
if (!symbol.isBound) return call
|
||||
val function = (symbol.owner as? IrFunction) ?: return call
|
||||
@@ -475,7 +489,11 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
||||
is EqualityOperator -> irCall(call, intrinsics.jsEqeq.symbol)
|
||||
is RuntimeFunctionCall -> irCall(call, intrinsics.jsEquals, true)
|
||||
is RuntimeOrMethodCall -> if (symbol.owner.descriptor.isFakeOverriddenFromAny()) {
|
||||
irCall(call, intrinsics.jsEquals, true)
|
||||
if (call.isSuperToAny()) {
|
||||
irCall(call, intrinsics.jsEqeqeq.symbol, dispatchReceiverAsFirstArgument = true)
|
||||
} else {
|
||||
irCall(call, intrinsics.jsEquals, dispatchReceiverAsFirstArgument = true)
|
||||
}
|
||||
} else {
|
||||
call
|
||||
}
|
||||
@@ -484,8 +502,6 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
||||
}
|
||||
|
||||
fun shouldReplaceToStringWithRuntimeCall(call: IrCall): Boolean {
|
||||
if (call.superQualifier != null) return false
|
||||
|
||||
// TODO: (KOTLIN-CR-2079)
|
||||
// - User defined extension functions Any?.toString() call can be lost during lowering.
|
||||
// - Use direct method call for dynamic types???
|
||||
@@ -501,8 +517,6 @@ fun shouldReplaceToStringWithRuntimeCall(call: IrCall): Boolean {
|
||||
}
|
||||
|
||||
fun shouldReplaceCompareToWithRuntimeCall(call: IrCall): Boolean {
|
||||
if (call.superQualifier != null) return false
|
||||
|
||||
// TODO: Replace all compareTo to with runtime call when Comparable<*>.compareTo() bridge is implemented
|
||||
return call.symbol.owner.dispatchReceiverParameter?.run {
|
||||
type is IrDynamicType
|
||||
@@ -650,8 +664,7 @@ fun irCall(
|
||||
newSymbol,
|
||||
newSymbol.descriptor,
|
||||
typeArgumentsCount,
|
||||
origin,
|
||||
superQualifierSymbol
|
||||
origin
|
||||
).apply {
|
||||
copyTypeAndValueArgumentsFrom(
|
||||
call,
|
||||
|
||||
+5
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.declarations.IrTypeParameter
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -56,3 +57,7 @@ fun IrDeclaration.isEffectivelyExternal() = descriptor.isEffectivelyExternal()
|
||||
fun IrSymbol.isEffectivelyExternal() = descriptor.isEffectivelyExternal()
|
||||
|
||||
fun IrSymbol.isDynamic() = descriptor.isDynamic()
|
||||
|
||||
fun IrCall.isSuperToAny() =
|
||||
superQualifier?.let { this.symbol.owner.descriptor.isFakeOverriddenFromAny() } ?: false
|
||||
|
||||
|
||||
+5
@@ -64,6 +64,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/builtins/hashCode.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("superCallsToAnyMethods.kt")
|
||||
public void testSuperCallsToAnyMethods() throws Exception {
|
||||
runTest("js/js.translator/testData/box/builtins/superCallsToAnyMethods.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("toString.kt")
|
||||
public void testToString() throws Exception {
|
||||
runTest("js/js.translator/testData/box/builtins/toString.kt");
|
||||
|
||||
+5
@@ -64,6 +64,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/builtins/hashCode.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("superCallsToAnyMethods.kt")
|
||||
public void testSuperCallsToAnyMethods() throws Exception {
|
||||
runTest("js/js.translator/testData/box/builtins/superCallsToAnyMethods.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("toString.kt")
|
||||
public void testToString() throws Exception {
|
||||
runTest("js/js.translator/testData/box/builtins/toString.kt");
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// EXPECTED_REACHABLE_NODES: 1117
|
||||
// IGNORE_BACKEND: JS
|
||||
package foo
|
||||
|
||||
class C {
|
||||
override fun toString() = super.toString()
|
||||
|
||||
override fun equals(other: Any?) = super.equals(other)
|
||||
|
||||
override fun hashCode() = super.hashCode()
|
||||
}
|
||||
|
||||
open class D
|
||||
|
||||
class E : D() {
|
||||
override fun toString() = super.toString()
|
||||
|
||||
override fun equals(other: Any?) = super.equals(other)
|
||||
|
||||
override fun hashCode() = super.hashCode()
|
||||
}
|
||||
|
||||
fun testAnyBuiltins(x1: Any, x2: Any): String {
|
||||
val s = x1.toString()
|
||||
if (s != "[object Object]") return "toString fail: ${s}"
|
||||
if (!x1.equals(x1)) return "equals fail #1"
|
||||
if (x1.equals(x2) != x2.equals(x1)) return "equals fail #2"
|
||||
if (x1.equals(x2) != x1.equals(x2)) return "equals fail #3"
|
||||
if (x1.hashCode() != x1.hashCode()) return "hashCode fail"
|
||||
return "OK"
|
||||
}
|
||||
|
||||
|
||||
fun box(): String {
|
||||
val resultC = testAnyBuiltins(C(), C())
|
||||
if (resultC != "OK") return resultC
|
||||
|
||||
val resultD = testAnyBuiltins(D(), D())
|
||||
if (resultD != "OK") return resultD
|
||||
|
||||
val resultE = testAnyBuiltins(E(), E())
|
||||
if (resultE != "OK") return resultE
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// EXPECTED_REACHABLE_NODES: 1136
|
||||
package foo
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// EXPECTED_REACHABLE_NODES: 1141
|
||||
class A(val x: Int) {
|
||||
override fun equals(other: Any?): Boolean = other is A && other.x == x
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
package kotlin.js
|
||||
|
||||
fun equals(obj1: dynamic, obj2: dynamic): Boolean {
|
||||
if (js("obj1 == null").unsafeCast<Boolean>()) {
|
||||
return js("obj2 == null").unsafeCast<Boolean>();
|
||||
if (obj1 == null) {
|
||||
return obj2 == null
|
||||
}
|
||||
if (js("obj2 == null").unsafeCast<Boolean>()) {
|
||||
return false;
|
||||
if (obj2 == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return js("""
|
||||
@@ -35,73 +35,55 @@ fun toString(o: dynamic): String = when {
|
||||
else -> js("o.toString()").unsafeCast<String>()
|
||||
}
|
||||
|
||||
// TODO: Simplify, extract kotlin declarations for inner helper functions
|
||||
fun anyToString(o: dynamic): String = js("Object.prototype.toString.call(o)")
|
||||
|
||||
fun hashCode(obj: dynamic): Int {
|
||||
return js(
|
||||
"""
|
||||
function hashCode(obj) {
|
||||
if (obj == null) {
|
||||
return 0;
|
||||
}
|
||||
var objType = typeof obj;
|
||||
if ("object" === objType) {
|
||||
return "function" === typeof obj.hashCode ? obj.hashCode() : getObjectHashCode(obj);
|
||||
}
|
||||
if ("function" === objType) {
|
||||
return getObjectHashCode(obj);
|
||||
}
|
||||
if ("number" === objType) {
|
||||
return getNumberHashCode(obj);
|
||||
}
|
||||
if ("boolean" === objType) {
|
||||
return Number(obj)
|
||||
}
|
||||
if (obj == null)
|
||||
return 0
|
||||
|
||||
var str = String(obj);
|
||||
return getStringHashCode(str);
|
||||
};
|
||||
return when (typeOf(obj)) {
|
||||
"object" -> if ("function" === js("typeof obj.hashCode")) js("obj.hashCode()") else getObjectHashCode(obj)
|
||||
"function" -> getObjectHashCode(obj)
|
||||
"number" -> getNumberHashCode(obj)
|
||||
"boolean" -> if(obj.unsafeCast<Boolean>()) 1 else 0
|
||||
else -> getStringHashCode(js("String(obj)"))
|
||||
}
|
||||
}
|
||||
|
||||
/** @const */
|
||||
fun getObjectHashCode(obj: dynamic) = js("""
|
||||
var POW_2_32 = 4294967296;
|
||||
// TODO: consider switching to Symbol type once we are on ES6.
|
||||
/** @const */
|
||||
var OBJECT_HASH_CODE_PROPERTY_NAME = "kotlinHashCodeValue${"$"}";
|
||||
|
||||
var byteBuffer = new ArrayBuffer(8);
|
||||
var bufFloat64 = new Float64Array(byteBuffer);
|
||||
var bufInt32 = new Int32Array(byteBuffer);
|
||||
|
||||
function getObjectHashCode(obj) {
|
||||
if (!(OBJECT_HASH_CODE_PROPERTY_NAME in obj)) {
|
||||
var hash = (Math.random() * POW_2_32) | 0; // Make 32-bit singed integer.
|
||||
Object.defineProperty(obj, OBJECT_HASH_CODE_PROPERTY_NAME, { value: hash, enumerable: false });
|
||||
}
|
||||
return obj[OBJECT_HASH_CODE_PROPERTY_NAME];
|
||||
if (!(OBJECT_HASH_CODE_PROPERTY_NAME in obj)) {
|
||||
var hash = (Math.random() * POW_2_32) | 0; // Make 32-bit singed integer.
|
||||
Object.defineProperty(obj, OBJECT_HASH_CODE_PROPERTY_NAME, { value: hash, enumerable: false });
|
||||
}
|
||||
return obj[OBJECT_HASH_CODE_PROPERTY_NAME];
|
||||
""").unsafeCast<Int>();
|
||||
|
||||
function getStringHashCode(str) {
|
||||
var hash = 0;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var code = str.charCodeAt(i);
|
||||
hash = (hash * 31 + code) | 0; // Keep it 32-bit.
|
||||
}
|
||||
return hash;
|
||||
fun getStringHashCode(str: String): Int {
|
||||
var hash = 0
|
||||
val length: Int = js("str.length") // TODO: Implement WString.length
|
||||
for (i in 0..length-1) {
|
||||
val code: Int = js("str.charCodeAt(i)")
|
||||
hash = hash * 31 + code
|
||||
}
|
||||
|
||||
function getNumberHashCode(obj) {
|
||||
if ((obj | 0) === obj) {
|
||||
return obj | 0;
|
||||
}
|
||||
else {
|
||||
bufFloat64[0] = obj;
|
||||
return (bufInt32[1] * 31 | 0) + bufInt32[0] | 0;
|
||||
}
|
||||
}
|
||||
|
||||
return hashCode(obj);
|
||||
"""
|
||||
).unsafeCast<Int>()
|
||||
return hash
|
||||
}
|
||||
|
||||
fun getNumberHashCode(obj: dynamic) = js("""
|
||||
if ((obj | 0) === obj) {
|
||||
return obj | 0;
|
||||
}
|
||||
else {
|
||||
var byteBuffer = new ArrayBuffer (8);
|
||||
var bufFloat64 = new Float64Array (byteBuffer);
|
||||
var bufInt32 = new Int32Array (byteBuffer);
|
||||
|
||||
bufFloat64[0] = obj;
|
||||
return (bufInt32[1] * 31 | 0)+bufInt32[0] | 0;
|
||||
}
|
||||
""").unsafeCast<Int>()
|
||||
|
||||
// TODO: Use getObjectHashCode instead
|
||||
fun identityHashCode(obj: dynamic): Int = hashCode(obj)
|
||||
Reference in New Issue
Block a user