From e7748e27003b80463c5cf8173b214caa403234c1 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 12 May 2015 16:39:42 +0300 Subject: [PATCH] Convert Parameter to Receiver: Replace parameter references with labeled this-expressions (before shortening them). Report conflict when this-label is ambiguous #KT-7681 Fixed --- .../dialogs/KotlinFindClassUsagesDialog.java | 1 - .../JetChangeSignatureUsageProcessor.java | 54 +++++++++++++++++-- .../usages/JetParameterUsage.kt | 13 ++--- .../convertParameterToReceiver/localObject.kt | 7 +++ .../localObject.kt.after | 7 +++ .../localObjectWithFunctionNameConflict.kt | 8 +++ .../intentions/IntentionTestGenerated.java | 12 +++++ 7 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 idea/testData/intentions/convertParameterToReceiver/localObject.kt create mode 100644 idea/testData/intentions/convertParameterToReceiver/localObject.kt.after create mode 100644 idea/testData/intentions/convertParameterToReceiver/localObjectWithFunctionNameConflict.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/findUsages/dialogs/KotlinFindClassUsagesDialog.java b/idea/src/org/jetbrains/kotlin/idea/findUsages/dialogs/KotlinFindClassUsagesDialog.java index dd31afe62f7..1de42c0ca75 100644 --- a/idea/src/org/jetbrains/kotlin/idea/findUsages/dialogs/KotlinFindClassUsagesDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/findUsages/dialogs/KotlinFindClassUsagesDialog.java @@ -136,7 +136,6 @@ public class KotlinFindClassUsagesDialog extends FindClassUsagesDialog { findWhatPanel, true ); - return findWhatPanel; } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java index bf075c66e60..0d304958d26 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java @@ -44,6 +44,7 @@ import kotlin.KotlinPackage; import kotlin.Unit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.analyzer.AnalyzerPackage; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.idea.JetFileType; import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage; @@ -59,19 +60,20 @@ import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.typeRefHelpers.TypeRefHelpersPackage; import org.jetbrains.kotlin.renderer.DescriptorRenderer; import org.jetbrains.kotlin.resolve.BindingContext; +import org.jetbrains.kotlin.resolve.BindingTraceContext; import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; +import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode; 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.types.JetType; +import org.jetbrains.kotlin.types.TypeUtils; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsageProcessor { @Override @@ -424,11 +426,55 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro if (function instanceof JetNamedFunction && newReceiverInfo != originalReceiverInfo) { findReceiverIntroducingConflicts(result, function, newReceiverInfo); findInternalExplicitReceiverConflicts(refUsages.get(), result, originalReceiverInfo); + findThisLabelConflicts((JetChangeInfo) info, refUsages, result, changeInfo, function); } return result; } + private static void findThisLabelConflicts( + JetChangeInfo info, + Ref refUsages, + MultiMap result, + JetChangeInfo changeInfo, + PsiElement function + ) { + JetPsiFactory psiFactory = new JetPsiFactory(function.getProject()); + for (UsageInfo usageInfo : refUsages.get()) { + if (!(usageInfo instanceof JetParameterUsage)) continue; + + String newExprText = ((JetParameterUsage) usageInfo).getReplacementText(changeInfo); + if (!newExprText.startsWith("this@")) continue; + + JetExpression originalExpr = (JetExpression) usageInfo.getElement(); + JetScope scope = ResolvePackage.analyze(originalExpr, BodyResolveMode.FULL) + .get(BindingContext.RESOLUTION_SCOPE, originalExpr); + if (scope == null) continue; + + JetThisExpression newExpr = (JetThisExpression) psiFactory.createExpression(newExprText); + JetSimpleNameExpression labelExpr = newExpr.getTargetLabel(); + if (labelExpr == null) continue; + + BindingContext newContext = + AnalyzerPackage.analyzeInContext(newExpr, + scope, + new BindingTraceContext(), + DataFlowInfo.EMPTY, + TypeUtils.NO_EXPECTED_TYPE, + DescriptorUtils.getContainingModule(scope.getContainingDeclaration())); + if (newContext.get(BindingContext.AMBIGUOUS_LABEL_TARGET, labelExpr) != null) { + result.putValue( + originalExpr, + "Parameter reference can't be safely replaced with " + + newExprText + + " since " + + labelExpr.getText() + + " is ambiguous in this context" + ); + } + } + } + private static void findInternalExplicitReceiverConflicts( UsageInfo[] usages, MultiMap result, diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetParameterUsage.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetParameterUsage.kt index c7e76ca6614..8661ed8f788 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetParameterUsage.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetParameterUsage.kt @@ -29,7 +29,7 @@ import org.jetbrains.kotlin.idea.util.ShortenReferences.Options // Explicit reference to function parameter or outer this public abstract class JetExplicitReferenceUsage(element: T) : JetUsageInfo(element) { - protected abstract fun getReplacementText(changeInfo: JetChangeInfo): String + abstract fun getReplacementText(changeInfo: JetChangeInfo): String protected open fun processReplacedElement(element: JetElement) { @@ -45,17 +45,18 @@ public abstract class JetExplicitReferenceUsage(element: T) : Jet public class JetParameterUsage( element: JetSimpleNameExpression, private val parameterInfo: JetParameterInfo, - private val containingFunction: JetFunctionDefinitionUsage<*> + val containingFunction: JetFunctionDefinitionUsage<*> ) : JetExplicitReferenceUsage(element) { override fun processReplacedElement(element: JetElement) { val qualifiedExpression = element.getParent() as? JetQualifiedExpression - if (qualifiedExpression?.getReceiverExpression() == element) { - qualifiedExpression!!.addToShorteningWaitSet(Options(removeThis = true)) - } + val elementToShorten = if (qualifiedExpression?.getReceiverExpression() == element) qualifiedExpression!! else element + elementToShorten.addToShorteningWaitSet(Options(removeThis = true, removeThisLabels = true)) } override fun getReplacementText(changeInfo: JetChangeInfo): String = - if (changeInfo.receiverParameterInfo != parameterInfo) parameterInfo.getInheritedName(containingFunction) else "this" + if (changeInfo.receiverParameterInfo != parameterInfo) { + parameterInfo.getInheritedName(containingFunction) + } else "this@${containingFunction.getOriginalFunctionDescriptor().getName().asString()}" } public class JetNonQualifiedOuterThisUsage( diff --git a/idea/testData/intentions/convertParameterToReceiver/localObject.kt b/idea/testData/intentions/convertParameterToReceiver/localObject.kt new file mode 100644 index 00000000000..68a3f4de6f0 --- /dev/null +++ b/idea/testData/intentions/convertParameterToReceiver/localObject.kt @@ -0,0 +1,7 @@ +fun foo(bar: Int) { + object { + fun baz() { + bar + 1 + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertParameterToReceiver/localObject.kt.after b/idea/testData/intentions/convertParameterToReceiver/localObject.kt.after new file mode 100644 index 00000000000..83e0d5c2d7b --- /dev/null +++ b/idea/testData/intentions/convertParameterToReceiver/localObject.kt.after @@ -0,0 +1,7 @@ +fun Int.foo() { + object { + fun baz() { + this@foo + 1 + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertParameterToReceiver/localObjectWithFunctionNameConflict.kt b/idea/testData/intentions/convertParameterToReceiver/localObjectWithFunctionNameConflict.kt new file mode 100644 index 00000000000..b9c613c361b --- /dev/null +++ b/idea/testData/intentions/convertParameterToReceiver/localObjectWithFunctionNameConflict.kt @@ -0,0 +1,8 @@ +// SHOULD_FAIL_WITH: Parameter reference can't be safely replaced with this@foo since @foo is ambiguous in this context +fun foo(bar: Int) { + object { + fun foo() { + bar + 1 + } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index daf300446ab..9e6cea8c4cf 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -3226,6 +3226,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("localObject.kt") + public void testLocalObject() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertParameterToReceiver/localObject.kt"); + doTest(fileName); + } + + @TestMetadata("localObjectWithFunctionNameConflict.kt") + public void testLocalObjectWithFunctionNameConflict() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertParameterToReceiver/localObjectWithFunctionNameConflict.kt"); + doTest(fileName); + } + @TestMetadata("memberFun.kt") public void testMemberFun() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertParameterToReceiver/memberFun.kt");