From 72be9ef738498948bb79e8b6cc3dd73e3c3c7820 Mon Sep 17 00:00:00 2001 From: goodsauce Date: Fri, 28 Dec 2018 04:12:10 +1100 Subject: [PATCH] #KT-27670 Add quick fix: wrap expression in a lambda if compatible functional type is required (#2010) --- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 3 + .../idea/quickfix/SurroundWithLambdaFix.kt | 79 +++++++++++++++++++ .../typeMismatch/paramTypeLambdaMatch.kt | 6 ++ .../paramTypeLambdaMatch.kt.after | 6 ++ .../typeMismatch/paramTypeLambdaMatchInt.kt | 6 ++ .../paramTypeLambdaMatchInt.kt.after | 6 ++ .../paramTypeLambdaMatchNullable.kt | 8 ++ .../paramTypeLambdaMatchNullable.kt.after | 8 ++ .../paramTypeLambdaMatchSubclass.kt | 9 +++ .../paramTypeLambdaMatchSubclass.kt.after | 9 +++ .../typeMismatch/paramTypeLambdaMismatch.kt | 11 +++ .../paramTypeLambdaMismatchNull.kt | 9 +++ .../idea/quickfix/QuickFixTestGenerated.java | 30 +++++++ 13 files changed, 190 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/SurroundWithLambdaFix.kt create mode 100644 idea/testData/quickfix/typeMismatch/paramTypeLambdaMatch.kt create mode 100644 idea/testData/quickfix/typeMismatch/paramTypeLambdaMatch.kt.after create mode 100644 idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchInt.kt create mode 100644 idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchInt.kt.after create mode 100644 idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchNullable.kt create mode 100644 idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchNullable.kt.after create mode 100644 idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchSubclass.kt create mode 100644 idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchSubclass.kt.after create mode 100644 idea/testData/quickfix/typeMismatch/paramTypeLambdaMismatch.kt create mode 100644 idea/testData/quickfix/typeMismatch/paramTypeLambdaMismatchNull.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 511025742ad..f55e1d3a100 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -591,5 +591,8 @@ class QuickFixRegistrar : QuickFixContributor { DECLARATION_CANT_BE_INLINED.registerFactory(DeclarationCantBeInlinedFactory) ASSIGN_OPERATOR_AMBIGUITY.registerFactory(AssignOperatorAmbiguityFactory) + + TYPE_MISMATCH.registerFactory(SurroundWithLambdaFix) + CONSTANT_EXPECTED_TYPE_MISMATCH.registerFactory(SurroundWithLambdaFix) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/SurroundWithLambdaFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/SurroundWithLambdaFix.kt new file mode 100644 index 00000000000..88cede60eef --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/SurroundWithLambdaFix.kt @@ -0,0 +1,79 @@ +package org.jetbrains.kotlin.idea.quickfix + +import com.intellij.codeInsight.intention.HighPriorityAction +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.builtins.isFunctionType +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages +import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType +import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf +import org.jetbrains.kotlin.types.typeUtil.makeNotNullable + +class SurroundWithLambdaFix( + expression: KtExpression +) : KotlinQuickFixAction(expression), HighPriorityAction { + + override fun getFamilyName() = text + override fun getText() = "Surround with lambda" + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val nameReference = element ?: return + + val newExpression = KtPsiFactory(project).buildExpression { + appendFixedText("{ ") + appendExpression(nameReference) + appendFixedText(" }") + } + nameReference.replace(newExpression) + } + + companion object : KotlinSingleIntentionActionFactory() { + private val LOG = Logger.getInstance(SurroundWithLambdaFix::class.java) + + override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction? { + val diagnosticFactory = diagnostic.factory + val expectedType: KotlinType + val expressionType: KotlinType + when (diagnosticFactory) { + Errors.TYPE_MISMATCH -> { + val diagnosticWithParameters = Errors.TYPE_MISMATCH.cast(diagnostic) + expectedType = diagnosticWithParameters.a + expressionType = diagnosticWithParameters.b + } + Errors.CONSTANT_EXPECTED_TYPE_MISMATCH -> { + val context = (diagnostic.psiFile as KtFile).analyzeWithContent() + val diagnosticWithParameters = Errors.CONSTANT_EXPECTED_TYPE_MISMATCH.cast(diagnostic) + + val diagnosticElement = diagnostic.psiElement + if (!(diagnosticElement is KtExpression)) { + LOG.error("Unexpected element: " + diagnosticElement.text) + return null + } + expectedType = diagnosticWithParameters.b + expressionType = context.getType(diagnosticElement) ?: return null + } + else -> { + LOG.error("Unexpected diagnostic: " + DefaultErrorMessages.render(diagnostic)) + return null + } + } + + if (!expectedType.isFunctionType) return null + if (expectedType.arguments.size != 1) return null + val lambdaReturnType = expectedType.arguments[0].type + + if (!expressionType.makeNotNullable().isSubtypeOf(lambdaReturnType) && + !(expressionType.isPrimitiveNumberType() && lambdaReturnType.isPrimitiveNumberType()) + ) return null + + val diagnosticElement = diagnostic.psiElement as KtExpression + return SurroundWithLambdaFix(diagnosticElement) + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatch.kt b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatch.kt new file mode 100644 index 00000000000..54d819ff7ae --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatch.kt @@ -0,0 +1,6 @@ +// "Surround with lambda" "true" +fun simple() { + str("foo") +} + +fun str(block: () -> String) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatch.kt.after b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatch.kt.after new file mode 100644 index 00000000000..43ca5232464 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatch.kt.after @@ -0,0 +1,6 @@ +// "Surround with lambda" "true" +fun simple() { + str({ "foo" }) +} + +fun str(block: () -> String) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchInt.kt b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchInt.kt new file mode 100644 index 00000000000..a22a37d4ba0 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchInt.kt @@ -0,0 +1,6 @@ +// "Surround with lambda" "true" +fun int() { + i(123) +} + +fun i(block: () -> Long) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchInt.kt.after b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchInt.kt.after new file mode 100644 index 00000000000..7afffee0271 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchInt.kt.after @@ -0,0 +1,6 @@ +// "Surround with lambda" "true" +fun int() { + i({ 123 }) +} + +fun i(block: () -> Long) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchNullable.kt b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchNullable.kt new file mode 100644 index 00000000000..add09234135 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchNullable.kt @@ -0,0 +1,8 @@ +// "Surround with lambda" "true" +// ERROR: Type mismatch: inferred type is String? but String was expected +fun nullableFn() { + val nullableStr: String? = null + str(nullableStr) +} + +fun str(block: () -> String) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchNullable.kt.after b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchNullable.kt.after new file mode 100644 index 00000000000..33cf4b03116 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchNullable.kt.after @@ -0,0 +1,8 @@ +// "Surround with lambda" "true" +// ERROR: Type mismatch: inferred type is String? but String was expected +fun nullableFn() { + val nullableStr: String? = null + str({ nullableStr }) +} + +fun str(block: () -> String) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchSubclass.kt b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchSubclass.kt new file mode 100644 index 00000000000..415cf85479f --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchSubclass.kt @@ -0,0 +1,9 @@ +// "Surround with lambda" "true" +fun subclass() { + base(Leaf()) +} + +fun base(base: () -> Base) {} + +open class Base {} +class Leaf : Base() \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchSubclass.kt.after b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchSubclass.kt.after new file mode 100644 index 00000000000..5e6bec9367b --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchSubclass.kt.after @@ -0,0 +1,9 @@ +// "Surround with lambda" "true" +fun subclass() { + base({ Leaf() }) +} + +fun base(base: () -> Base) {} + +open class Base {} +class Leaf : Base() \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/paramTypeLambdaMismatch.kt b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMismatch.kt new file mode 100644 index 00000000000..5ec917598e8 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMismatch.kt @@ -0,0 +1,11 @@ +// "Surround with lambda" "false" +// ERROR: Type mismatch: inferred type is Object but () -> String was expected +// ACTION: Annotate constructor 'Object'... +// ACTION: Change parameter 'block' type of function 'str' to 'Object' +// ACTION: Create function 'str' +// ACTION: Edit method contract of 'Object' +fun fn() { + str(Object()) +} + +fun str(block: () -> String) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/paramTypeLambdaMismatchNull.kt b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMismatchNull.kt new file mode 100644 index 00000000000..d26c47c4b4c --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/paramTypeLambdaMismatchNull.kt @@ -0,0 +1,9 @@ +// "Surround with lambda" "false" +// ERROR: Null can not be a value of a non-null type () -> String +// ACTION: Change parameter 'block' type of function 'str' to '(() -> String)?' +// ACTION: Do not show hints for current method +fun nullFn() { + str(null) +} + +fun str(block: () -> String) {} \ 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 2d41b135199..cddbfc5227b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -12276,6 +12276,36 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { runTest("idea/testData/quickfix/typeMismatch/nullArgumentForNonNullParameter.kt"); } + @TestMetadata("paramTypeLambdaMatch.kt") + public void testParamTypeLambdaMatch() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/paramTypeLambdaMatch.kt"); + } + + @TestMetadata("paramTypeLambdaMatchInt.kt") + public void testParamTypeLambdaMatchInt() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchInt.kt"); + } + + @TestMetadata("paramTypeLambdaMatchNullable.kt") + public void testParamTypeLambdaMatchNullable() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchNullable.kt"); + } + + @TestMetadata("paramTypeLambdaMatchSubclass.kt") + public void testParamTypeLambdaMatchSubclass() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/paramTypeLambdaMatchSubclass.kt"); + } + + @TestMetadata("paramTypeLambdaMismatch.kt") + public void testParamTypeLambdaMismatch() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/paramTypeLambdaMismatch.kt"); + } + + @TestMetadata("paramTypeLambdaMismatchNull.kt") + public void testParamTypeLambdaMismatchNull() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/paramTypeLambdaMismatchNull.kt"); + } + @TestMetadata("parameterDefaultValue.kt") public void testParameterDefaultValue() throws Exception { runTest("idea/testData/quickfix/typeMismatch/parameterDefaultValue.kt");