diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/utils/ScopeUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/utils/ScopeUtils.kt index 9d7bd94d3c6..3944f962bab 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/utils/ScopeUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/utils/ScopeUtils.kt @@ -281,16 +281,28 @@ public fun LexicalScope.addImportScope(importScope: JetScope): LexicalScope { val fileScope = getFileScope() val scopeWithAdditionImport = LexicalChainedScope(fileScope, fileScope.ownerDescriptor, false, null, "Scope with addition import", importScope) - return LexicalScopeWrapper(this, scopeWithAdditionImport) + return replaceFileScope(scopeWithAdditionImport) } +public fun LexicalScope.replaceFileScope(fileScopeReplace: LexicalScope): LexicalScope { + if (this is FileScope) return fileScopeReplace + + return LexicalScopeWrapper(this, fileScopeReplace) +} + +public fun LexicalScope.withNoFileScope(): LexicalScope = replaceFileScope(MemberScopeToFileScopeAdapter(JetScope.Empty)) + private class LexicalScopeWrapper(val delegate: LexicalScope, val fileScopeReplace: LexicalScope): LexicalScope by delegate { override val parent: LexicalScope? by lazy(LazyThreadSafetyMode.NONE) { - if (delegate is FileScope) { + assert(delegate !is FileScope) { "We should replace FileScope($delegate) to $fileScopeReplace" } + val parent = delegate.parent!! + + if (parent is FileScope) { fileScopeReplace } else { - LexicalScopeWrapper(delegate.parent!!, fileScopeReplace) + LexicalScopeWrapper(parent, fileScopeReplace) } } } + diff --git a/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt b/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt index 2ab2250dab6..ab865669974 100644 --- a/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt @@ -21,13 +21,12 @@ import com.intellij.lang.ImportOptimizer import com.intellij.openapi.progress.ProgressIndicatorProvider import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.PackageViewDescriptor +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getFileTopLevelScope import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.core.formatter.JetCodeStyleSettings +import org.jetbrains.kotlin.idea.core.getResolutionScope import org.jetbrains.kotlin.idea.references.JetReference import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver import org.jetbrains.kotlin.idea.util.ImportInsertHelper @@ -36,14 +35,15 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.renderer.render import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils -import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension +import org.jetbrains.kotlin.resolve.scopes.JetScope +import org.jetbrains.kotlin.resolve.scopes.utils.asJetScope +import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy +import org.jetbrains.kotlin.resolve.scopes.utils.withNoFileScope import java.util.* public class KotlinImportOptimizer() : ImportOptimizer { @@ -112,7 +112,7 @@ public class KotlinImportOptimizer() : ImportOptimizer { if (referencedName != null && importableDescriptor.name != referencedName) continue // resolved via alias - if (isAccessibleAsMember(importableDescriptor, element)) continue + if (isAccessibleAsMember(importableDescriptor, element, bindingContext)) continue _descriptors.add(importableDescriptor) } @@ -121,15 +121,29 @@ public class KotlinImportOptimizer() : ImportOptimizer { super.visitJetElement(element) } - private fun isAccessibleAsMember(target: DeclarationDescriptor, place: JetElement): Boolean { - val container = target.containingDeclaration - if (container !is ClassDescriptor) return false - val scope = if (DescriptorUtils.isCompanionObject(container)) - container.getContainingDeclaration() as? ClassDescriptor ?: return false - else - container - val classBody = (DescriptorToSourceUtils.getSourceFromDescriptor(scope) as? JetClassOrObject)?.getBody() - return classBody != null && classBody.containingFile == file && classBody.isAncestor(place) + private fun isAccessibleAsMember(target: DeclarationDescriptor, place: JetElement, bindingContext: BindingContext): Boolean { + if (target.containingDeclaration !is ClassDescriptor) return false + + fun isInScope(scope: JetScope): Boolean { + return when (target) { + is FunctionDescriptor -> + scope.getFunctions(target.name, NoLookupLocation.FROM_IDE).contains(target) + + is PropertyDescriptor -> + scope.getProperties(target.name, NoLookupLocation.FROM_IDE).contains(target) + + is ClassDescriptor -> + scope.getClassifier(target.name, NoLookupLocation.FROM_IDE) == target + + else -> false + } + } + + val resolutionScope = place.getResolutionScope(bindingContext, place.getResolutionFacade()) + val noImportsScope = resolutionScope.withNoFileScope() + + return isInScope(noImportsScope.asJetScope()) + || resolutionScope.getImplicitReceiversHierarchy().any { isInScope(it.type.memberScope) } } } diff --git a/idea/testData/editor/optimizeImports/FromCompanionObject.kt b/idea/testData/editor/optimizeImports/FromCompanionObject.kt new file mode 100644 index 00000000000..e46e3ac8e1f --- /dev/null +++ b/idea/testData/editor/optimizeImports/FromCompanionObject.kt @@ -0,0 +1,16 @@ +import java.util.ArrayList + +class B { + companion object { + fun foo(){} + + val property = 0 + + class Nested + } + + fun f(): Nested { + foo() + property + } +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/FromCompanionObject.kt.after b/idea/testData/editor/optimizeImports/FromCompanionObject.kt.after new file mode 100644 index 00000000000..e016bd8ce63 --- /dev/null +++ b/idea/testData/editor/optimizeImports/FromCompanionObject.kt.after @@ -0,0 +1,14 @@ +class B { + companion object { + fun foo(){} + + val property = 0 + + class Nested + } + + fun f(): Nested { + foo() + property + } +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/FromCompanionObjectGeneric.kt b/idea/testData/editor/optimizeImports/FromCompanionObjectGeneric.kt new file mode 100644 index 00000000000..724d3fd48a5 --- /dev/null +++ b/idea/testData/editor/optimizeImports/FromCompanionObjectGeneric.kt @@ -0,0 +1,18 @@ +import A.Companion.run + +interface Action { + fun run(t: T) +} + +open class A { + companion object : Action { + override fun run(t: String) { + } + } +} + +class B : A() { + fun foo() { + run("") + } +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/FromCompanionObjectGeneric.kt.after b/idea/testData/editor/optimizeImports/FromCompanionObjectGeneric.kt.after new file mode 100644 index 00000000000..40a55d0b8af --- /dev/null +++ b/idea/testData/editor/optimizeImports/FromCompanionObjectGeneric.kt.after @@ -0,0 +1,16 @@ +interface Action { + fun run(t: T) +} + +open class A { + companion object : Action { + override fun run(t: String) { + } + } +} + +class B : A() { + fun foo() { + run("") + } +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/StaticMethodFromSuper.kt b/idea/testData/editor/optimizeImports/StaticMethodFromSuper.kt new file mode 100644 index 00000000000..57057f71fb1 --- /dev/null +++ b/idea/testData/editor/optimizeImports/StaticMethodFromSuper.kt @@ -0,0 +1,7 @@ +import java.util.ArrayList + +class B : Thread() { + init { + currentThread() + } +} \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/StaticMethodFromSuper.kt.after b/idea/testData/editor/optimizeImports/StaticMethodFromSuper.kt.after new file mode 100644 index 00000000000..ff650e24a43 --- /dev/null +++ b/idea/testData/editor/optimizeImports/StaticMethodFromSuper.kt.after @@ -0,0 +1,5 @@ +class B : Thread() { + init { + currentThread() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java index 303c3785db3..3a018d1e6a2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java @@ -107,6 +107,18 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest { doTest(fileName); } + @TestMetadata("FromCompanionObject.kt") + public void testFromCompanionObject() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/FromCompanionObject.kt"); + doTest(fileName); + } + + @TestMetadata("FromCompanionObjectGeneric.kt") + public void testFromCompanionObjectGeneric() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/FromCompanionObjectGeneric.kt"); + doTest(fileName); + } + @TestMetadata("InvokeFunction.kt") public void testInvokeFunction() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/InvokeFunction.kt"); @@ -191,6 +203,12 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest { doTest(fileName); } + @TestMetadata("StaticMethodFromSuper.kt") + public void testStaticMethodFromSuper() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/StaticMethodFromSuper.kt"); + doTest(fileName); + } + @TestMetadata("UnusedImports.kt") public void testUnusedImports() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/UnusedImports.kt");