Extract Function: Replace non-denotable parameter types with their super types when possible
#KT-6187 Fixed
This commit is contained in:
@@ -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()) {
|
||||
|
||||
+49
-28
@@ -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
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
// SIBLING:
|
||||
val x = object {
|
||||
val t = 1
|
||||
|
||||
fun test() {
|
||||
<selection>println(this.t)</selection>
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
Cannot extract method since following types are not denotable in the target scope: <no name provided>
|
||||
+10
@@ -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>
|
||||
}
|
||||
}
|
||||
+14
@@ -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)
|
||||
}
|
||||
+17
@@ -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>
|
||||
}
|
||||
+21
@@ -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)
|
||||
}
|
||||
+18
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user