diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt index 628eaefca90..35317d87f07 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt @@ -389,9 +389,6 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour val valueParameters = extractedFunction.valueParameters var paramIndex = 0 for (param in parameters) { - - if (param.callText.contains(THIS_NAME)) continue - val valueParameter = valueParameters[paramIndex++] val paramRef = valueParameter.typeReference diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt index 63417c31eb9..e67963b070d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt @@ -38,15 +38,14 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector -import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector -import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.typeUtil.builtIns import org.jetbrains.kotlin.types.typeUtil.isUnit +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import java.util.* interface Parameter { @@ -95,6 +94,17 @@ class RenameReplacement(override val parameter: Parameter): ParameterReplacement } } +class WrapInWithReplacement(override val parameter: Parameter): ParameterReplacement { + override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement { + val call = e.parents.firstIsInstance().getQualifiedExpressionForSelectorOrThis() + val replacingExpression = KtPsiFactory(e).createExpressionByPattern("with($0) { $1 }", parameter.name, call.text) + val replace = call.replace(replacingExpression) + return (replace as KtCallExpression).functionLiteralArguments.first().getFunctionLiteral().bodyExpression!!.statements.first() + } + + override fun copy(parameter: Parameter) = WrapInWithReplacement(parameter) +} + class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacement { override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index 8e792aef399..46ae89eb685 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -695,7 +695,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult { val adjustedParameters = paramsInfo.parameters.filterTo(HashSet()) { it.refCount > 0 } val receiverCandidates = adjustedParameters.filterTo(HashSet()) { it.receiverCandidate } - val receiverParameter = if (receiverCandidates.size == 1) receiverCandidates.first() else null + val receiverParameter = if (receiverCandidates.size == 1 && !options.canWrapInWith) receiverCandidates.first() else null receiverParameter?.let { adjustedParameters.remove(it) } return AnalysisResult( diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt index 16ff2e83f7d..895e90160e0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt @@ -84,6 +84,11 @@ internal fun ExtractionData.inferParametersInfo( extractReceiver(receiverToExtract, ref, project, originalElements, originalDescriptor, info, options, targetScope, refInfo, originalDeclaration, originalRef, extractedDescriptorToParameter, resolvedCall, pseudocode, codeFragmentText, bindingContext, false) + if (options.canWrapInWith && resolvedCall != null && isMemberExtensionFunction(resolvedCall, ref)) { + extractReceiver(resolvedCall.dispatchReceiver, ref, project, originalElements, + originalDescriptor, info, options, targetScope, refInfo, + originalDeclaration, originalRef, extractedDescriptorToParameter, resolvedCall, pseudocode, codeFragmentText, bindingContext, true) + } } val varNameValidator = NewDeclarationNameValidator( @@ -159,7 +164,7 @@ private fun extractReceiver( } as? ClassifierDescriptor } - if (referencedClassifierDescriptor != null) { + if (referencedClassifierDescriptor != null && !(isMemberExtensionFunction && options.canWrapInWith)) { if (!referencedClassifierDescriptor.defaultType.processTypeIfExtractable( info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor )) return @@ -253,6 +258,7 @@ private fun extractReceiver( info.replacementMap.putValue(refInfo.offsetInBody, when { + isMemberExtensionFunction -> WrapInWithReplacement(parameter) hasThisReceiver && extractThis -> AddPrefixReplacement(parameter) else -> RenameReplacement(parameter) }) @@ -297,4 +303,12 @@ private fun suggestParameterType( receiverToExtract.exists() -> receiverToExtract.type else -> null } ?: builtIns.defaultParameterType +} + +private fun isMemberExtensionFunction(resolvedCall: ResolvedCall<*>, ref: KtSimpleNameExpression): Boolean { + // TODO temporary hack because we couldn't correctly extract member extension function with two explicit receivers + if (ref.parent !is KtCallExpression || ref.parent.parent !is KtQualifiedExpression) return false + + val resultingDescriptor = resolvedCall.resultingDescriptor + return resultingDescriptor is FunctionDescriptor && resolvedCall.extensionReceiver != ReceiverValue.NO_RECEIVER && resolvedCall.dispatchReceiver != ReceiverValue.NO_RECEIVER } \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/outs/extensionMemberFunction.out b/idea/testData/debugger/tinyApp/outs/extensionMemberFunction.out new file mode 100644 index 00000000000..338a4975390 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/extensionMemberFunction.out @@ -0,0 +1,41 @@ +LineBreakpoint created at extensionMemberFunction.kt:13 +LineBreakpoint created at extensionMemberFunction.kt:19 +LineBreakpoint created at extensionMemberFunction.kt:25 +LineBreakpoint created at extensionMemberFunction.kt:31 +LineBreakpoint created at extensionMemberFunction.kt:41 +LineBreakpoint created at extensionMemberFunction.kt:46 +LineBreakpoint created at extensionMemberFunction.kt:51 +LineBreakpoint created at extensionMemberFunction.kt:56 +LineBreakpoint created at extensionMemberFunction.kt:70 +LineBreakpoint created at extensionMemberFunction.kt:76 +LineBreakpoint created at extensionMemberFunction.kt:82 +LineBreakpoint created at extensionMemberFunction.kt:88 +LineBreakpoint created at extensionMemberFunction.kt:98 +LineBreakpoint created at extensionMemberFunction.kt:103 +LineBreakpoint created at extensionMemberFunction.kt:108 +LineBreakpoint created at extensionMemberFunction.kt:113 +!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! extensionMemberFunction.ExtensionMemberFunctionKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +extensionMemberFunction.kt:13 +Compile bytecode for extClass.testPublic() +extensionMemberFunction.kt:19 +extensionMemberFunction.kt:25 +Compile bytecode for extClass.testPrivate() +extensionMemberFunction.kt:31 +extensionMemberFunction.kt:41 +extensionMemberFunction.kt:46 +extensionMemberFunction.kt:51 +extensionMemberFunction.kt:56 +extensionMemberFunction.kt:70 +Compile bytecode for extClass.testCompPublic() +extensionMemberFunction.kt:76 +extensionMemberFunction.kt:82 +Compile bytecode for extClass.testCompPrivate() +extensionMemberFunction.kt:88 +extensionMemberFunction.kt:98 +extensionMemberFunction.kt:103 +extensionMemberFunction.kt:108 +extensionMemberFunction.kt:113 +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberFunction.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberFunction.kt new file mode 100644 index 00000000000..5b8b022ac6d --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberFunction.kt @@ -0,0 +1,120 @@ +package extensionMemberFunction + +fun main(args: Array) { + MemberClass().testMember(ExtClass()) + MemberClass.testCompanion(ExtClass()) +} + +class MemberClass { + fun testMember(extClass: ExtClass) { + // EXPRESSION: extClass.testPublic() + // RESULT: 1: I + //Breakpoint! + extClass.testPublic() + + with(extClass) { + // EXPRESSION: testPublic() + // RESULT: Following declarations are unavailable in debug scope: testPublic + //Breakpoint! + testPublic() + } + + // EXPRESSION: extClass.testPrivate() + // RESULT: 1: I + //Breakpoint! + extClass.testPrivate() + + with(extClass) { + // EXPRESSION: testPrivate() + // RESULT: Following declarations are unavailable in debug scope: testPrivate + //Breakpoint! + testPrivate() + } + + extClass.testExtMember() + } + + fun ExtClass.testExtMember() { + // EXPRESSION: testPublic() + // RESULT: Following declarations are unavailable in debug scope: testPublic + //Breakpoint! + testPublic() + + // EXPRESSION: this.testPublic() + // RESULT: A receiver of type extensionMemberFunction.ExtClass is required + //Breakpoint! + this.testPublic() + + // EXPRESSION: testPrivate() + // RESULT: Following declarations are unavailable in debug scope: testPrivate + //Breakpoint! + testPrivate() + + // EXPRESSION: this.testPrivate() + // RESULT: A receiver of type extensionMemberFunction.ExtClass is required + //Breakpoint! + this.testPrivate() + } + + public fun ExtClass.testPublic() = a + private fun ExtClass.testPrivate() = a + + companion object { + public fun ExtClass.testCompPublic() = a + private fun ExtClass.testCompPrivate() = a + + fun testCompanion(extClass: ExtClass) { + // EXPRESSION: extClass.testCompPublic() + // RESULT: 1: I + //Breakpoint! + extClass.testCompPublic() + + with(extClass) { + // EXPRESSION: testCompPublic() + // RESULT: Following declarations are unavailable in debug scope: testCompPublic + //Breakpoint! + testCompPublic() + } + + // EXPRESSION: extClass.testCompPrivate() + // RESULT: 1: I + //Breakpoint! + extClass.testCompPrivate() + + with(extClass) { + // EXPRESSION: testCompPrivate() + // RESULT: Following declarations are unavailable in debug scope: testCompPrivate + //Breakpoint! + testCompPrivate() + } + + extClass.testExtCompanion() + } + + fun ExtClass.testExtCompanion() { + // EXPRESSION: testCompPublic() + // RESULT: Following declarations are unavailable in debug scope: testCompPublic + //Breakpoint! + testCompPublic() + + // EXPRESSION: this.testCompPublic() + // RESULT: A receiver of type extensionMemberFunction.ExtClass is required + //Breakpoint! + this.testCompPublic() + + // EXPRESSION: testCompPrivate() + // RESULT: Following declarations are unavailable in debug scope: testCompPrivate + //Breakpoint! + testCompPrivate() + + // EXPRESSION: this.testCompPrivate() + // RESULT: A receiver of type extensionMemberFunction.ExtClass is required + //Breakpoint! + this.testCompPrivate() + } + } +} + +class ExtClass { + val a = 1 +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java index 94bd1b0d2b2..41a2b78745b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -676,6 +676,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doMultipleBreakpointsTest(fileName); } + @TestMetadata("extensionMemberFunction.kt") + public void testExtensionMemberFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/extensionMemberFunction.kt"); + doMultipleBreakpointsTest(fileName); + } + @TestMetadata("funFromOuterClassInLamdba.kt") public void testFunFromOuterClassInLamdba() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/funFromOuterClassInLamdba.kt");