From f6fe490e427bd5df8059dbfad6f24bfcd59f3aa7 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Thu, 15 Feb 2024 16:33:47 +0100 Subject: [PATCH] [kotlin] k2: port SpecifyExplicitLambdaSignatureIntention - parameter names in functional types are not supported yet (KT-65846) ^KTIJ-27431 fixed GitOrigin-RevId: efd9a1fdcafb98db5b43600acb47f1f19e7c9153 --- .../kotlin.code-insight.intentions.k2.xml | 7 ++ ...SpecifyExplicitLambdaSignatureIntention.kt | 59 ++++++++++ .../tests/K2IntentionTestGenerated.java | 105 +++++++++++++++++- .../.k2Intention | 1 + .../lambdaWithLambdaAsParam.kt | 2 + .../lambdaWithLambdaAsParam.kt.after | 2 + .../util/ExplicitLambdaSignatureUtil.kt | 62 +++++++++++ .../codeinsight/GenerateK2IntentionTests.kt | 2 +- 8 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/SpecifyExplicitLambdaSignatureIntention.kt create mode 100644 plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/.k2Intention create mode 100644 plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/util/ExplicitLambdaSignatureUtil.kt diff --git a/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions.k2.xml b/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions.k2.xml index 901f7cd5b750..a77a6fc39c07 100644 --- a/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions.k2.xml +++ b/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions.k2.xml @@ -30,6 +30,13 @@ group.names.kotlin + + kotlin + org.jetbrains.kotlin.idea.k2.codeinsight.intentions.SpecifyExplicitLambdaSignatureIntention + messages.KotlinBundle + group.names.kotlin + + kotlin org.jetbrains.kotlin.idea.k2.codeinsight.intentions.AddPropertyAccessorsIntention diff --git a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/SpecifyExplicitLambdaSignatureIntention.kt b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/SpecifyExplicitLambdaSignatureIntention.kt new file mode 100644 index 000000000000..0a7e524aa37f --- /dev/null +++ b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/SpecifyExplicitLambdaSignatureIntention.kt @@ -0,0 +1,59 @@ +// 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.intentions + +import com.intellij.codeInspection.util.IntentionFamilyName +import com.intellij.codeInspection.util.IntentionName +import com.intellij.modcommand.ModPsiUpdater +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtAnonymousFunctionSymbol +import org.jetbrains.kotlin.analysis.api.types.KtErrorType +import org.jetbrains.kotlin.analysis.api.types.KtFunctionalType +import org.jetbrains.kotlin.idea.base.psi.shouldLambdaParameterBeNamed +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.AbstractKotlinModCommandWithContext +import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.AnalysisActionContext +import org.jetbrains.kotlin.idea.codeinsight.api.applicators.KotlinApplicabilityRange +import org.jetbrains.kotlin.idea.codeinsight.api.applicators.applicabilityRange +import org.jetbrains.kotlin.idea.codeinsight.utils.NamedArgumentUtils +import org.jetbrains.kotlin.idea.codeinsights.impl.base.applicators.ApplicabilityRanges +import org.jetbrains.kotlin.idea.k2.refactoring.util.LambdaToAnonymousFunctionUtil +import org.jetbrains.kotlin.idea.k2.refactoring.util.getExplicitLambdaSignature +import org.jetbrains.kotlin.idea.k2.refactoring.util.specifyExplicitLambdaSignature +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtCallElement +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.KtLambdaArgument +import org.jetbrains.kotlin.psi.KtLambdaExpression +import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType + +internal class SpecifyExplicitLambdaSignatureIntention: AbstractKotlinModCommandWithContext( + KtLambdaExpression::class +) { + + override fun getFamilyName(): @IntentionFamilyName String = KotlinBundle.message("specify.explicit.lambda.signature") + + override fun getActionName(element: KtLambdaExpression, context: String): @IntentionName String = familyName + + context(KtAnalysisSession) + override fun prepareContext(element: KtLambdaExpression): String? { + return getExplicitLambdaSignature(element) + } + + override fun getApplicabilityRange(): KotlinApplicabilityRange { + return ApplicabilityRanges.SELF + } + + override fun isApplicableByPsi(element: KtLambdaExpression): Boolean { + return element.functionLiteral.arrow == null || !element.valueParameters.all { it.typeReference != null } + } + + override fun apply( + element: KtLambdaExpression, + context: AnalysisActionContext, + updater: ModPsiUpdater + ) { + specifyExplicitLambdaSignature(element, context.analyzeContext) + } +} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java b/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java index c8e58cf8a525..fcc151b4cc03 100644 --- a/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java +++ b/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java @@ -5351,6 +5351,109 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest { } } + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature") + public static class SpecifyExplicitLambdaSignature extends AbstractK2IntentionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("anonymous.kt") + public void testAnonymous() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/anonymous.kt"); + } + + @TestMetadata("coercionToUnit.kt") + public void testCoercionToUnit() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/coercionToUnit.kt"); + } + + @TestMetadata("cursorOnRightBrace.kt") + public void testCursorOnRightBrace() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/cursorOnRightBrace.kt"); + } + + @TestMetadata("definitelyNonNullTypeMultipleParam.kt") + public void testDefinitelyNonNullTypeMultipleParam() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/definitelyNonNullTypeMultipleParam.kt"); + } + + @TestMetadata("definitelyNonNullTypeSingleParam.kt") + public void testDefinitelyNonNullTypeSingleParam() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/definitelyNonNullTypeSingleParam.kt"); + } + + @TestMetadata("delegate.kt") + public void testDelegate() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/delegate.kt"); + } + + @TestMetadata("destructuring.kt") + public void testDestructuring() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/destructuring.kt"); + } + + @TestMetadata("emptyParamListWithBrackets.kt") + public void testEmptyParamListWithBrackets() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/emptyParamListWithBrackets.kt"); + } + + @TestMetadata("emptyParamListWithIt.kt") + public void testEmptyParamListWithIt() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/emptyParamListWithIt.kt"); + } + + @TestMetadata("emptyParamListWithWhiteSpace.kt") + public void testEmptyParamListWithWhiteSpace() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/emptyParamListWithWhiteSpace.kt"); + } + + @TestMetadata("emptyParamListWithoutItWithArrow.kt") + public void testEmptyParamListWithoutItWithArrow() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/emptyParamListWithoutItWithArrow.kt"); + } + + @TestMetadata("lambdaWithLambdaAsParam.kt") + public void testLambdaWithLambdaAsParam() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/lambdaWithLambdaAsParam.kt"); + } + + @TestMetadata("manyNewlines.kt") + public void testManyNewlines() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/manyNewlines.kt"); + } + + @TestMetadata("multipleParam.kt") + public void testMultipleParam() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/multipleParam.kt"); + } + + @TestMetadata("noParameters.kt") + public void testNoParameters() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/noParameters.kt"); + } + + @TestMetadata("shortenReferencesForParams.kt") + public void testShortenReferencesForParams() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/shortenReferencesForParams.kt"); + } + + @TestMetadata("singleParam.kt") + public void testSingleParam() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/singleParam.kt"); + } + + @TestMetadata("typesAlreadyExplicit.kt") + public void testTypesAlreadyExplicit() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/typesAlreadyExplicit.kt"); + } + + @TestMetadata("typesNotInferable.kt") + public void testTypesNotInferable() throws Exception { + runTest("../../../idea/tests/testData/intentions/specifyExplicitLambdaSignature/typesNotInferable.kt"); + } + } + @@ -5616,8 +5719,6 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest { - - diff --git a/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/.k2Intention b/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/.k2Intention new file mode 100644 index 000000000000..eb8f1d164aa6 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/.k2Intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.k2.codeinsight.intentions.SpecifyExplicitLambdaSignatureIntention diff --git a/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/lambdaWithLambdaAsParam.kt b/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/lambdaWithLambdaAsParam.kt index 9b78aa5581c4..523e062bcfe6 100644 --- a/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/lambdaWithLambdaAsParam.kt +++ b/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/lambdaWithLambdaAsParam.kt @@ -9,3 +9,5 @@ public class TestingUse { fun main() { val funcInfunc = TestingUse().test6({f -> f(5) > 20}, {x -> x + 2}) } + +// IGNORE_K2 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/lambdaWithLambdaAsParam.kt.after b/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/lambdaWithLambdaAsParam.kt.after index 167f21c2fcce..c0ac5c87dd9d 100644 --- a/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/lambdaWithLambdaAsParam.kt.after +++ b/plugins/kotlin/idea/tests/testData/intentions/specifyExplicitLambdaSignature/lambdaWithLambdaAsParam.kt.after @@ -9,3 +9,5 @@ public class TestingUse { fun main() { val funcInfunc = TestingUse().test6({ f: (x: Int) -> Int -> f(5) > 20}, { x -> x + 2}) } + +// IGNORE_K2 diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/util/ExplicitLambdaSignatureUtil.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/util/ExplicitLambdaSignatureUtil.kt new file mode 100644 index 000000000000..fcd3d026f585 --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/util/ExplicitLambdaSignatureUtil.kt @@ -0,0 +1,62 @@ +// 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.refactoring.util + +import com.intellij.psi.PsiWhiteSpace +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol +import org.jetbrains.kotlin.analysis.api.types.KtErrorType +import org.jetbrains.kotlin.idea.base.analysis.api.utils.shortenReferences +import org.jetbrains.kotlin.psi.KtFunctionLiteral +import org.jetbrains.kotlin.psi.KtLambdaExpression +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtParameterList +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.types.Variance + +context(KtAnalysisSession) +fun getExplicitLambdaSignature(element: KtLambdaExpression): String? { + val lambdaSymbol = element.functionLiteral.getSymbol() as KtFunctionLikeSymbol + val valueParameters = lambdaSymbol.valueParameters + if (valueParameters.any { it.returnType is KtErrorType } ) return null + return valueParameters.joinToString { param -> + val parameter = param.psi as? KtParameter + if (parameter != null) { + (parameter.name ?: parameter.destructuringDeclaration?.text + ?: "_") + ": " + param.returnType.render(position = Variance.IN_VARIANCE) + } else param.render() + } +} + +fun specifyExplicitLambdaSignature(element: KtLambdaExpression) { + val parameterString = analyze(element) { getExplicitLambdaSignature(element) } ?: return + specifyExplicitLambdaSignature(element, parameterString) +} + +fun specifyExplicitLambdaSignature(element: KtLambdaExpression, parameterString: String) { + val psiFactory = KtPsiFactory(element.project) + val functionLiteral = element.functionLiteral + val newParameterList = (psiFactory.createExpression("{ $parameterString -> }") as KtLambdaExpression).functionLiteral.valueParameterList + + fun KtFunctionLiteral.setParameterListIfAny(psiFactory: KtPsiFactory, newParameterList: KtParameterList?) { + val oldParameterList = valueParameterList + if (oldParameterList != null && newParameterList != null) { + oldParameterList.replace(newParameterList) + } else { + val openBraceElement = lBrace + val nextSibling = openBraceElement.nextSibling + val addNewline = nextSibling is PsiWhiteSpace && nextSibling.text?.contains("\n") ?: false + val (whitespace, arrow) = psiFactory.createWhitespaceAndArrow() + addRangeAfter(whitespace, arrow, openBraceElement) + if (newParameterList != null) { + addAfter(newParameterList, openBraceElement) + } + + if (addNewline) { + addAfter(psiFactory.createNewLine(), openBraceElement) + } + } + } + functionLiteral.setParameterListIfAny(psiFactory, newParameterList) + shortenReferences(element.valueParameters) +} diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt index d9cb27d0cb4e..6e49f06c36a7 100644 --- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt +++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt @@ -51,6 +51,7 @@ internal fun MutableTWorkspace.generateK2IntentionTests() { model("${idea}intentions/invertIfCondition", pattern = pattern) model("${idea}intentions/lambdaToAnonymousFunction", pattern = pattern) model("${idea}intentions/removeExplicitTypeArguments", pattern = pattern) + model("${idea}intentions/specifyExplicitLambdaSignature", pattern = pattern) // model("${idea}intentions/addJvmOverloads", pattern = pattern, isIgnored = true) @@ -91,7 +92,6 @@ internal fun MutableTWorkspace.generateK2IntentionTests() { model("${idea}intentions/branched", pattern = pattern, isIgnored = true) model("${idea}intentions/convertVariableAssignmentToExpression", pattern = pattern, isIgnored = true) model("${idea}intentions/convertNullablePropertyToLateinit", pattern = pattern, isIgnored = true) - model("${idea}intentions/specifyExplicitLambdaSignature", pattern = pattern, isIgnored = true) model("${idea}intentions/replaceSizeCheckWithIsNotEmpty", pattern = pattern, isIgnored = true) model("${idea}intentions/replaceTypeArgumentWithUnderscore", pattern = pattern, isIgnored = true) model("${idea}intentions/convertSecondaryConstructorToPrimary", pattern = pattern, isIgnored = true)