diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/OptionalParametersHelper.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/OptionalParametersHelper.kt index 8760e974b17..a02e2744738 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/OptionalParametersHelper.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/OptionalParametersHelper.kt @@ -52,10 +52,14 @@ public object OptionalParametersHelper { val arguments = resolvedCall.getCall().getValueArguments() val argumentsToDrop = ArrayList() + //TODO: can drop arguments leaving last functional literal one (outside of parenthesis) + for (argument in arguments.reverse()) { - if (!canDrop(argument)) break // TODO: not correct because of named arguments - if (!argument.matchesDefault(resolvedCall, parameterToDefaultValue)) break // TODO: not correct because of named arguments + if (!canDrop(argument) || !argument.matchesDefault(resolvedCall, parameterToDefaultValue)) { + if (!argument.isNamed()) break else continue // for a named argument we can try to drop arguments before it as well + } + argumentsToDrop.add(argument) } return argumentsToDrop diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFixBase.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFixBase.kt index 8a5d5490ef0..40590e29d17 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFixBase.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFixBase.kt @@ -52,6 +52,7 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses import org.jetbrains.kotlin.resolve.calls.model.* import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns @@ -60,7 +61,8 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.JetType -import java.util.ArrayList +import org.jetbrains.kotlin.utils.addIfNotNull +import java.util.* //TODO: replacement of class usages //TODO: different replacements for property accessors @@ -179,7 +181,12 @@ public abstract class DeprecatedSymbolUsageFixBase( val originalParameter = parameter.getOriginal() val usages = expression.collectDescendantsOfType { it[PARAMETER_USAGE_KEY] == originalParameter } - usages.forEach { it.replace(argument.wrapped) } + usages.forEach { + if (argument.isNamed) { + (it.getParent() as? JetValueArgument)?.mark(MAKE_ARGUMENT_NAMED_KEY) + } + it.replace(argument.wrapped) + } //TODO: sometimes we need to add explicit type arguments here because we don't have expected type in the new context @@ -220,6 +227,9 @@ public abstract class DeprecatedSymbolUsageFixBase( ImportInsertHelper.getInstance(project).importDescriptor(file, descriptorToImport) } + // TODO: process introduced variables too + introduceNamedArguments(result) + //TODO: do this earlier dropArgumentsForDefaultValues(result) @@ -237,11 +247,14 @@ public abstract class DeprecatedSymbolUsageFixBase( } result = ShortenReferences({ ShortenReferences.Options(removeThis = true) }).process(result, shortenFilter) as JetExpression + // TODO: process introduced variables too simplifySpreadArrayOfArguments(result) + // TODO: process introduced variables too restoreFunctionLiteralArguments(result) // clean up user data + //TODO: not correct - we do not process introduced declarations before!!! result.forEachDescendantOfType { it.clear(USER_CODE_KEY) it.clear(PARAMETER_USAGE_KEY) @@ -250,6 +263,9 @@ public abstract class DeprecatedSymbolUsageFixBase( it.clear(DEFAULT_PARAMETER_VALUE_KEY) it.clear(WAS_FUNCTION_LITERAL_ARGUMENT_KEY) } + result.forEachDescendantOfType { + it.clear(MAKE_ARGUMENT_NAMED_KEY) + } return result } @@ -409,7 +425,8 @@ public abstract class DeprecatedSymbolUsageFixBase( val expression: JetExpression, val wrapped: JetExpression, val expressionType: JetType?, - val isDefaultValue: Boolean) + val isDefaultValue: Boolean = false, + val isNamed: Boolean = false) private fun argumentForParameter( parameter: ValueParameterDescriptor, @@ -427,7 +444,7 @@ public abstract class DeprecatedSymbolUsageFixBase( if (valueArgument is FunctionLiteralArgument) { expression.mark(WAS_FUNCTION_LITERAL_ARGUMENT_KEY) } - return Argument(expression, expression, bindingContext.getType(expression), isDefaultValue = false) + return Argument(expression, expression, bindingContext.getType(expression), isNamed = valueArgument.isNamed()) } is DefaultValueArgument -> { @@ -453,7 +470,7 @@ public abstract class DeprecatedSymbolUsageFixBase( val single = arguments.singleOrNull() if (single != null && single.getSpreadElement() != null) { val expression = single.getArgumentExpression()!!.marked(USER_CODE_KEY) - return Argument(expression, expression, bindingContext.getType(expression), isDefaultValue = false) + return Argument(expression, expression, bindingContext.getType(expression), isNamed = single.isNamed()) } val elementType = parameter.getVarargElementType()!! @@ -469,13 +486,42 @@ public abstract class DeprecatedSymbolUsageFixBase( } appendFixedText(")") } - return Argument(expression, expression, parameter.getType(), isDefaultValue = false) + return Argument(expression, expression, parameter.getType(), isNamed = single?.isNamed() ?: false) } else -> error("Unknown argument type: $resolvedArgument") } } + private fun introduceNamedArguments(result: JetExpression) { + val callsToProcess = LinkedHashSet() + result.forEachDescendantOfType { + if (it[MAKE_ARGUMENT_NAMED_KEY] && !it.isNamed()) { + val callExpression = (it.getParent() as? JetValueArgumentList)?.getParent() as? JetCallExpression + callsToProcess.addIfNotNull(callExpression) + } + } + + val psiFactory = JetPsiFactory(result) + + for (callExpression in callsToProcess) { + val bindingContext = callExpression.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = callExpression.getResolvedCall(bindingContext) ?: return + if (!resolvedCall.getStatus().isSuccess()) return + + val argumentsToMakeNamed = callExpression.getValueArguments().dropWhile { !it[MAKE_ARGUMENT_NAMED_KEY] } + for (argument in argumentsToMakeNamed) { + if (argument.isNamed()) continue + if (argument is JetFunctionLiteralArgument) continue + val argumentMatch = resolvedCall.getArgumentMapping(argument) as ArgumentMatch + val name = argumentMatch.valueParameter.getName().asString() + //TODO: not always correct for vararg's + val newArgument = psiFactory.createArgument(argument.getArgumentExpression()!!, name, argument.getSpreadElement() != null) + argument.replace(newArgument) + } + } + } + private fun dropArgumentsForDefaultValues(result: JetExpression) { val project = result.getProject() val newBindingContext = result.analyze() @@ -595,7 +641,8 @@ public abstract class DeprecatedSymbolUsageFixBase( if (callExpression.getFunctionLiteralArguments().isNotEmpty()) return val resolvedCall = callExpression.getResolvedCall(callExpression.analyze(BodyResolveMode.PARTIAL)) ?: return - val argumentMatch = resolvedCall.getArgumentMapping(argument) as? ArgumentMatch ?: return + if (!resolvedCall.getStatus().isSuccess()) return + val argumentMatch = resolvedCall.getArgumentMapping(argument) as ArgumentMatch if (argumentMatch.valueParameter != resolvedCall.getResultingDescriptor().getValueParameters().last()) return callExpressions.add(callExpression) @@ -607,20 +654,25 @@ public abstract class DeprecatedSymbolUsageFixBase( //TODO: making functions below private causes VerifyError fun PsiElement.get(key: Key): T? = getCopyableUserData(key) fun PsiElement.get(key: Key): Boolean = getCopyableUserData(key) != null - fun JetExpression.clear(key: Key) = putCopyableUserData(key, null) - fun JetExpression.put(key: Key, value: T) = putCopyableUserData(key, value) - fun JetExpression.mark(key: Key) = putCopyableUserData(key, Unit) - fun T.marked(key: Key): T { + fun JetElement.clear(key: Key) = putCopyableUserData(key, null) + fun JetElement.put(key: Key, value: T) = putCopyableUserData(key, value) + fun JetElement.mark(key: Key) = putCopyableUserData(key, Unit) + + fun T.marked(key: Key): T { putCopyableUserData(key, Unit) return this } + // keys below are used on expressions private val USER_CODE_KEY = Key("USER_CODE") private val PARAMETER_USAGE_KEY = Key("PARAMETER_USAGE") private val PARAMETER_VALUE_KEY = Key("PARAMETER_VALUE") private val RECEIVER_VALUE_KEY = Key("RECEIVER_VALUE") private val DEFAULT_PARAMETER_VALUE_KEY = Key("DEFAULT_PARAMETER_VALUE") private val WAS_FUNCTION_LITERAL_ARGUMENT_KEY = Key("WAS_FUNCTION_LITERAL_ARGUMENT") + + // this key is used on JetValueArgument + private val MAKE_ARGUMENT_NAMED_KEY = Key("MAKE_ARGUMENT_NAMED") } } diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/keepArgumentNamed.kt b/idea/testData/quickfix/deprecatedSymbolUsage/keepArgumentNamed.kt new file mode 100644 index 00000000000..dfee25ab3ba --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/keepArgumentNamed.kt @@ -0,0 +1,12 @@ +// "Replace with 'newFun(p1, p2, null, option)'" "true" + +interface I { + @deprecated("", ReplaceWith("newFun(p1, p2, null, option)")) + fun oldFun(p1: String, p2: Int, option: Int = 1) + + fun newFun(p1: String, p2New: Int, p3: String?, option: Int = 2) +} + +fun foo(i: I) { + i.oldFun("a", p2 = 0) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/keepArgumentNamed.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/keepArgumentNamed.kt.after new file mode 100644 index 00000000000..3f21239b0ab --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/keepArgumentNamed.kt.after @@ -0,0 +1,12 @@ +// "Replace with 'newFun(p1, p2, null, option)'" "true" + +interface I { + @deprecated("", ReplaceWith("newFun(p1, p2, null, option)")) + fun oldFun(p1: String, p2: Int, option: Int = 1) + + fun newFun(p1: String, p2New: Int, p3: String?, option: Int = 2) +} + +fun foo(i: I) { + i.newFun("a", p2New = 0, p3 = null, option = 1) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters/namedArgument.kt b/idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters/namedArgument.kt new file mode 100644 index 00000000000..73a6d82fe99 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters/namedArgument.kt @@ -0,0 +1,12 @@ +// "Replace with 'newFun(option1, option2, option3, null)'" "true" + +interface I { + @deprecated("", ReplaceWith("newFun(option1, option2, option3, null)")) + fun oldFun(option1: String = "", option2: Int = 0, option3: Int = -1) + + fun newFun(option1: String = "", option2: Int = 0, option3: Int = -1, option4: String? = "x") +} + +fun foo(i: I) { + i.oldFun(option2 = 1) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters/namedArgument.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters/namedArgument.kt.after new file mode 100644 index 00000000000..ec62a242b49 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters/namedArgument.kt.after @@ -0,0 +1,12 @@ +// "Replace with 'newFun(option1, option2, option3, null)'" "true" + +interface I { + @deprecated("", ReplaceWith("newFun(option1, option2, option3, null)")) + fun oldFun(option1: String = "", option2: Int = 0, option3: Int = -1) + + fun newFun(option1: String = "", option2: Int = 0, option3: Int = -1, option4: String? = "x") +} + +fun foo(i: I) { + i.newFun(option2 = 1, option4 = null) +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index ef0244f6c47..9fade75bff0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -2938,6 +2938,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("keepArgumentNamed.kt") + public void testKeepArgumentNamed() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/keepArgumentNamed.kt"); + doTest(fileName); + } + @TestMetadata("keepInfixCall.kt") public void testKeepInfixCall() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/keepInfixCall.kt"); @@ -3213,6 +3219,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); } + @TestMetadata("namedArgument.kt") + public void testNamedArgument() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters/namedArgument.kt"); + doTest(fileName); + } + @TestMetadata("optionalParameters1.kt") public void testOptionalParameters1() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters/optionalParameters1.kt");