Prohibit identity equals on inline class types

This commit is contained in:
Mikhail Zarechenskiy
2018-06-07 15:11:04 +03:00
parent 9891f562cc
commit b2d9015723
10 changed files with 91 additions and 5 deletions
@@ -744,6 +744,7 @@ public interface Errors {
DiagnosticFactory2<KtBinaryExpression, KotlinType, KotlinType> DEPRECATED_IDENTITY_EQUALS = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<KtBinaryExpression, KotlinType, KotlinType> IMPLICIT_BOXING_IN_IDENTITY_EQUALS = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<KtBinaryExpression, KotlinType, KotlinType> FORBIDDEN_IDENTITY_EQUALS = DiagnosticFactory2.create(ERROR);
DiagnosticFactory2<PsiElement, FunctionDescriptor, String> DEPRECATED_BINARY_MOD = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<PsiElement, FunctionDescriptor, String> DEPRECATED_BINARY_MOD_AS_REM = DiagnosticFactory2.create(WARNING);
@@ -393,6 +393,7 @@ public class DefaultErrorMessages {
MAP.put(DEPRECATED_IDENTITY_EQUALS, "Identity equality for arguments of types {0} and {1} is deprecated", RENDER_TYPE, RENDER_TYPE);
MAP.put(IMPLICIT_BOXING_IN_IDENTITY_EQUALS, "Identity equality for arguments of types {0} and {1} can be unstable because of implicit boxing", RENDER_TYPE, RENDER_TYPE);
MAP.put(FORBIDDEN_IDENTITY_EQUALS, "Identity equality for arguments of types {0} and {1} is forbidden", RENDER_TYPE, RENDER_TYPE);
MAP.put(DEPRECATED_BINARY_MOD, "Deprecated convention for ''{0}''. Use ''{1}''", NAME, STRING);
MAP.put(DEPRECATED_BINARY_MOD_AS_REM, "''%'' is resolved to deprecated ''{0}'' operator. Replace with ''.{0}'' or add operator ''{1}''", NAME, STRING);
@@ -39,7 +39,6 @@ import org.jetbrains.kotlin.lexer.KtKeywordToken;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
import org.jetbrains.kotlin.psi.psiUtil.ReservedCheckingKt;
import org.jetbrains.kotlin.resolve.*;
@@ -1069,7 +1068,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
ensureNonemptyIntersectionOfOperandTypes(expression, context);
// TODO : Check comparison pointlessness
result = TypeInfoFactoryKt.createTypeInfo(components.builtIns.getBooleanType(), context);
checkIdentityOnPrimitiveTypes(expression, context);
checkIdentityOnPrimitiveOrInlineClassTypes(expression, context);
}
else if (OperatorConventions.IN_OPERATIONS.contains(operationType)) {
ValueArgument leftArgument = CallMaker.makeValueArgument(left, left != null ? left : operationSign);
@@ -1091,15 +1090,23 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
return components.dataFlowAnalyzer.checkType(result, expression, contextWithExpectedType);
}
private static void checkIdentityOnPrimitiveTypes(@NotNull KtBinaryExpression expression, @NotNull ExpressionTypingContext context) {
private static void checkIdentityOnPrimitiveOrInlineClassTypes(
@NotNull KtBinaryExpression expression,
@NotNull ExpressionTypingContext context
) {
if (expression.getLeft() == null || expression.getRight() == null) return;
KotlinType leftType = context.trace.getType(expression.getLeft());
KotlinType rightType = context.trace.getType(expression.getRight());
if (leftType == null || rightType == null) return;
if (KotlinTypeChecker.DEFAULT.equalTypes(leftType, rightType) && KotlinBuiltIns.isPrimitiveType(leftType)) {
context.trace.report(DEPRECATED_IDENTITY_EQUALS.on(expression, leftType, rightType));
if (KotlinTypeChecker.DEFAULT.equalTypes(leftType, rightType)) {
if (KotlinBuiltIns.isPrimitiveType(leftType)) {
context.trace.report(DEPRECATED_IDENTITY_EQUALS.on(expression, leftType, rightType));
}
else if (InlineClassesUtilsKt.isInlineClassType(leftType)) {
context.trace.report(FORBIDDEN_IDENTITY_EQUALS.on(expression, leftType, rightType));
}
}
else if (isIdentityComparedWithImplicitBoxing(leftType, rightType) || isIdentityComparedWithImplicitBoxing(rightType, leftType)) {
context.trace.report(IMPLICIT_BOXING_IN_IDENTITY_EQUALS.on(expression, leftType, rightType));
@@ -0,0 +1,15 @@
// !LANGUAGE: +InlineClasses
// !DIAGNOSTICS: -UNUSED_VARIABLE
inline class Foo(val x: Int)
inline class Bar(val y: String)
fun test(f1: Foo, f2: Foo, b1: Bar, fn1: Foo?, fn2: Foo?) {
val a1 = <!FORBIDDEN_IDENTITY_EQUALS!>f1 === f2<!> || <!FORBIDDEN_IDENTITY_EQUALS!>f1 !== f2<!>
val a2 = <!FORBIDDEN_IDENTITY_EQUALS!>f1 === f1<!>
val a3 = <!EQUALITY_NOT_APPLICABLE!>f1 === b1<!> || <!EQUALITY_NOT_APPLICABLE!>f1 !== b1<!>
val c1 = <!FORBIDDEN_IDENTITY_EQUALS!>fn1 === fn2<!> || <!FORBIDDEN_IDENTITY_EQUALS!>fn1 !== fn2<!>
val c2 = f1 === fn1 || f1 !== fn1
val c3 = <!EQUALITY_NOT_APPLICABLE!>b1 === fn1<!> || <!EQUALITY_NOT_APPLICABLE!>b1 !== fn1<!>
}
@@ -0,0 +1,19 @@
package
public fun test(/*0*/ f1: Foo, /*1*/ f2: Foo, /*2*/ b1: Bar, /*3*/ fn1: Foo?, /*4*/ fn2: Foo?): kotlin.Unit
public final inline class Bar {
public constructor Bar(/*0*/ y: kotlin.String)
public final val y: kotlin.String
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final inline class Foo {
public constructor Foo(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,25 @@
// !LANGUAGE: +InlineClasses
// !SKIP_METADATA_VERSION_CHECK
// !DIAGNOSTICS: -UNUSED_VARIABLE
fun test(
ub1: UByte, ub2: UByte,
us1: UShort, us2: UShort,
ui1: UInt, ui2: UInt,
ul1: ULong, ul2: ULong
) {
val ub = <!FORBIDDEN_IDENTITY_EQUALS!>ub1 === ub2<!> || <!FORBIDDEN_IDENTITY_EQUALS!>ub1 !== ub2<!>
val us = <!FORBIDDEN_IDENTITY_EQUALS!>us1 === us2<!> || <!FORBIDDEN_IDENTITY_EQUALS!>us1 !== us2<!>
val ui = <!FORBIDDEN_IDENTITY_EQUALS!>ui1 === ui2<!> || <!FORBIDDEN_IDENTITY_EQUALS!>ui1 !== ui2<!>
val ul = <!FORBIDDEN_IDENTITY_EQUALS!>ul1 === ul2<!> || <!FORBIDDEN_IDENTITY_EQUALS!>ul1 !== ul2<!>
val u = <!EQUALITY_NOT_APPLICABLE!>ub1 === ul1<!>
val a1 = <!FORBIDDEN_IDENTITY_EQUALS!>1u === 2u<!> || <!FORBIDDEN_IDENTITY_EQUALS!>1u !== 2u<!>
val a2 = <!FORBIDDEN_IDENTITY_EQUALS!>0xFFFF_FFFF_FFFF_FFFFu === 0xFFFF_FFFF_FFFF_FFFFu<!>
val bu1 = 1u
val bu2 = 1u
val c1 = <!FORBIDDEN_IDENTITY_EQUALS!>bu1 === bu2<!> || <!FORBIDDEN_IDENTITY_EQUALS!>bu1 !== bu2<!>
}
@@ -0,0 +1,3 @@
package
public fun test(/*0*/ ub1: kotlin.UByte, /*1*/ ub2: kotlin.UByte, /*2*/ us1: kotlin.UShort, /*3*/ us2: kotlin.UShort, /*4*/ ui1: kotlin.UInt, /*5*/ ui2: kotlin.UInt, /*6*/ ul1: kotlin.ULong, /*7*/ ul2: kotlin.ULong): kotlin.Unit
@@ -10706,6 +10706,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
runTest("compiler/testData/diagnostics/tests/inlineClasses/basicInlineClassDeclarationDisabled.kt");
}
@TestMetadata("identityComparisonWithInlineClasses.kt")
public void testIdentityComparisonWithInlineClasses() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt");
}
@TestMetadata("inlineClassDeclarationCheck.kt")
public void testInlineClassDeclarationCheck() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/inlineClassDeclarationCheck.kt");
@@ -29,6 +29,11 @@ public class DiagnosticsWithUnsignedTypesGenerated extends AbstractDiagnosticsWi
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithUnsignedTypes"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("forbiddenEqualsOnUnsignedTypes.kt")
public void testForbiddenEqualsOnUnsignedTypes() throws Exception {
runTest("compiler/testData/diagnostics/testsWithUnsignedTypes/forbiddenEqualsOnUnsignedTypes.kt");
}
@TestMetadata("overloadResolutionOfBasicOperations.kt")
public void testOverloadResolutionOfBasicOperations() throws Exception {
runTest("compiler/testData/diagnostics/testsWithUnsignedTypes/overloadResolutionOfBasicOperations.kt");
@@ -10706,6 +10706,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
runTest("compiler/testData/diagnostics/tests/inlineClasses/basicInlineClassDeclarationDisabled.kt");
}
@TestMetadata("identityComparisonWithInlineClasses.kt")
public void testIdentityComparisonWithInlineClasses() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt");
}
@TestMetadata("inlineClassDeclarationCheck.kt")
public void testInlineClassDeclarationCheck() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/inlineClassDeclarationCheck.kt");