Change Signature: Support receiver <-> parameter conversion for function expressions

#KT-9309 Fixed
This commit is contained in:
Alexey Sedunov
2015-09-28 16:19:36 +03:00
parent 2b354cd4db
commit b805645489
9 changed files with 90 additions and 14 deletions
@@ -65,7 +65,7 @@ fun JetCallableDeclaration.setReceiverTypeReference(typeRef: JetTypeReference?):
oldTypeRef.replace(typeRef) as JetTypeReference oldTypeRef.replace(typeRef) as JetTypeReference
} }
else { else {
val anchor = getNameIdentifier() val anchor = getNameIdentifier() ?: valueParameterList
val newTypeRef = addBefore(typeRef, anchor) as JetTypeReference val newTypeRef = addBefore(typeRef, anchor) as JetTypeReference
addAfter(JetPsiFactory(getProject()).createDot(), newTypeRef) addAfter(JetPsiFactory(getProject()).createDot(), newTypeRef)
newTypeRef newTypeRef
@@ -75,6 +75,7 @@ import org.jetbrains.kotlin.resolve.scopes.JetScope;
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue; import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver; import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver;
import org.jetbrains.kotlin.resolve.scopes.utils.ScopeUtilsKt; import org.jetbrains.kotlin.resolve.scopes.utils.ScopeUtilsKt;
import org.jetbrains.kotlin.resolve.source.KotlinSourceElementKt;
import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.JetType;
import java.util.*; import java.util.*;
@@ -671,7 +672,7 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
if (function instanceof JetCallableDeclaration && newReceiverInfo != originalReceiverInfo) { if (function instanceof JetCallableDeclaration && newReceiverInfo != originalReceiverInfo) {
findReceiverIntroducingConflicts(result, function, newReceiverInfo); findReceiverIntroducingConflicts(result, function, newReceiverInfo);
findInternalExplicitReceiverConflicts(refUsages.get(), result, originalReceiverInfo); findInternalExplicitReceiverConflicts(refUsages.get(), result, originalReceiverInfo);
findThisLabelConflicts((JetChangeInfo) info, refUsages, result, changeInfo, function); findThisLabelConflicts(refUsages, result, changeInfo, (JetCallableDeclaration) function);
} }
for (UsageInfo usageInfo : usageInfos) { for (UsageInfo usageInfo : usageInfos) {
@@ -716,18 +717,17 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
} }
private static void findThisLabelConflicts( private static void findThisLabelConflicts(
JetChangeInfo info,
Ref<UsageInfo[]> refUsages, Ref<UsageInfo[]> refUsages,
MultiMap<PsiElement, String> result, MultiMap<PsiElement, String> result,
JetChangeInfo changeInfo, JetChangeInfo changeInfo,
PsiElement function JetCallableDeclaration callable
) { ) {
JetPsiFactory psiFactory = new JetPsiFactory(function.getProject()); JetPsiFactory psiFactory = new JetPsiFactory(callable.getProject());
for (UsageInfo usageInfo : refUsages.get()) { for (UsageInfo usageInfo : refUsages.get()) {
if (!(usageInfo instanceof JetParameterUsage)) continue; if (!(usageInfo instanceof JetParameterUsage)) continue;
String newExprText = ((JetParameterUsage) usageInfo).getReplacementText(changeInfo); String newExprText = ((JetParameterUsage) usageInfo).getReplacementText(changeInfo);
if (!newExprText.startsWith("this@")) continue; if (!newExprText.startsWith("this")) continue;
if (usageInfo.getElement() instanceof KDocName) continue; // TODO support converting parameter to receiver in KDoc if (usageInfo.getElement() instanceof KDocName) continue; // TODO support converting parameter to receiver in KDoc
@@ -737,12 +737,11 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
if (scope == null) continue; if (scope == null) continue;
JetThisExpression newExpr = (JetThisExpression) psiFactory.createExpression(newExprText); JetThisExpression newExpr = (JetThisExpression) psiFactory.createExpression(newExprText);
JetSimpleNameExpression labelExpr = newExpr.getTargetLabel();
if (labelExpr == null) continue;
BindingContext newContext = AnalysisPackage.analyzeInContext(newExpr, scope, originalExpr); BindingContext newContext = AnalysisPackage.analyzeInContext(newExpr, scope, originalExpr);
if (newContext.get(BindingContext.AMBIGUOUS_LABEL_TARGET, labelExpr) != null) { JetSimpleNameExpression labelExpr = newExpr.getTargetLabel();
if (labelExpr != null && newContext.get(BindingContext.AMBIGUOUS_LABEL_TARGET, labelExpr) != null) {
result.putValue( result.putValue(
originalExpr, originalExpr,
"Parameter reference can't be safely replaced with " + "Parameter reference can't be safely replaced with " +
@@ -751,6 +750,20 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
labelExpr.getText() + labelExpr.getText() +
" is ambiguous in this context" " is ambiguous in this context"
); );
continue;
}
DeclarationDescriptor thisTarget = newContext.get(BindingContext.REFERENCE_TARGET, newExpr.getInstanceReference());
PsiElement thisTargetPsi = thisTarget instanceof DeclarationDescriptorWithSource
? KotlinSourceElementKt.getPsi(((DeclarationDescriptorWithSource) thisTarget).getSource())
: null;
if (thisTargetPsi != null && PsiTreeUtil.isAncestor(callable, thisTargetPsi, true)) {
result.putValue(
originalExpr,
"Parameter reference can't be safely replaced with " +
newExprText +
" since target function can't be referenced in this context"
);
} }
} }
} }
@@ -23,13 +23,14 @@ import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetChangeInfo
import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetParameterInfo import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetParameterInfo
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.psi.JetQualifiedExpression import org.jetbrains.kotlin.psi.JetQualifiedExpression
import org.jetbrains.kotlin.psi.JetElement import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.JetThisExpression import org.jetbrains.kotlin.psi.JetThisExpression
import org.jetbrains.kotlin.idea.util.ShortenReferences.Options import org.jetbrains.kotlin.idea.util.ShortenReferences.Options
// Explicit reference to function parameter or outer this // Explicit reference to function parameter or outer this
public abstract class JetExplicitReferenceUsage<T: JetElement>(element: T) : JetUsageInfo<T>(element) { public abstract class JetExplicitReferenceUsage<T : JetElement>(element: T) : JetUsageInfo<T>(element) {
abstract fun getReplacementText(changeInfo: JetChangeInfo): String abstract fun getReplacementText(changeInfo: JetChangeInfo): String
protected open fun processReplacedElement(element: JetElement) { protected open fun processReplacedElement(element: JetElement) {
@@ -55,10 +56,14 @@ public class JetParameterUsage(
elementToShorten.addToShorteningWaitSet(Options(removeThis = true, removeThisLabels = true)) elementToShorten.addToShorteningWaitSet(Options(removeThis = true, removeThisLabels = true))
} }
override fun getReplacementText(changeInfo: JetChangeInfo): String = override fun getReplacementText(changeInfo: JetChangeInfo): String {
if (changeInfo.receiverParameterInfo != parameterInfo) { if (changeInfo.receiverParameterInfo != parameterInfo) return parameterInfo.getInheritedName(containingCallable)
parameterInfo.getInheritedName(containingCallable)
} else "this@${changeInfo.getNewName()}" val newName = changeInfo.getNewName()
if (KotlinNameSuggester.isIdentifier(newName)) return "this@$newName"
return "this"
}
} }
public class JetNonQualifiedOuterThisUsage( public class JetNonQualifiedOuterThisUsage(
@@ -0,0 +1,7 @@
interface T {
val foo: Int
}
val f = fun(<caret>t: T): Int {
return t.foo
}
@@ -0,0 +1,7 @@
interface T {
val foo: Int
}
val f = fun T.(): Int {
return foo
}
@@ -0,0 +1,12 @@
// SHOULD_FAIL_WITH: Parameter reference can't be safely replaced with this since target function can't be referenced in this context
interface T {
val foo: Int
}
val f = fun(<caret>t: T) {
object {
fun f(): Int {
return t.foo
}
}
}
@@ -0,0 +1,7 @@
interface T {
val foo: Int
}
val f = fun <caret>T.(): Int {
return foo
}
@@ -0,0 +1,7 @@
interface T {
val foo: Int
}
val f = fun(t: T): Int {
return t.foo
}
@@ -3565,6 +3565,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("functionExpression.kt")
public void testFunctionExpression() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertParameterToReceiver/functionExpression.kt");
doTest(fileName);
}
@TestMetadata("functionExpressionWithThisConflict.kt")
public void testFunctionExpressionWithThisConflict() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertParameterToReceiver/functionExpressionWithThisConflict.kt");
doTest(fileName);
}
@TestMetadata("lambdaParameter.kt") @TestMetadata("lambdaParameter.kt")
public void testLambdaParameter() throws Exception { public void testLambdaParameter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertParameterToReceiver/lambdaParameter.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertParameterToReceiver/lambdaParameter.kt");
@@ -3727,6 +3739,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertReceiverToParameter"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertReceiverToParameter"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
} }
@TestMetadata("functionExpression.kt")
public void testFunctionExpression() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertReceiverToParameter/functionExpression.kt");
doTest(fileName);
}
@TestMetadata("genericReceiver.kt") @TestMetadata("genericReceiver.kt")
public void testGenericReceiver() throws Exception { public void testGenericReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertReceiverToParameter/genericReceiver.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertReceiverToParameter/genericReceiver.kt");