Extract Function: Fix replacement of type references and constructor references
This commit is contained in:
@@ -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)
|
||||
|
||||
+29
-16
@@ -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<JetType> {
|
||||
private fun JetType.collectReferencedTypes(processTypeArguments: Boolean): List<JetType> {
|
||||
if (!processTypeArguments) return Collections.singletonList(this)
|
||||
return DFS.dfsFromNode(
|
||||
this,
|
||||
object: Neighbors<JetType> {
|
||||
@@ -268,9 +269,10 @@ fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List<J
|
||||
private fun JetType.processTypeIfExtractable(
|
||||
bindingContext: BindingContext,
|
||||
typeParameters: MutableSet<TypeParameter>,
|
||||
nonDenotableTypes: MutableSet<JetType>
|
||||
nonDenotableTypes: MutableSet<JetType>,
|
||||
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<JetTypeReference>()) != 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, (JetElement) -> JetElement>()
|
||||
val originalOffsetByExpr = HashMap<JetElement, Int>()
|
||||
val originalOffsetByExpr = LinkedHashMap<JetElement, Int>()
|
||||
|
||||
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<JetSimpleNameExpression>())
|
||||
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
class foo.A will no longer be accessible after extraction
|
||||
Cannot extract method since following types are not denotable in the target scope: foo.A
|
||||
@@ -1 +1 @@
|
||||
class foo.A will no longer be accessible after extraction
|
||||
Cannot extract method since following types are not denotable in the target scope: foo.A
|
||||
@@ -0,0 +1,8 @@
|
||||
// SIBLING:
|
||||
fun foo(a: Int): Int {
|
||||
object A {
|
||||
fun bar(): Int = a + 10
|
||||
}
|
||||
|
||||
return <selection>A.bar()</selection>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Cannot extract method since following types are not denotable in the target scope: foo.A
|
||||
@@ -0,0 +1,9 @@
|
||||
// SIBLING:
|
||||
class MyClass {
|
||||
fun test() {
|
||||
<selection>[P] val t: Int = 1
|
||||
t</selection>
|
||||
}
|
||||
|
||||
public annotation class P
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// SIBLING:
|
||||
class MyClass {
|
||||
fun test() {
|
||||
unit()
|
||||
}
|
||||
|
||||
public annotation class P
|
||||
}
|
||||
|
||||
fun unit() {
|
||||
[MyClass.P] val t: Int = 1
|
||||
t
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// SIBLING:
|
||||
class MyClass {
|
||||
fun test() {
|
||||
<selection>val t: P<P.Q>? = null</selection>
|
||||
}
|
||||
|
||||
public class P<T> {
|
||||
public class Q {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// SIBLING:
|
||||
class MyClass {
|
||||
fun test() {
|
||||
unit()
|
||||
}
|
||||
|
||||
public class P<T> {
|
||||
public class Q {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unit() {
|
||||
val t: MyClass.P<MyClass.P.Q>? = null
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// SIBLING:
|
||||
class MyClass {
|
||||
fun test() {
|
||||
<selection>val a: Any = P.Q()
|
||||
val t = P.R<P.Q>(a as P.Q)</selection>
|
||||
}
|
||||
|
||||
public class P<T> {
|
||||
public class Q {
|
||||
|
||||
}
|
||||
|
||||
public class R<T>(val t: T) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// SIBLING:
|
||||
class MyClass {
|
||||
fun test() {
|
||||
unit()
|
||||
}
|
||||
|
||||
public class P<T> {
|
||||
public class Q {
|
||||
|
||||
}
|
||||
|
||||
public class R<T>(val t: T) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unit() {
|
||||
val a: Any = MyClass.P.Q()
|
||||
val t = MyClass.P.R<MyClass.P.Q>(a as MyClass.P.Q)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// SIBLING:
|
||||
class MyClass {
|
||||
fun test() {
|
||||
<selection>val t: P.Q = P.Q()
|
||||
t</selection>
|
||||
}
|
||||
|
||||
public class P {
|
||||
public class Q {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// SIBLING:
|
||||
class MyClass {
|
||||
fun test() {
|
||||
<selection>val t: P = P()
|
||||
t</selection>
|
||||
}
|
||||
|
||||
public class P
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// SIBLING:
|
||||
class MyClass {
|
||||
fun test() {
|
||||
unit()
|
||||
}
|
||||
|
||||
public class P
|
||||
}
|
||||
|
||||
fun unit() {
|
||||
val t: MyClass.P = MyClass.P()
|
||||
t
|
||||
}
|
||||
+30
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user