Values equality no more provides guarantee for their types equality (for open types or types with overridden equals) #KT-11280 Fixed
This commit is contained in:
@@ -70,9 +70,10 @@ interface DataFlowInfo {
|
||||
fun assign(a: DataFlowValue, b: DataFlowValue): DataFlowInfo
|
||||
|
||||
/**
|
||||
* Call this function when it's known than a == b
|
||||
* Call this function when it's known than a == b.
|
||||
* sameTypes should be true iff we have guarantee that a and b have the same type
|
||||
*/
|
||||
fun equate(a: DataFlowValue, b: DataFlowValue): DataFlowInfo
|
||||
fun equate(a: DataFlowValue, b: DataFlowValue, sameTypes: Boolean): DataFlowInfo
|
||||
|
||||
/**
|
||||
* Call this function when it's known than a != b
|
||||
|
||||
+14
-11
@@ -151,7 +151,7 @@ internal class DelegatingDataFlowInfo private constructor(
|
||||
return create(this, ImmutableMap.copyOf(nullability), if (newTypeInfo.isEmpty) EMPTY_TYPE_INFO else newTypeInfo, a)
|
||||
}
|
||||
|
||||
override fun equate(a: DataFlowValue, b: DataFlowValue): DataFlowInfo {
|
||||
override fun equate(a: DataFlowValue, b: DataFlowValue, sameTypes: Boolean): DataFlowInfo {
|
||||
val builder = Maps.newHashMap<DataFlowValue, Nullability>()
|
||||
val nullabilityOfA = getPredictableNullability(a)
|
||||
val nullabilityOfB = getPredictableNullability(b)
|
||||
@@ -159,19 +159,22 @@ internal class DelegatingDataFlowInfo private constructor(
|
||||
var changed = putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB)) or
|
||||
putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA))
|
||||
|
||||
// NB: == has no guarantees of type equality, see KT-11280 for the example
|
||||
val newTypeInfo = newTypeInfo()
|
||||
newTypeInfo.putAll(a, getPredictableTypes(b, false))
|
||||
newTypeInfo.putAll(b, getPredictableTypes(a, false))
|
||||
if (a.type != b.type) {
|
||||
// To avoid recording base types of own type
|
||||
if (!a.type.isSubtypeOf(b.type)) {
|
||||
newTypeInfo.put(a, b.type)
|
||||
}
|
||||
if (!b.type.isSubtypeOf(a.type)) {
|
||||
newTypeInfo.put(b, a.type)
|
||||
if (sameTypes) {
|
||||
newTypeInfo.putAll(a, getPredictableTypes(b, false))
|
||||
newTypeInfo.putAll(b, getPredictableTypes(a, false))
|
||||
if (a.type != b.type) {
|
||||
// To avoid recording base types of own type
|
||||
if (!a.type.isSubtypeOf(b.type)) {
|
||||
newTypeInfo.put(a, b.type)
|
||||
}
|
||||
if (!b.type.isSubtypeOf(a.type)) {
|
||||
newTypeInfo.put(b, a.type)
|
||||
}
|
||||
}
|
||||
changed = changed or !newTypeInfo.isEmpty
|
||||
}
|
||||
changed = changed or !newTypeInfo.isEmpty
|
||||
|
||||
return if (!changed) {
|
||||
this
|
||||
|
||||
@@ -22,20 +22,25 @@ import com.intellij.psi.tree.IElementType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtilsKt;
|
||||
import org.jetbrains.kotlin.incremental.KotlinLookupLocation;
|
||||
import org.jetbrains.kotlin.lexer.KtTokens;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace;
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker;
|
||||
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext;
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.*;
|
||||
import org.jetbrains.kotlin.resolve.constants.*;
|
||||
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator;
|
||||
import org.jetbrains.kotlin.types.KotlinType;
|
||||
import org.jetbrains.kotlin.types.TypeConstructor;
|
||||
import org.jetbrains.kotlin.types.TypeUtils;
|
||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
||||
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt;
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -65,9 +70,52 @@ public class DataFlowAnalyzer {
|
||||
this.facade = facade;
|
||||
}
|
||||
|
||||
// NB: use this method only for functions from 'Any'
|
||||
@Nullable
|
||||
private static FunctionDescriptor getOverriddenDescriptorFromClass(@NotNull FunctionDescriptor descriptor) {
|
||||
if (descriptor.getKind() != CallableMemberDescriptor.Kind.FAKE_OVERRIDE) return descriptor;
|
||||
Collection<? extends FunctionDescriptor> overriddenDescriptors = descriptor.getOverriddenDescriptors();
|
||||
if (overriddenDescriptors.isEmpty()) return descriptor;
|
||||
for (FunctionDescriptor overridden : overriddenDescriptors) {
|
||||
DeclarationDescriptor containingDeclaration = overridden.getContainingDeclaration();
|
||||
if (DescriptorUtils.isClass(containingDeclaration) || DescriptorUtils.isObject(containingDeclaration)) {
|
||||
// Exactly one class should exist in the list
|
||||
return getOverriddenDescriptorFromClass(overridden);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean typeHasOverriddenEquals(@NotNull KotlinType type, @NotNull KtElement lookupElement) {
|
||||
Collection<FunctionDescriptor> members = type.getMemberScope().getContributedFunctions(
|
||||
OperatorNameConventions.EQUALS, new KotlinLookupLocation(lookupElement));
|
||||
for (FunctionDescriptor member : members) {
|
||||
KotlinType returnType = member.getReturnType();
|
||||
if (returnType == null || !KotlinBuiltIns.isBoolean(returnType)) continue;
|
||||
if (member.getValueParameters().size() != 1) continue;
|
||||
KotlinType parameterType = member.getValueParameters().iterator().next().getType();
|
||||
if (!KotlinBuiltIns.isNullableAny(parameterType)) continue;
|
||||
FunctionDescriptor fromSuperClass = getOverriddenDescriptorFromClass(member);
|
||||
if (fromSuperClass == null) return false;
|
||||
ClassifierDescriptor superClassDescriptor = (ClassifierDescriptor) fromSuperClass.getContainingDeclaration();
|
||||
// We should have override fun in class other than Any (to prove unknown behaviour)
|
||||
return !KotlinBuiltIns.isAnyOrNullableAny(superClassDescriptor.getDefaultType());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true if we can prove that 'type' has equals method from 'Any' base type
|
||||
public static boolean typeHasEqualsFromAny(@NotNull KotlinType type, @NotNull KtElement lookupElement) {
|
||||
TypeConstructor constructor = type.getConstructor();
|
||||
// Subtypes can override equals for non-final types
|
||||
if (!constructor.isFinal()) return false;
|
||||
// check whether 'equals' is overriden
|
||||
return !typeHasOverriddenEquals(type, lookupElement);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DataFlowInfo extractDataFlowInfoFromCondition(
|
||||
@Nullable KtExpression condition,
|
||||
@Nullable final KtExpression condition,
|
||||
final boolean conditionValue,
|
||||
final ExpressionTypingContext context
|
||||
) {
|
||||
@@ -126,7 +174,9 @@ public class DataFlowAnalyzer {
|
||||
}
|
||||
if (equals != null) {
|
||||
if (equals == conditionValue) { // this means: equals && conditionValue || !equals && !conditionValue
|
||||
result.set(context.dataFlowInfo.equate(leftValue, rightValue).and(expressionFlowInfo));
|
||||
boolean byIdentity = operationToken == KtTokens.EQEQEQ || operationToken == KtTokens.EXCLEQEQEQ ||
|
||||
typeHasEqualsFromAny(lhsType, condition);
|
||||
result.set(context.dataFlowInfo.equate(leftValue, rightValue, byIdentity).and(expressionFlowInfo));
|
||||
}
|
||||
else {
|
||||
result.set(context.dataFlowInfo.disequate(leftValue, rightValue).and(expressionFlowInfo));
|
||||
|
||||
+2
-1
@@ -335,7 +335,8 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
|
||||
val expressionDataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, newContext)
|
||||
val result = noChange(newContext)
|
||||
return ConditionalDataFlowInfo(
|
||||
result.thenInfo.equate(subjectDataFlowValue, expressionDataFlowValue),
|
||||
result.thenInfo.equate(subjectDataFlowValue, expressionDataFlowValue,
|
||||
DataFlowAnalyzer.typeHasEqualsFromAny(subjectType, expression)),
|
||||
result.elseInfo.disequate(subjectDataFlowValue, expressionDataFlowValue))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
abstract class Base {
|
||||
override fun equals(other: Any?) = other is Base
|
||||
}
|
||||
|
||||
class Derived1 : Base() {
|
||||
fun foo() {}
|
||||
}
|
||||
|
||||
class Derived2 : Base()
|
||||
|
||||
fun check(x1: Derived1, x: Base) {
|
||||
if (x1 == x) {
|
||||
// Smart cast here will provoke CCA
|
||||
x.<!UNRESOLVED_REFERENCE!>foo<!>()
|
||||
}
|
||||
if (x1 === x) {
|
||||
// OK
|
||||
<!DEBUG_INFO_SMARTCAST!>x<!>.foo()
|
||||
}
|
||||
if (x1 !== x) {} else {
|
||||
// OK
|
||||
<!DEBUG_INFO_SMARTCAST!>x<!>.foo()
|
||||
}
|
||||
}
|
||||
|
||||
class FinalClass { // <-- 'equals' on instances of this class is useful for smart casts
|
||||
fun use() {}
|
||||
|
||||
fun equals(x: Int): Boolean = x > 42
|
||||
}
|
||||
|
||||
fun foo(x: FinalClass?, y: Any) {
|
||||
if (x == y) {
|
||||
// OK
|
||||
<!DEBUG_INFO_SMARTCAST!>x<!>.hashCode()
|
||||
// OK
|
||||
<!DEBUG_INFO_SMARTCAST!>y<!>.use()
|
||||
}
|
||||
when (x) {
|
||||
// OK (equals from FinalClass)
|
||||
y -> <!DEBUG_INFO_SMARTCAST!>y<!>.use()
|
||||
}
|
||||
when (y) {
|
||||
// ERROR (equals from Any)
|
||||
x -> y.<!UNRESOLVED_REFERENCE!>use<!>()
|
||||
}
|
||||
}
|
||||
|
||||
open class OpenClass {
|
||||
override fun equals(other: Any?) = other is OpenClass
|
||||
}
|
||||
|
||||
interface Dummy // should not influence anything
|
||||
|
||||
class FinalClass2 : Dummy, OpenClass() { // but here not
|
||||
fun use() {}
|
||||
}
|
||||
|
||||
fun bar(x: FinalClass2?, y: Any) {
|
||||
if (x == y) {
|
||||
// OK
|
||||
<!DEBUG_INFO_SMARTCAST!>x<!>.hashCode()
|
||||
// ERROR
|
||||
y.<!UNRESOLVED_REFERENCE!>use<!>()
|
||||
}
|
||||
}
|
||||
|
||||
open class OpenClass2 // and here too
|
||||
|
||||
fun bar(x: OpenClass2?, y: Any) {
|
||||
if (x == y) {
|
||||
// OK
|
||||
<!DEBUG_INFO_SMARTCAST!>x<!>.hashCode()
|
||||
// ERROR
|
||||
y.<!UNRESOLVED_REFERENCE!>use<!>()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Sealed {
|
||||
override fun equals(other: Any?) = other is Sealed
|
||||
|
||||
class Sealed1 : Sealed() {
|
||||
fun gav() {}
|
||||
}
|
||||
|
||||
object Sealed2 : Sealed()
|
||||
|
||||
fun check(arg: Sealed1) {
|
||||
if (arg == this) {
|
||||
// Smart cast here will provoke CCA
|
||||
this.<!UNRESOLVED_REFERENCE!>gav<!>()
|
||||
<!UNRESOLVED_REFERENCE!>gav<!>()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package
|
||||
|
||||
public fun bar(/*0*/ x: FinalClass2?, /*1*/ y: kotlin.Any): kotlin.Unit
|
||||
public fun bar(/*0*/ x: OpenClass2?, /*1*/ y: kotlin.Any): kotlin.Unit
|
||||
public fun check(/*0*/ x1: Derived1, /*1*/ x: Base): kotlin.Unit
|
||||
public fun foo(/*0*/ x: FinalClass?, /*1*/ y: kotlin.Any): kotlin.Unit
|
||||
|
||||
public abstract class Base {
|
||||
public constructor Base()
|
||||
public open override /*1*/ 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 class Derived1 : Base {
|
||||
public constructor Derived1()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final fun foo(): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public final class Derived2 : Base {
|
||||
public constructor Derived2()
|
||||
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 interface Dummy {
|
||||
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 class FinalClass {
|
||||
public constructor FinalClass()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final fun equals(/*0*/ x: kotlin.Int): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
public final fun use(): kotlin.Unit
|
||||
}
|
||||
|
||||
public final class FinalClass2 : Dummy, OpenClass {
|
||||
public constructor FinalClass2()
|
||||
public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
public final fun use(): kotlin.Unit
|
||||
}
|
||||
|
||||
public open class OpenClass {
|
||||
public constructor OpenClass()
|
||||
public open override /*1*/ 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 open class OpenClass2 {
|
||||
public constructor OpenClass2()
|
||||
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 sealed class Sealed {
|
||||
private constructor Sealed()
|
||||
public final fun check(/*0*/ arg: Sealed.Sealed1): kotlin.Unit
|
||||
public open override /*1*/ 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 class Sealed1 : Sealed {
|
||||
public constructor Sealed1()
|
||||
public final override /*1*/ /*fake_override*/ fun check(/*0*/ arg: Sealed.Sealed1): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final fun gav(): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public object Sealed2 : Sealed {
|
||||
private constructor Sealed2()
|
||||
public final override /*1*/ /*fake_override*/ fun check(/*0*/ arg: Sealed.Sealed1): kotlin.Unit
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ fun foo(): Int {
|
||||
val x: Any? = null
|
||||
val y = 2
|
||||
if (x == y) {
|
||||
return <!DEBUG_INFO_SMARTCAST!>x<!> + y
|
||||
return x <!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>+<!> y
|
||||
}
|
||||
return y
|
||||
}
|
||||
@@ -16170,6 +16170,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("fakeSmartCastOnEquality.kt")
|
||||
public void testFakeSmartCastOnEquality() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/fakeSmartCastOnEquality.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("falseReceiverSmartCast.kt")
|
||||
public void testFalseReceiverSmartCast() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/falseReceiverSmartCast.kt");
|
||||
|
||||
Reference in New Issue
Block a user