From cbcef67d82134027e9508030273dffd2d6e7387b Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Mon, 15 Aug 2016 17:17:18 +0300 Subject: [PATCH] KT-13426: store map from ImplicitReceiver into KotlinType for implicit receiver smart casts --- .../kotlin/resolve/BindingContext.java | 3 +- .../calls/smartcasts/ImplicitSmartCasts.kt | 26 ++++++++++ .../calls/smartcasts/SmartCastManager.kt | 14 +++-- .../tests/smartCasts/implicitReceiver.kt | 5 ++ .../tests/smartCasts/implicitReceiver.txt | 9 ++++ .../smartCasts/threeImplicitReceivers.kt | 27 ++++++++++ .../smartCasts/threeImplicitReceivers.txt | 51 +++++++++++++++++++ .../tests/smartCasts/twoImplicitReceivers.kt | 18 +++++++ .../tests/smartCasts/twoImplicitReceivers.txt | 30 +++++++++++ .../checkers/DiagnosticsTestGenerated.java | 12 +++++ .../VariablesHighlightingVisitor.kt | 15 ++++-- .../extractFunctionForDebuggerUtil.kt | 2 +- .../checker/infos/threeImplicitReceivers.kt | 29 +++++++++++ .../checker/infos/twoImplicitReceivers.kt | 20 ++++++++ .../checkers/PsiCheckerTestGenerated.java | 12 +++++ 15 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/ImplicitSmartCasts.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/threeImplicitReceivers.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/threeImplicitReceivers.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/twoImplicitReceivers.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/twoImplicitReceivers.txt create mode 100644 idea/testData/checker/infos/threeImplicitReceivers.kt create mode 100644 idea/testData/checker/infos/twoImplicitReceivers.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java index 3c4ef3338ce..4e8ff281346 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemCompleter; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue; +import org.jetbrains.kotlin.resolve.calls.smartcasts.ImplicitSmartCasts; import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant; import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics; import org.jetbrains.kotlin.resolve.scopes.LexicalScope; @@ -144,7 +145,7 @@ public interface BindingContext { WritableSlice SMARTCAST = Slices.createSimpleSlice(); WritableSlice SMARTCAST_NULL = Slices.createSimpleSlice(); - WritableSlice IMPLICIT_RECEIVER_SMARTCAST = Slices.createSimpleSlice(); + WritableSlice IMPLICIT_RECEIVER_SMARTCAST = new BasicWritableSlice(DO_NOTHING); WritableSlice EXHAUSTIVE_WHEN = Slices.createSimpleSlice(); WritableSlice IMPLICIT_EXHAUSTIVE_WHEN = Slices.createSimpleSlice(); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/ImplicitSmartCasts.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/ImplicitSmartCasts.kt new file mode 100644 index 00000000000..43503036c2c --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/ImplicitSmartCasts.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.resolve.calls.smartcasts + +import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver +import org.jetbrains.kotlin.types.KotlinType + +data class ImplicitSmartCasts private constructor(val receiverTypes: Map) { + operator fun plus(other: ImplicitSmartCasts) = ImplicitSmartCasts(receiverTypes + other.receiverTypes) + + constructor(receiver: ImplicitReceiver, type: KotlinType): this(mapOf(receiver to type)) +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt index 895ff44e502..493caa1f584 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt @@ -20,12 +20,13 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Errors.SMARTCAST_IMPOSSIBLE import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.BindingContext.IMPLICIT_RECEIVER_SMARTCAST -import org.jetbrains.kotlin.resolve.BindingContext.SMARTCAST +import org.jetbrains.kotlin.resolve.BindingContext.* import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.calls.ArgumentTypeResolver import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext +import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeIntersector @@ -155,7 +156,14 @@ class SmartCastManager { recordCastOrError(expression, possibleType, c.trace, dataFlowValue, recordExpressionType) } else if (calleeExpression != null && dataFlowValue.isStable) { - c.trace.record(IMPLICIT_RECEIVER_SMARTCAST, calleeExpression, possibleType) + val receiver = (dataFlowValue.identifierInfo as? IdentifierInfo.Receiver)?.value + if (receiver is ImplicitReceiver) { + val oldSmartCasts = c.trace[IMPLICIT_RECEIVER_SMARTCAST, calleeExpression] + val newSmartCasts = ImplicitSmartCasts(receiver, possibleType) + c.trace.record(IMPLICIT_RECEIVER_SMARTCAST, calleeExpression, + oldSmartCasts?.let { it + newSmartCasts } ?: newSmartCasts) + + } } return SmartCastResult(possibleType, dataFlowValue.isStable) } diff --git a/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt b/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt index 73d9021af1e..7f99e077f24 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt @@ -3,8 +3,13 @@ open class A { val a = "FAIL" } + class C : A() { + val a = "FATAL" + } + fun foo(): String { if (this is B) return a + else if (this is C) return a return "OK" } } diff --git a/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.txt b/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.txt index bc01f6a4fa8..d8641e4f161 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.txt +++ b/compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.txt @@ -18,6 +18,15 @@ public open class A { public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } + + public final class C : A { + public constructor C() + public final val a: kotlin.String = "FATAL" + 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 { diff --git a/compiler/testData/diagnostics/tests/smartCasts/threeImplicitReceivers.kt b/compiler/testData/diagnostics/tests/smartCasts/threeImplicitReceivers.kt new file mode 100644 index 00000000000..026ca185916 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/threeImplicitReceivers.kt @@ -0,0 +1,27 @@ +interface IA +interface IB +interface IC + +object Host : IB + +object Prop : IA { + val Host.foo: Callee get() = Callee +} + +object Callee + +object Invoke : IC { + operator fun Callee.invoke() { } +} + +fun test(a: IA, b: IB, c: IC) { + with(a) lambdaA@{ + with(b) lambdaB@{ + with(c) lambdaC@{ + if (this@lambdaA is Prop && this@lambdaB is Host && this@lambdaC is Invoke) { + foo() + } + } + } + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/threeImplicitReceivers.txt b/compiler/testData/diagnostics/tests/smartCasts/threeImplicitReceivers.txt new file mode 100644 index 00000000000..003c54be081 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/threeImplicitReceivers.txt @@ -0,0 +1,51 @@ +package + +public fun test(/*0*/ a: IA, /*1*/ b: IB, /*2*/ c: IC): kotlin.Unit + +public object Callee { + private constructor Callee() + 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 object Host : IB { + private constructor Host() + 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 IA { + 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 IB { + 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 IC { + 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 object Invoke : IC { + private constructor Invoke() + 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 operator fun Callee.invoke(): kotlin.Unit +} + +public object Prop : IA { + private constructor Prop() + public final val Host.foo: Callee + 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/twoImplicitReceivers.kt b/compiler/testData/diagnostics/tests/smartCasts/twoImplicitReceivers.kt new file mode 100644 index 00000000000..ae2726e2b64 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/twoImplicitReceivers.kt @@ -0,0 +1,18 @@ +interface IA +interface IB + +object A : IA { + fun B.foo() { } +} + +object B : IB + +fun test(a: IA, b: IB) { + with(a) lambda1@{ + with(b) lambda2@{ + if (this@lambda1 is A && this@lambda2 is B) { + foo() + } + } + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/twoImplicitReceivers.txt b/compiler/testData/diagnostics/tests/smartCasts/twoImplicitReceivers.txt new file mode 100644 index 00000000000..9523fd7a010 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/twoImplicitReceivers.txt @@ -0,0 +1,30 @@ +package + +public fun test(/*0*/ a: IA, /*1*/ b: IB): kotlin.Unit + +public object A : IA { + private constructor A() + 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 B.foo(): kotlin.Unit +} + +public object B : IB { + private constructor B() + 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 IA { + 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 IB { + 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/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index a9d696861d4..3bb57c44253 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -17661,6 +17661,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("threeImplicitReceivers.kt") + public void testThreeImplicitReceivers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/threeImplicitReceivers.kt"); + doTest(fileName); + } + + @TestMetadata("twoImplicitReceivers.kt") + public void testTwoImplicitReceivers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/twoImplicitReceivers.kt"); + doTest(fileName); + } + @TestMetadata("typeDegradation.kt") public void testTypeDegradation() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/typeDegradation.kt"); diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.kt index 536abfbe1f7..80e556bed87 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.kt @@ -29,6 +29,8 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext.* import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic +import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver +import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver import org.jetbrains.kotlin.types.expressions.CaptureKind internal class VariablesHighlightingVisitor(holder: AnnotationHolder, bindingContext: BindingContext) @@ -79,9 +81,16 @@ internal class VariablesHighlightingVisitor(holder: AnnotationHolder, bindingCon override fun visitExpression(expression: KtExpression) { val implicitSmartCast = bindingContext.get(IMPLICIT_RECEIVER_SMARTCAST, expression) if (implicitSmartCast != null) { - holder.createInfoAnnotation(expression, - "Implicit receiver smart cast to " + DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(implicitSmartCast)) - .textAttributes = SMART_CAST_RECEIVER + for ((receiver, type) in implicitSmartCast.receiverTypes) { + val receiverName = when (receiver) { + is ExtensionReceiver -> "Extension implicit receiver" + is ImplicitClassReceiver -> "Implicit receiver" + else -> "Unknown receiver" + } + holder.createInfoAnnotation(expression, + "$receiverName smart cast to " + DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(type)) + .textAttributes = SMART_CAST_RECEIVER + } } val nullSmartCast = bindingContext.get(SMARTCAST_NULL, expression) == true diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt index bd91d3ec9ea..83c40bd976b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt @@ -160,7 +160,7 @@ private fun KtCodeFragment.markSmartCasts() { val factory = KtPsiFactory(project) getContentElement()?.forEachDescendantOfType { expression -> - val smartCast = bindingContext.get(SMARTCAST, expression) ?: bindingContext.get(IMPLICIT_RECEIVER_SMARTCAST, expression) + val smartCast = bindingContext.get(SMARTCAST, expression) if (smartCast != null) { val smartCastedExpression = factory.createExpressionByPattern( "($0 as ${DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast)})", diff --git a/idea/testData/checker/infos/threeImplicitReceivers.kt b/idea/testData/checker/infos/threeImplicitReceivers.kt new file mode 100644 index 00000000000..0faf40074c3 --- /dev/null +++ b/idea/testData/checker/infos/threeImplicitReceivers.kt @@ -0,0 +1,29 @@ +interface IA +interface IB +interface IC + +object Host : IB + +object Prop : IA { + val Host.foo: Callee get() = Callee +} + +object Callee + +object Invoke : IC { + operator fun Callee.invoke() { } +} + +public inline fun with(receiver: T, block: T.() -> R): R = receiver.block() + +fun test(a: IA, b: IB, c: IC) { + with(a) lambdaA@{ + with(b) lambdaB@{ + with(c) lambdaC@{ + if (this@lambdaA is Prop && this@lambdaB is Host && this@lambdaC is Invoke) { + foo() + } + } + } + } +} diff --git a/idea/testData/checker/infos/twoImplicitReceivers.kt b/idea/testData/checker/infos/twoImplicitReceivers.kt new file mode 100644 index 00000000000..58e56a6fab4 --- /dev/null +++ b/idea/testData/checker/infos/twoImplicitReceivers.kt @@ -0,0 +1,20 @@ +interface IA +interface IB + +object A : IA { + fun B.foo() { } +} + +object B : IB + +public inline fun with(receiver: T, block: T.() -> R): R = receiver.block() + +fun test(a: IA, b: IB) { + with(a) lambda1@{ + with(b) lambda2@{ + if (this@lambda1 is A && this@lambda2 is B) { + foo() + } + } + } +} diff --git a/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java b/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java index c5e33abf608..51f504336be 100644 --- a/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java @@ -928,6 +928,18 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest { doTestWithInfos(fileName); } + @TestMetadata("threeImplicitReceivers.kt") + public void testThreeImplicitReceivers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/checker/infos/threeImplicitReceivers.kt"); + doTestWithInfos(fileName); + } + + @TestMetadata("twoImplicitReceivers.kt") + public void testTwoImplicitReceivers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/checker/infos/twoImplicitReceivers.kt"); + doTestWithInfos(fileName); + } + @TestMetadata("Typos.kt") public void testTypos() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/checker/infos/Typos.kt");