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
|
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
|
* 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)
|
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 builder = Maps.newHashMap<DataFlowValue, Nullability>()
|
||||||
val nullabilityOfA = getPredictableNullability(a)
|
val nullabilityOfA = getPredictableNullability(a)
|
||||||
val nullabilityOfB = getPredictableNullability(b)
|
val nullabilityOfB = getPredictableNullability(b)
|
||||||
@@ -159,19 +159,22 @@ internal class DelegatingDataFlowInfo private constructor(
|
|||||||
var changed = putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB)) or
|
var changed = putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB)) or
|
||||||
putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA))
|
putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA))
|
||||||
|
|
||||||
|
// NB: == has no guarantees of type equality, see KT-11280 for the example
|
||||||
val newTypeInfo = newTypeInfo()
|
val newTypeInfo = newTypeInfo()
|
||||||
newTypeInfo.putAll(a, getPredictableTypes(b, false))
|
if (sameTypes) {
|
||||||
newTypeInfo.putAll(b, getPredictableTypes(a, false))
|
newTypeInfo.putAll(a, getPredictableTypes(b, false))
|
||||||
if (a.type != b.type) {
|
newTypeInfo.putAll(b, getPredictableTypes(a, false))
|
||||||
// To avoid recording base types of own type
|
if (a.type != b.type) {
|
||||||
if (!a.type.isSubtypeOf(b.type)) {
|
// To avoid recording base types of own type
|
||||||
newTypeInfo.put(a, b.type)
|
if (!a.type.isSubtypeOf(b.type)) {
|
||||||
}
|
newTypeInfo.put(a, b.type)
|
||||||
if (!b.type.isSubtypeOf(a.type)) {
|
}
|
||||||
newTypeInfo.put(b, a.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) {
|
return if (!changed) {
|
||||||
this
|
this
|
||||||
|
|||||||
@@ -22,20 +22,25 @@ import com.intellij.psi.tree.IElementType;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||||
|
import org.jetbrains.kotlin.descriptors.*;
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtilsKt;
|
import org.jetbrains.kotlin.diagnostics.DiagnosticUtilsKt;
|
||||||
|
import org.jetbrains.kotlin.incremental.KotlinLookupLocation;
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens;
|
import org.jetbrains.kotlin.lexer.KtTokens;
|
||||||
import org.jetbrains.kotlin.psi.*;
|
import org.jetbrains.kotlin.psi.*;
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace;
|
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.checkers.AdditionalTypeChecker;
|
||||||
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext;
|
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext;
|
||||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.*;
|
import org.jetbrains.kotlin.resolve.calls.smartcasts.*;
|
||||||
import org.jetbrains.kotlin.resolve.constants.*;
|
import org.jetbrains.kotlin.resolve.constants.*;
|
||||||
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator;
|
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator;
|
||||||
import org.jetbrains.kotlin.types.KotlinType;
|
import org.jetbrains.kotlin.types.KotlinType;
|
||||||
|
import org.jetbrains.kotlin.types.TypeConstructor;
|
||||||
import org.jetbrains.kotlin.types.TypeUtils;
|
import org.jetbrains.kotlin.types.TypeUtils;
|
||||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
||||||
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt;
|
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt;
|
||||||
|
import org.jetbrains.kotlin.util.OperatorNameConventions;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
@@ -65,9 +70,52 @@ public class DataFlowAnalyzer {
|
|||||||
this.facade = facade;
|
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
|
@NotNull
|
||||||
public DataFlowInfo extractDataFlowInfoFromCondition(
|
public DataFlowInfo extractDataFlowInfoFromCondition(
|
||||||
@Nullable KtExpression condition,
|
@Nullable final KtExpression condition,
|
||||||
final boolean conditionValue,
|
final boolean conditionValue,
|
||||||
final ExpressionTypingContext context
|
final ExpressionTypingContext context
|
||||||
) {
|
) {
|
||||||
@@ -126,7 +174,9 @@ public class DataFlowAnalyzer {
|
|||||||
}
|
}
|
||||||
if (equals != null) {
|
if (equals != null) {
|
||||||
if (equals == conditionValue) { // this means: equals && conditionValue || !equals && !conditionValue
|
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 {
|
else {
|
||||||
result.set(context.dataFlowInfo.disequate(leftValue, rightValue).and(expressionFlowInfo));
|
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 expressionDataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, newContext)
|
||||||
val result = noChange(newContext)
|
val result = noChange(newContext)
|
||||||
return ConditionalDataFlowInfo(
|
return ConditionalDataFlowInfo(
|
||||||
result.thenInfo.equate(subjectDataFlowValue, expressionDataFlowValue),
|
result.thenInfo.equate(subjectDataFlowValue, expressionDataFlowValue,
|
||||||
|
DataFlowAnalyzer.typeHasEqualsFromAny(subjectType, expression)),
|
||||||
result.elseInfo.disequate(subjectDataFlowValue, expressionDataFlowValue))
|
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 x: Any? = null
|
||||||
val y = 2
|
val y = 2
|
||||||
if (x == y) {
|
if (x == y) {
|
||||||
return <!DEBUG_INFO_SMARTCAST!>x<!> + y
|
return x <!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>+<!> y
|
||||||
}
|
}
|
||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
@@ -16170,6 +16170,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("falseReceiverSmartCast.kt")
|
||||||
public void testFalseReceiverSmartCast() throws Exception {
|
public void testFalseReceiverSmartCast() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/falseReceiverSmartCast.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/falseReceiverSmartCast.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user