diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt index df4e47b7c79..c98a1cc68bd 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt @@ -38,16 +38,13 @@ import org.jetbrains.jet.lang.psi.psiUtil.isInsideOf import java.util.ArrayList import com.intellij.psi.PsiNamedElement import org.jetbrains.jet.lang.psi.JetSuperExpression -import org.jetbrains.jet.lang.types.JetType -import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache -import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElementSelector import org.jetbrains.jet.lang.descriptors.ClassDescriptor -import org.jetbrains.jet.lang.descriptors.PackageFragmentDescriptor import org.jetbrains.jet.lang.resolve.scopes.receivers.ThisReceiver import org.jetbrains.jet.lang.descriptors.ClassKind import org.jetbrains.jet.lang.psi.psiUtil.getParentByType import org.jetbrains.jet.lang.psi.JetDeclaration import org.jetbrains.jet.lang.psi.JetDeclarationWithBody +import org.jetbrains.jet.lang.psi.JetUserType data class ExtractionOptions(val inferUnitTypeForUnusedValues: Boolean) { class object { @@ -140,6 +137,8 @@ class ExtractionData( if ((receiverDescriptor as? ClassDescriptor)?.getKind() != ClassKind.CLASS_OBJECT && parent.getReceiverExpression() !is JetSuperExpression) continue } + // Skip P in type references like 'P.Q' + if (parent is JetUserType && (parent.getParent() as? JetUserType)?.getQualifier() == parent) continue val descriptor = context[BindingContext.REFERENCE_TARGET, ref] if (!compareDescriptors(originalResolveResult.descriptor, descriptor) diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt index 03e02db721e..9ccd72e719d 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractFunctionUtils.kt @@ -232,7 +232,8 @@ fun ExtractionData.createTemporaryFunction(functionText: String): JetNamedFuncti private fun ExtractionData.createTemporaryCodeBlock(): JetBlockExpression = createTemporaryFunction("fun() {\n${getCodeFragmentText()}\n}\n").getBodyExpression() as JetBlockExpression -private fun JetType.collectReferencedTypes(): List { +private fun JetType.collectReferencedTypes(processTypeArguments: Boolean): List { + if (!processTypeArguments) return Collections.singletonList(this) return DFS.dfsFromNode( this, object: Neighbors { @@ -268,9 +269,10 @@ fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List, - nonDenotableTypes: MutableSet + nonDenotableTypes: MutableSet, + processTypeArguments: Boolean = true ): Boolean { - return collectReferencedTypes().fold(true) { (extractable, typeToCheck) -> + return collectReferencedTypes(processTypeArguments).fold(true) { (extractable, typeToCheck) -> val parameterTypeDescriptor = typeToCheck.getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor val typeParameter = parameterTypeDescriptor?.let { BindingContextUtils.descriptorToDeclaration(bindingContext, it) @@ -331,21 +333,28 @@ private fun ExtractionData.inferParametersInfo( val hasThisReceiver = thisDescriptor != null val thisExpr = ref.getParent() as? JetThisExpression - val typeDescriptor = ((thisDescriptor ?: originalDescriptor) as? ClassDescriptor)?.let { - when(it.getKind()) { - ClassKind.OBJECT, ClassKind.ENUM_CLASS -> it - ClassKind.CLASS_OBJECT, ClassKind.ENUM_ENTRY -> it.getContainingDeclaration() as? ClassDescriptor + val referencedClassDescriptor: ClassDescriptor? = (thisDescriptor ?: originalDescriptor).let { + when (it) { + is ClassDescriptor -> + when(it.getKind()) { + ClassKind.OBJECT, ClassKind.ENUM_CLASS -> it as ClassDescriptor + ClassKind.CLASS_OBJECT, ClassKind.ENUM_ENTRY -> it.getContainingDeclaration() as? ClassDescriptor + else -> if (ref.getParentByType(javaClass()) != null) it as ClassDescriptor else null + } + + is ConstructorDescriptor -> it.getContainingDeclaration() + else -> null } } - if (typeDescriptor != null) { - if (typeDescriptor.canBeReferencedViaImport()) { - replacementMap[refInfo.offsetInBody] = FqNameReplacement(DescriptorUtils.getFqNameSafe(originalDescriptor)) - } - else { - nonDenotableTypes.add(typeDescriptor.getDefaultType()) - } + if (referencedClassDescriptor != null) { + if (!referencedClassDescriptor.getDefaultType().processTypeIfExtractable( + bindingContext, typeParameters, nonDenotableTypes, false + )) continue + + val replacingDescriptor = (originalDescriptor as? ConstructorDescriptor)?.getContainingDeclaration() ?: originalDescriptor + replacementMap[refInfo.offsetInBody] = FqNameReplacement(DescriptorUtils.getFqNameSafe(replacingDescriptor)) } else { val extractThis = hasThisReceiver || thisExpr != null @@ -682,12 +691,16 @@ fun ExtractionDescriptor.generateFunction( val body = function.getBodyExpression() as JetBlockExpression val exprReplacementMap = HashMap JetElement>() - val originalOffsetByExpr = HashMap() + val originalOffsetByExpr = LinkedHashMap() val bodyOffset = body.getBlockContentOffset() val file = body.getContainingFile()!! - for ((offsetInBody, resolveResult) in extractionData.refOffsetToDeclaration) { + /* + * Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced + * before calls/types themselves + */ + for ((offsetInBody, resolveResult) in extractionData.refOffsetToDeclaration.entrySet().sortDescendingBy { it.key }) { val expr = file.findElementAt(bodyOffset + offsetInBody)?.getParentByType(javaClass()) assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'") diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt index bc089494f44..5efae6ad3a2 100644 --- a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt @@ -67,7 +67,10 @@ fun JetSimpleNameExpression.changeQualifiedName(fqName: FqName): JetElement { val elementToReplace = getQualifiedElement() return when (elementToReplace) { - is JetUserType -> elementToReplace.replace(JetPsiFactory.createType(project, text).getTypeElement()!!) + is JetUserType -> { + val typeText = "$text${elementToReplace.getTypeArgumentList()?.getText() ?: ""}" + elementToReplace.replace(JetPsiFactory.createType(project, typeText).getTypeElement()!!) + } else -> elementToReplace.replace(JetPsiFactory.createExpression(project, text)) } as JetElement } diff --git a/idea/testData/refactoring/extractFunction/basic/localClassFunctionRef.kt.conflicts b/idea/testData/refactoring/extractFunction/basic/localClassFunctionRef.kt.conflicts index 92c4bca0bb9..626e9188d07 100644 --- a/idea/testData/refactoring/extractFunction/basic/localClassFunctionRef.kt.conflicts +++ b/idea/testData/refactoring/extractFunction/basic/localClassFunctionRef.kt.conflicts @@ -1 +1 @@ -class foo.A will no longer be accessible after extraction \ No newline at end of file +Cannot extract method since following types are not denotable in the target scope: foo.A \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/basic/localClassPropertyRef.kt.conflicts b/idea/testData/refactoring/extractFunction/basic/localClassPropertyRef.kt.conflicts index 92c4bca0bb9..626e9188d07 100644 --- a/idea/testData/refactoring/extractFunction/basic/localClassPropertyRef.kt.conflicts +++ b/idea/testData/refactoring/extractFunction/basic/localClassPropertyRef.kt.conflicts @@ -1 +1 @@ -class foo.A will no longer be accessible after extraction \ No newline at end of file +Cannot extract method since following types are not denotable in the target scope: foo.A \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/basic/localObjectRef.kt b/idea/testData/refactoring/extractFunction/basic/localObjectRef.kt new file mode 100644 index 00000000000..f6ce68849a6 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/basic/localObjectRef.kt @@ -0,0 +1,8 @@ +// SIBLING: +fun foo(a: Int): Int { + object A { + fun bar(): Int = a + 10 + } + + return A.bar() +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/basic/localObjectRef.kt.conflicts b/idea/testData/refactoring/extractFunction/basic/localObjectRef.kt.conflicts new file mode 100644 index 00000000000..626e9188d07 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/basic/localObjectRef.kt.conflicts @@ -0,0 +1 @@ +Cannot extract method since following types are not denotable in the target scope: foo.A \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedAnnotation.kt b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedAnnotation.kt new file mode 100644 index 00000000000..e61b704f522 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedAnnotation.kt @@ -0,0 +1,9 @@ +// SIBLING: +class MyClass { + fun test() { + [P] val t: Int = 1 + t + } + + public annotation class P +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedAnnotation.kt.after b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedAnnotation.kt.after new file mode 100644 index 00000000000..3f8fe319547 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedAnnotation.kt.after @@ -0,0 +1,13 @@ +// SIBLING: +class MyClass { + fun test() { + unit() + } + + public annotation class P +} + +fun unit() { + [MyClass.P] val t: Int = 1 + t +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeArg.kt b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeArg.kt new file mode 100644 index 00000000000..efc38aa01be --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeArg.kt @@ -0,0 +1,12 @@ +// SIBLING: +class MyClass { + fun test() { + val t: P? = null + } + + public class P { + public class Q { + + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeArg.kt.after b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeArg.kt.after new file mode 100644 index 00000000000..a7974d97740 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeArg.kt.after @@ -0,0 +1,16 @@ +// SIBLING: +class MyClass { + fun test() { + unit() + } + + public class P { + public class Q { + + } + } +} + +fun unit() { + val t: MyClass.P? = null +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeInValueArg.kt b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeInValueArg.kt new file mode 100644 index 00000000000..f1acaf6ae71 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeInValueArg.kt @@ -0,0 +1,17 @@ +// SIBLING: +class MyClass { + fun test() { + val a: Any = P.Q() + val t = P.R(a as P.Q) + } + + public class P { + public class Q { + + } + + public class R(val t: T) { + + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeInValueArg.kt.after b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeInValueArg.kt.after new file mode 100644 index 00000000000..7b7ccee5096 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeInValueArg.kt.after @@ -0,0 +1,21 @@ +// SIBLING: +class MyClass { + fun test() { + unit() + } + + public class P { + public class Q { + + } + + public class R(val t: T) { + + } + } +} + +fun unit() { + val a: Any = MyClass.P.Q() + val t = MyClass.P.R(a as MyClass.P.Q) +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeRef.kt b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeRef.kt new file mode 100644 index 00000000000..8cbf020b0f5 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeRef.kt @@ -0,0 +1,13 @@ +// SIBLING: +class MyClass { + fun test() { + val t: P.Q = P.Q() + t + } + + public class P { + public class Q { + + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeRef.kt.after b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeRef.kt.after new file mode 100644 index 00000000000..3b1f0607e09 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeRef.kt.after @@ -0,0 +1,17 @@ +// SIBLING: +class MyClass { + fun test() { + unit() + } + + public class P { + public class Q { + + } + } +} + +fun unit() { + val t: MyClass.P.Q = MyClass.P.Q() + t +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/misc/typeRef.kt b/idea/testData/refactoring/extractFunction/parameters/misc/typeRef.kt new file mode 100644 index 00000000000..46e2ddfa85b --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/misc/typeRef.kt @@ -0,0 +1,9 @@ +// SIBLING: +class MyClass { + fun test() { + val t: P = P() + t + } + + public class P +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/parameters/misc/typeRef.kt.after b/idea/testData/refactoring/extractFunction/parameters/misc/typeRef.kt.after new file mode 100644 index 00000000000..60d1ca0eb20 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/parameters/misc/typeRef.kt.after @@ -0,0 +1,13 @@ +// SIBLING: +class MyClass { + fun test() { + unit() + } + + public class P +} + +fun unit() { + val t: MyClass.P = MyClass.P() + t +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java index e0729c5e51c..a867122af38 100644 --- a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java @@ -233,6 +233,11 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest("idea/testData/refactoring/extractFunction/basic/localFunctionRef.kt"); } + @TestMetadata("localObjectRef.kt") + public void testLocalObjectRef() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/basic/localObjectRef.kt"); + } + @TestMetadata("malformedExpression.kt") public void testMalformedExpression() throws Exception { doExtractFunctionTest("idea/testData/refactoring/extractFunction/basic/malformedExpression.kt"); @@ -739,6 +744,11 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/misc/multipleOccurrences.kt"); } + @TestMetadata("qualifiedAnnotation.kt") + public void testQualifiedAnnotation() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/misc/qualifiedAnnotation.kt"); + } + @TestMetadata("qualifiedClassObject.kt") public void testQualifiedClassObject() throws Exception { doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/misc/qualifiedClassObject.kt"); @@ -759,6 +769,26 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/misc/qualifiedPackage.kt"); } + @TestMetadata("qualifiedTypeArg.kt") + public void testQualifiedTypeArg() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeArg.kt"); + } + + @TestMetadata("qualifiedTypeInValueArg.kt") + public void testQualifiedTypeInValueArg() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeInValueArg.kt"); + } + + @TestMetadata("qualifiedTypeRef.kt") + public void testQualifiedTypeRef() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/misc/qualifiedTypeRef.kt"); + } + + @TestMetadata("typeRef.kt") + public void testTypeRef() throws Exception { + doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/misc/typeRef.kt"); + } + @TestMetadata("usagesInCallArgs.kt") public void testUsagesInCallArgs() throws Exception { doExtractFunctionTest("idea/testData/refactoring/extractFunction/parameters/misc/usagesInCallArgs.kt");