From 72ba98bc92e9379f55796db1eec14090e8185bca Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Wed, 16 Jul 2025 13:11:27 +0200 Subject: [PATCH] [kotlin] k2 extract function context parameters - extract explicitly used context parameter as context - skip the same typed context parameters used implicitly ^KTIJ-29441 fixed GitOrigin-RevId: 14fb4f0d205d57c184493f083fd720f9e4697f58 --- .../introduce/ExtractionTestGenerated.java | 15 +++++ .../basic/sameParameterNames.kt.fir.after | 4 +- .../explicitParameterUsage.kt | 10 ++++ .../explicitParameterUsage.kt.after | 14 +++++ .../contextParameters/multipleParameters.kt | 20 +++++++ .../multipleParameters.kt.after | 24 ++++++++ .../contextParameters/typeRelations.kt | 21 +++++++ .../contextParameters/typeRelations.kt.after | 23 ++++++++ .../misc/differentReceivers.kt.after | 6 +- .../extractFunction/parametersUtil.kt | 57 +++++++++---------- .../ExtractionDataAnalyzer.kt | 2 + .../K2IntroduceFunctionTestGenerated.java | 15 +++++ 12 files changed, 177 insertions(+), 34 deletions(-) create mode 100644 plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/explicitParameterUsage.kt create mode 100644 plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/explicitParameterUsage.kt.after create mode 100644 plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/multipleParameters.kt create mode 100644 plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/multipleParameters.kt.after create mode 100644 plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/typeRelations.kt create mode 100644 plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/typeRelations.kt.after diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java index 8f6291e14f8e..edce79b2be35 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java @@ -1628,6 +1628,21 @@ public abstract class ExtractionTestGenerated extends AbstractExtractionTest { runTest("testData/refactoring/extractFunction/contextParameters/base.kt"); } + @TestMetadata("explicitParameterUsage.kt") + public void testExplicitParameterUsage() throws Exception { + runTest("testData/refactoring/extractFunction/contextParameters/explicitParameterUsage.kt"); + } + + @TestMetadata("multipleParameters.kt") + public void testMultipleParameters() throws Exception { + runTest("testData/refactoring/extractFunction/contextParameters/multipleParameters.kt"); + } + + @TestMetadata("typeRelations.kt") + public void testTypeRelations() throws Exception { + runTest("testData/refactoring/extractFunction/contextParameters/typeRelations.kt"); + } + @TestMetadata("unnamed.kt") public void testUnnamed() throws Exception { runTest("testData/refactoring/extractFunction/contextParameters/unnamed.kt"); diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt.fir.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt.fir.after index 9d66d7a80952..4bc7976fa1c3 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt.fir.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/basic/sameParameterNames.kt.fir.after @@ -14,8 +14,8 @@ class Foo { } } - private fun string(string: String, string0: String, string1: String): String = - string + string0 + string1 + string + this@Foo.toString() + private fun string(string: String, string2: String, string3: String): String = + string + string2 + string3 + string + this@Foo.toString() } private inline fun block(t: T, block: T.() -> Unit) { diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/explicitParameterUsage.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/explicitParameterUsage.kt new file mode 100644 index 000000000000..3ac8aad98f0c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/explicitParameterUsage.kt @@ -0,0 +1,10 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +context(a: String) +fun bar(p: Int) {} + +context(a: String, b: Int) +fun m() { + bar(b) +} + +// IGNORE_K1 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/explicitParameterUsage.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/explicitParameterUsage.kt.after new file mode 100644 index 000000000000..03053200211d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/explicitParameterUsage.kt.after @@ -0,0 +1,14 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +context(a: String) +fun bar(p: Int) {} + +context(a: String, b: Int) +fun m() { + __dummyTestFun__() +} + +context(b: Int, a: String) private fun __dummyTestFun__() { + bar(b) +} + +// IGNORE_K1 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/multipleParameters.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/multipleParameters.kt new file mode 100644 index 000000000000..96e80f8f2045 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/multipleParameters.kt @@ -0,0 +1,20 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +context(a: String) +fun bar(p: Int) {} + +context(a1: String) +fun bar1(p: Int) {} + +context(i: Int) +val prop: Int + get() = 42 + +context(a: String, b: Int) +fun m() { + val i = prop + bar(i) + bar1(i) + +} + +// IGNORE_K1 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/multipleParameters.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/multipleParameters.kt.after new file mode 100644 index 000000000000..fa798a4845c9 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/multipleParameters.kt.after @@ -0,0 +1,24 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +context(a: String) +fun bar(p: Int) {} + +context(a1: String) +fun bar1(p: Int) {} + +context(i: Int) +val prop: Int + get() = 42 + +context(a: String, b: Int) +fun m() { + __dummyTestFun__() + +} + +context(i: Int, a: String) private fun __dummyTestFun__() { + val i = prop + bar(i) + bar1(i) +} + +// IGNORE_K1 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/typeRelations.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/typeRelations.kt new file mode 100644 index 000000000000..87cbfe169377 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/typeRelations.kt @@ -0,0 +1,21 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +interface A +interface B: A + +context(a: A) +fun foo() {} + +context(b: B) +fun bar() {} + +context(b: B) +fun baz() { + if (b != null) { + foo() + bar() + } +} + +// IGNORE_K1 + + diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/typeRelations.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/typeRelations.kt.after new file mode 100644 index 000000000000..be454479b279 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/typeRelations.kt.after @@ -0,0 +1,23 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +interface A +interface B: A + +context(a: A) +fun foo() {} + +context(b: B) +fun bar() {} + +context(b: B) +fun baz() { + __dummyTestFun__() +} + +context(b: B) private fun __dummyTestFun__() { + if (b != null) { + foo() + bar() + } +} + +// IGNORE_K1 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/differentReceivers.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/differentReceivers.kt.after index b9888e75d9f8..a9f95b36621b 100644 --- a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/differentReceivers.kt.after +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/parameters/misc/differentReceivers.kt.after @@ -6,9 +6,9 @@ fun foo(x: X, y: X) { } } -private fun __dummyTestFun__(field: Any, field0: Any) { +private fun __dummyTestFun__(field: Any, field2: Any) { println(field.hashCode()) println(field.hashCode()) - println(field0.hashCode()) + println(field2.hashCode()) } -// IGNORE_K1 \ No newline at end of file +// IGNORE_K1 diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/parametersUtil.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/parametersUtil.kt index 780aa0e3f4f3..fe0f96030066 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/parametersUtil.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/parametersUtil.kt @@ -6,6 +6,7 @@ import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiNamedElement import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.parentOfType +import com.intellij.util.text.UniqueNameGenerator import org.jetbrains.kotlin.analysis.api.KaExperimentalApi import org.jetbrains.kotlin.analysis.api.KaImplementationDetail import org.jetbrains.kotlin.analysis.api.KaSession @@ -20,7 +21,6 @@ import org.jetbrains.kotlin.analysis.api.resolution.singleCallOrNull import org.jetbrains.kotlin.analysis.api.resolution.symbol import org.jetbrains.kotlin.analysis.api.components.KaDiagnosticCheckerFilter import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaFirDiagnostic -import org.jetbrains.kotlin.analysis.api.impl.base.components.KaBaseIllegalPsiException import org.jetbrains.kotlin.analysis.api.resolution.KaErrorCallInfo import org.jetbrains.kotlin.analysis.api.signatures.KaCallableSignature import org.jetbrains.kotlin.analysis.api.symbols.* @@ -135,32 +135,22 @@ internal fun ExtractionData.inferParametersInfo( } - val unknownContextParameters = mutableSetOf() - analyze(virtualBlock) { + val unknownContextParameters = analyze(virtualBlock) { + val parameters = mutableSetOf() for (referenceExpression in virtualBlock.collectDescendantsOfType { it.resolveResult != null }) { val call = referenceExpression.resolveToCall() if (call is KaErrorCallInfo) { val diagnostic = call.diagnostic if (diagnostic is KaFirDiagnostic.NoContextArgument) { - unknownContextParameters.addIfNotNull((diagnostic.symbol as? KaContextParameterSymbol)?.psi as? KtParameter) + val contextParameterSymbol = diagnostic.symbol as? KaContextParameterSymbol + if (contextParameterSymbol != null && + extractedDescriptorToParameter.none { it.value.contextParameter && (it.value.originalDescriptor as? KtParameter)?.returnType?.isSubtypeOf(contextParameterSymbol.returnType) == true }) { + parameters.addIfNotNull(contextParameterSymbol.psi as? KtParameter) + } } } } - } - - unknownContextParameters.forEach { - val name = it.name ?: "_" - val parameter = MutableParameter( - name, - it.ownerDeclaration as KtNamedDeclaration, - false, - it.returnType, - targetSibling as KtElement, - contextParameter = true - ) - parameter.refCount++ - parameter.currentName = name - info.parameters.add(parameter) + parameters.distinctBy { it.returnType } } val varNameValidator = KotlinDeclarationNameValidator( @@ -169,7 +159,7 @@ internal fun ExtractionData.inferParametersInfo( KotlinNameSuggestionProvider.ValidatorTarget.PARAMETER ) - val existingParameterNames = hashSetOf() + val nameGenerator = UniqueNameGenerator() val generateArguments: (KaType) -> List = { ktType -> (ktType as? KaClassType)?.typeArguments?.mapNotNull { it.type } ?: emptyList() } for ((namedElement, parameter) in extractedDescriptorToParameter) { @@ -197,14 +187,7 @@ internal fun ExtractionData.inferParametersInfo( require(currentName != null || parameter.receiverCandidate) if (currentName != null) { - if ("$currentName" in existingParameterNames) { - var index = 0 - while ("$currentName$index" in existingParameterNames) { - index++ - } - currentName = "$currentName$index" - } - currentName?.let { existingParameterNames += it } + currentName = nameGenerator.generateUniqueName(currentName!!) } else { currentName = "receiver" } @@ -216,6 +199,21 @@ internal fun ExtractionData.inferParametersInfo( } } + unknownContextParameters.forEach { contextParam -> + val name = contextParam.name ?: "_" + val parameter = MutableParameter( + name, + contextParam.ownerDeclaration as KtNamedDeclaration, + false, + contextParam.returnType, + targetSibling as KtElement, + contextParameter = true + ) + parameter.refCount++ + parameter.currentName = name.takeIf { it == "_" } ?: nameGenerator.generateUniqueName(name) + info.parameters.add(parameter) + } + for (typeToCheck in info.typeParameters.flatMap { it.collectReferencedTypes() }.map { it.type }) { typeToCheck.processTypeIfExtractable( info.typeParameters, @@ -301,7 +299,8 @@ private fun ExtractionData.registerParameter( receiverToExtract ) - MutableParameter(argumentText, elementToExtract, extractThis, originalType, targetSibling as KtElement, contextParameter = false) + val asContextParameter = originalDeclaration is KtParameter && originalDeclaration.isContextParameter + MutableParameter(argumentText, elementToExtract, extractThis, originalType, targetSibling as KtElement, contextParameter = asContextParameter) } // TODO add type predicate based on called functions https://youtrack.jetbrains.com/issue/KTIJ-29166 diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/ExtractionDataAnalyzer.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/ExtractionDataAnalyzer.kt index b538504d8da9..fa18780e0f37 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/ExtractionDataAnalyzer.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/extractionEngine/ExtractionDataAnalyzer.kt @@ -356,6 +356,7 @@ private fun ExtractableCodeDescriptor.validateTempResult( val namedFunction = result.declaration as? KtNamedFunction val valueParameterList = namedFunction?.valueParameterList + val contextReceiverList = namedFunction?.contextReceiverList val typeParameterList = namedFunction?.typeParameterList fun processReference(currentRefExpr: KtSimpleNameExpression) { @@ -366,6 +367,7 @@ private fun ExtractableCodeDescriptor.validateTempResult( val currentDescriptor = currentRefExpr.mainReference.resolve() if (currentDescriptor is KtParameter && currentDescriptor.parent == valueParameterList) return + if (currentDescriptor is KtParameter && currentDescriptor.isContextParameter && currentDescriptor.parent == contextReceiverList) return if (currentDescriptor is KtTypeParameter && currentDescriptor.parent == typeParameterList) return if (currentDescriptor is KtProperty && currentDescriptor.isLocal && parameters.any { it.mirrorVarName == currentDescriptor.name } diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2IntroduceFunctionTestGenerated.java b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2IntroduceFunctionTestGenerated.java index 8572795c33a6..09d614d6421c 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2IntroduceFunctionTestGenerated.java +++ b/plugins/kotlin/refactorings/kotlin.refactorings.tests.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/introduce/K2IntroduceFunctionTestGenerated.java @@ -397,6 +397,21 @@ public abstract class K2IntroduceFunctionTestGenerated extends AbstractK2Introdu runTest("../../idea/tests/testData/refactoring/extractFunction/contextParameters/base.kt"); } + @TestMetadata("explicitParameterUsage.kt") + public void testExplicitParameterUsage() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/contextParameters/explicitParameterUsage.kt"); + } + + @TestMetadata("multipleParameters.kt") + public void testMultipleParameters() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/contextParameters/multipleParameters.kt"); + } + + @TestMetadata("typeRelations.kt") + public void testTypeRelations() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/contextParameters/typeRelations.kt"); + } + @TestMetadata("unnamed.kt") public void testUnnamed() throws Exception { runTest("../../idea/tests/testData/refactoring/extractFunction/contextParameters/unnamed.kt");