Shorten References: Support 'this' simplification
This commit is contained in:
@@ -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<DeclarationDescriptor>()
|
||||
|
||||
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<T> {
|
||||
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<DeclarationDescriptor>
|
||||
) : ShorteningVisitor<JetThisExpression>(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)
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(n: Int): Int = <selection>this.n + 1</selection>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(n: Int): Int = this.n + 1
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A(val n: Int) {
|
||||
fun A.foo(): Int = <selection>this@A.n + n + 1</selection>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A(val n: Int) {
|
||||
fun A.foo(): Int = this@A.n + n + 1
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class A(val n: Int) {
|
||||
|
||||
}
|
||||
|
||||
fun A.foo(): Int = <selection>this.n + 1</selection>
|
||||
@@ -0,0 +1,5 @@
|
||||
class A(val n: Int) {
|
||||
|
||||
}
|
||||
|
||||
fun A.foo(): Int = n + 1
|
||||
@@ -0,0 +1,3 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(): Int = <selection>this.n + 1</selection>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(): Int = n + 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
class A {
|
||||
fun test(b: B) {
|
||||
<selection>this.b()</selection>
|
||||
}
|
||||
}
|
||||
|
||||
class B() {
|
||||
fun A.invoke() {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
class A {
|
||||
fun test(b: B) {
|
||||
b()
|
||||
}
|
||||
}
|
||||
|
||||
class B() {
|
||||
fun A.invoke() {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(n: Int): Int = <selection>this@A.n + 1</selection>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(n: Int): Int = this.n + 1
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(): Int = <selection>this@A.n + 1</selection>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(): Int = n + 1
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package test
|
||||
|
||||
fun foo(n: Int) {
|
||||
|
||||
}
|
||||
|
||||
class A(val n: Int) {
|
||||
fun test() {
|
||||
<selection>test.foo(this@A)</selection>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package test
|
||||
|
||||
fun foo(n: Int) {
|
||||
|
||||
}
|
||||
|
||||
class A(val n: Int) {
|
||||
fun test() {
|
||||
foo(this)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user