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 7d142c18cf2..617172c7764 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 @@ -672,27 +672,31 @@ private fun ExtractionData.inferParametersInfo( continue } - val referencedClassDescriptor: ClassDescriptor? = (thisDescriptor ?: originalDescriptor).let { + val referencedClassifierDescriptor: ClassifierDescriptor? = (thisDescriptor ?: originalDescriptor).let { when (it) { is ClassDescriptor -> when(it.getKind()) { - ClassKind.OBJECT, ClassKind.ENUM_CLASS -> it : ClassDescriptor + ClassKind.OBJECT, ClassKind.ENUM_CLASS -> it as ClassifierDescriptor ClassKind.ENUM_ENTRY -> it.getContainingDeclaration() as? ClassDescriptor - else -> if (ref.getNonStrictParentOfType() != null) it : ClassDescriptor else null + else -> if (ref.getNonStrictParentOfType() != null) it as ClassifierDescriptor else null } + is TypeParameterDescriptor -> it as ClassifierDescriptor + is ConstructorDescriptor -> it.getContainingDeclaration() else -> null } } - if (referencedClassDescriptor != null) { - if (!referencedClassDescriptor.getDefaultType().processTypeIfExtractable( - info.typeParameters, info.nonDenotableTypes, options, targetScope, false + if (referencedClassifierDescriptor != null) { + if (!referencedClassifierDescriptor.getDefaultType().processTypeIfExtractable( + info.typeParameters, info.nonDenotableTypes, options, targetScope, referencedClassifierDescriptor is TypeParameterDescriptor )) continue - info.replacementMap[refInfo.offsetInBody] = FqNameReplacement(originalDescriptor.getImportableDescriptor().fqNameSafe) + if (referencedClassifierDescriptor is ClassDescriptor) { + info.replacementMap[refInfo.offsetInBody] = FqNameReplacement(originalDescriptor.getImportableDescriptor().fqNameSafe) + } } else { val extractThis = (hasThisReceiver && refInfo.smartCast == null) || thisExpr != null @@ -998,6 +1002,7 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts ).generateDeclaration() val valueParameterList = (result.declaration as? JetNamedFunction)?.getValueParameterList() + val typeParameterList = (result.declaration as? JetNamedFunction)?.getTypeParameterList() val body = result.declaration.getGeneratedBody() val bindingContext = body.analyzeFully() @@ -1017,6 +1022,7 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts val currentTarget = currentDescriptor?.let { DescriptorToSourceUtilsIde.getAnyDeclaration(extractionData.project, it) } as? PsiNamedElement if (currentTarget is JetParameter && currentTarget.getParent() == valueParameterList) continue + if (currentTarget is JetTypeParameter && currentTarget.getParent() == typeParameterList) continue if (currentDescriptor is LocalVariableDescriptor && parameters.any { it.mirrorVarName == currentDescriptor.getName().asString() }) continue diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index a6a60197b85..6158e0d6d16 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.idea.core.* @@ -25,6 +27,7 @@ import org.jetbrains.kotlin.idea.core.refactoring.isMultiLine import org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyIntention import org.jetbrains.kotlin.idea.intentions.InfixCallToOrdinaryIntention import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention +import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArgumentsIntention import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ExpressionValue import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Initializer import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Jump @@ -205,7 +208,7 @@ fun ExtractableCodeDescriptor.findDuplicates(): List { val unifier = JetPsiUnifier(unifierParameters, true) - val scopeElement = extractionData.duplicateContainer ?: extractionData.targetSibling.getParent() ?: return Collections.emptyList() + val scopeElement = getOccurrenceContainer() ?: return Collections.emptyList() val originalTextRange = extractionData.originalRange.getTextRange() return extractionData .originalRange @@ -220,6 +223,10 @@ fun ExtractableCodeDescriptor.findDuplicates(): List { .toList() } +private fun ExtractableCodeDescriptor.getOccurrenceContainer(): PsiElement? { + return extractionData.duplicateContainer ?: extractionData.targetSibling.getParent() +} + private fun makeCall( extractableDescriptor: ExtractableCodeDescriptor, declaration: JetNamedDeclaration, @@ -264,8 +271,14 @@ private fun makeCall( val calleeName = declaration.getName() val callText = when (declaration) { - is JetNamedFunction -> - arguments.joinToString(separator = ", ", prefix = "$calleeName(", postfix = ")") + is JetNamedFunction -> { + val argumentsText = arguments.joinToString(separator = ", ", prefix = "(", postfix = ")") + val typeArguments = extractableDescriptor.typeParameters.map { it.originalDeclaration.name } + val typeArgumentsText = with(typeArguments) { + if (isNotEmpty()) joinToString(separator = ", ", prefix = "<", postfix = ">") else "" + } + "$calleeName$typeArgumentsText$argumentsText" + } else -> calleeName } @@ -634,6 +647,18 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( ShortenReferences.DEFAULT.process(declaration) } + if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, emptyMap(), nameByOffset) + val duplicateReplacers = duplicates.map { it.range to { makeCall(descriptor, declaration, it.controlFlow, it.range, it.arguments) } }.toMap() + + if (descriptor.typeParameters.isNotEmpty()) { + for (ref in ReferencesSearch.search(declaration, LocalSearchScope(descriptor.getOccurrenceContainer()!!))) { + val typeArgumentList = (ref.element.parent as? JetCallExpression)?.typeArgumentList ?: continue + if (RemoveExplicitTypeArgumentsIntention.isApplicableTo(typeArgumentList, false)) { + typeArgumentList.delete() + } + } + } + return ExtractionResult(this, declaration, duplicateReplacers, nameByOffset) } diff --git a/idea/testData/debugger/tinyApp/outs/typeParameterRef.out b/idea/testData/debugger/tinyApp/outs/typeParameterRef.out new file mode 100644 index 00000000000..1cc7908b883 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/typeParameterRef.out @@ -0,0 +1,8 @@ +LineBreakpoint created at typeParameterRef.kt:13 +!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! typeParameterRef.TypeParameterRefPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +typeParameterRef.kt:13 +Compile bytecode for foo() +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/singleBreakpoint/typeParameterRef.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/typeParameterRef.kt new file mode 100644 index 00000000000..416343cecd9 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/typeParameterRef.kt @@ -0,0 +1,17 @@ +package typeParameterRef + +fun main(args: Array) { + test() +} + +fun foo(): Int { + return 1 +} + +fun test() { + //Breakpoint! + val a = foo() +} + +// EXPRESSION: foo() +// RESULT: 1: I \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt new file mode 100644 index 00000000000..3eb3e5e0806 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt @@ -0,0 +1,6 @@ +// SUGGESTED_NAMES: i, getA +fun foo() = 1 + +fun test() { + val a = foo() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt.after b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt.after new file mode 100644 index 00000000000..4a83ac6716e --- /dev/null +++ b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt.after @@ -0,0 +1,8 @@ +// SUGGESTED_NAMES: i, getA +fun foo() = 1 + +fun test() { + val a = i() +} + +private fun i() = foo() \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt new file mode 100644 index 00000000000..e12202196e1 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt @@ -0,0 +1,8 @@ +// SUGGESTED_NAMES: i, getA +// PARAM_DESCRIPTOR: value-parameter val t: T defined in test +// PARAM_TYPES: T +fun foo(u: U) = 1 + +fun test(t: T) { + val a = foo(t) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt.after b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt.after new file mode 100644 index 00000000000..062cee22764 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt.after @@ -0,0 +1,10 @@ +// SUGGESTED_NAMES: i, getA +// PARAM_DESCRIPTOR: value-parameter val t: T defined in test +// PARAM_TYPES: T +fun foo(u: U) = 1 + +fun test(t: T) { + val a = i(t) +} + +private fun i(t: T) = foo(t) \ 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 0597b321d0c..13901a948fb 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -253,6 +253,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doSingleBreakpointTest(fileName); } + @TestMetadata("typeParameterRef.kt") + public void testTypeParameterRef() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/typeParameterRef.kt"); + doSingleBreakpointTest(fileName); + } + @TestMetadata("unboxParam.kt") public void testUnboxParam() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/unboxParam.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java index 1a4a7dcd10b..c2ac4eefee6 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java @@ -2340,6 +2340,18 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest(fileName); } + @TestMetadata("typeParameterRef.kt") + public void testTypeParameterRef() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/typeParameters/typeParameterRef.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("typeParameterRefCanOmit.kt") + public void testTypeParameterRefCanOmit() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/typeParameters/typeParameterRefCanOmit.kt"); + doExtractFunctionTest(fileName); + } + @TestMetadata("typeParameterResolvableInTargetScope.kt") public void testTypeParameterResolvableInTargetScope() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/typeParameters/typeParameterResolvableInTargetScope.kt");