"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
|
package org.jetbrains.kotlin.idea.quickfix
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.intention.IntentionAction
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
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.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
|
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
|
||||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getDataFlowAwareTypes
|
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getDataFlowAwareTypes
|
||||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
|
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
|
||||||
import org.jetbrains.kotlin.psi.KtCallElement
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import org.jetbrains.kotlin.psi.ValueArgument
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
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.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
|
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.resolve.descriptorUtil.builtIns
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||||
@@ -40,7 +44,8 @@ import java.util.*
|
|||||||
class AddFunctionParametersFix(
|
class AddFunctionParametersFix(
|
||||||
callElement: KtCallElement,
|
callElement: KtCallElement,
|
||||||
functionDescriptor: FunctionDescriptor,
|
functionDescriptor: FunctionDescriptor,
|
||||||
private val hasTypeMismatches: Boolean
|
private val hasTypeMismatches: Boolean,
|
||||||
|
private val argumentIndex: Int? = null
|
||||||
) : ChangeFunctionSignatureFix(callElement, functionDescriptor) {
|
) : ChangeFunctionSignatureFix(callElement, functionDescriptor) {
|
||||||
private val callElement: KtCallElement?
|
private val callElement: KtCallElement?
|
||||||
get() = element as? KtCallElement
|
get() = element as? KtCallElement
|
||||||
@@ -65,10 +70,21 @@ class AddFunctionParametersFix(
|
|||||||
"function '$functionName'"
|
"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)
|
return if (hasTypeMismatches)
|
||||||
"Change the signature of $callableDescription"
|
"Change the signature of $callableDescription"
|
||||||
else
|
else
|
||||||
"Add parameter$subjectSuffix to $callableDescription"
|
"Add ${parameterOrdinal}parameter$subjectSuffix to $callableDescription"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean {
|
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
|
// newParametersCnt <= 0: psi for this quickfix is no longer valid
|
||||||
val newParametersCnt = callElement.valueArguments.size - functionDescriptor.valueParameters.size
|
val newParametersCnt = callElement.valueArguments.size - functionDescriptor.valueParameters.size
|
||||||
|
if (argumentIndex != null && newParametersCnt != 1) return false
|
||||||
return newParametersCnt > 0
|
return newParametersCnt > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,11 +107,23 @@ class AddFunctionParametersFix(
|
|||||||
override fun configure(originalDescriptor: KotlinMethodDescriptor): KotlinMethodDescriptor {
|
override fun configure(originalDescriptor: KotlinMethodDescriptor): KotlinMethodDescriptor {
|
||||||
return originalDescriptor.modify(fun(descriptor: KotlinMutableMethodDescriptor) {
|
return originalDescriptor.modify(fun(descriptor: KotlinMutableMethodDescriptor) {
|
||||||
val callElement = callElement ?: return
|
val callElement = callElement ?: return
|
||||||
val call = callElement.getCall(callElement.analyze()) ?: return
|
|
||||||
val parameters = functionDescriptor.valueParameters
|
|
||||||
val arguments = callElement.valueArguments
|
val arguments = callElement.valueArguments
|
||||||
|
val parameters = functionDescriptor.valueParameters
|
||||||
val validator = CollectingNameValidator()
|
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) {
|
for (i in arguments.indices) {
|
||||||
val argument = arguments[i]
|
val argument = arguments[i]
|
||||||
val expression = argument.getArgumentExpression()
|
val expression = argument.getArgumentExpression()
|
||||||
@@ -118,12 +147,6 @@ class AddFunctionParametersFix(
|
|||||||
argument,
|
argument,
|
||||||
validator
|
validator
|
||||||
)
|
)
|
||||||
parameterInfo.originalTypeInfo.type?.let { typesToShorten.add(it) }
|
|
||||||
|
|
||||||
if (expression != null) {
|
|
||||||
parameterInfo.defaultValueForCall = expression
|
|
||||||
}
|
|
||||||
|
|
||||||
descriptor.addParameter(parameterInfo)
|
descriptor.addParameter(parameterInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,8 +168,11 @@ class AddFunctionParametersFix(
|
|||||||
val name = getNewArgumentName(argument, validator)
|
val name = getNewArgumentName(argument, validator)
|
||||||
val expression = argument.getArgumentExpression()
|
val expression = argument.getArgumentExpression()
|
||||||
val type = expression?.let { getDataFlowAwareTypes(it).firstOrNull() } ?: functionDescriptor.builtIns.nullableAnyType
|
val type = expression?.let { getDataFlowAwareTypes(it).firstOrNull() } ?: functionDescriptor.builtIns.nullableAnyType
|
||||||
return KotlinParameterInfo(functionDescriptor, -1, name, KotlinTypeInfo(false, null))
|
return KotlinParameterInfo(functionDescriptor, -1, name, KotlinTypeInfo(false, null)).apply {
|
||||||
.apply { currentTypeInfo = KotlinTypeInfo(false, type) }
|
currentTypeInfo = KotlinTypeInfo(false, type)
|
||||||
|
originalTypeInfo.type?.let { typesToShorten.add(it) }
|
||||||
|
if (expression != null) defaultValueForCall = expression
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hasOtherUsages(function: PsiElement): Boolean {
|
private fun hasOtherUsages(function: PsiElement): Boolean {
|
||||||
@@ -157,4 +183,45 @@ class AddFunctionParametersFix(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isConstructor() = functionDescriptor is ConstructorDescriptor
|
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)
|
TOO_MANY_ARGUMENTS.registerFactory(ChangeFunctionSignatureFix)
|
||||||
NO_VALUE_FOR_PARAMETER.registerFactory(ChangeFunctionSignatureFix)
|
NO_VALUE_FOR_PARAMETER.registerFactory(ChangeFunctionSignatureFix)
|
||||||
|
TYPE_MISMATCH.registerFactory(AddFunctionParametersFix.TypeMismatchFactory)
|
||||||
UNUSED_PARAMETER.registerFactory(RemoveUnusedFunctionParameterFix)
|
UNUSED_PARAMETER.registerFactory(RemoveUnusedFunctionParameterFix)
|
||||||
UNUSED_ANONYMOUS_PARAMETER.registerFactory(RenameToUnderscoreFix.Factory)
|
UNUSED_ANONYMOUS_PARAMETER.registerFactory(RenameToUnderscoreFix.Factory)
|
||||||
UNUSED_ANONYMOUS_PARAMETER.registerFactory(RemoveSingleLambdaParameterFix)
|
UNUSED_ANONYMOUS_PARAMETER.registerFactory(RemoveSingleLambdaParameterFix)
|
||||||
|
|||||||
+4
@@ -32,6 +32,10 @@ class KotlinMutableMethodDescriptor(override val original: KotlinMethodDescripto
|
|||||||
fun addParameter(parameter: KotlinParameterInfo) {
|
fun addParameter(parameter: KotlinParameterInfo) {
|
||||||
parameters.add(parameter)
|
parameters.add(parameter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addParameter(index: Int, parameter: KotlinParameterInfo) {
|
||||||
|
parameters.add(index, parameter)
|
||||||
|
}
|
||||||
|
|
||||||
fun removeParameter(index: Int) {
|
fun removeParameter(index: Int) {
|
||||||
val paramInfo = parameters.removeAt(index)
|
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");
|
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")
|
@TestMetadata("addFunctionParameterLongNameRuntime.kt")
|
||||||
public void testAddFunctionParameterLongNameRuntime() throws Exception {
|
public void testAddFunctionParameterLongNameRuntime() throws Exception {
|
||||||
runTest("idea/testData/quickfix/changeSignature/addFunctionParameterLongNameRuntime.kt");
|
runTest("idea/testData/quickfix/changeSignature/addFunctionParameterLongNameRuntime.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user