Extract Function: Replace non-denotable parameter types with their super types when possible

#KT-6187 Fixed
This commit is contained in:
Alexey Sedunov
2014-11-12 19:10:11 +03:00
parent 94efdeb3d5
commit 558e038ba5
9 changed files with 141 additions and 29 deletions
@@ -43,7 +43,7 @@ public fun DeclarationDescriptor.canBeReferencedViaImport(): Boolean {
if (this is PackageViewDescriptor ||
DescriptorUtils.isTopLevelDeclaration(this) ||
(this is CallableDescriptor && DescriptorUtils.isStaticDeclaration(this))) {
return true
return !getName().isSpecial()
}
val parent = getContainingDeclaration()!!
if (parent !is ClassDescriptor || !parent.canBeReferencedViaImport()) {
@@ -395,6 +395,17 @@ fun TypeParameter.collectReferencedTypes(bindingContext: BindingContext): List<J
.filterNotNull()
}
private fun JetType.isExtractable(): Boolean {
return collectReferencedTypes(true).fold(true) { (extractable, typeToCheck) ->
val parameterTypeDescriptor = typeToCheck.getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor
val typeParameter = parameterTypeDescriptor?.let {
DescriptorToSourceUtils.descriptorToDeclaration(it)
} as? JetTypeParameter
extractable && (typeParameter != null || typeToCheck.canBeReferencedViaImport())
}
}
private fun JetType.processTypeIfExtractable(
typeParameters: MutableSet<TypeParameter>,
nonDenotableTypes: MutableSet<JetType>,
@@ -429,8 +440,6 @@ private fun JetType.processTypeIfExtractable(
private class MutableParameter(
override val argumentText: String,
override val originalDescriptor: DeclarationDescriptor,
override val name: String,
override val mirrorVarName: String?,
override val receiverCandidate: Boolean
): Parameter {
// All modifications happen in the same thread
@@ -441,24 +450,32 @@ private class MutableParameter(
var refCount: Int = 0
fun addDefaultType(jetType: JetType) {
assert(writable, "Can't add type to non-writable parameter $name")
assert(writable, "Can't add type to non-writable parameter $currentName")
defaultTypes.add(jetType)
}
fun addTypePredicate(predicate: TypePredicate) {
assert(writable, "Can't add type predicate to non-writable parameter $name")
assert(writable, "Can't add type predicate to non-writable parameter $currentName")
typePredicates.add(predicate)
}
var currentName: String? = null
override val name: String get() = currentName!!
override var mirrorVarName: String? = null
private val defaultType: JetType by Delegates.lazy {
writable = false
CommonSupertypes.commonSupertype(defaultTypes)
}
override val parameterTypeCandidates: List<JetType> by Delegates.lazy {
writable = false
listOf(parameterType) + TypeUtils.getAllSupertypes(parameterType).filter(and(typePredicates))
val superTypes = TypeUtils.getAllSupertypes(defaultType).filter(and(typePredicates))
(Collections.singletonList(defaultType) + superTypes).filter { it.isExtractable() }
}
override val parameterType: JetType by Delegates.lazy {
writable = false
CommonSupertypes.commonSupertype(defaultTypes)
}
override val parameterType: JetType by Delegates.lazy { parameterTypeCandidates.firstOrNull() ?: defaultType }
override fun copy(name: String, parameterType: JetType): Parameter = DelegatingParameter(this, name, parameterType)
}
@@ -488,12 +505,6 @@ private fun ExtractionData.inferParametersInfo(
): ParametersInfo {
val info = ParametersInfo()
val varNameValidator = JetNameValidatorImpl(
commonParent.getParentByType(javaClass<JetExpression>()),
originalElements.first,
JetNameValidatorImpl.Target.PROPERTIES
)
val extractedDescriptorToParameter = HashMap<DeclarationDescriptor, MutableParameter>()
for (refInfo in getBrokenReferencesInfo(createTemporaryCodeBlock())) {
@@ -557,28 +568,21 @@ private fun ExtractionData.inferParametersInfo(
?: DEFAULT_PARAMETER_TYPE
}
if (!parameterType.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes)) continue
val parameterTypePredicate =
and(pseudocode.getElementValuesRecursively(originalRef).map { getExpectedTypePredicate(it, bindingContext) })
val parameter = extractedDescriptorToParameter.getOrPut(descriptorToExtract) {
val parameterName =
if (extractThis) {
JetNameSuggester.suggestNames(parameterType, varNameValidator, null).first()
}
else originalDeclaration.getName()!!
val mirrorVarName =
if (descriptorToExtract in modifiedVarDescriptors) varNameValidator.validateName(parameterName)!! else null
val argumentText =
if (hasThisReceiver && extractThis)
"this@${parameterType.getConstructor().getDeclarationDescriptor()!!.getName().asString()}"
else
(thisExpr ?: ref).getText() ?: throw AssertionError("'this' reference shouldn't be empty: code fragment = ${getCodeFragmentText()}")
MutableParameter(argumentText, descriptorToExtract, parameterName, mirrorVarName, extractThis)
MutableParameter(argumentText, descriptorToExtract, extractThis)
}
if (!extractThis) {
parameter.currentName = originalDeclaration.getName()
}
parameter.refCount++
@@ -593,11 +597,28 @@ private fun ExtractionData.inferParametersInfo(
}
}
val varNameValidator = JetNameValidatorImpl(
commonParent.getParentByType(javaClass<JetExpression>()),
originalElements.first,
JetNameValidatorImpl.Target.PROPERTIES
)
for ((descriptorToExtract, parameter) in extractedDescriptorToParameter) {
if (!parameter.parameterType.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes)) continue
with (parameter) {
if (currentName == null) {
currentName = JetNameSuggester.suggestNames(parameterType, varNameValidator, null).first()
}
mirrorVarName = if (descriptorToExtract in modifiedVarDescriptors) varNameValidator.validateName(name) else null
info.parameters.add(this)
}
}
for (typeToCheck in info.typeParameters.flatMapTo(HashSet<JetType>()) { it.collectReferencedTypes(bindingContext) }) {
typeToCheck.processTypeIfExtractable(info.typeParameters, info.nonDenotableTypes)
}
info.parameters.addAll(extractedDescriptorToParameter.values())
return info
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// SIBLING:
val x = object {
val t = 1
fun test() {
<selection>println(this.t)</selection>
}
}
@@ -0,0 +1 @@
Cannot extract method since following types are not denotable in the target scope: &lt;no name provided&gt;
@@ -0,0 +1,10 @@
// PARAM_DESCRIPTOR: internal final class <no name provided> defined in root package
// PARAM_TYPES: kotlin.Any
// WITH_RUNTIME
// SIBLING:
val x = object {
fun test() {
<selection>println(this)</selection>
}
}
@@ -0,0 +1,14 @@
// PARAM_DESCRIPTOR: internal final class <no name provided> defined in root package
// PARAM_TYPES: kotlin.Any
// WITH_RUNTIME
// SIBLING:
val x = object {
fun test() {
unit()
}
}
private fun Any.unit() {
println(this)
}
@@ -0,0 +1,17 @@
// PARAM_DESCRIPTOR: val x: <no name provided> defined in test
// PARAM_TYPES: A
// WITH_RUNTIME
open class A {
}
fun foo(a: A) {
}
// SIBLING:
fun test() {
val x = object: A() { }
<selection>foo(x)</selection>
}
@@ -0,0 +1,21 @@
// PARAM_DESCRIPTOR: val x: <no name provided> defined in test
// PARAM_TYPES: A
// WITH_RUNTIME
open class A {
}
fun foo(a: A) {
}
// SIBLING:
fun test() {
val x = object: A() { }
unit(x)
}
private fun unit(x: A) {
foo(x)
}
@@ -1361,6 +1361,24 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/extractFunction/parameters/candidateTypes"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("cantLiftAnonymousToSupertype.kt")
public void testCantLiftAnonymousToSupertype() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/candidateTypes/cantLiftAnonymousToSupertype.kt");
doExtractFunctionTest(fileName);
}
@TestMetadata("liftAnonymousToSupertype1.kt")
public void testLiftAnonymousToSupertype1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/candidateTypes/liftAnonymousToSupertype1.kt");
doExtractFunctionTest(fileName);
}
@TestMetadata("liftAnonymousToSupertype2.kt")
public void testLiftAnonymousToSupertype2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/candidateTypes/liftAnonymousToSupertype2.kt");
doExtractFunctionTest(fileName);
}
@TestMetadata("nonNullableTypes.kt")
public void testNonNullableTypes() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/candidateTypes/nonNullableTypes.kt");