diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index 154dc5f5d97..4fd5e9e88e7 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -126,7 +126,9 @@ class JsIntrinsics( val jsToJsType = defineToJsType() // creates name reference to KotlinType val jsCode = getInternalFunction("js") // js("") val jsHashCode = getInternalFunction("hashCode") + val jsGetObjectHashCode = getInternalFunction("getObjectHashCode") val jsToString = getInternalFunction("toString") + val jsAnyToString = getInternalFunction("anyToString") val jsCompareTo = getInternalFunction("compareTo") val jsEquals = getInternalFunction("equals") diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt index f6eccf5682f..cfa3d8b348a 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt @@ -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, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/descriptorBasedUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/descriptorBasedUtils.kt index c46efe166d6..d208791a45b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/descriptorBasedUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/descriptorBasedUtils.kt @@ -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 + diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 75528b5a3b4..062b18eaccc 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -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"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java index 2a07f8d0c79..ddfd9c57e71 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java @@ -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"); diff --git a/js/js.translator/testData/box/builtins/superCallsToAnyMethods.kt b/js/js.translator/testData/box/builtins/superCallsToAnyMethods.kt new file mode 100644 index 00000000000..fd272f99358 --- /dev/null +++ b/js/js.translator/testData/box/builtins/superCallsToAnyMethods.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" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/expression/equals/superEquals.kt b/js/js.translator/testData/box/expression/equals/superEquals.kt index 0dd578ef7ec..583ea6efded 100644 --- a/js/js.translator/testData/box/expression/equals/superEquals.kt +++ b/js/js.translator/testData/box/expression/equals/superEquals.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1136 package foo diff --git a/js/js.translator/testData/box/superCall/intrinsic.kt b/js/js.translator/testData/box/superCall/intrinsic.kt index cceb26369da..4fc046a4139 100644 --- a/js/js.translator/testData/box/superCall/intrinsic.kt +++ b/js/js.translator/testData/box/superCall/intrinsic.kt @@ -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 diff --git a/libraries/stdlib/js/irRuntime/core.kt b/libraries/stdlib/js/irRuntime/core.kt index 49c9c4420e3..09159bbc7f5 100644 --- a/libraries/stdlib/js/irRuntime/core.kt +++ b/libraries/stdlib/js/irRuntime/core.kt @@ -6,11 +6,11 @@ package kotlin.js fun equals(obj1: dynamic, obj2: dynamic): Boolean { - if (js("obj1 == null").unsafeCast()) { - return js("obj2 == null").unsafeCast(); + if (obj1 == null) { + return obj2 == null } - if (js("obj2 == null").unsafeCast()) { - return false; + if (obj2 == null) { + return false } return js(""" @@ -35,73 +35,55 @@ fun toString(o: dynamic): String = when { else -> js("o.toString()").unsafeCast() } -// 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()) 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(); - 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() + 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() + // TODO: Use getObjectHashCode instead fun identityHashCode(obj: dynamic): Int = hashCode(obj) \ No newline at end of file