DeprecatedSymbolUsageFix: keeping named arguments

This commit is contained in:
Valentin Kipyatkov
2015-05-22 16:23:17 +03:00
parent 0c397548e8
commit 2e2701aa61
7 changed files with 129 additions and 13 deletions
@@ -52,10 +52,14 @@ public object OptionalParametersHelper {
val arguments = resolvedCall.getCall().getValueArguments()
val argumentsToDrop = ArrayList<ValueArgument>()
//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
@@ -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<JetExpression> { 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<JetExpression> {
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<JetValueArgument> {
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<JetCallExpression>()
result.forEachDescendantOfType<JetValueArgument> {
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 <T: Any> PsiElement.get(key: Key<T>): T? = getCopyableUserData(key)
fun PsiElement.get(key: Key<Unit>): Boolean = getCopyableUserData(key) != null
fun <T: Any> JetExpression.clear(key: Key<T>) = putCopyableUserData(key, null)
fun <T: Any> JetExpression.put(key: Key<T>, value: T) = putCopyableUserData(key, value)
fun JetExpression.mark(key: Key<Unit>) = putCopyableUserData(key, Unit)
fun <T: JetExpression> T.marked(key: Key<Unit>): T {
fun <T: Any> JetElement.clear(key: Key<T>) = putCopyableUserData(key, null)
fun <T: Any> JetElement.put(key: Key<T>, value: T) = putCopyableUserData(key, value)
fun JetElement.mark(key: Key<Unit>) = putCopyableUserData(key, Unit)
fun <T: JetElement> T.marked(key: Key<Unit>): T {
putCopyableUserData(key, Unit)
return this
}
// keys below are used on expressions
private val USER_CODE_KEY = Key<Unit>("USER_CODE")
private val PARAMETER_USAGE_KEY = Key<ValueParameterDescriptor>("PARAMETER_USAGE")
private val PARAMETER_VALUE_KEY = Key<ValueParameterDescriptor>("PARAMETER_VALUE")
private val RECEIVER_VALUE_KEY = Key<Unit>("RECEIVER_VALUE")
private val DEFAULT_PARAMETER_VALUE_KEY = Key<Unit>("DEFAULT_PARAMETER_VALUE")
private val WAS_FUNCTION_LITERAL_ARGUMENT_KEY = Key<Unit>("WAS_FUNCTION_LITERAL_ARGUMENT")
// this key is used on JetValueArgument
private val MAKE_ARGUMENT_NAMED_KEY = Key<Unit>("MAKE_ARGUMENT_NAMED")
}
}
@@ -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.<caret>oldFun("a", p2 = 0)
}
@@ -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.<caret>newFun("a", p2New = 0, p3 = null, option = 1)
}
@@ -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.<caret>oldFun(option2 = 1)
}
@@ -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.<caret>newFun(option2 = 1, option4 = null)
}
@@ -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");