diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerTestUtil.java b/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerTestUtil.java index f606ba745b6..4b5b9c2c9e8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerTestUtil.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerTestUtil.java @@ -155,6 +155,12 @@ public class CheckerTestUtil { debugAnnotations.add(new DebugInfoDiagnostic(expression, DebugInfoDiagnosticFactory.SMARTCAST)); } } + //noinspection TestOnlyProblems + for (KtExpression expression : bindingContext.getSliceContents(BindingContext.IMPLICIT_RECEIVER_SMARTCAST).keySet()) { + if (PsiTreeUtil.isAncestor(root, expression, false)) { + debugAnnotations.add(new DebugInfoDiagnostic(expression, DebugInfoDiagnosticFactory.IMPLICIT_RECEIVER_SMARTCAST)); + } + } return debugAnnotations; } @@ -510,6 +516,7 @@ public class CheckerTestUtil { public static class DebugInfoDiagnosticFactory extends DiagnosticFactory { public static final DebugInfoDiagnosticFactory SMARTCAST = new DebugInfoDiagnosticFactory("SMARTCAST"); + public static final DebugInfoDiagnosticFactory IMPLICIT_RECEIVER_SMARTCAST = new DebugInfoDiagnosticFactory("IMPLICIT_RECEIVER_SMARTCAST"); public static final DebugInfoDiagnosticFactory ELEMENT_WITH_ERROR_TYPE = new DebugInfoDiagnosticFactory("ELEMENT_WITH_ERROR_TYPE"); public static final DebugInfoDiagnosticFactory UNRESOLVED_WITH_TARGET = new DebugInfoDiagnosticFactory("UNRESOLVED_WITH_TARGET"); public static final DebugInfoDiagnosticFactory MISSING_UNRESOLVED = new DebugInfoDiagnosticFactory("MISSING_UNRESOLVED"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java index e9339e21ad3..aaeb5c83e4e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java @@ -131,6 +131,7 @@ public interface BindingContext { WritableSlice> INDEXED_LVALUE_SET = Slices.createSimpleSlice(); WritableSlice SMARTCAST = Slices.createSimpleSlice(); + WritableSlice IMPLICIT_RECEIVER_SMARTCAST = Slices.createSimpleSlice(); WritableSlice EXHAUSTIVE_WHEN = Slices.createSimpleSlice(); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt index a9b3a02e449..7bca04c566b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt @@ -451,7 +451,7 @@ public class CandidateResolver( val dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiverArgument, this) val smartCastResult = SmartCastManager.checkAndRecordPossibleCast( - dataFlowValue, expectedReceiverParameterType, expression, this, /*recordType =*/ true + dataFlowValue, expectedReceiverParameterType, expression, this, candidateCall.call.calleeExpression, /*recordType =*/true ) if (smartCastResult == null) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.java index a2b3058ba83..ccab841758f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.java @@ -40,6 +40,7 @@ import java.util.List; import java.util.Set; import static org.jetbrains.kotlin.diagnostics.Errors.SMARTCAST_IMPOSSIBLE; +import static org.jetbrains.kotlin.resolve.BindingContext.IMPLICIT_RECEIVER_SMARTCAST; import static org.jetbrains.kotlin.resolve.BindingContext.SMARTCAST; public class SmartCastManager { @@ -174,6 +175,7 @@ public class SmartCastManager { @NotNull KotlinType expectedType, @Nullable KtExpression expression, @NotNull ResolutionContext c, + @Nullable KtExpression calleeExpression, boolean recordExpressionType ) { for (KotlinType possibleType : c.dataFlowInfo.getPossibleTypes(dataFlowValue)) { @@ -181,6 +183,9 @@ public class SmartCastManager { if (expression != null) { recordCastOrError(expression, possibleType, c.trace, dataFlowValue, recordExpressionType); } + else if (calleeExpression != null && dataFlowValue.isPredictable()) { + c.trace.record(IMPLICIT_RECEIVER_SMARTCAST, calleeExpression, possibleType); + } return new SmartCastResult(possibleType, dataFlowValue.isPredictable()); } } @@ -211,7 +216,7 @@ public class SmartCastManager { return new SmartCastResult(dataFlowValue.getType(), immanentlyNotNull || dataFlowValue.isPredictable()); } - return checkAndRecordPossibleCast(dataFlowValue, nullableExpectedType, expression, c, recordExpressionType); + return checkAndRecordPossibleCast(dataFlowValue, nullableExpectedType, expression, c, calleeExpression, recordExpressionType); } return null; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java index 7693949b816..5ac5e896d7c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java @@ -252,7 +252,7 @@ public class DataFlowAnalyzer { ) { DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, expressionType, c); - SmartCastResult result = SmartCastManager.checkAndRecordPossibleCast(dataFlowValue, c.expectedType, expression, c, false); + SmartCastResult result = SmartCastManager.checkAndRecordPossibleCast(dataFlowValue, c.expectedType, expression, c, null, false); return result != null ? result.getResultType() : null; } diff --git a/compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.kt b/compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.kt index 5781f113101..c2a338f311e 100644 --- a/compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.kt +++ b/compiler/testData/diagnostics/tests/generics/nullability/smartCastsOnThis.kt @@ -25,7 +25,7 @@ fun T.foo() { length if (this is String) { - length + length this?.length bar1() diff --git a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt2109.kt b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt2109.kt index 290fbb84fe4..66a110f65c6 100644 --- a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt2109.kt +++ b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt2109.kt @@ -9,7 +9,7 @@ fun A?.bar() { if (this == null) { return } - foo() + foo() } fun A.baz() { diff --git a/compiler/testData/diagnostics/tests/smartCasts/falseReceiverSmartCast.kt b/compiler/testData/diagnostics/tests/smartCasts/falseReceiverSmartCast.kt new file mode 100644 index 00000000000..1909527cc42 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/falseReceiverSmartCast.kt @@ -0,0 +1,15 @@ +open class SuperFoo { + public fun bar(): String { + if (this is Foo) { + superFoo() + return baz() + } + return baz() + } + + public fun baz() = "OK" +} + +class Foo : SuperFoo() { + public fun superFoo() {} +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/falseReceiverSmartCast.txt b/compiler/testData/diagnostics/tests/smartCasts/falseReceiverSmartCast.txt new file mode 100644 index 00000000000..06f03b27aa3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/falseReceiverSmartCast.txt @@ -0,0 +1,20 @@ +package + +public final class Foo : SuperFoo { + public constructor Foo() + public final override /*1*/ /*fake_override*/ fun bar(): kotlin.String + public final override /*1*/ /*fake_override*/ fun baz(): 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 final fun superFoo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class SuperFoo { + public constructor SuperFoo() + public final fun bar(): kotlin.String + public final fun baz(): 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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt b/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt new file mode 100644 index 00000000000..73d9021af1e --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt @@ -0,0 +1,24 @@ +open class A { + class B : A() { + val a = "FAIL" + } + + fun foo(): String { + if (this is B) return a + return "OK" + } +} + +fun A?.bar() { + if (this != null) foo() +} + +fun A.gav() = if (this is A.B) a else "" + +class C { + fun A?.complex(): String { + if (this is A.B) return a + else if (this != null) return foo() + else return "" + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.txt b/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.txt new file mode 100644 index 00000000000..bc01f6a4fa8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.txt @@ -0,0 +1,29 @@ +package + +public fun A?.bar(): kotlin.Unit +public fun A.gav(): kotlin.String + +public open class A { + public constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class B : A { + public constructor B() + public final val a: kotlin.String = "FAIL" + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun foo(): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public final class C { + public constructor C() + 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 fun A?.complex(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/implicitToGrandSon.kt b/compiler/testData/diagnostics/tests/smartCasts/implicitToGrandSon.kt new file mode 100644 index 00000000000..8f3898da7b1 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/implicitToGrandSon.kt @@ -0,0 +1,11 @@ +open class A { + open fun foo() = "FAIL" + + fun bar() = if (this is C) foo() else "FAIL" +} + +open class B : A() + +open class C : B() { + override fun foo() = "OK" +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/implicitToGrandSon.txt b/compiler/testData/diagnostics/tests/smartCasts/implicitToGrandSon.txt new file mode 100644 index 00000000000..49aa4e6ecfe --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/implicitToGrandSon.txt @@ -0,0 +1,28 @@ +package + +public open class A { + public constructor A() + public final fun bar(): kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open fun foo(): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class B : A { + public constructor B() + public final override /*1*/ /*fake_override*/ fun bar(): kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun foo(): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class C : B { + public constructor C() + public final override /*1*/ /*fake_override*/ fun bar(): kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ fun foo(): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/SmartCastImplicitReceiver.kt b/compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/SmartCastImplicitReceiver.kt index 7da8bd686ca..86d3ba4110e 100644 --- a/compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/SmartCastImplicitReceiver.kt +++ b/compiler/testData/diagnostics/tests/syntheticExtensions/javaProperties/SmartCastImplicitReceiver.kt @@ -1,8 +1,8 @@ // FILE: KotlinFile.kt fun Any.foo(): Int { if (this is JavaClass) { - something++ - return x + something++ + return x } return 0 } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 0f835af3065..cb66f4fa937 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -14586,6 +14586,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("falseReceiverSmartCast.kt") + public void testFalseReceiverSmartCast() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/falseReceiverSmartCast.kt"); + doTest(fileName); + } + @TestMetadata("fieldExclExcl.kt") public void testFieldExclExcl() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/fieldExclExcl.kt"); @@ -14604,6 +14610,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("implicitReceiver.kt") + public void testImplicitReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt"); + doTest(fileName); + } + + @TestMetadata("implicitToGrandSon.kt") + public void testImplicitToGrandSon() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/implicitToGrandSon.kt"); + doTest(fileName); + } + @TestMetadata("incDecToNull.kt") public void testIncDecToNull() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/incDecToNull.kt"); diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.java index 5e12caa4fb5..a1942d45cb8 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.java +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.java @@ -74,6 +74,11 @@ class VariablesHighlightingVisitor extends AfterAnalysisHighlightingVisitor { holder.createInfoAnnotation(expression, "Smart cast to " + DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast)).setTextAttributes( KotlinHighlightingColors.SMART_CAST_VALUE); } + smartCast = bindingContext.get(IMPLICIT_RECEIVER_SMARTCAST, expression); + if (smartCast != null) { + holder.createInfoAnnotation(expression, "Implicit receiver smart cast to " + DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast)).setTextAttributes( + KotlinHighlightingColors.SMART_CAST_VALUE); + } super.visitExpression(expression); } diff --git a/idea/testData/checker/infos/SmartCasts.kt b/idea/testData/checker/infos/SmartCasts.kt index 6574dd64507..4f0ec598eae 100644 --- a/idea/testData/checker/infos/SmartCasts.kt +++ b/idea/testData/checker/infos/SmartCasts.kt @@ -1,5 +1,9 @@ open class A() { - fun foo() {} + fun foo() { + if (this is B) { + bar() + } + } } class B() : A() {