#KT-27670 Add quick fix: wrap expression in a lambda if compatible functional type is required (#2010)

This commit is contained in:
goodsauce
2018-12-28 04:12:10 +11:00
committed by Vyacheslav Gerasimov
parent ed8d9be483
commit 72be9ef738
13 changed files with 190 additions and 0 deletions
@@ -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)
}
}
@@ -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<KtExpression>(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<KtExpression>? {
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)
}
}
}
@@ -0,0 +1,6 @@
// "Surround with lambda" "true"
fun simple() {
str(<caret>"foo")
}
fun str(block: () -> String) {}
@@ -0,0 +1,6 @@
// "Surround with lambda" "true"
fun simple() {
str({ "foo" })
}
fun str(block: () -> String) {}
@@ -0,0 +1,6 @@
// "Surround with lambda" "true"
fun int() {
i(<caret>123)
}
fun i(block: () -> Long) {}
@@ -0,0 +1,6 @@
// "Surround with lambda" "true"
fun int() {
i({ 123 })
}
fun i(block: () -> Long) {}
@@ -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(<caret>nullableStr)
}
fun str(block: () -> String) {}
@@ -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) {}
@@ -0,0 +1,9 @@
// "Surround with lambda" "true"
fun subclass() {
base(<caret>Leaf())
}
fun base(base: () -> Base) {}
open class Base {}
class Leaf : Base()
@@ -0,0 +1,9 @@
// "Surround with lambda" "true"
fun subclass() {
base({ Leaf() })
}
fun base(base: () -> Base) {}
open class Base {}
class Leaf : Base()
@@ -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(<caret>Object())
}
fun str(block: () -> String) {}
@@ -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(<caret>null)
}
fun str(block: () -> String) {}
@@ -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");