diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt index 0a6fdd5cc3a..eb4e039b079 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt @@ -19,7 +19,11 @@ package org.jetbrains.kotlin.idea.quickfix import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.diagnostic.Logger import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.* +import org.jetbrains.kotlin.builtins.functions.FunctionClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.annotations.AnnotationsImpl +import org.jetbrains.kotlin.descriptors.annotations.BuiltInAnnotationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages @@ -41,15 +45,29 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentForExpression import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.typeUtil.isInterface -import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType -import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf -import org.jetbrains.kotlin.types.typeUtil.makeNullable +import org.jetbrains.kotlin.types.KotlinTypeFactory +import org.jetbrains.kotlin.types.TypeProjectionImpl +import org.jetbrains.kotlin.types.typeUtil.* import java.util.* //TODO: should use change signature to deal with cases of multiple overridden descriptors class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() { + private fun KotlinType.dropParameterNameAnnotations(): KotlinType { + return KotlinTypeFactory.simpleNotNullType(annotations, constructor.declarationDescriptor as ClassDescriptor, arguments.map { + TypeProjectionImpl( + it.projectionKind, + it.type.replaceAnnotations(AnnotationsImpl(it.type.annotations.filter { it !is BuiltInAnnotationDescriptor })) + ) + }) + } + + private fun KotlinType.reflectToRegularFunctionType(): KotlinType { + val isTypeAnnotatedWithExtensionFunctionType = annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.extensionFunctionType) != null + val parameterCount = if (isTypeAnnotatedWithExtensionFunctionType) arguments.size - 2 else arguments.size - 1 + return KotlinTypeFactory.simpleNotNullType(annotations, builtIns.getFunction(parameterCount), arguments) + } + override fun doCreateActions(diagnostic: Diagnostic): List { val actions = LinkedList() @@ -142,6 +160,22 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() { } } + fun addChangeTypeFix( + callable: D, + expressionType: KotlinType, + createFix: (D, KotlinType) -> KotlinQuickFixAction + ) { + val scope = callable.getResolutionScope(context, callable.getResolutionFacade()) + val typeToInsert = expressionType.approximateWithResolvableType(scope, false) + if (typeToInsert.constructor.declarationDescriptor?.getFunctionalClassKind() == FunctionClassDescriptor.Kind.KFunction) { + val reflectType = typeToInsert.dropParameterNameAnnotations() + actions.add(createFix(callable, reflectType.reflectToRegularFunctionType())) + actions.add(createFix(callable, reflectType)) + } else { + actions.add(createFix(callable, typeToInsert)) + } + } + // Property initializer type mismatch property type: val property = PsiTreeUtil.getParentOfType(diagnosticElement, KtProperty::class.java) if (property != null) { @@ -150,9 +184,7 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() { if (QuickFixUtil.canEvaluateTo(initializer, diagnosticElement) || getter != null && QuickFixUtil.canFunctionOrGetterReturnExpression(getter, diagnosticElement) ) { - val scope = property.getResolutionScope(context, property.getResolutionFacade()) - val typeToInsert = expressionType.approximateWithResolvableType(scope, false) - actions.add(ChangeVariableTypeFix(property, typeToInsert)) + addChangeTypeFix(property, expressionType, ::ChangeVariableTypeFix) } } @@ -165,9 +197,7 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() { else PsiTreeUtil.getParentOfType(diagnosticElement, KtFunction::class.java, true) if (function is KtFunction && QuickFixUtil.canFunctionOrGetterReturnExpression(function, diagnosticElement)) { - val scope = function.getResolutionScope(context, function.getResolutionFacade()) - val typeToInsert = expressionType.approximateWithResolvableType(scope, false) - actions.add(ChangeCallableReturnTypeFix.ForEnclosing(function, typeToInsert)) + addChangeTypeFix(function, expressionType, ChangeCallableReturnTypeFix::ForEnclosing) } // Fixing overloaded operators: diff --git a/idea/testData/quickfix/typeMismatch/functionExtensionType.kt b/idea/testData/quickfix/typeMismatch/functionExtensionType.kt new file mode 100644 index 00000000000..a040750ced1 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionExtensionType.kt @@ -0,0 +1,8 @@ +// "Change return type of enclosing function 'myFunction' to '(Int) -> Boolean'" "true" +// WITH_RUNTIME + +fun foo() { + fun myFunction(s: String): (String, Int) -> Boolean = s::verifyData +} + +fun String.verifyData(a: Int) = this.length > a \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/functionExtensionType.kt.after b/idea/testData/quickfix/typeMismatch/functionExtensionType.kt.after new file mode 100644 index 00000000000..4e3fb44789f --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionExtensionType.kt.after @@ -0,0 +1,8 @@ +// "Change return type of enclosing function 'myFunction' to '(Int) -> Boolean'" "true" +// WITH_RUNTIME + +fun foo() { + fun myFunction(s: String): (Int) -> Boolean = s::verifyData +} + +fun String.verifyData(a: Int) = this.length > a \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/functionNestedType.kt b/idea/testData/quickfix/typeMismatch/functionNestedType.kt new file mode 100644 index 00000000000..21ea6c02ca9 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionNestedType.kt @@ -0,0 +1,8 @@ +// "Change type of 'myFunction' to '(Int, (Int) -> Boolean) -> Boolean'" "true" +// WITH_RUNTIME + +fun foo() { + var myFunction: (Int, Int) -> Int = ::verifyData +} + +fun verifyData(a: Int, b: (Int) -> Boolean) = b(a) \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/functionNestedType.kt.after b/idea/testData/quickfix/typeMismatch/functionNestedType.kt.after new file mode 100644 index 00000000000..831b3619191 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionNestedType.kt.after @@ -0,0 +1,8 @@ +// "Change type of 'myFunction' to '(Int, (Int) -> Boolean) -> Boolean'" "true" +// WITH_RUNTIME + +fun foo() { + var myFunction: (Int, (Int) -> Boolean) -> Boolean = ::verifyData +} + +fun verifyData(a: Int, b: (Int) -> Boolean) = b(a) \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/functionNestedType2.kt b/idea/testData/quickfix/typeMismatch/functionNestedType2.kt new file mode 100644 index 00000000000..e2ab049ec98 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionNestedType2.kt @@ -0,0 +1,10 @@ +// "Change type of 'myFunction' to '(Int) -> KFunction0'" "true" +// WITH_RUNTIME + +fun foo() { + var myFunction: (Int, Int) -> Int = ::verifyData +} + +fun Int.internalVerifyData() = this > 0 + +fun verifyData(a: Int) = a::internalVerifyData \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/functionNestedType2.kt.after b/idea/testData/quickfix/typeMismatch/functionNestedType2.kt.after new file mode 100644 index 00000000000..44cbe0b55a7 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionNestedType2.kt.after @@ -0,0 +1,12 @@ +import kotlin.reflect.KFunction0 + +// "Change type of 'myFunction' to '(Int) -> KFunction0'" "true" +// WITH_RUNTIME + +fun foo() { + var myFunction: (Int) -> KFunction0 = ::verifyData +} + +fun Int.internalVerifyData() = this > 0 + +fun verifyData(a: Int) = a::internalVerifyData \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/functionReflectType.kt b/idea/testData/quickfix/typeMismatch/functionReflectType.kt new file mode 100644 index 00000000000..7ec4890a3e0 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionReflectType.kt @@ -0,0 +1,8 @@ +// "Change type of 'myFunction' to 'KFunction2'" "true" +// WITH_RUNTIME + +fun foo() { + var myFunction: (Int, Int) -> Int = ::verifyData +} + +fun verifyData(a: Int, b: Int) = (a > 10 && b > 10) \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/functionReflectType.kt.after b/idea/testData/quickfix/typeMismatch/functionReflectType.kt.after new file mode 100644 index 00000000000..fbc95d25667 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionReflectType.kt.after @@ -0,0 +1,10 @@ +import kotlin.reflect.KFunction2 + +// "Change type of 'myFunction' to 'KFunction2'" "true" +// WITH_RUNTIME + +fun foo() { + var myFunction: KFunction2 = ::verifyData +} + +fun verifyData(a: Int, b: Int) = (a > 10 && b > 10) \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/functionType.kt b/idea/testData/quickfix/typeMismatch/functionType.kt new file mode 100644 index 00000000000..f4414b9efef --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionType.kt @@ -0,0 +1,8 @@ +// "Change type of 'myFunction' to '(Int, Int) -> Boolean'" "true" +// WITH_RUNTIME + +fun foo() { + var myFunction: (Int, Int) -> Int = ::verifyData +} + +fun verifyData(a: Int, b: Int) = (a > 10 && b > 10) \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/functionType.kt.after b/idea/testData/quickfix/typeMismatch/functionType.kt.after new file mode 100644 index 00000000000..01dedfc4abc --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionType.kt.after @@ -0,0 +1,8 @@ +// "Change type of 'myFunction' to '(Int, Int) -> Boolean'" "true" +// WITH_RUNTIME + +fun foo() { + var myFunction: (Int, Int) -> Boolean = ::verifyData +} + +fun verifyData(a: Int, b: Int) = (a > 10 && b > 10) \ 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 dfe3e66cb84..55f5e66596d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -10858,6 +10858,31 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { runTest("idea/testData/quickfix/typeMismatch/expectedParameterTypeMismatchLongNameRuntime.kt"); } + @TestMetadata("functionExtensionType.kt") + public void testFunctionExtensionType() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/functionExtensionType.kt"); + } + + @TestMetadata("functionNestedType.kt") + public void testFunctionNestedType() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/functionNestedType.kt"); + } + + @TestMetadata("functionNestedType2.kt") + public void testFunctionNestedType2() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/functionNestedType2.kt"); + } + + @TestMetadata("functionReflectType.kt") + public void testFunctionReflectType() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/functionReflectType.kt"); + } + + @TestMetadata("functionType.kt") + public void testFunctionType() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/functionType.kt"); + } + @TestMetadata("hasNextFunctionReturnTypeMismatch.kt") public void testHasNextFunctionReturnTypeMismatch() throws Exception { runTest("idea/testData/quickfix/typeMismatch/hasNextFunctionReturnTypeMismatch.kt");