JS: don't use strict comparison (===) for enums when both left-hand side and right-hand side are nullable. See KT-12598

This commit is contained in:
Alexey Andreev
2016-12-19 12:15:03 +03:00
parent c285e5ba3b
commit e02d281ed4
6 changed files with 80 additions and 26 deletions
@@ -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");
@@ -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
@@ -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
}
@@ -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
@@ -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<Pair<KtToken, FunctionDescriptor>, BinaryOperationIntrinsic>()
private val intrinsicCache = mutableMapOf<IntrinsicKey, BinaryOperationIntrinsic>()
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<out KtToken>
fun getIntrinsic(descriptor: FunctionDescriptor): BinaryOperationIntrinsic?
fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic?
}
abstract class AbstractBinaryOperationIntrinsic : BinaryOperationIntrinsic {
@@ -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 <T : A?> testGenericEquals(a: T, b: T) = a == b
fun <T : A?> testGenericEqualsToNull(a: T) = a == null
fun <T : A> 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"
}