[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
This commit is contained in:
Anna Kozlova
2025-07-16 17:29:19 +00:00
committed by intellij-monorepo-bot
parent c0a1462c9a
commit 72ba98bc92
12 changed files with 177 additions and 34 deletions
@@ -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");
@@ -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 <T> block(t: T, block: T.() -> Unit) {
@@ -0,0 +1,10 @@
// COMPILER_ARGUMENTS: -Xcontext-parameters
context(a: String)
fun bar(p: Int) {}
context(a: String, b: Int)
fun m() {
<selection>bar(b)</selection>
}
// IGNORE_K1
@@ -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
@@ -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() {
<selection>val i = prop
bar(i)
bar1(i)
</selection>
}
// IGNORE_K1
@@ -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
@@ -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() {
<selection>if (b != null) {
foo()
bar()
}</selection>
}
// IGNORE_K1
@@ -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
@@ -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
// IGNORE_K1
@@ -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<KtParameter>()
analyze(virtualBlock) {
val unknownContextParameters = analyze(virtualBlock) {
val parameters = mutableSetOf<KtParameter>()
for (referenceExpression in virtualBlock.collectDescendantsOfType<KtReferenceExpression> { 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<String>()
val nameGenerator = UniqueNameGenerator()
val generateArguments: (KaType) -> List<KaType> =
{ 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
@@ -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 }
@@ -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");