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 40c7b6ac05e..63f0a15cd78 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 @@ -1433,6 +1433,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("equalsNullUndefined.kt") + public void testEqualsNullUndefined() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/enum/equalsNullUndefined.kt"); + doTest(fileName); + } + @TestMetadata("implementsComparable.kt") public void testImplementsComparable() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/enum/implementsComparable.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/CompareToBOIF.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/CompareToBOIF.kt index 2d8002f8d21..5720fb1651c 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/CompareToBOIF.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/CompareToBOIF.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic +import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.expressions.OperatorConventions object CompareToBOIF : BinaryOperationIntrinsicFactory { @@ -66,7 +67,7 @@ object CompareToBOIF : BinaryOperationIntrinsicFactory { override fun getSupportTokens() = OperatorConventions.COMPARISON_OPERATIONS - override fun getIntrinsic(descriptor: FunctionDescriptor): BinaryOperationIntrinsic? { + override fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic? { if (descriptor.isDynamic()) return CompareToIntrinsic if (!KotlinBuiltIns.isBuiltIn(descriptor)) return null diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/EqualsBOIF.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/EqualsBOIF.kt index 032575a3942..2e8035bfee4 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/EqualsBOIF.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/EqualsBOIF.kt @@ -33,13 +33,13 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.types.isDynamic import java.util.* object EqualsBOIF : BinaryOperationIntrinsicFactory { - - private object EqualsIntrinsic : AbstractBinaryOperationIntrinsic() { override fun apply(expression: KtBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression { val isNegated = expression.isNegated() @@ -80,11 +80,11 @@ object EqualsBOIF : BinaryOperationIntrinsicFactory { } } - override fun getSupportTokens() = OperatorConventions.EQUALS_OPERATIONS + override fun getSupportTokens() = OperatorConventions.EQUALS_OPERATIONS!! - override fun getIntrinsic(descriptor: FunctionDescriptor): BinaryOperationIntrinsic? = + override fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic? = when { - DescriptorUtils.isEnumClass(descriptor.containingDeclaration) -> EnumEqualsIntrinsic + isEnumIntrinsicApplicable(descriptor, leftType, rightType) -> EnumEqualsIntrinsic KotlinBuiltIns.isBuiltIn(descriptor) || TopLevelFIF.EQUALS_IN_ANY.apply(descriptor) -> EqualsIntrinsic @@ -92,5 +92,10 @@ object EqualsBOIF : BinaryOperationIntrinsicFactory { else -> null } + private fun isEnumIntrinsicApplicable(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): Boolean { + return DescriptorUtils.isEnumClass(descriptor.containingDeclaration) && leftType != null && rightType != null && + !TypeUtils.isNullableType(leftType) && !TypeUtils.isNullableType(rightType) + } + private fun KtBinaryExpression.isNegated() = getOperationToken(this) == KtTokens.EXCLEQ } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/LongCompareToBOIF.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/LongCompareToBOIF.kt index daa43088682..33b7024e874 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/LongCompareToBOIF.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/LongCompareToBOIF.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.js.translate.operation.OperatorTable import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.* import org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.utils.identity as ID @@ -71,7 +72,7 @@ object LongCompareToBOIF : BinaryOperationIntrinsicFactory { override fun getSupportTokens() = OperatorConventions.COMPARISON_OPERATIONS - override fun getIntrinsic(descriptor: FunctionDescriptor): BinaryOperationIntrinsic? { + override fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic? { if (KotlinBuiltIns.isBuiltIn(descriptor)) { return when { FLOATING_POINT_COMPARE_TO_LONG_PATTERN.apply(descriptor) -> FLOATING_POINT_COMPARE_TO_LONG diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/binaryOperationIntrinsics.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/binaryOperationIntrinsics.kt index 023a5b5af6a..82552f3bd3d 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/binaryOperationIntrinsics.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/operation/binaryOperationIntrinsics.kt @@ -16,15 +16,15 @@ package org.jetbrains.kotlin.js.translate.intrinsic.operation +import com.google.common.collect.ImmutableSet +import com.google.dart.compiler.backend.js.ast.JsExpression import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.psi.KtBinaryExpression -import org.jetbrains.kotlin.lexer.KtToken import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression import org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken -import gnu.trove.THashMap -import com.google.dart.compiler.backend.js.ast.JsExpression -import com.google.common.collect.ImmutableSet +import org.jetbrains.kotlin.lexer.KtToken +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.types.KotlinType interface BinaryOperationIntrinsic { @@ -35,7 +35,7 @@ interface BinaryOperationIntrinsic { class BinaryOperationIntrinsics { - private val intrinsicCache = THashMap, BinaryOperationIntrinsic>() + private val intrinsicCache = mutableMapOf() private val factories = listOf(LongCompareToBOIF, EqualsBOIF, CompareToBOIF) @@ -46,22 +46,20 @@ class BinaryOperationIntrinsics { return NO_INTRINSIC } - return lookUpCache(token, descriptor) ?: computeAndCacheIntrinsic(token, descriptor) + val leftType = expression.left?.let { context.bindingContext().getType(it) } + val rightType = expression.right?.let { context.bindingContext().getType(it) } + + val key = IntrinsicKey(token, descriptor, leftType, rightType) + return intrinsicCache.getOrPut(key) { computeIntrinsic(token, descriptor, leftType, rightType) } } - private fun lookUpCache(token: KtToken, descriptor: FunctionDescriptor): BinaryOperationIntrinsic? = - intrinsicCache.get(Pair(token, descriptor)) - - private fun computeAndCacheIntrinsic(token: KtToken, descriptor: FunctionDescriptor): BinaryOperationIntrinsic { - val result = computeIntrinsic(token, descriptor) - intrinsicCache.put(Pair(token, descriptor), result) - return result - } - - private fun computeIntrinsic(token: KtToken, descriptor: FunctionDescriptor): BinaryOperationIntrinsic { + private fun computeIntrinsic( + token: KtToken, descriptor: FunctionDescriptor, + leftType: KotlinType?, rightType: KotlinType? + ): BinaryOperationIntrinsic { for (factory in factories) { if (factory.getSupportTokens().contains(token)) { - val intrinsic = factory.getIntrinsic(descriptor) + val intrinsic = factory.getIntrinsic(descriptor, leftType, rightType) if (intrinsic != null) { return intrinsic } @@ -71,11 +69,16 @@ class BinaryOperationIntrinsics { } } +private data class IntrinsicKey( + val token: KtToken, val function: FunctionDescriptor, + val leftType: KotlinType?, val rightType: KotlinType? +) + interface BinaryOperationIntrinsicFactory { fun getSupportTokens(): ImmutableSet - fun getIntrinsic(descriptor: FunctionDescriptor): BinaryOperationIntrinsic? + fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic? } abstract class AbstractBinaryOperationIntrinsic : BinaryOperationIntrinsic { diff --git a/js/js.translator/testData/box/enum/equalsNullUndefined.kt b/js/js.translator/testData/box/enum/equalsNullUndefined.kt new file mode 100644 index 00000000000..ae5b5b7a238 --- /dev/null +++ b/js/js.translator/testData/box/enum/equalsNullUndefined.kt @@ -0,0 +1,38 @@ +enum class A { + X, + Y +} + +fun getNull(): A? = null + +fun getUndefined(): A? = js("void 0") + +fun getX(): A? = A.X + +fun testGenericEquals(a: T, b: T) = a == b + +fun testGenericEqualsToNull(a: T) = a == null + +fun testGenericEqualsNotNull(a: T, b: T) = a == b + +fun box(): String { + if (getX() == getNull()) return "fail1" + if (getX() == getUndefined()) return "fail2" + if (getNull() != null) return "fail3" + if (getUndefined() != null) return "fail4" + if (getUndefined() != getNull()) return "fail5" + if (A.X == getUndefined()) return "fail6" + if (A.X == getNull()) return "fail6" + if (A.X == null) return "fail7" + if (A.X == A.Y) return "fail8" + if (A.X != getX()) return "fail9" + if (!testGenericEquals(getUndefined(), getNull())) return "fail10" + if (testGenericEqualsNotNull(A.X, A.Y)) return "fail11" + if (!testGenericEqualsNotNull(A.X, getX()!!)) return "fail12" + if (!testGenericEqualsToNull(getNull())) return "fail13" + if (!testGenericEqualsToNull(getUndefined())) return "fail14" + if (testGenericEqualsToNull(getX())) return "fail15" + + return "OK" +} +