Anonymous function to lambda: add lambda parameter if type parameter is used, even if parameter is unused

#KT-39393 Fixed
This commit is contained in:
Toshiaki Kameyama
2020-06-05 10:47:42 +09:00
committed by igoriakovlev
parent 311b2d7969
commit e4a1c8dcef
8 changed files with 70 additions and 9 deletions
@@ -9,7 +9,6 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.core.getLastLambdaExpression
@@ -23,6 +22,8 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
import org.jetbrains.kotlin.resolve.calls.callUtil.getParameterForArgument
import org.jetbrains.kotlin.resolve.calls.components.isVararg
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class AnonymousFunctionToLambdaIntention : SelfTargetingRangeIntention<KtNamedFunction>(
@@ -46,10 +47,18 @@ class AnonymousFunctionToLambdaIntention : SelfTargetingRangeIntention<KtNamedFu
override fun applyTo(element: KtNamedFunction, editor: Editor?) {
val argument = element.getStrictParentOfType<KtValueArgument>() ?: return
val callElement = argument.getStrictParentOfType<KtCallElement>() ?: return
val functionParameterTypes = if (callElement.typeArgumentList == null) {
callElement.resolveToCall()?.getParameterForArgument(argument)?.original?.type?.arguments.orEmpty()
val typeParameterIndexes = if (callElement.typeArgumentList == null) {
val functionalType = callElement.resolveToCall()?.getParameterForArgument(argument)?.let {
if (it.isVararg) it.original.type.arguments.firstOrNull()?.type else it.original.type
}
val typeArguments = functionalType?.arguments?.let {
if (it.isNotEmpty()) it.dropLast(1) else it
}.orEmpty()
typeArguments.mapIndexedNotNull { index, typeProjection ->
if (typeProjection.type.isTypeParameter()) index else null
}.toSet()
} else {
emptyList()
emptySet()
}
val commentSaver = CommentSaver(element)
@@ -62,18 +71,17 @@ class AnonymousFunctionToLambdaIntention : SelfTargetingRangeIntention<KtNamedFu
val parameters = element.valueParameters
val needParameters =
parameters.count() > 1 || parameters.any { parameter -> ReferencesSearch.search(parameter, LocalSearchScope(body)).any() }
val needParameters = typeParameterIndexes.isNotEmpty() || parameters.count() > 1 || parameters.any { parameter ->
ReferencesSearch.search(parameter, LocalSearchScope(body)).any()
}
if (needParameters) {
parameters.forEachIndexed { index, parameter ->
if (index > 0) {
appendFixedText(",")
}
appendName(parameter.nameAsSafeName)
val needParameterType =
functionParameterTypes.getOrNull(index)?.type?.constructor?.declarationDescriptor is TypeParameterDescriptor
val typeReference = parameter.typeReference
if (needParameterType && typeReference != null) {
if (typeReference != null && index in typeParameterIndexes) {
appendFixedText(": ")
appendTypeReference(typeReference)
}
@@ -0,0 +1,6 @@
fun <T> foo(fn: (T) -> Unit) {}
fun test() {
foo(<caret>fun(x: String) {
})
}
@@ -0,0 +1,5 @@
fun <T> foo(fn: (T) -> Unit) {}
fun test() {
foo { x: String -> }
}
@@ -0,0 +1,6 @@
fun <T> foo(fn: (String) -> T) {}
fun test() {
foo(<caret>fun(x: String) {
})
}
@@ -0,0 +1,5 @@
fun <T> foo(fn: (String) -> T) {}
fun test() {
foo { }
}
@@ -0,0 +1,9 @@
fun foo(p: String) {}
fun <T> bar(vararg fn: (T) -> Unit) {}
fun test() {
bar(<caret>fun(x: String) {
foo(x)
})
}
@@ -0,0 +1,7 @@
fun foo(p: String) {}
fun <T> bar(vararg fn: (T) -> Unit) {}
fun test() {
bar({ x: String -> foo(x) })
}
@@ -2115,6 +2115,21 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
public void testTypeParameter3() throws Exception {
runTest("idea/testData/intentions/anonymousFunctionToLambda/typeParameter3.kt");
}
@TestMetadata("typeParameterWithUnusedParameter.kt")
public void testTypeParameterWithUnusedParameter() throws Exception {
runTest("idea/testData/intentions/anonymousFunctionToLambda/typeParameterWithUnusedParameter.kt");
}
@TestMetadata("typeParameterWithUnusedParameter2.kt")
public void testTypeParameterWithUnusedParameter2() throws Exception {
runTest("idea/testData/intentions/anonymousFunctionToLambda/typeParameterWithUnusedParameter2.kt");
}
@TestMetadata("typeParameterWithVararg.kt")
public void testTypeParameterWithVararg() throws Exception {
runTest("idea/testData/intentions/anonymousFunctionToLambda/typeParameterWithVararg.kt");
}
}
@TestMetadata("idea/testData/intentions/branched")