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
}
else {
val anchor = getNameIdentifier()
val anchor = getNameIdentifier() ?: valueParameterList
val newTypeRef = addBefore(typeRef, anchor) as JetTypeReference
addAfter(JetPsiFactory(getProject()).createDot(), 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.ThisReceiver;
import org.jetbrains.kotlin.resolve.scopes.utils.ScopeUtilsKt;
import org.jetbrains.kotlin.resolve.source.KotlinSourceElementKt;
import org.jetbrains.kotlin.types.JetType;
import java.util.*;
@@ -671,7 +672,7 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
if (function instanceof JetCallableDeclaration && newReceiverInfo != originalReceiverInfo) {
findReceiverIntroducingConflicts(result, function, newReceiverInfo);
findInternalExplicitReceiverConflicts(refUsages.get(), result, originalReceiverInfo);
findThisLabelConflicts((JetChangeInfo) info, refUsages, result, changeInfo, function);
findThisLabelConflicts(refUsages, result, changeInfo, (JetCallableDeclaration) function);
}
for (UsageInfo usageInfo : usageInfos) {
@@ -716,18 +717,17 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
}
private static void findThisLabelConflicts(
JetChangeInfo info,
Ref<UsageInfo[]> refUsages,
MultiMap<PsiElement, String> result,
JetChangeInfo changeInfo,
PsiElement function
JetCallableDeclaration callable
) {
JetPsiFactory psiFactory = new JetPsiFactory(function.getProject());
JetPsiFactory psiFactory = new JetPsiFactory(callable.getProject());
for (UsageInfo usageInfo : refUsages.get()) {
if (!(usageInfo instanceof JetParameterUsage)) continue;
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
@@ -737,12 +737,11 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
if (scope == null) continue;
JetThisExpression newExpr = (JetThisExpression) psiFactory.createExpression(newExprText);
JetSimpleNameExpression labelExpr = newExpr.getTargetLabel();
if (labelExpr == null) continue;
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(
originalExpr,
"Parameter reference can't be safely replaced with " +
@@ -751,6 +750,20 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
labelExpr.getText() +
" 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.descriptors.DeclarationDescriptor
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.JetElement
import org.jetbrains.kotlin.psi.JetThisExpression
import org.jetbrains.kotlin.idea.util.ShortenReferences.Options
// 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
protected open fun processReplacedElement(element: JetElement) {
@@ -55,10 +56,14 @@ public class JetParameterUsage(
elementToShorten.addToShorteningWaitSet(Options(removeThis = true, removeThisLabels = true))
}
override fun getReplacementText(changeInfo: JetChangeInfo): String =
if (changeInfo.receiverParameterInfo != parameterInfo) {
parameterInfo.getInheritedName(containingCallable)
} else "this@${changeInfo.getNewName()}"
override fun getReplacementText(changeInfo: JetChangeInfo): String {
if (changeInfo.receiverParameterInfo != parameterInfo) return parameterInfo.getInheritedName(containingCallable)
val newName = changeInfo.getNewName()
if (KotlinNameSuggester.isIdentifier(newName)) return "this@$newName"
return "this"
}
}
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);
}
@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")
public void testLambdaParameter() throws Exception {
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);
}
@TestMetadata("functionExpression.kt")
public void testFunctionExpression() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertReceiverToParameter/functionExpression.kt");
doTest(fileName);
}
@TestMetadata("genericReceiver.kt")
public void testGenericReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertReceiverToParameter/genericReceiver.kt");