#KT-27670 Add quick fix: wrap expression in a lambda if compatible functional type is required (#2010)
This commit is contained in:
committed by
Vyacheslav Gerasimov
parent
ed8d9be483
commit
72be9ef738
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user