Rewritten resolving conflicting imports on adding new import with star action (KT-18503)

#KT-18503 Fixed
This commit is contained in:
Alexander Podkhalyuzin
2017-07-12 09:20:16 +03:00
committed by Nikolay Krasko
parent ed5051a5e3
commit 5aa7216c13
18 changed files with 200 additions and 40 deletions
@@ -220,26 +220,12 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper(
return true return true
} }
private fun addStarImport(target: DeclarationDescriptor): ImportDescriptorResult { private fun addStarImport(targetDescriptor: DeclarationDescriptor): ImportDescriptorResult {
val targetFqName = target.importableFqName!! val targetFqName = targetDescriptor.importableFqName!!
val parentFqName = targetFqName.parent() val parentFqName = targetFqName.parent()
val moduleDescriptor = resolutionFacade.moduleDescriptor val moduleDescriptor = resolutionFacade.moduleDescriptor
val imports = file.importDirectives
val scopeToImport = getMemberScope(parentFqName, moduleDescriptor) ?: return ImportDescriptorResult.FAIL val scopeToImport = getMemberScope(parentFqName, moduleDescriptor) ?: return ImportDescriptorResult.FAIL
val importedScopes = imports
.asSequence()
.filter { it.isAllUnder }
.mapNotNull {
val importPath = it.importPath
if (importPath != null) {
val fqName = importPath.fqName
getMemberScope(fqName, moduleDescriptor)
} else {
null
}
}
.toList()
val filePackage = moduleDescriptor.getPackage(file.packageFqName) val filePackage = moduleDescriptor.getPackage(file.packageFqName)
@@ -249,38 +235,57 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper(
return !visibility.mustCheckInImports() || Visibilities.isVisibleIgnoringReceiver(descriptor, filePackage) return !visibility.mustCheckInImports() || Visibilities.isVisibleIgnoringReceiver(descriptor, filePackage)
} }
val classNamesToImport = scopeToImport val kindFilter = DescriptorKindFilter.ALL.withoutKinds(DescriptorKindFilter.PACKAGES_MASK)
.getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS, { true }) val allNamesToImport = scopeToImport.getDescriptorsFiltered(kindFilter).filter(::isVisible).map { it.name }.toSet()
.filter(::isVisible)
.map { it.name }
val topLevelScope = resolutionFacade.getFileResolutionScope(file) fun targetFqNameAndType(ref: KtReferenceExpression): Pair<FqName, Class<out Any>>? {
val conflictCandidates: List<ClassifierDescriptor> = classNamesToImport val descriptors = ref.resolveTargets()
.flatMap { val fqName: FqName? = descriptors.filter(::isVisible). map { it.importableFqName }.toSet().singleOrNull()
importedScopes.mapNotNull { scope -> scope.getContributedClassifier(it, NoLookupLocation.FROM_IDE) } return if (fqName != null) {
Pair(fqName, descriptors.elementAt(0).javaClass)
} else null
}
val futureCheckMap = HashMap<KtSimpleNameExpression, Pair<FqName, Class<out Any>>>()
file.accept(object : KtVisitorVoid() {
override fun visitElement(element: PsiElement): Unit = element.acceptChildren(this)
override fun visitImportList(importList: KtImportList) {}
override fun visitPackageDirective(directive: KtPackageDirective) {}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
val refName = expression.getReferencedNameAsName()
if (allNamesToImport.contains(refName)) {
val target = targetFqNameAndType(expression)
if (target != null) {
futureCheckMap += Pair(expression, target)
}
} }
.filter { importedClass -> }
isVisible(importedClass) })
// check that class is really imported
&& topLevelScope.findClassifier(importedClass.name, NoLookupLocation.FROM_IDE) == importedClass
// and not yet imported explicitly
&& imports.all { it.importPath != ImportPath(importedClass.importableFqName!!, false) }
}
val conflicts = detectNeededImports(conflictCandidates)
val addedImport = addImport(parentFqName, true) val addedImport = addImport(parentFqName, true)
val newTopLevelScope = resolutionFacade.getFileResolutionScope(file) if (!isAlreadyImported(targetDescriptor, resolutionFacade.getFileResolutionScope(file), targetFqName)) {
if (!isAlreadyImported(target, newTopLevelScope, targetFqName)) {
addedImport.delete() addedImport.delete()
return ImportDescriptorResult.FAIL return ImportDescriptorResult.FAIL
} }
for (conflict in conflicts) { dropRedundantExplicitImports(parentFqName)
addImport(DescriptorUtils.getFqNameSafe(conflict), false)
val conflicts = futureCheckMap
.mapNotNull { (expr, fqNameAndType) ->
if (targetFqNameAndType(expr) != fqNameAndType) fqNameAndType.first else null
}
.toSet()
fun isNotImported(fqName: FqName): Boolean {
return file.importDirectives.none { directive ->
!directive.isAllUnder && directive.alias == null && directive.importedFqName == fqName
}
} }
dropRedundantExplicitImports(parentFqName) for (conflict in conflicts.filter(::isNotImported)) {
addImport(conflict, false)
}
return ImportDescriptorResult.IMPORT_ADDED return ImportDescriptorResult.IMPORT_ADDED
} }
@@ -3,4 +3,4 @@ package p
import dependency.* import dependency.*
val d = Date(1, 2) val d = Date("") //import dependency.Date is not necessary
@@ -2,7 +2,6 @@
package p package p
import dependency.* import dependency.*
import dependency.Date
import java.util.* import java.util.*
val d = Date(1, 2) val d = Date("") //import dependency.Date is not necessary
@@ -0,0 +1,5 @@
package dependency
class Date
fun Date(s: String) = Date()
@@ -0,0 +1,6 @@
// IMPORT: java.util.Calendar
package p
import dependency.*
val d = Date()
@@ -0,0 +1,8 @@
// IMPORT: java.util.Calendar
package p
import dependency.*
import dependency.Date
import java.util.*
val d = Date()
@@ -0,0 +1,9 @@
package conflicts.extensions
fun Byte.inv(): Byte = (255 - this).toByte()
fun foo1() {}
fun foo2() {}
fun foo3() {}
fun foo4() {}
fun foo5() {}
@@ -0,0 +1,3 @@
package conflicts.extensions.deps
fun Byte.inv(): Byte = (255 - this).toByte()
+13
View File
@@ -0,0 +1,13 @@
//IMPORT: conflicts.extensions.foo5
package p
import conflicts.extensions.foo1
import conflicts.extensions.foo2
import conflicts.extensions.foo3
import conflicts.extensions.foo4
import conflicts.extensions.deps.*
fun main(args: Array<String>) {
val b: Byte = 1
b.inv()
}
+11
View File
@@ -0,0 +1,11 @@
//IMPORT: conflicts.extensions.foo5
package p
import conflicts.extensions.*
import conflicts.extensions.deps.*
import conflicts.extensions.deps.inv
fun main(args: Array<String>) {
val b: Byte = 1
b.inv()
}
@@ -0,0 +1,9 @@
package conflicts.extensions
fun Byte.inv(): Byte = (255 - this).toByte()
fun foo1() {}
fun foo2() {}
fun foo3() {}
fun foo4() {}
fun foo5() {}
@@ -0,0 +1,3 @@
package conflicts.extensions.deps
fun Byte.inv(max: Int): Byte = (max - this).toByte()
+13
View File
@@ -0,0 +1,13 @@
//IMPORT: conflicts.extensions.foo5
package p
import conflicts.extensions.foo1
import conflicts.extensions.foo2
import conflicts.extensions.foo3
import conflicts.extensions.foo4
import conflicts.extensions.deps.*
fun main(args: Array<String>) {
val b: Byte = 1
b.inv(255)
}
+10
View File
@@ -0,0 +1,10 @@
//IMPORT: conflicts.extensions.foo5
package p
import conflicts.extensions.*
import conflicts.extensions.deps.*
fun main(args: Array<String>) {
val b: Byte = 1
b.inv(255)
}
@@ -0,0 +1,8 @@
package dependency
typealias Date = String
fun util1() = 1
fun util2() = 2
fun util3() = 3
fun util4() = 4
+18
View File
@@ -0,0 +1,18 @@
// IMPORT: dependency.util4
package p
import dependency.util1
import dependency.util2
import dependency.util3
import dependency.Date
import java.util.*
val aMap: AbstractMap<Int, Int>? = null
val calendar: Calendar? = null
fun klass(): Date {
util1()
util2()
util3()
return Date()
}
@@ -0,0 +1,16 @@
// IMPORT: dependency.util4
package p
import dependency.*
import dependency.Date
import java.util.*
val aMap: AbstractMap<Int, Int>? = null
val calendar: Calendar? = null
fun klass(): Date {
util1()
util2()
util3()
return Date()
}
@@ -89,6 +89,12 @@ public class AddImportTestGenerated extends AbstractAddImportTest {
runTest("idea/testData/addImport/ConflictingNameAppearsAndHasUsage6.kt"); runTest("idea/testData/addImport/ConflictingNameAppearsAndHasUsage6.kt");
} }
@TestMetadata("ConflictingNameAppearsAndHasUsage7.kt")
public void testConflictingNameAppearsAndHasUsage7() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.kt");
doTest(fileName);
}
@TestMetadata("ConflictingNameAppearsButUsageIsQualified.kt") @TestMetadata("ConflictingNameAppearsButUsageIsQualified.kt")
public void testConflictingNameAppearsButUsageIsQualified() throws Exception { public void testConflictingNameAppearsButUsageIsQualified() throws Exception {
runTest("idea/testData/addImport/ConflictingNameAppearsButUsageIsQualified.kt"); runTest("idea/testData/addImport/ConflictingNameAppearsButUsageIsQualified.kt");
@@ -129,6 +135,24 @@ public class AddImportTestGenerated extends AbstractAddImportTest {
runTest("idea/testData/addImport/ConflictingNameNoAllUnderImport3.kt"); runTest("idea/testData/addImport/ConflictingNameNoAllUnderImport3.kt");
} }
@TestMetadata("ConflictsExtensions.kt")
public void testConflictsExtensions() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/addImport/ConflictsExtensions.kt");
doTest(fileName);
}
@TestMetadata("ConflictsExtensions1.kt")
public void testConflictsExtensions1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/addImport/ConflictsExtensions1.kt");
doTest(fileName);
}
@TestMetadata("DoNotDropConflictingOnStar.kt")
public void testDoNotDropConflictingOnStar() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/addImport/DoNotDropConflictingOnStar.kt");
doTest(fileName);
}
@TestMetadata("DropExplicitImports.kt") @TestMetadata("DropExplicitImports.kt")
public void testDropExplicitImports() throws Exception { public void testDropExplicitImports() throws Exception {
runTest("idea/testData/addImport/DropExplicitImports.kt"); runTest("idea/testData/addImport/DropExplicitImports.kt");