From 60f4ed914a35dcd0b64ef1752d8b449708d813ac Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Sat, 7 Sep 2019 15:44:25 +0900 Subject: [PATCH] "Add parameter to function" quick fix: apply for TYPE_MISMATCH error #KT-8478 Fixed --- .../idea/quickfix/AddFunctionParametersFix.kt | 97 ++++++++++++++++--- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 1 + .../KotlinMutableMethodDescriptor.kt | 4 + .../addFunctionParameterForTypeMismatch1.kt | 8 ++ ...FunctionParameterForTypeMismatch1.kt.after | 8 ++ .../addFunctionParameterForTypeMismatch2.kt | 8 ++ ...FunctionParameterForTypeMismatch2.kt.after | 8 ++ .../addFunctionParameterForTypeMismatch3.kt | 8 ++ ...FunctionParameterForTypeMismatch3.kt.after | 8 ++ .../addFunctionParameterForTypeMismatch4.kt | 8 ++ ...FunctionParameterForTypeMismatch4.kt.after | 8 ++ .../addFunctionParameterForTypeMismatch5.kt | 13 +++ .../idea/quickfix/QuickFixTestGenerated.java | 25 +++++ 13 files changed, 189 insertions(+), 15 deletions(-) create mode 100644 idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch1.kt create mode 100644 idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch1.kt.after create mode 100644 idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch2.kt create mode 100644 idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch2.kt.after create mode 100644 idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch3.kt create mode 100644 idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch3.kt.after create mode 100644 idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch4.kt create mode 100644 idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch4.kt.after create mode 100644 idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch5.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunctionParametersFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunctionParametersFix.kt index 1e0095dfb31..67264111424 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunctionParametersFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunctionParametersFix.kt @@ -16,22 +16,26 @@ package org.jetbrains.kotlin.idea.quickfix +import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.search.searches.ReferencesSearch import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory +import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.core.CollectingNameValidator import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getDataFlowAwareTypes import org.jetbrains.kotlin.idea.refactoring.changeSignature.* -import org.jetbrains.kotlin.psi.KtCallElement -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.ValueArgument +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getCall +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.checker.KotlinTypeChecker @@ -40,7 +44,8 @@ import java.util.* class AddFunctionParametersFix( callElement: KtCallElement, functionDescriptor: FunctionDescriptor, - private val hasTypeMismatches: Boolean + private val hasTypeMismatches: Boolean, + private val argumentIndex: Int? = null ) : ChangeFunctionSignatureFix(callElement, functionDescriptor) { private val callElement: KtCallElement? get() = element as? KtCallElement @@ -65,10 +70,21 @@ class AddFunctionParametersFix( "function '$functionName'" } + val parameterOrdinal = argumentIndex?.let { + val number = (it + 1).toString() + val suffix = when { + number.endsWith("1") -> "st" + number.endsWith("2") -> "nd" + number.endsWith("3") -> "rd" + else -> "th" + } + "$number$suffix " + } ?: "" + return if (hasTypeMismatches) "Change the signature of $callableDescription" else - "Add parameter$subjectSuffix to $callableDescription" + "Add ${parameterOrdinal}parameter$subjectSuffix to $callableDescription" } override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean { @@ -77,6 +93,7 @@ class AddFunctionParametersFix( // newParametersCnt <= 0: psi for this quickfix is no longer valid val newParametersCnt = callElement.valueArguments.size - functionDescriptor.valueParameters.size + if (argumentIndex != null && newParametersCnt != 1) return false return newParametersCnt > 0 } @@ -90,11 +107,23 @@ class AddFunctionParametersFix( override fun configure(originalDescriptor: KotlinMethodDescriptor): KotlinMethodDescriptor { return originalDescriptor.modify(fun(descriptor: KotlinMutableMethodDescriptor) { val callElement = callElement ?: return - val call = callElement.getCall(callElement.analyze()) ?: return - val parameters = functionDescriptor.valueParameters val arguments = callElement.valueArguments + val parameters = functionDescriptor.valueParameters val validator = CollectingNameValidator() + if (argumentIndex != null) { + parameters.forEach { validator.addName(it.name.asString()) } + val argument = arguments[argumentIndex] + val parameterInfo = getNewParameterInfo( + originalDescriptor.baseDescriptor as FunctionDescriptor, + argument, + validator + ) + descriptor.addParameter(argumentIndex, parameterInfo) + return + } + + val call = callElement.getCall(callElement.analyze()) ?: return for (i in arguments.indices) { val argument = arguments[i] val expression = argument.getArgumentExpression() @@ -118,12 +147,6 @@ class AddFunctionParametersFix( argument, validator ) - parameterInfo.originalTypeInfo.type?.let { typesToShorten.add(it) } - - if (expression != null) { - parameterInfo.defaultValueForCall = expression - } - descriptor.addParameter(parameterInfo) } } @@ -145,8 +168,11 @@ class AddFunctionParametersFix( val name = getNewArgumentName(argument, validator) val expression = argument.getArgumentExpression() val type = expression?.let { getDataFlowAwareTypes(it).firstOrNull() } ?: functionDescriptor.builtIns.nullableAnyType - return KotlinParameterInfo(functionDescriptor, -1, name, KotlinTypeInfo(false, null)) - .apply { currentTypeInfo = KotlinTypeInfo(false, type) } + return KotlinParameterInfo(functionDescriptor, -1, name, KotlinTypeInfo(false, null)).apply { + currentTypeInfo = KotlinTypeInfo(false, type) + originalTypeInfo.type?.let { typesToShorten.add(it) } + if (expression != null) defaultValueForCall = expression + } } private fun hasOtherUsages(function: PsiElement): Boolean { @@ -157,4 +183,45 @@ class AddFunctionParametersFix( } private fun isConstructor() = functionDescriptor is ConstructorDescriptor + + companion object TypeMismatchFactory : KotlinSingleIntentionActionFactoryWithDelegate>() { + override fun getElementOfInterest(diagnostic: Diagnostic): KtCallElement? { + val (_, valueArgumentList) = diagnostic.valueArgument() ?: return null + return valueArgumentList.getStrictParentOfType() + } + + override fun extractFixData(element: KtCallElement, diagnostic: Diagnostic): Pair? { + val (valueArgument, valueArgumentList) = diagnostic.valueArgument() ?: return null + val arguments = valueArgumentList.arguments + val argumentIndex = arguments.indexOfFirst { it == valueArgument } + val context = element.analyze() + val functionDescriptor = element.getResolvedCall(context)?.resultingDescriptor as? FunctionDescriptor ?: return null + val parameters = functionDescriptor.valueParameters + if (arguments.size - 1 != parameters.size) return null + if ((arguments - valueArgument).zip(parameters).any { (argument, parameter) -> + val argumentType = argument.getArgumentExpression()?.let { context.getType(it) } + argumentType == null || !KotlinTypeChecker.DEFAULT.isSubtypeOf(argumentType, parameter.type) + }) return null + + return functionDescriptor to argumentIndex + } + + override fun createFix(originalElement: KtCallElement, data: Pair): IntentionAction? { + val (functionDescriptor, argumentIndex) = data + val parameters = functionDescriptor.valueParameters + val arguments = originalElement.valueArguments + return if (arguments.size > parameters.size) { + AddFunctionParametersFix(originalElement, functionDescriptor, false, argumentIndex) + } else { + null + } + } + + private fun Diagnostic.valueArgument(): Pair? { + val element = DiagnosticFactory.cast(this, Errors.TYPE_MISMATCH).psiElement + val valueArgument = element.getStrictParentOfType() ?: return null + val valueArgumentList = valueArgument.getStrictParentOfType() ?: return null + return valueArgument to valueArgumentList + } + } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 734b260aebd..90d787c5444 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -319,6 +319,7 @@ class QuickFixRegistrar : QuickFixContributor { TOO_MANY_ARGUMENTS.registerFactory(ChangeFunctionSignatureFix) NO_VALUE_FOR_PARAMETER.registerFactory(ChangeFunctionSignatureFix) + TYPE_MISMATCH.registerFactory(AddFunctionParametersFix.TypeMismatchFactory) UNUSED_PARAMETER.registerFactory(RemoveUnusedFunctionParameterFix) UNUSED_ANONYMOUS_PARAMETER.registerFactory(RenameToUnderscoreFix.Factory) UNUSED_ANONYMOUS_PARAMETER.registerFactory(RemoveSingleLambdaParameterFix) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinMutableMethodDescriptor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinMutableMethodDescriptor.kt index 12195bd95f6..b7faf7d6442 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinMutableMethodDescriptor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinMutableMethodDescriptor.kt @@ -32,6 +32,10 @@ class KotlinMutableMethodDescriptor(override val original: KotlinMethodDescripto fun addParameter(parameter: KotlinParameterInfo) { parameters.add(parameter) } + + fun addParameter(index: Int, parameter: KotlinParameterInfo) { + parameters.add(index, parameter) + } fun removeParameter(index: Int) { val paramInfo = parameters.removeAt(index) diff --git a/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch1.kt b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch1.kt new file mode 100644 index 00000000000..b0d530d4179 --- /dev/null +++ b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch1.kt @@ -0,0 +1,8 @@ +// "Add 1st parameter to function 'foo'" "true" +// DISABLE-ERRORS +fun foo(i1: Int, i2: Int, i3: Int, i4: Int) { +} + +fun test() { + foo("", 2, 3, 4, 5) +} \ No newline at end of file diff --git a/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch1.kt.after b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch1.kt.after new file mode 100644 index 00000000000..92ee6ee28e4 --- /dev/null +++ b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch1.kt.after @@ -0,0 +1,8 @@ +// "Add 1st parameter to function 'foo'" "true" +// DISABLE-ERRORS +fun foo(i11: String, i1: Int, i2: Int, i3: Int, i4: Int) { +} + +fun test() { + foo("", 2, 3, 4, 5) +} \ No newline at end of file diff --git a/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch2.kt b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch2.kt new file mode 100644 index 00000000000..d3561715121 --- /dev/null +++ b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch2.kt @@ -0,0 +1,8 @@ +// "Add 2nd parameter to function 'foo'" "true" +// DISABLE-ERRORS +fun foo(i1: Int, i2: Int, i3: Int, i4: Int) { +} + +fun test() { + foo(1, "", 3, 4, 5) +} \ No newline at end of file diff --git a/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch2.kt.after b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch2.kt.after new file mode 100644 index 00000000000..9c7920ae268 --- /dev/null +++ b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch2.kt.after @@ -0,0 +1,8 @@ +// "Add 2nd parameter to function 'foo'" "true" +// DISABLE-ERRORS +fun foo(i1: Int, i21: String, i2: Int, i3: Int, i4: Int) { +} + +fun test() { + foo(1, "", 3, 4, 5) +} \ No newline at end of file diff --git a/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch3.kt b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch3.kt new file mode 100644 index 00000000000..196d9fe4aa5 --- /dev/null +++ b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch3.kt @@ -0,0 +1,8 @@ +// "Add 3rd parameter to function 'foo'" "true" +// DISABLE-ERRORS +fun foo(i1: Int, i2: Int, i3: Int, i4: Int) { +} + +fun test() { + foo(1, 2, "", 4, 5) +} \ No newline at end of file diff --git a/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch3.kt.after b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch3.kt.after new file mode 100644 index 00000000000..f9253f50376 --- /dev/null +++ b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch3.kt.after @@ -0,0 +1,8 @@ +// "Add 3rd parameter to function 'foo'" "true" +// DISABLE-ERRORS +fun foo(i1: Int, i2: Int, i31: String, i3: Int, i4: Int) { +} + +fun test() { + foo(1, 2, "", 4, 5) +} \ No newline at end of file diff --git a/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch4.kt b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch4.kt new file mode 100644 index 00000000000..dfa9286f3b0 --- /dev/null +++ b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch4.kt @@ -0,0 +1,8 @@ +// "Add 4th parameter to function 'foo'" "true" +// DISABLE-ERRORS +fun foo(i1: Int, i2: Int, i3: Int, i4: Int) { +} + +fun test() { + foo(1, 2, 3, "", 5) +} \ No newline at end of file diff --git a/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch4.kt.after b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch4.kt.after new file mode 100644 index 00000000000..1b634f2f21e --- /dev/null +++ b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch4.kt.after @@ -0,0 +1,8 @@ +// "Add 4th parameter to function 'foo'" "true" +// DISABLE-ERRORS +fun foo(i1: Int, i2: Int, i3: Int, i41: String, i4: Int) { +} + +fun test() { + foo(1, 2, 3, "", 5) +} \ No newline at end of file diff --git a/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch5.kt b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch5.kt new file mode 100644 index 00000000000..3abe304d0fd --- /dev/null +++ b/idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch5.kt @@ -0,0 +1,13 @@ +// "Add 1st parameter to function 'foo'" "false" +// ACTION: Change parameter 'i1' type of function 'foo' to 'String' +// ACTION: Create function 'foo' +// ACTION: Do not show hints for current method +// ACTION: Put arguments on separate lines +// ACTION: To raw string literal +// DISABLE-ERRORS +fun foo(i1: Int, i2: Int, i3: Int, i4: Int) { +} + +fun test() { + foo("", "", 3, 4, 5) +} \ 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 aaf37d6962e..6cfdb4a62c9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -2138,6 +2138,31 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { runTest("idea/testData/quickfix/changeSignature/addFunctionParameterAndChangeTypes.kt"); } + @TestMetadata("addFunctionParameterForTypeMismatch1.kt") + public void testAddFunctionParameterForTypeMismatch1() throws Exception { + runTest("idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch1.kt"); + } + + @TestMetadata("addFunctionParameterForTypeMismatch2.kt") + public void testAddFunctionParameterForTypeMismatch2() throws Exception { + runTest("idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch2.kt"); + } + + @TestMetadata("addFunctionParameterForTypeMismatch3.kt") + public void testAddFunctionParameterForTypeMismatch3() throws Exception { + runTest("idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch3.kt"); + } + + @TestMetadata("addFunctionParameterForTypeMismatch4.kt") + public void testAddFunctionParameterForTypeMismatch4() throws Exception { + runTest("idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch4.kt"); + } + + @TestMetadata("addFunctionParameterForTypeMismatch5.kt") + public void testAddFunctionParameterForTypeMismatch5() throws Exception { + runTest("idea/testData/quickfix/changeSignature/addFunctionParameterForTypeMismatch5.kt"); + } + @TestMetadata("addFunctionParameterLongNameRuntime.kt") public void testAddFunctionParameterLongNameRuntime() throws Exception { runTest("idea/testData/quickfix/changeSignature/addFunctionParameterLongNameRuntime.kt");