diff --git a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertContextParameterToRegularParameterIntention.kt b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertContextParameterToRegularParameterIntention.kt index 22d82880f461..454675960bf9 100644 --- a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertContextParameterToRegularParameterIntention.kt +++ b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertContextParameterToRegularParameterIntention.kt @@ -9,6 +9,8 @@ import org.jetbrains.kotlin.idea.k2.codeinsight.intentions.contexts.ContextParam import org.jetbrains.kotlin.idea.k2.codeinsight.intentions.contexts.ContextParameterUtils.isConvertibleContextParameter import org.jetbrains.kotlin.idea.k2.codeinsight.intentions.contexts.ContextParameterUtils.runChangeSignatureForParameter import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.KotlinChangeInfo +import org.jetbrains.kotlin.psi.KtContextReceiverList +import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtParameter class ConvertContextParameterToRegularParameterIntention : SelfTargetingIntention( @@ -18,6 +20,7 @@ class ConvertContextParameterToRegularParameterIntention : SelfTargetingIntentio override fun isApplicableTo(element: KtParameter, caretOffset: Int): Boolean { return isConvertibleContextParameter(element) + && (element.parent as? KtContextReceiverList)?.ownerDeclaration is KtNamedFunction } override fun applyTo(element: KtParameter, editor: Editor?) { diff --git a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertReceiverParameterToContextParameterIntention.kt b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertReceiverParameterToContextParameterIntention.kt index 92cf0ab9d4c2..bb28b8ac2f6d 100644 --- a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertReceiverParameterToContextParameterIntention.kt +++ b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertReceiverParameterToContextParameterIntention.kt @@ -11,10 +11,12 @@ import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingIntention +import org.jetbrains.kotlin.idea.k2.codeinsight.intentions.contexts.ContextParameterUtils.getContextParameters import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.KotlinChangeInfo import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.KotlinChangeSignatureProcessor import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.KotlinMethodDescriptor import org.jetbrains.kotlin.idea.refactoring.rename.KotlinMemberInplaceRenameHandler +import org.jetbrains.kotlin.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType @@ -27,19 +29,20 @@ class ConvertReceiverParameterToContextParameterIntention : SelfTargetingIntenti override fun startInWriteAction(): Boolean = false override fun isApplicableTo(element: KtTypeReference, caretOffset: Int): Boolean { - return element.languageVersionSettings.supportsFeature(LanguageFeature.ContextParameters) + if (!element.languageVersionSettings.supportsFeature(LanguageFeature.ContextParameters)) return false + return (element.parent as? KtNamedFunction)?.receiverTypeReference == element // disabled for properties, TODO KTIJ-34531 } override fun applyTo(element: KtTypeReference, editor: Editor?) { - val ktFunction = element.getStrictParentOfType() ?: return - val methodDescriptor = KotlinMethodDescriptor(ktFunction) + val ktCallable = element.getStrictParentOfType() ?: return + val methodDescriptor = KotlinMethodDescriptor(ktCallable) val changeInfo = KotlinChangeInfo(methodDescriptor) if (!configureChangeInfo(changeInfo)) return object : KotlinChangeSignatureProcessor(element.project, changeInfo) { override fun performRefactoring(usages: Array) { super.performRefactoring(usages) DumbService.getInstance(element.project).smartInvokeLater { - renameLastContextParameter(ktFunction, editor) + renameLastContextParameter(ktCallable, editor) } } }.also { @@ -54,11 +57,11 @@ class ConvertReceiverParameterToContextParameterIntention : SelfTargetingIntenti return true } - private fun renameLastContextParameter(ktFunction: KtNamedFunction, editor: Editor?) { - if (!ktFunction.isValid || editor == null || editor.isDisposed) return - val lastContextParameter = ktFunction.contextReceiverList?.contextParameters()?.lastOrNull() ?: return + private fun renameLastContextParameter(ktCallable: KtCallableDeclaration, editor: Editor?) { + if (!ktCallable.isValid || editor == null || editor.isDisposed) return + val lastContextParameter = ktCallable.getContextParameters()?.lastOrNull() ?: return editor.caretModel.moveToOffset(lastContextParameter.startOffset) - PsiDocumentManager.getInstance(ktFunction.project).doPostponedOperationsAndUnblockDocument(editor.document) + PsiDocumentManager.getInstance(ktCallable.project).doPostponedOperationsAndUnblockDocument(editor.document) KotlinMemberInplaceRenameHandler().doRename(lastContextParameter, editor, null) } } 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 7ac9350c6259..dd57024f4295 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 @@ -14193,6 +14193,11 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest { KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); } + @TestMetadata("contextProperty.kt") + public void testContextProperty() throws Exception { + runTest("../../../idea/tests/testData/intentions/contextParameters/contextParameterToReceiver/contextProperty.kt"); + } + @TestMetadata("contextUsage1.kt") public void testContextUsage1() throws Exception { runTest("../../../idea/tests/testData/intentions/contextParameters/contextParameterToReceiver/contextUsage1.kt"); @@ -14386,6 +14391,11 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest { runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/contextMemberExtensionFunCallInBody.kt"); } + @TestMetadata("contextValPropertyWithReceiver.kt") + public void testContextValPropertyWithReceiver() throws Exception { + runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/contextValPropertyWithReceiver.kt"); + } + @TestMetadata("explicitExtensionArgument.kt") public void testExplicitExtensionArgument() throws Exception { runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/explicitExtensionArgument.kt"); @@ -14426,6 +14436,16 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest { runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/memberFunCallInBody.kt"); } + @TestMetadata("valPropertyWithReceiver.kt") + public void testValPropertyWithReceiver() throws Exception { + runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/valPropertyWithReceiver.kt"); + } + + @TestMetadata("varPropertyWithReceiver.kt") + public void testVarPropertyWithReceiver() throws Exception { + runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/varPropertyWithReceiver.kt"); + } + @TestMetadata("withAnotherContextBefore.kt") public void testWithAnotherContextBefore() throws Exception { runTest("../../../idea/tests/testData/intentions/contextParameters/receiverToContextParameter/withAnotherContextBefore.kt"); diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/contexts/ContextParameterUtils.kt b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/contexts/ContextParameterUtils.kt index 374dcbb38b89..76adfe02fdde 100644 --- a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/contexts/ContextParameterUtils.kt +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/contexts/ContextParameterUtils.kt @@ -10,13 +10,8 @@ import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.KotlinMethodDesc import org.jetbrains.kotlin.idea.k2.refactoring.changeSignature.KotlinParameterInfo import org.jetbrains.kotlin.idea.refactoring.isAbstract import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.KtContextReceiverList -import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.psi.KtParameter -import org.jetbrains.kotlin.psi.KtParameterList +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -import org.jetbrains.kotlin.utils.addToStdlib.ifFalse object ContextParameterUtils { /** @@ -26,8 +21,7 @@ object ContextParameterUtils { if (!ktParameter.languageVersionSettings.supportsFeature(LanguageFeature.ContextParameters)) return false val contextParameterList = ktParameter.parent as? KtContextReceiverList ?: return false val contextParameterListOwner = contextParameterList.ownerDeclaration - // Change Signature support for context properties is required KTIJ-34042 - if (contextParameterListOwner !is KtNamedFunction) return false + if (contextParameterListOwner !is KtCallableDeclaration) return false if (contextParameterListOwner.isOpenAbstractOrOverride()) return false return true @@ -36,7 +30,7 @@ object ContextParameterUtils { fun isValueParameterConvertibleToContext(ktParameter: KtParameter): Boolean { if (!ktParameter.languageVersionSettings.supportsFeature(LanguageFeature.ContextParameters)) return false val valueParameterList = ktParameter.parent as? KtParameterList ?: return false - val owner = valueParameterList.ownerFunction as? KtNamedFunction ?: return false + val owner = valueParameterList.ownerFunction as? KtCallableDeclaration ?: return false if (owner.isOpenAbstractOrOverride()) return false return true @@ -47,10 +41,10 @@ object ContextParameterUtils { * The Change Signature refactoring runs with this info if [configureChangeInfo] returns `true`. */ fun runChangeSignatureForParameter(element: KtParameter, configureChangeInfo: (KotlinChangeInfo) -> Boolean) { - val ktFunction = element.getStrictParentOfType() ?: return - val methodDescriptor = KotlinMethodDescriptor(ktFunction) + val ktCallable = element.getStrictParentOfType() ?: return + val methodDescriptor = KotlinMethodDescriptor(ktCallable) val changeInfo = KotlinChangeInfo(methodDescriptor) - configureChangeInfo(changeInfo).ifFalse { return } + if (!configureChangeInfo(changeInfo)) return KotlinChangeSignatureProcessor(element.project, changeInfo).also { it.prepareSuccessfulSwingThreadCallback = Runnable { } }.run() @@ -72,6 +66,20 @@ object ContextParameterUtils { !it.isContextParameter && it.oldName == ktParameter.name } + /** + * Utility for getting context parameters from a callable declaration. + * Returns the list of context parameters if the declaration is a function or a property with context parameters, and null otherwise. + * + * The utility mitigates the awkward declaration of context parameters in the Kotlin PSI hierarchy. + */ + fun KtCallableDeclaration.getContextParameters(): List? { + return when (this) { + is KtNamedFunction -> contextReceiverList?.contextParameters() + is KtProperty -> contextReceiverList?.contextParameters() + else -> null + } + } + // to avoid overrides and overridable declarations KTIJ-34463 private fun KtDeclaration.isOpenAbstractOrOverride(): Boolean = isAbstract() || hasModifier(KtTokens.OPEN_KEYWORD) || hasModifier(KtTokens.OVERRIDE_KEYWORD) diff --git a/plugins/kotlin/idea/tests/testData/intentions/contextParameters/contextParameterToReceiver/contextProperty.kt b/plugins/kotlin/idea/tests/testData/intentions/contextParameters/contextParameterToReceiver/contextProperty.kt new file mode 100644 index 000000000000..c6caf6a879f7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/contextParameters/contextParameterToReceiver/contextProperty.kt @@ -0,0 +1,17 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters + +context(c1: String) +var prop: Int + get() = c1.length + set(value) { + println(c1) + foo() + c1.bar() + } + +context(c: String) +fun foo() { +} + +fun String.bar() { +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/contextParameters/contextParameterToReceiver/contextProperty.kt.after b/plugins/kotlin/idea/tests/testData/intentions/contextParameters/contextParameterToReceiver/contextProperty.kt.after new file mode 100644 index 000000000000..c2c1e926af4f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/contextParameters/contextParameterToReceiver/contextProperty.kt.after @@ -0,0 +1,16 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters + +var String.prop: Int + get() = length + set(value) { + println(this) + foo() + bar() + } + +context(c: String) +fun foo() { +} + +fun String.bar() { +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/contextParameters/receiverToContextParameter/contextValPropertyWithReceiver.kt b/plugins/kotlin/idea/tests/testData/intentions/contextParameters/receiverToContextParameter/contextValPropertyWithReceiver.kt new file mode 100644 index 000000000000..0fbcc9137d19 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/contextParameters/receiverToContextParameter/contextValPropertyWithReceiver.kt @@ -0,0 +1,6 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +// IS_APPLICABLE: false + +context(i: Int) +val String.foo: Int + get() = i + length diff --git a/plugins/kotlin/idea/tests/testData/intentions/contextParameters/receiverToContextParameter/valPropertyWithReceiver.kt b/plugins/kotlin/idea/tests/testData/intentions/contextParameters/receiverToContextParameter/valPropertyWithReceiver.kt new file mode 100644 index 000000000000..ee72f03577ca --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/contextParameters/receiverToContextParameter/valPropertyWithReceiver.kt @@ -0,0 +1,5 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +// IS_APPLICABLE: false + +val String.foo: Int + get() = length diff --git a/plugins/kotlin/idea/tests/testData/intentions/contextParameters/receiverToContextParameter/varPropertyWithReceiver.kt b/plugins/kotlin/idea/tests/testData/intentions/contextParameters/receiverToContextParameter/varPropertyWithReceiver.kt new file mode 100644 index 000000000000..1aa06151624f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/contextParameters/receiverToContextParameter/varPropertyWithReceiver.kt @@ -0,0 +1,6 @@ +// COMPILER_ARGUMENTS: -Xcontext-parameters +// IS_APPLICABLE: false + +var String.foo: Int + get() = length + set(value) { println(this) }