Convert Parameter to Receiver: Replace parameter references with labeled this-expressions (before shortening them). Report conflict when this-label is ambiguous

#KT-7681 Fixed
This commit is contained in:
Alexey Sedunov
2015-05-12 16:39:42 +03:00
parent 2dac70a4f7
commit e7748e2700
7 changed files with 91 additions and 11 deletions
@@ -136,7 +136,6 @@ public class KotlinFindClassUsagesDialog extends FindClassUsagesDialog {
findWhatPanel,
true
);
return findWhatPanel;
}
@@ -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<UsageInfo[]> refUsages,
MultiMap<PsiElement, String> 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<PsiElement, String> result,
@@ -29,7 +29,7 @@ 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) {
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<T: JetElement>(element: T) : Jet
public class JetParameterUsage(
element: JetSimpleNameExpression,
private val parameterInfo: JetParameterInfo,
private val containingFunction: JetFunctionDefinitionUsage<*>
val containingFunction: JetFunctionDefinitionUsage<*>
) : JetExplicitReferenceUsage<JetSimpleNameExpression>(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(
@@ -0,0 +1,7 @@
fun foo(<caret>bar: Int) {
object {
fun baz() {
bar + 1
}
}
}
@@ -0,0 +1,7 @@
fun Int.foo() {
object {
fun baz() {
this@foo + 1
}
}
}
@@ -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(<caret>bar: Int) {
object {
fun foo() {
bar + 1
}
}
}
@@ -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");