diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index 063d22ed3e5b..a94f1dcad575 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -253,6 +253,8 @@ internal class MutableParameter( private val originalType: KotlinType, private val possibleTypes: Set ) : Parameter, IMutableParameter { + override val contextParameter: Boolean = false + // All modifications happen in the same thread private var writable: Boolean = true private val defaultTypes = LinkedHashSet() 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 096501c8efd4..8f6291e14f8e 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 @@ -1610,6 +1610,35 @@ public abstract class ExtractionTestGenerated extends AbstractExtractionTest { } } + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("testData/refactoring/extractFunction/contextParameters") + public static class ContextParameters extends AbstractExtractionTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K1; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("base.kt") + public void testBase() throws Exception { + runTest("testData/refactoring/extractFunction/contextParameters/base.kt"); + } + + @TestMetadata("unnamed.kt") + public void testUnnamed() throws Exception { + runTest("testData/refactoring/extractFunction/contextParameters/unnamed.kt"); + } + + @TestMetadata("withProvidedContext.kt") + public void testWithProvidedContext() throws Exception { + runTest("testData/refactoring/extractFunction/contextParameters/withProvidedContext.kt"); + } + } + @RunWith(JUnit3RunnerWithInners.class) @TestMetadata("testData/refactoring/extractFunction/controlFlow") public abstract static class ControlFlow extends AbstractExtractionTest { diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/base.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/base.kt new file mode 100644 index 000000000000..cb85c3bb6994 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/base.kt @@ -0,0 +1,15 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +context(a: String) +fun bar(p: Int) {} + +context(i: Int) +val prop: Int + get() = 42 + +context(a: String, b: Int) +fun m() { + val i = prop + bar(i) +} + +// IGNORE_K1 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/base.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/base.kt.after new file mode 100644 index 000000000000..c7f7c681cb7a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/base.kt.after @@ -0,0 +1,19 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +context(a: String) +fun bar(p: Int) {} + +context(i: Int) +val prop: Int + get() = 42 + +context(a: String, b: Int) +fun m() { + val i = prop + __dummyTestFun__(i) +} + +context(a: String) private fun __dummyTestFun__(i: Int) { + bar(i) +} + +// IGNORE_K1 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/unnamed.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/unnamed.kt new file mode 100644 index 000000000000..78fbd025b3d1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/unnamed.kt @@ -0,0 +1,15 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +context(_: String) +fun bar(p: Int) {} + +context(_: Int) +val prop: Int + get() = 42 + +context(a: String, b: Int) +fun m() { + val i = prop + bar(i) +} + +// IGNORE_K1 \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/unnamed.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/unnamed.kt.after new file mode 100644 index 000000000000..da40efa013ce --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/unnamed.kt.after @@ -0,0 +1,19 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +context(_: String) +fun bar(p: Int) {} + +context(_: Int) +val prop: Int + get() = 42 + +context(a: String, b: Int) +fun m() { + val i = prop + __dummyTestFun__(i) +} + +context(_: String) private fun __dummyTestFun__(i: Int) { + bar(i) +} + +// IGNORE_K1 diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/withProvidedContext.kt b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/withProvidedContext.kt new file mode 100644 index 000000000000..009297444b41 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/withProvidedContext.kt @@ -0,0 +1,19 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +context(a: String) +fun bar(p: Int) {} + +context(i: Int) +val prop: Int + get() = 42 + +context(a: String, b: Int) +fun m() { + val i = prop + + object: Function0 { + override fun invoke(): Int { + bar(i) + return 1 + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/withProvidedContext.kt.after b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/withProvidedContext.kt.after new file mode 100644 index 000000000000..c46e3be89c01 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/refactoring/extractFunction/contextParameters/withProvidedContext.kt.after @@ -0,0 +1,23 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +context(a: String) +fun bar(p: Int) {} + +context(i: Int) +val prop: Int + get() = 42 + +context(a: String, b: Int) +fun m() { + val i = prop + + object: Function0 { + override fun invoke(): Int { + __dummyTestFun__() + return 1 + } + + private fun __dummyTestFun__() { + bar(i) + } + } +} diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractFunctionGenerator.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractFunctionGenerator.kt index af1a8356ca83..564f45113b02 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractFunctionGenerator.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractFunctionGenerator.kt @@ -457,7 +457,7 @@ abstract class ExtractFunctionGenerator CallableBuilder.Target.READ_ONLY_PROPERTY } return CallableBuilder(builderTarget).apply { + + val typeDescriptor = createTypeDescriptor(descriptor.extractionData) + + val contextParameters = descriptor.parameters.filter { it.contextParameter } + if (contextParameters.isNotEmpty()) { + val contextString = contextParameters.joinToString(prefix = "context(", postfix = ")") { + it.name + ": " + typeDescriptor.renderType(it.parameterType, isReceiver = false, Variance.IN_VARIANCE) + } + modifier(contextString) + } + val visibility = descriptor.visibility?.value ?: "" fun TypeParameter.isReified() = originalDeclaration.hasModifier(KtTokens.REIFIED_KEYWORD) @@ -600,7 +611,6 @@ abstract class ExtractFunctionGenerator + descriptor.parameters.filter { !it.contextParameter }.forEach { parameter -> param(parameter.name, typeDescriptor.renderType(parameter.parameterType, isReceiver = false, Variance.IN_VARIANCE)) } diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IParameter.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IParameter.kt index 5315197319f4..c6c30906e23c 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IParameter.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/IParameter.kt @@ -6,6 +6,7 @@ interface IParameter { val name: String val mirrorVarName: String? val receiverCandidate: Boolean + val contextParameter: Boolean val parameterType: KotlinType diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/Parameter.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/Parameter.kt index e35b79c74c03..c895177893e8 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/Parameter.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/extractFunction/Parameter.kt @@ -49,7 +49,8 @@ internal class MutableParameter( override val originalDescriptor: PsiNamedElement, override val receiverCandidate: Boolean, private val originalType: KaType, - private val scope: KtElement + private val scope: KtElement, + override val contextParameter: Boolean ) : Parameter, IMutableParameter { private val typePredicates = mutableSetOf() 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 9fae18a0848c..84d7ea717eec 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 @@ -21,6 +21,7 @@ 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.* import org.jetbrains.kotlin.analysis.api.types.KaClassType @@ -81,6 +82,7 @@ import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.idea.k2.refactoring.introduce.K2SemanticMatcher.isSemanticMatch import org.jetbrains.kotlin.psi.KtReferenceExpression import org.jetbrains.kotlin.psi.psiUtil.findLabelAndCall +import org.jetbrains.kotlin.utils.addIfNotNull /** * Represents a parameter candidate as it's original declaration and a reference in code. @@ -102,6 +104,7 @@ private class ParameterWithReference(val parameterOrigin: PsiNamedElement, val r } } +@OptIn(KaExperimentalApi::class) context(KaSession) internal fun ExtractionData.inferParametersInfo( virtualBlock: KtBlockExpression, @@ -131,6 +134,34 @@ internal fun ExtractionData.inferParametersInfo( } + val unknownContextParameters = mutableSetOf() + analyze(virtualBlock) { + 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) + } + } + } + } + + 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) + } + val varNameValidator = KotlinDeclarationNameValidator( commonParent, true, @@ -252,7 +283,7 @@ private fun ExtractionData.registerParameter( if (extractThis || extractOrdinaryParameter || extractFunctionRef) { val parameterExpression = getParameterArgumentExpression(originalRef, receiverToExtract, refInfo.smartCast) val parameter = extractedDescriptorToParameter.getOrPut(ParameterWithReference(elementToExtract, originalRef.takeUnless { extractThis })) { - var argumentText = + val argumentText = calculateArgumentText( hasThisReceiver, extractThis, @@ -269,7 +300,7 @@ private fun ExtractionData.registerParameter( receiverToExtract ) - MutableParameter(argumentText, elementToExtract, extractThis, originalType, targetSibling as KtElement) + MutableParameter(argumentText, elementToExtract, extractThis, originalType, targetSibling as KtElement, contextParameter = false) } // TODO add type predicate based on called functions https://youtrack.jetbrains.com/issue/KTIJ-29166 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 36bbf014e0f5..8572795c33a6 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 @@ -379,6 +379,35 @@ public abstract class K2IntroduceFunctionTestGenerated extends AbstractK2Introdu } } + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/contextParameters") + public static class ContextParameters extends AbstractK2IntroduceFunctionTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K2; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doExtractFunctionTest, this, testDataFilePath); + } + + @TestMetadata("base.kt") + public void testBase() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/contextParameters/base.kt"); + } + + @TestMetadata("unnamed.kt") + public void testUnnamed() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/contextParameters/unnamed.kt"); + } + + @TestMetadata("withProvidedContext.kt") + public void testWithProvidedContext() throws Exception { + runTest("../../idea/tests/testData/refactoring/extractFunction/contextParameters/withProvidedContext.kt"); + } + } + @RunWith(JUnit3RunnerWithInners.class) @TestMetadata("../../idea/tests/testData/refactoring/extractFunction/controlFlow") public abstract static class ControlFlow extends AbstractK2IntroduceFunctionTest {