diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/refactoring/JetNameSuggester.java b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/refactoring/JetNameSuggester.java index 0595744b124..cbdc3957abc 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/refactoring/JetNameSuggester.java +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/refactoring/JetNameSuggester.java @@ -45,11 +45,10 @@ public class JetNameSuggester { } private static void addName(ArrayList result, @Nullable String name, JetNameValidator validator) { + if (name == null) return; if ("class".equals(name)) name = "clazz"; if (!isIdentifier(name)) return; - String newName = validator.validateName(name); - if (newName == null) return; - result.add(newName); + result.add(validator.validateName(name)); } /** @@ -93,8 +92,13 @@ public class JetNameSuggester { } public static @NotNull String[] suggestNamesForExpression(@NotNull JetExpression expression, @NotNull JetNameValidator validator) { + return suggestNamesForExpression(expression, validator, null); + } + + public static @NotNull String[] suggestNamesForExpression(@NotNull JetExpression expression, @NotNull JetNameValidator validator, @Nullable String defaultName) { ArrayList result = new ArrayList(); addNamesForExpression(result, expression, validator); + if (result.isEmpty()) addName(result, defaultName, validator); return ArrayUtil.toStringArray(result); } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt index bb226383874..708d9afa8ac 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt @@ -32,6 +32,8 @@ import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester +import org.jetbrains.kotlin.idea.core.refactoring.JetNameValidator import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers @@ -97,9 +99,9 @@ public class DeprecatedSymbolUsageFix( val bindingContext = element.analyze() val resolvedCall = element.getResolvedCall(bindingContext)!! val descriptor = resolvedCall.getResultingDescriptor() - val callElement = resolvedCall.getCall().getCallElement() as JetExpression - val qualifiedExpression = callElement.getParent() as? JetQualifiedExpression - val expressionToReplace = qualifiedExpression ?: callElement + val callExpression = resolvedCall.getCall().getCallElement() as JetExpression + val qualifiedExpression = callExpression.getParent() as? JetQualifiedExpression + val expressionToReplace = qualifiedExpression ?: callExpression val USER_CODE_KEY = Key("USER_CODE") @@ -125,8 +127,19 @@ public class DeprecatedSymbolUsageFix( } if (expressionToReplace.isUsedAsExpression(bindingContext)) { - expression = psiFactory.createExpressionByPattern("$0?.let { $1 }", explicitReceiver!!, expression) - thisReplacement = psiFactory.createExpression("it") + if (!isNameUsed("it", callExpression, expression)) { + expression = psiFactory.createExpressionByPattern("$0?.let { $1 }", explicitReceiver!!, expression) + thisReplacement = psiFactory.createExpression("it") + } + else { + val nameValidator = object : JetNameValidator() { + override fun validateInner(name: String) = !isNameUsed(name, callExpression, expression) + } + val name = JetNameSuggester.suggestNamesForExpression(explicitReceiver!!, nameValidator, "t").first() + val nameInCode = IdeDescriptorRenderers.SOURCE_CODE.renderName(Name.identifier(name)) + expression = psiFactory.createExpressionByPattern("$0?.let { $nameInCode -> $1 }", explicitReceiver, expression) + thisReplacement = psiFactory.createExpression(nameInCode) + } } else { expression = psiFactory.createExpressionByPattern("if ($0 != null) { $1 }", explicitReceiver!!, expression) @@ -301,6 +314,24 @@ public class DeprecatedSymbolUsageFix( } } + private fun isNameUsed(name: String, vararg inExpressions: JetExpression): Boolean { + var result = false + inExpressions.forEach { + it.accept(object : JetVisitorVoid(){ + override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) { + if (expression.getReferencedName() == name) { + result = true + } + } + + override fun visitJetElement(element: JetElement) { + element.acceptChildren(this) + } + }) + } + return result + } + companion object : JetSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { val nameExpression = diagnostic.getPsiElement() as? JetSimpleNameExpression ?: return null diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue1Runtime.kt b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue1Runtime.kt new file mode 100644 index 00000000000..599c55d93d4 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue1Runtime.kt @@ -0,0 +1,18 @@ +// "Replace with 'c.newFun(this)'" "true" + +class X { + @deprecated("", ReplaceWith("c.newFun(this)")) + fun oldFun(c: Char): Char = c.newFun(this) +} + +fun Char.newFun(x: X): Char = this + +fun foo(x: X?, p: Boolean, s: String) { + val chars = s.filter { + val v = if (p) + x?.oldFun(it) + else + null + v != 'a' + } +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue1Runtime.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue1Runtime.kt.after new file mode 100644 index 00000000000..94d55e27667 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue1Runtime.kt.after @@ -0,0 +1,18 @@ +// "Replace with 'c.newFun(this)'" "true" + +class X { + @deprecated("", ReplaceWith("c.newFun(this)")) + fun oldFun(c: Char): Char = c.newFun(this) +} + +fun Char.newFun(x: X): Char = this + +fun foo(x: X?, p: Boolean, s: String) { + val chars = s.filter { + val v = if (p) + x?.let { x -> it.newFun(x) } + else + null + v != 'a' + } +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue2Runtime.kt b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue2Runtime.kt new file mode 100644 index 00000000000..217f58eb9cf --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue2Runtime.kt @@ -0,0 +1,14 @@ +// "Replace with 's.filter { it != c }'" "true" + +class X { + @deprecated("", ReplaceWith("s.filter { it != c }")) + fun oldFun(s: String): CharSequence = s.filter { it != c } + + val c = 'x' +} + +fun foo(x: X?, s: String) { + bar(x?.oldFun(s)) +} + +fun bar(s: CharSequence?){} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue2Runtime.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue2Runtime.kt.after new file mode 100644 index 00000000000..e89fafe4590 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue2Runtime.kt.after @@ -0,0 +1,14 @@ +// "Replace with 's.filter { it != c }'" "true" + +class X { + @deprecated("", ReplaceWith("s.filter { it != c }")) + fun oldFun(s: String): CharSequence = s.filter { it != c } + + val c = 'x' +} + +fun foo(x: X?, s: String) { + bar(x?.let { x -> s.filter { it != x.c } }) +} + +fun bar(s: CharSequence?){} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue3Runtime.kt b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue3Runtime.kt new file mode 100644 index 00000000000..9f81637a9b1 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue3Runtime.kt @@ -0,0 +1,20 @@ +// "Replace with 'c1.newFun(this, c2)'" "true" + +class X { + @deprecated("", ReplaceWith("c1.newFun(this, c2)")) + fun oldFun(c1: Char, c2: Char): Char = c1.newFun(this, c2) + + val c: Char = 'a' +} + +fun Char.newFun(x: X, c: Char): Char = this + +fun foo(s: String, x: X) { + val chars = s.filter { + O.x?.oldFun(it, x.c) != 'a' + } +} + +object O { + var x: X? = null +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue3Runtime.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue3Runtime.kt.after new file mode 100644 index 00000000000..1683fe791e7 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue3Runtime.kt.after @@ -0,0 +1,20 @@ +// "Replace with 'c1.newFun(this, c2)'" "true" + +class X { + @deprecated("", ReplaceWith("c1.newFun(this, c2)")) + fun oldFun(c1: Char, c2: Char): Char = c1.newFun(this, c2) + + val c: Char = 'a' +} + +fun Char.newFun(x: X, c: Char): Char = this + +fun foo(s: String, x: X) { + val chars = s.filter { + O.x?.let { x1 -> it.newFun(x1, x.c) } != 'a' + } +} + +object O { + var x: X? = null +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue4Runtime.kt b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue4Runtime.kt new file mode 100644 index 00000000000..38cffee478d --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue4Runtime.kt @@ -0,0 +1,16 @@ +// "Replace with 'c1.newFun(this, c2)'" "true" + +class X(val c: Char) { + @deprecated("", ReplaceWith("c1.newFun(this, c2)")) + fun oldFun(c1: Char, c2: Char): Char = c1.newFun(this, c2) +} + +fun Char.newFun(x: X, c: Char): Char = this + +fun foo(s: String, t: X) { + val chars = s.filter { + (X('a') + X('b'))?.oldFun(it, t.c) != 'a' + } +} + +fun X.plus(x: X): X? = null \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue4Runtime.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue4Runtime.kt.after new file mode 100644 index 00000000000..9a0f48807fe --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue4Runtime.kt.after @@ -0,0 +1,16 @@ +// "Replace with 'c1.newFun(this, c2)'" "true" + +class X(val c: Char) { + @deprecated("", ReplaceWith("c1.newFun(this, c2)")) + fun oldFun(c1: Char, c2: Char): Char = c1.newFun(this, c2) +} + +fun Char.newFun(x: X, c: Char): Char = this + +fun foo(s: String, t: X) { + val chars = s.filter { + (X('a') + X('b'))?.let { t1 -> it.newFun(t1, t.c) } != 'a' + } +} + +fun X.plus(x: X): X? = null \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 5782becf973..3ddc4e6261d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -2896,6 +2896,30 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("changeThisSafeCallWithValue1Runtime.kt") + public void testChangeThisSafeCallWithValue1Runtime() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue1Runtime.kt"); + doTest(fileName); + } + + @TestMetadata("changeThisSafeCallWithValue2Runtime.kt") + public void testChangeThisSafeCallWithValue2Runtime() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue2Runtime.kt"); + doTest(fileName); + } + + @TestMetadata("changeThisSafeCallWithValue3Runtime.kt") + public void testChangeThisSafeCallWithValue3Runtime() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue3Runtime.kt"); + doTest(fileName); + } + + @TestMetadata("changeThisSafeCallWithValue4Runtime.kt") + public void testChangeThisSafeCallWithValue4Runtime() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValue4Runtime.kt"); + doTest(fileName); + } + @TestMetadata("changeThisSafeCallWithValueRuntime.kt") public void testChangeThisSafeCallWithValueRuntime() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/changeThisSafeCallWithValueRuntime.kt");