[kotlin] ported ConvertExtensionToFunctionTypeFix to K2

^KTIJ-29490 fixed

GitOrigin-RevId: 38a2f2a95d12cf8b1d32c84b20c0f53a15d12f7a
This commit is contained in:
Frederik Haselmeier
2024-08-06 18:23:53 +00:00
committed by intellij-monorepo-bot
parent cf6c17e165
commit bd80926e8a
17 changed files with 209 additions and 66 deletions
@@ -0,0 +1,58 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.k2.codeinsight.fixes
import com.intellij.util.containers.addIfNotNull
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaFirDiagnostic
import org.jetbrains.kotlin.analysis.api.renderer.base.annotations.KaAnnotationRendererForSource
import org.jetbrains.kotlin.analysis.api.renderer.base.annotations.KaRendererAnnotationsFilter
import org.jetbrains.kotlin.analysis.api.renderer.types.KaTypeRenderer
import org.jetbrains.kotlin.analysis.api.renderer.types.impl.KaTypeRendererForSource
import org.jetbrains.kotlin.analysis.api.types.KaFunctionType
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinQuickFixFactory
import org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.types.Variance
internal object ConvertExtensionToFunctionTypeFixFactory {
/**
* Renders the [functionType] but moves a potential receiver type to be a parameter instead.
*/
@OptIn(KaExperimentalApi::class)
private fun KaSession.renderFunctionTypeWithoutReceiver(functionType: KaFunctionType, renderer: KaTypeRenderer): String {
val renderedParametersList = buildList {
addIfNotNull(functionType.receiverType)
addAll(functionType.parameterTypes)
}.joinToString(", ", "(", ")") {
it.render(renderer, Variance.INVARIANT)
}
val renderedReturnType = functionType.returnType.render(renderer, Variance.INVARIANT)
return "$renderedParametersList -> $renderedReturnType"
}
@OptIn(KaExperimentalApi::class)
private val shortNameRenderer = KaTypeRendererForSource.WITH_SHORT_NAMES.with {
annotationsRenderer = KaAnnotationRendererForSource.WITH_SHORT_NAMES.with {
annotationFilter = KaRendererAnnotationsFilter.NONE
}
}
@OptIn(KaExperimentalApi::class)
private fun KaSession.createQuickFix(typeReference: KtTypeReference): ConvertExtensionToFunctionTypeFix? {
val type = typeReference.type as? KaFunctionType ?: return null
if (!type.hasReceiver) return null
// We do not support rendering context receivers
if (type.contextReceivers.isNotEmpty()) return null
val shortName = renderFunctionTypeWithoutReceiver(type, shortNameRenderer)
val longName = renderFunctionTypeWithoutReceiver(type, KaTypeRendererForSource.WITH_QUALIFIED_NAMES)
return ConvertExtensionToFunctionTypeFix(typeReference, shortName, longName)
}
val superTypeIsExtensionFunctionType =
KotlinQuickFixFactory.ModCommandBased { diagnostic: KaFirDiagnostic.SupertypeIsExtensionFunctionType ->
val typeReference = diagnostic.psi as? KtTypeReference ?: return@ModCommandBased emptyList()
listOfNotNull(createQuickFix(typeReference))
}
}
@@ -442,6 +442,8 @@ class KotlinK2QuickFixRegistrar : KotlinQuickFixRegistrar() {
registerFactory(ChangeMemberFunctionSignatureFixFactory.nothingToOverrideFixFactory)
registerFactory(ReplaceJvmFieldWithConstFixFactory.inapplicableJvmField)
registerFactory(ConvertExtensionToFunctionTypeFixFactory.superTypeIsExtensionFunctionType)
}
private val optIn = KtQuickFixesListBuilder.registerPsiQuickFix {
@@ -10216,7 +10216,39 @@ public abstract class HighLevelQuickFixTestGenerated extends AbstractHighLevelQu
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../../idea/tests/testData/quickfix/superTypeIsExtensionType")
public static class SuperTypeIsExtensionType extends AbstractHighLevelQuickFixTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K2;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("typeWith1Argument.kt")
public void testTypeWith1Argument() throws Exception {
runTest("../../../idea/tests/testData/quickfix/superTypeIsExtensionType/typeWith1Argument.kt");
}
@TestMetadata("typeWithAnnotations.kt")
public void testTypeWithAnnotations() throws Exception {
runTest("../../../idea/tests/testData/quickfix/superTypeIsExtensionType/typeWithAnnotations.kt");
}
@TestMetadata("typeWithNoArgument.kt")
public void testTypeWithNoArgument() throws Exception {
runTest("../../../idea/tests/testData/quickfix/superTypeIsExtensionType/typeWithNoArgument.kt");
}
@TestMetadata("typeWithTypeArgument.kt")
public void testTypeWithTypeArgument() throws Exception {
runTest("../../../idea/tests/testData/quickfix/superTypeIsExtensionType/typeWithTypeArgument.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../../idea/tests/testData/quickfix/surroundWithNullCheck")
@@ -0,0 +1,33 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.modcommand.Presentation
import com.intellij.modcommand.PsiUpdateModCommandAction
import org.jetbrains.kotlin.idea.base.codeInsight.ShortenReferencesFacility
import org.jetbrains.kotlin.idea.base.psi.replaced
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtTypeReference
class ConvertExtensionToFunctionTypeFix(
element: KtTypeReference,
private val targetTypeStringShort: String,
private val targetTypeStringLong: String
) : PsiUpdateModCommandAction<KtTypeReference>(element) {
override fun getPresentation(context: ActionContext, element: KtTypeReference): Presentation? {
return Presentation.of(KotlinBundle.message("convert.supertype.to.0", targetTypeStringShort))
}
override fun getFamilyName(): String = KotlinBundle.message("convert.extension.function.type.to.regular.function.type")
override fun invoke(
context: ActionContext,
element: KtTypeReference,
updater: ModPsiUpdater
) {
val replaced = element.replaced(KtPsiFactory(context.project).createType(targetTypeStringLong))
ShortenReferencesFacility.getInstance().shorten(replaced)
}
}
@@ -1,58 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.base.psi.replaced
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
class ConvertExtensionToFunctionTypeFix(element: KtTypeReference, type: KotlinType) : KotlinQuickFixAction<KtTypeReference>(element) {
private val targetTypeStringShort = type.renderType(IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS)
private val targetTypeStringLong = type.renderType(IdeDescriptorRenderers.SOURCE_CODE)
override fun getText() = KotlinBundle.message("convert.supertype.to.0", targetTypeStringShort)
override fun getFamilyName() = KotlinBundle.message("convert.extension.function.type.to.regular.function.type")
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return
val replaced = element.replaced(KtPsiFactory(project).createType(targetTypeStringLong))
ShortenReferences.DEFAULT.process(replaced)
}
private fun KotlinType.renderType(renderer: DescriptorRenderer) = buildString {
append('(')
arguments.dropLast(1).joinTo(this@buildString, ", ") { renderer.renderType(it.type) }
append(") -> ")
append(renderer.renderType(this@renderType.getReturnTypeFromFunctionType()))
}
companion object Factory : KotlinIntentionActionsFactory() {
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val casted = Errors.SUPERTYPE_IS_EXTENSION_FUNCTION_TYPE.cast(diagnostic)
val element = casted.psiElement
val type = element.analyze(BodyResolveMode.PARTIAL).get(BindingContext.TYPE, element) ?: return emptyList()
if (!type.isExtensionFunctionType) return emptyList()
return listOf(ConvertExtensionToFunctionTypeFix(element, type))
}
}
}
@@ -0,0 +1,37 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
object ConvertExtensionToFunctionTypeFixFactory : KotlinIntentionActionsFactory() {
private fun KotlinType.renderType(renderer: DescriptorRenderer) = buildString {
append('(')
arguments.dropLast(1).joinTo(this@buildString, ", ") { renderer.renderType(it.type) }
append(") -> ")
append(renderer.renderType(this@renderType.getReturnTypeFromFunctionType()))
}
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val casted = Errors.SUPERTYPE_IS_EXTENSION_FUNCTION_TYPE.cast(diagnostic)
val element = casted.psiElement
val type = element.analyze(BodyResolveMode.PARTIAL).get(BindingContext.TYPE, element) ?: return emptyList()
if (!type.isExtensionFunctionType) return emptyList()
val targetTypeStringShort = type.renderType(IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS)
val targetTypeStringLong = type.renderType(IdeDescriptorRenderers.SOURCE_CODE)
return listOf(ConvertExtensionToFunctionTypeFix(element, targetTypeStringShort, targetTypeStringLong).asIntention())
}
}
@@ -577,7 +577,7 @@ class QuickFixRegistrar : QuickFixContributor {
UNRESOLVED_REFERENCE.registerFactory(MakeConstructorParameterPropertyFix)
DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE.registerFactory(SpecifyOverrideExplicitlyFix)
SUPERTYPE_IS_EXTENSION_FUNCTION_TYPE.registerFactory(ConvertExtensionToFunctionTypeFix)
SUPERTYPE_IS_EXTENSION_FUNCTION_TYPE.registerFactory(ConvertExtensionToFunctionTypeFixFactory)
UNUSED_LAMBDA_EXPRESSION.registerFactory(AddRunToLambdaFix)
@@ -15834,6 +15834,11 @@ public abstract class K1QuickFixTestGenerated extends AbstractK1QuickFixTest {
runTest("testData/quickfix/superTypeIsExtensionType/typeWith1Argument.kt");
}
@TestMetadata("typeWithAnnotations.kt")
public void testTypeWithAnnotations() throws Exception {
runTest("testData/quickfix/superTypeIsExtensionType/typeWithAnnotations.kt");
}
@TestMetadata("typeWithNoArgument.kt")
public void testTypeWithNoArgument() throws Exception {
runTest("testData/quickfix/superTypeIsExtensionType/typeWithNoArgument.kt");
@@ -4,4 +4,5 @@ class Foo : <caret>String.(String) -> Unit {
override fun invoke(p1: String, p2: String) {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
@@ -4,4 +4,5 @@ class Foo : <caret>(String, String) -> Unit {
override fun invoke(p1: String, p2: String) {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
@@ -0,0 +1,14 @@
// "Convert supertype to '(String, T) -> Unit'" "true"
@Target(AnnotationTarget.TYPE)
annotation class TestA
@Target(AnnotationTarget.TYPE)
annotation class TestB
@Target(AnnotationTarget.TYPE)
annotation class TestC
class Foo<T> : (@TestA <caret>String).(@TestB T) -> @TestC Unit {
override fun invoke(p1: String, p2: T) {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
@@ -0,0 +1,14 @@
// "Convert supertype to '(String, T) -> Unit'" "true"
@Target(AnnotationTarget.TYPE)
annotation class TestA
@Target(AnnotationTarget.TYPE)
annotation class TestB
@Target(AnnotationTarget.TYPE)
annotation class TestC
class Foo<T> : (@TestA String, @TestB T) -> @TestC Unit {
override fun invoke(p1: String, p2: T) {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
@@ -4,4 +4,5 @@ class Foo : <caret>String.() -> Unit {
override fun invoke(p1: String) {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
@@ -4,4 +4,5 @@ class Foo : <caret>(String) -> Unit {
override fun invoke(p1: String) {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
@@ -4,4 +4,5 @@ class Foo<T> : <caret>String.(T) -> Unit {
override fun invoke(p1: String, p2: T) {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
@@ -4,4 +4,5 @@ class Foo<T> : <caret>(String, T) -> Unit {
override fun invoke(p1: String, p2: T) {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ConvertExtensionToFunctionTypeFix
@@ -186,7 +186,7 @@ internal fun MutableTWorkspace.generateK2FixTests() {
model("$idea/quickfix/specifyOverrideExplicitly", pattern = pattern)
model("$idea/quickfix/specifySuperExplicitly", pattern = pattern, isIgnored = true)
model("$idea/quickfix/specifyTypeExplicitly", pattern = pattern, isIgnored = true)
model("$idea/quickfix/superTypeIsExtensionType", pattern = pattern, isIgnored = true)
model("$idea/quickfix/superTypeIsExtensionType", pattern = pattern)
model("$idea/quickfix/surroundWithNullCheck", pattern = pattern)
model("$idea/quickfix/suspiciousCollectionReassignment", pattern = pattern, isIgnored = true)
model("$idea/quickfix/tooLongCharLiteralToString", pattern = pattern)