diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ShortenReferences.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ShortenReferences.kt index efc494f8dae..459911cf102 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ShortenReferences.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ShortenReferences.kt @@ -33,6 +33,12 @@ import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import java.util.ArrayList import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue +import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind +import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver +import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver public object ShortenReferences { public fun process(element: JetElement) { @@ -113,18 +119,16 @@ public object ShortenReferences { val failedToImportDescriptors = LinkedHashSet() while (true) { - val visitor1 = ShortenTypesVisitor(file, elementFilter, failedToImportDescriptors) - val visitor2 = ShortenQualifiedExpressionsVisitor(file, elementFilter, failedToImportDescriptors) + // Visitor order is important here so that enclosing elements are not shortened before their children are, e.g. + // test.foo(this@A) -> foo(this) + val visitors = listOf( + ShortenTypesVisitor(file, elementFilter, failedToImportDescriptors), + ShortenThisExpressionsVisitor(file, elementFilter, failedToImportDescriptors), + ShortenQualifiedExpressionsVisitor(file, elementFilter, failedToImportDescriptors) + ) + val descriptorsToImport = visitors.flatMap { analyzeReferences(elementsToUse, it) }.toSet() + visitors.forEach { elementsToUse.removeAll(it.shortenElements()) } - val descriptorsToImport1 = analyzeReferences(elementsToUse, visitor1) - val descriptorsToImport2 = analyzeReferences(elementsToUse, visitor2) - - elementsToUse.removeAll(visitor1.shortenElements()) - elementsToUse.removeAll(visitor2.shortenElements()) - - if (descriptorsToImport1.isEmpty() && descriptorsToImport2.isEmpty()) break - - val descriptorsToImport = (descriptorsToImport1 + descriptorsToImport2).toSet() var anyChange = false for (descriptor in descriptorsToImport) { assert(descriptor !in failedToImportDescriptors) @@ -193,7 +197,7 @@ public object ShortenReferences { public fun shortenElements(): Collection { for (element in elementsToShorten) { - assert (element.isValid()) + if (!element.isValid()) continue shortenElement(element) } return elementsToShorten @@ -267,7 +271,8 @@ public object ShortenReferences { private fun process(qualifiedExpression: JetDotQualifiedExpression): Boolean { val bindingContext = resolutionFacade.analyze(qualifiedExpression) - if (bindingContext[BindingContext.QUALIFIER, qualifiedExpression.getReceiverExpression()] == null) return false + val receiver = qualifiedExpression.getReceiverExpression() + if (receiver !is JetThisExpression && bindingContext[BindingContext.QUALIFIER, receiver] == null) return false if (PsiTreeUtil.getParentOfType( qualifiedExpression, @@ -281,15 +286,29 @@ public object ShortenReferences { val selectorCopy = selector.copy() as JetReferenceExpression val newContext = selectorCopy.analyzeInContext(scope) val targetsWhenShort = (selectorCopy.getCalleeExpressionIfAny() as JetReferenceExpression).targets(newContext) + val targetsMatch = targetsWhenShort.singleOrNull()?.asString() == target.asString() - val canShortenNow = targetsWhenShort.singleOrNull()?.asString() == target.asString() + if (receiver is JetThisExpression) { + if (!targetsMatch) return false + val originalCall = selector.getResolvedCall(bindingContext) ?: return false + val newCall = selectorCopy.getResolvedCall(newContext) ?: return false + val receiverKind = originalCall.getExplicitReceiverKind() + val newReceiver = when (receiverKind) { + ExplicitReceiverKind.BOTH_RECEIVERS, ExplicitReceiverKind.EXTENSION_RECEIVER -> newCall.getExtensionReceiver() + ExplicitReceiverKind.DISPATCH_RECEIVER -> newCall.getDispatchReceiver() + else -> return false + } as? ThisReceiver ?: return false - if (!canShortenNow && targetsWhenShort.any { it !is ClassDescriptor && it !is PackageViewDescriptor }) { + val thisTarget = receiver.getInstanceReference().targets(bindingContext).singleOrNull() + if (newReceiver.getDeclarationDescriptor().asString() != thisTarget?.asString()) return false + } + + if (!targetsMatch && targetsWhenShort.any { it !is ClassDescriptor && it !is PackageViewDescriptor }) { // it makes no sense to insert import when there is a conflict with function, property etc return false } - processQualifiedElement(qualifiedExpression, target, canShortenNow) + processQualifiedElement(qualifiedExpression, target, targetsMatch) return true } @@ -300,6 +319,41 @@ public object ShortenReferences { } } + private class ShortenThisExpressionsVisitor( + file: JetFile, + elementFilter: (PsiElement) -> FilterResult, + failedToImportDescriptors: Set + ) : ShorteningVisitor(file, elementFilter, failedToImportDescriptors) { + private val simpleThis = JetPsiFactory(file).createExpression("this") as JetThisExpression + + private fun process(thisExpression: JetThisExpression) { + if (thisExpression.getTargetLabel() == null) return + + val bindingContext = resolutionFacade.analyze(thisExpression) + + val targetBefore = thisExpression.getInstanceReference().targets(bindingContext).singleOrNull() ?: return + val scope = bindingContext[BindingContext.RESOLUTION_SCOPE, thisExpression] ?: return + val newContext = simpleThis.analyzeInContext(scope) + val targetAfter = simpleThis.getInstanceReference().targets(newContext).singleOrNull() + if (targetBefore == targetAfter) { + processQualifiedElement(thisExpression, targetBefore, true) + } + } + + override fun visitThisExpression(expression: JetThisExpression) { + if (elementFilter(expression) == FilterResult.PROCESS) { + process(expression) + } + } + + override fun qualifier(element: JetThisExpression): JetElement = + throw AssertionError("Qualifier requested: ${JetPsiUtil.getElementTextWithContext(element)}") + + override fun shortenElement(element: JetThisExpression) { + element.replace(simpleThis) + } + } + private fun DeclarationDescriptor.asString() = DescriptorRenderer.FQ_NAMES_IN_TYPES.render(this) diff --git a/idea/testData/shortenRefs/this/cantShortenThis.kt b/idea/testData/shortenRefs/this/cantShortenThis.kt new file mode 100644 index 00000000000..3df94a359b4 --- /dev/null +++ b/idea/testData/shortenRefs/this/cantShortenThis.kt @@ -0,0 +1,3 @@ +class A(val n: Int) { + fun foo(n: Int): Int = this.n + 1 +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/cantShortenThis.kt.after b/idea/testData/shortenRefs/this/cantShortenThis.kt.after new file mode 100644 index 00000000000..8ad72fb06a6 --- /dev/null +++ b/idea/testData/shortenRefs/this/cantShortenThis.kt.after @@ -0,0 +1,3 @@ +class A(val n: Int) { + fun foo(n: Int): Int = this.n + 1 +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/cantShortenThisLabel.kt b/idea/testData/shortenRefs/this/cantShortenThisLabel.kt new file mode 100644 index 00000000000..bacd8d21fb0 --- /dev/null +++ b/idea/testData/shortenRefs/this/cantShortenThisLabel.kt @@ -0,0 +1,3 @@ +class A(val n: Int) { + fun A.foo(): Int = this@A.n + n + 1 +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/cantShortenThisLabel.kt.after b/idea/testData/shortenRefs/this/cantShortenThisLabel.kt.after new file mode 100644 index 00000000000..3727442a9ab --- /dev/null +++ b/idea/testData/shortenRefs/this/cantShortenThisLabel.kt.after @@ -0,0 +1,3 @@ +class A(val n: Int) { + fun A.foo(): Int = this@A.n + n + 1 +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/shortenExtensionThis.kt b/idea/testData/shortenRefs/this/shortenExtensionThis.kt new file mode 100644 index 00000000000..e5e8cee5f29 --- /dev/null +++ b/idea/testData/shortenRefs/this/shortenExtensionThis.kt @@ -0,0 +1,5 @@ +class A(val n: Int) { + +} + +fun A.foo(): Int = this.n + 1 \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/shortenExtensionThis.kt.after b/idea/testData/shortenRefs/this/shortenExtensionThis.kt.after new file mode 100644 index 00000000000..f5447346366 --- /dev/null +++ b/idea/testData/shortenRefs/this/shortenExtensionThis.kt.after @@ -0,0 +1,5 @@ +class A(val n: Int) { + +} + +fun A.foo(): Int = n + 1 \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/shortenThis.kt b/idea/testData/shortenRefs/this/shortenThis.kt new file mode 100644 index 00000000000..d18f2da5951 --- /dev/null +++ b/idea/testData/shortenRefs/this/shortenThis.kt @@ -0,0 +1,3 @@ +class A(val n: Int) { + fun foo(): Int = this.n + 1 +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/shortenThis.kt.after b/idea/testData/shortenRefs/this/shortenThis.kt.after new file mode 100644 index 00000000000..7c3e7558fc8 --- /dev/null +++ b/idea/testData/shortenRefs/this/shortenThis.kt.after @@ -0,0 +1,3 @@ +class A(val n: Int) { + fun foo(): Int = n + 1 +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/shortenThisBothReceivers.kt b/idea/testData/shortenRefs/this/shortenThisBothReceivers.kt new file mode 100644 index 00000000000..59172fa33dd --- /dev/null +++ b/idea/testData/shortenRefs/this/shortenThisBothReceivers.kt @@ -0,0 +1,9 @@ +class A { + fun test(b: B) { + this.b() + } +} + +class B() { + fun A.invoke() {} +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/shortenThisBothReceivers.kt.after b/idea/testData/shortenRefs/this/shortenThisBothReceivers.kt.after new file mode 100644 index 00000000000..1247c0d7718 --- /dev/null +++ b/idea/testData/shortenRefs/this/shortenThisBothReceivers.kt.after @@ -0,0 +1,9 @@ +class A { + fun test(b: B) { + b() + } +} + +class B() { + fun A.invoke() {} +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/shortenThisLabel.kt b/idea/testData/shortenRefs/this/shortenThisLabel.kt new file mode 100644 index 00000000000..b2d261a3a3f --- /dev/null +++ b/idea/testData/shortenRefs/this/shortenThisLabel.kt @@ -0,0 +1,3 @@ +class A(val n: Int) { + fun foo(n: Int): Int = this@A.n + 1 +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/shortenThisLabel.kt.after b/idea/testData/shortenRefs/this/shortenThisLabel.kt.after new file mode 100644 index 00000000000..8ad72fb06a6 --- /dev/null +++ b/idea/testData/shortenRefs/this/shortenThisLabel.kt.after @@ -0,0 +1,3 @@ +class A(val n: Int) { + fun foo(n: Int): Int = this.n + 1 +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/shortenThisWithLabel.kt b/idea/testData/shortenRefs/this/shortenThisWithLabel.kt new file mode 100644 index 00000000000..3084bacdce6 --- /dev/null +++ b/idea/testData/shortenRefs/this/shortenThisWithLabel.kt @@ -0,0 +1,3 @@ +class A(val n: Int) { + fun foo(): Int = this@A.n + 1 +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/shortenThisWithLabel.kt.after b/idea/testData/shortenRefs/this/shortenThisWithLabel.kt.after new file mode 100644 index 00000000000..7c3e7558fc8 --- /dev/null +++ b/idea/testData/shortenRefs/this/shortenThisWithLabel.kt.after @@ -0,0 +1,3 @@ +class A(val n: Int) { + fun foo(): Int = n + 1 +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/visitorConflict.kt b/idea/testData/shortenRefs/this/visitorConflict.kt new file mode 100644 index 00000000000..b4d60f0bc5c --- /dev/null +++ b/idea/testData/shortenRefs/this/visitorConflict.kt @@ -0,0 +1,11 @@ +package test + +fun foo(n: Int) { + +} + +class A(val n: Int) { + fun test() { + test.foo(this@A) + } +} \ No newline at end of file diff --git a/idea/testData/shortenRefs/this/visitorConflict.kt.after b/idea/testData/shortenRefs/this/visitorConflict.kt.after new file mode 100644 index 00000000000..29b1364f78f --- /dev/null +++ b/idea/testData/shortenRefs/this/visitorConflict.kt.after @@ -0,0 +1,11 @@ +package test + +fun foo(n: Int) { + +} + +class A(val n: Int) { + fun test() { + foo(this) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/shortenRefs/ShortenRefsTestGenerated.java b/idea/tests/org/jetbrains/kotlin/shortenRefs/ShortenRefsTestGenerated.java index 14052458666..39763f74873 100644 --- a/idea/tests/org/jetbrains/kotlin/shortenRefs/ShortenRefsTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/shortenRefs/ShortenRefsTestGenerated.java @@ -30,7 +30,7 @@ import java.util.regex.Pattern; @SuppressWarnings("all") @TestMetadata("idea/testData/shortenRefs") @TestDataPath("$PROJECT_ROOT") -@InnerTestClasses({ShortenRefsTestGenerated.Constructor.class, ShortenRefsTestGenerated.Imports.class, ShortenRefsTestGenerated.Java.class, ShortenRefsTestGenerated.Type.class}) +@InnerTestClasses({ShortenRefsTestGenerated.Constructor.class, ShortenRefsTestGenerated.Imports.class, ShortenRefsTestGenerated.Java.class, ShortenRefsTestGenerated.This.class, ShortenRefsTestGenerated.Type.class}) @RunWith(JUnit3RunnerWithInners.class) public class ShortenRefsTestGenerated extends AbstractShortenRefsTest { public void testAllFilesPresentInShortenRefs() throws Exception { @@ -238,6 +238,63 @@ public class ShortenRefsTestGenerated extends AbstractShortenRefsTest { } } + @TestMetadata("idea/testData/shortenRefs/this") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class This extends AbstractShortenRefsTest { + public void testAllFilesPresentInThis() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/shortenRefs/this"), Pattern.compile("^([^\\.]+)\\.kt$"), true); + } + + @TestMetadata("cantShortenThis.kt") + public void testCantShortenThis() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/shortenRefs/this/cantShortenThis.kt"); + doTest(fileName); + } + + @TestMetadata("cantShortenThisLabel.kt") + public void testCantShortenThisLabel() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/shortenRefs/this/cantShortenThisLabel.kt"); + doTest(fileName); + } + + @TestMetadata("shortenExtensionThis.kt") + public void testShortenExtensionThis() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/shortenRefs/this/shortenExtensionThis.kt"); + doTest(fileName); + } + + @TestMetadata("shortenThis.kt") + public void testShortenThis() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/shortenRefs/this/shortenThis.kt"); + doTest(fileName); + } + + @TestMetadata("shortenThisBothReceivers.kt") + public void testShortenThisBothReceivers() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/shortenRefs/this/shortenThisBothReceivers.kt"); + doTest(fileName); + } + + @TestMetadata("shortenThisLabel.kt") + public void testShortenThisLabel() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/shortenRefs/this/shortenThisLabel.kt"); + doTest(fileName); + } + + @TestMetadata("shortenThisWithLabel.kt") + public void testShortenThisWithLabel() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/shortenRefs/this/shortenThisWithLabel.kt"); + doTest(fileName); + } + + @TestMetadata("visitorConflict.kt") + public void testVisitorConflict() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/shortenRefs/this/visitorConflict.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/shortenRefs/type") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)