diff --git a/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt b/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt index cbded7f21ae..5db43dc63cf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt +++ b/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt @@ -220,26 +220,12 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper( return true } - private fun addStarImport(target: DeclarationDescriptor): ImportDescriptorResult { - val targetFqName = target.importableFqName!! + private fun addStarImport(targetDescriptor: DeclarationDescriptor): ImportDescriptorResult { + val targetFqName = targetDescriptor.importableFqName!! val parentFqName = targetFqName.parent() val moduleDescriptor = resolutionFacade.moduleDescriptor - val imports = file.importDirectives 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) @@ -249,38 +235,57 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper( return !visibility.mustCheckInImports() || Visibilities.isVisibleIgnoringReceiver(descriptor, filePackage) } - val classNamesToImport = scopeToImport - .getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS, { true }) - .filter(::isVisible) - .map { it.name } + val kindFilter = DescriptorKindFilter.ALL.withoutKinds(DescriptorKindFilter.PACKAGES_MASK) + val allNamesToImport = scopeToImport.getDescriptorsFiltered(kindFilter).filter(::isVisible).map { it.name }.toSet() - val topLevelScope = resolutionFacade.getFileResolutionScope(file) - val conflictCandidates: List = classNamesToImport - .flatMap { - importedScopes.mapNotNull { scope -> scope.getContributedClassifier(it, NoLookupLocation.FROM_IDE) } + fun targetFqNameAndType(ref: KtReferenceExpression): Pair>? { + val descriptors = ref.resolveTargets() + val fqName: FqName? = descriptors.filter(::isVisible). map { it.importableFqName }.toSet().singleOrNull() + return if (fqName != null) { + Pair(fqName, descriptors.elementAt(0).javaClass) + } else null + } + + val futureCheckMap = HashMap>>() + 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 newTopLevelScope = resolutionFacade.getFileResolutionScope(file) - if (!isAlreadyImported(target, newTopLevelScope, targetFqName)) { + if (!isAlreadyImported(targetDescriptor, resolutionFacade.getFileResolutionScope(file), targetFqName)) { addedImport.delete() return ImportDescriptorResult.FAIL } - for (conflict in conflicts) { - addImport(DescriptorUtils.getFqNameSafe(conflict), false) + dropRedundantExplicitImports(parentFqName) + + 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 } diff --git a/idea/testData/addImport/ConflictingNameAppearsAndHasUsage3.kt b/idea/testData/addImport/ConflictingNameAppearsAndHasUsage3.kt index 82a3b79468b..704e81fcb99 100644 --- a/idea/testData/addImport/ConflictingNameAppearsAndHasUsage3.kt +++ b/idea/testData/addImport/ConflictingNameAppearsAndHasUsage3.kt @@ -3,4 +3,4 @@ package p import dependency.* -val d = Date(1, 2) +val d = Date("") //import dependency.Date is not necessary \ No newline at end of file diff --git a/idea/testData/addImport/ConflictingNameAppearsAndHasUsage3.kt.after b/idea/testData/addImport/ConflictingNameAppearsAndHasUsage3.kt.after index c221f40d2a5..187296dbf0e 100644 --- a/idea/testData/addImport/ConflictingNameAppearsAndHasUsage3.kt.after +++ b/idea/testData/addImport/ConflictingNameAppearsAndHasUsage3.kt.after @@ -2,7 +2,6 @@ package p import dependency.* -import dependency.Date import java.util.* -val d = Date(1, 2) +val d = Date("") //import dependency.Date is not necessary \ No newline at end of file diff --git a/idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.dependency.kt b/idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.dependency.kt new file mode 100644 index 00000000000..d687aec6e15 --- /dev/null +++ b/idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.dependency.kt @@ -0,0 +1,5 @@ +package dependency + +class Date + +fun Date(s: String) = Date() \ No newline at end of file diff --git a/idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.kt b/idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.kt new file mode 100644 index 00000000000..bf578667502 --- /dev/null +++ b/idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.kt @@ -0,0 +1,6 @@ +// IMPORT: java.util.Calendar +package p + +import dependency.* + +val d = Date() diff --git a/idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.kt.after b/idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.kt.after new file mode 100644 index 00000000000..40e3ce264e8 --- /dev/null +++ b/idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.kt.after @@ -0,0 +1,8 @@ +// IMPORT: java.util.Calendar +package p + +import dependency.* +import dependency.Date +import java.util.* + +val d = Date() diff --git a/idea/testData/addImport/ConflictsExtensions.dependency.kt b/idea/testData/addImport/ConflictsExtensions.dependency.kt new file mode 100644 index 00000000000..69d9027f4de --- /dev/null +++ b/idea/testData/addImport/ConflictsExtensions.dependency.kt @@ -0,0 +1,9 @@ +package conflicts.extensions + +fun Byte.inv(): Byte = (255 - this).toByte() + +fun foo1() {} +fun foo2() {} +fun foo3() {} +fun foo4() {} +fun foo5() {} \ No newline at end of file diff --git a/idea/testData/addImport/ConflictsExtensions.dependency1.kt b/idea/testData/addImport/ConflictsExtensions.dependency1.kt new file mode 100644 index 00000000000..b29c719a5de --- /dev/null +++ b/idea/testData/addImport/ConflictsExtensions.dependency1.kt @@ -0,0 +1,3 @@ +package conflicts.extensions.deps + +fun Byte.inv(): Byte = (255 - this).toByte() \ No newline at end of file diff --git a/idea/testData/addImport/ConflictsExtensions.kt b/idea/testData/addImport/ConflictsExtensions.kt new file mode 100644 index 00000000000..4cab46b05f2 --- /dev/null +++ b/idea/testData/addImport/ConflictsExtensions.kt @@ -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) { + val b: Byte = 1 + b.inv() +} \ No newline at end of file diff --git a/idea/testData/addImport/ConflictsExtensions.kt.after b/idea/testData/addImport/ConflictsExtensions.kt.after new file mode 100644 index 00000000000..171f2ac00bf --- /dev/null +++ b/idea/testData/addImport/ConflictsExtensions.kt.after @@ -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) { + val b: Byte = 1 + b.inv() +} \ No newline at end of file diff --git a/idea/testData/addImport/ConflictsExtensions1.dependency.kt b/idea/testData/addImport/ConflictsExtensions1.dependency.kt new file mode 100644 index 00000000000..69d9027f4de --- /dev/null +++ b/idea/testData/addImport/ConflictsExtensions1.dependency.kt @@ -0,0 +1,9 @@ +package conflicts.extensions + +fun Byte.inv(): Byte = (255 - this).toByte() + +fun foo1() {} +fun foo2() {} +fun foo3() {} +fun foo4() {} +fun foo5() {} \ No newline at end of file diff --git a/idea/testData/addImport/ConflictsExtensions1.dependency1.kt b/idea/testData/addImport/ConflictsExtensions1.dependency1.kt new file mode 100644 index 00000000000..29bda66618a --- /dev/null +++ b/idea/testData/addImport/ConflictsExtensions1.dependency1.kt @@ -0,0 +1,3 @@ +package conflicts.extensions.deps + +fun Byte.inv(max: Int): Byte = (max - this).toByte() \ No newline at end of file diff --git a/idea/testData/addImport/ConflictsExtensions1.kt b/idea/testData/addImport/ConflictsExtensions1.kt new file mode 100644 index 00000000000..b3c25529823 --- /dev/null +++ b/idea/testData/addImport/ConflictsExtensions1.kt @@ -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) { + val b: Byte = 1 + b.inv(255) +} \ No newline at end of file diff --git a/idea/testData/addImport/ConflictsExtensions1.kt.after b/idea/testData/addImport/ConflictsExtensions1.kt.after new file mode 100644 index 00000000000..80d1acf3d6c --- /dev/null +++ b/idea/testData/addImport/ConflictsExtensions1.kt.after @@ -0,0 +1,10 @@ +//IMPORT: conflicts.extensions.foo5 +package p + +import conflicts.extensions.* +import conflicts.extensions.deps.* + +fun main(args: Array) { + val b: Byte = 1 + b.inv(255) +} \ No newline at end of file diff --git a/idea/testData/addImport/DoNotDropConflictingOnStar.dependency.kt b/idea/testData/addImport/DoNotDropConflictingOnStar.dependency.kt new file mode 100644 index 00000000000..1cd02c50456 --- /dev/null +++ b/idea/testData/addImport/DoNotDropConflictingOnStar.dependency.kt @@ -0,0 +1,8 @@ +package dependency + +typealias Date = String + +fun util1() = 1 +fun util2() = 2 +fun util3() = 3 +fun util4() = 4 diff --git a/idea/testData/addImport/DoNotDropConflictingOnStar.kt b/idea/testData/addImport/DoNotDropConflictingOnStar.kt new file mode 100644 index 00000000000..2a0ee76d18b --- /dev/null +++ b/idea/testData/addImport/DoNotDropConflictingOnStar.kt @@ -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? = null +val calendar: Calendar? = null + +fun klass(): Date { + util1() + util2() + util3() + return Date() +} \ No newline at end of file diff --git a/idea/testData/addImport/DoNotDropConflictingOnStar.kt.after b/idea/testData/addImport/DoNotDropConflictingOnStar.kt.after new file mode 100644 index 00000000000..252771d1222 --- /dev/null +++ b/idea/testData/addImport/DoNotDropConflictingOnStar.kt.after @@ -0,0 +1,16 @@ +// IMPORT: dependency.util4 +package p + +import dependency.* +import dependency.Date +import java.util.* + +val aMap: AbstractMap? = null +val calendar: Calendar? = null + +fun klass(): Date { + util1() + util2() + util3() + return Date() +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/addImport/AddImportTestGenerated.java b/idea/tests/org/jetbrains/kotlin/addImport/AddImportTestGenerated.java index 69d39a5c59c..f89b9dca6db 100644 --- a/idea/tests/org/jetbrains/kotlin/addImport/AddImportTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/addImport/AddImportTestGenerated.java @@ -89,6 +89,12 @@ public class AddImportTestGenerated extends AbstractAddImportTest { 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") public void testConflictingNameAppearsButUsageIsQualified() throws Exception { runTest("idea/testData/addImport/ConflictingNameAppearsButUsageIsQualified.kt"); @@ -129,6 +135,24 @@ public class AddImportTestGenerated extends AbstractAddImportTest { 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") public void testDropExplicitImports() throws Exception { runTest("idea/testData/addImport/DropExplicitImports.kt");