"Add parameter to function" quick fix: apply for TYPE_MISMATCH error
#KT-8478 Fixed
This commit is contained in:
committed by
Dmitry Gridin
parent
636f5da883
commit
60f4ed914a
@@ -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<KtCallElement, Pair<FunctionDescriptor, Int>>() {
|
||||
override fun getElementOfInterest(diagnostic: Diagnostic): KtCallElement? {
|
||||
val (_, valueArgumentList) = diagnostic.valueArgument() ?: return null
|
||||
return valueArgumentList.getStrictParentOfType()
|
||||
}
|
||||
|
||||
override fun extractFixData(element: KtCallElement, diagnostic: Diagnostic): Pair<FunctionDescriptor, Int>? {
|
||||
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<FunctionDescriptor, Int>): 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<KtValueArgument, KtValueArgumentList>? {
|
||||
val element = DiagnosticFactory.cast(this, Errors.TYPE_MISMATCH).psiElement
|
||||
val valueArgument = element.getStrictParentOfType<KtValueArgument>() ?: return null
|
||||
val valueArgumentList = valueArgument.getStrictParentOfType<KtValueArgumentList>() ?: return null
|
||||
return valueArgument to valueArgumentList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+4
@@ -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)
|
||||
|
||||
+8
@@ -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(<caret>"", 2, 3, 4, 5)
|
||||
}
|
||||
+8
@@ -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(<caret>"", 2, 3, 4, 5)
|
||||
}
|
||||
+8
@@ -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, <caret>"", 3, 4, 5)
|
||||
}
|
||||
+8
@@ -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, <caret>"", 3, 4, 5)
|
||||
}
|
||||
+8
@@ -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, <caret>"", 4, 5)
|
||||
}
|
||||
+8
@@ -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, <caret>"", 4, 5)
|
||||
}
|
||||
+8
@@ -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, <caret>"", 5)
|
||||
}
|
||||
+8
@@ -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, <caret>"", 5)
|
||||
}
|
||||
+13
@@ -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(<caret>"", "", 3, 4, 5)
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user