Extract Function: Fix replacement of type references and constructor references

This commit is contained in:
Alexey Sedunov
2014-05-26 14:51:45 +04:00
parent 3d6fc6e015
commit 4463c67e28
18 changed files with 217 additions and 23 deletions
@@ -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)
@@ -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
}
@@ -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) {
}
}
}
@@ -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
}
@@ -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");