Refactored sorting in completion

Also changed semantics of DeclarationDescriptor.importableFqName - it now returns null when descriptor cannot be referenced by import + dropped DeclarationDescriptor.importableFqNameSafe
This commit is contained in:
Valentin Kipyatkov
2015-08-21 16:15:05 +03:00
parent 68c0c83879
commit 0f332bd8b9
19 changed files with 289 additions and 255 deletions
@@ -22,23 +22,22 @@ import org.jetbrains.kotlin.psi.JetReferenceExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.types.JetType
public val DeclarationDescriptor.importableFqName: FqName?
get() {
val mayBeUnsafe = DescriptorUtils.getFqName(getImportableDescriptor())
return if (mayBeUnsafe.isSafe()) mayBeUnsafe.toSafe() else null
val importableDescriptor = getImportableDescriptor()
if (!importableDescriptor.canBeReferencedViaImport()) return null
return importableDescriptor.fqNameSafe
}
public val DeclarationDescriptor.importableFqNameSafe: FqName
get() = DescriptorUtils.getFqNameSafe(getImportableDescriptor())
public fun DeclarationDescriptor.canBeReferencedViaImport(): Boolean {
if (this is PackageViewDescriptor ||
DescriptorUtils.isTopLevelDeclaration(this) ||
(this is CallableDescriptor && DescriptorUtils.isStaticDeclaration(this))) {
return !getName().isSpecial()
this is CallableDescriptor && DescriptorUtils.isStaticDeclaration(this)) {
return !name.isSpecial
}
val parentClass = getContainingDeclaration() as? ClassDescriptor ?: return false
@@ -217,7 +217,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
collector.addElements(additionalItems)
}
collector.addDescriptorElements(referenceVariants, suppressAutoInsertion = false)
collector.addDescriptorElements(referenceVariants)
val keywordsPrefix = prefix.substringBefore('@') // if there is '@' in the prefix - use shorter prefix to not loose 'this' etc
KeywordCompletion.complete(expression ?: parameters.getPosition(), keywordsPrefix) { lookupElement ->
@@ -280,7 +280,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
if (position.getContainingFile() is JetCodeFragment) {
flushToResultSet()
collector.addDescriptorElements(getRuntimeReceiverTypeReferenceVariants(), suppressAutoInsertion = false, withReceiverCast = true)
collector.addDescriptorElements(getRuntimeReceiverTypeReferenceVariants(), withReceiverCast = true)
}
}
}
@@ -290,7 +290,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
private fun addNonImported(completionKind: CompletionKind) {
if (completionKind == CompletionKind.ALL) {
collector.addDescriptorElements(getTopLevelExtensions(), suppressAutoInsertion = true)
collector.addDescriptorElements(getTopLevelExtensions(), notImported = true)
}
flushToResultSet()
@@ -299,7 +299,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
completionKind.classKindFilter?.let { addAllClasses(it) }
if (completionKind == CompletionKind.ALL) {
collector.addDescriptorElements(getTopLevelCallables(), suppressAutoInsertion = true)
collector.addDescriptorElements(getTopLevelCallables(), notImported = true)
}
}
@@ -31,10 +31,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.getResolveScope
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
import org.jetbrains.kotlin.idea.core.comparePossiblyOverridingDescriptors
import org.jetbrains.kotlin.idea.core.getResolutionScope
import org.jetbrains.kotlin.idea.core.isVisible
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.resolve.frontendService
@@ -234,9 +231,11 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
protected open fun createSorter(): CompletionSorter {
var sorter = CompletionSorter.defaultSorter(parameters, prefixMatcher)!!
sorter = sorter.weighBefore("stats", DeprecatedWeigher, PriorityWeigher, KindWeigher)
val importableFqNameClassifier = ImportableFqNameClassifier(file)
sorter = sorter.weighAfter("stats", SymbolUsageProximityWeigher(parameters.position.containingFile as JetFile))
sorter = sorter.weighBefore("stats", DeprecatedWeigher, PriorityWeigher, NotImportedWeigher(importableFqNameClassifier), KindWeigher)
sorter = sorter.weighAfter("stats", ImportedWeigher(importableFqNameClassifier), LocationWeigher(file, parameters.originalFile as JetFile))
sorter = sorter.weighBefore("middleMatching", PreferMatchingItemWeigher)
@@ -303,8 +302,8 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
protected fun addAllClasses(kindFilter: (ClassKind) -> Boolean) {
AllClassesCompletion(parameters, indicesHelper, prefixMatcher, kindFilter)
.collect(
{ descriptor -> collector.addDescriptorElements(descriptor, suppressAutoInsertion = true) },
{ javaClass -> collector.addElementWithAutoInsertionSuppressed(lookupElementFactory.createLookupElementForJavaClass(javaClass)) }
{ descriptor -> collector.addDescriptorElements(descriptor, notImported = true) },
{ javaClass -> collector.addElement(lookupElementFactory.createLookupElementForJavaClass(javaClass), notImported = true) }
)
}
}
@@ -27,9 +27,9 @@ import com.intellij.util.PlatformIcons
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.JetIcons
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.completion.handlers.CastReceiverInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.idea.util.findLabelAndCall
@@ -46,7 +46,7 @@ import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
import org.jetbrains.kotlin.types.typeUtil.nullability
import java.util.ArrayList
import java.util.*
enum class ItemPriority {
DEFAULT,
@@ -61,6 +61,8 @@ fun LookupElement.assignPriority(priority: ItemPriority): LookupElement {
return this
}
val NOT_IMPORTED_KEY = Key<Unit>("NOT_IMPORTED_KEY")
fun LookupElement.suppressAutoInsertion() = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(this)
fun LookupElement.withReceiverCast(): LookupElement {
@@ -27,11 +27,10 @@ import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.completion.handlers.*
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.types.JetType
import java.util.ArrayList
import java.util.LinkedHashMap
import java.util.*
class LookupElementsCollector(
private val defaultPrefixMatcher: PrefixMatcher,
@@ -79,15 +78,15 @@ class LookupElementsCollector(
}
public fun addDescriptorElements(descriptors: Iterable<DeclarationDescriptor>,
suppressAutoInsertion: Boolean, // auto-insertion suppression is used for elements that require adding an import
notImported: Boolean = false,
withReceiverCast: Boolean = false
) {
for (descriptor in descriptors) {
addDescriptorElements(descriptor, suppressAutoInsertion, withReceiverCast)
addDescriptorElements(descriptor, notImported, withReceiverCast)
}
}
public fun addDescriptorElements(descriptor: DeclarationDescriptor, suppressAutoInsertion: Boolean, withReceiverCast: Boolean = false) {
public fun addDescriptorElements(descriptor: DeclarationDescriptor, notImported: Boolean = false, withReceiverCast: Boolean = false) {
run {
var lookupElement = lookupElementFactory.createLookupElement(descriptor, true)
@@ -99,12 +98,7 @@ class LookupElementsCollector(
lookupElement = lookupElement.withBracesSurrounding()
}
if (suppressAutoInsertion) {
addElementWithAutoInsertionSuppressed(lookupElement)
}
else {
addElement(lookupElement)
}
addElement(lookupElement, notImported = notImported)
}
// add special item for function with one argument of function type with more than one parameter
@@ -163,35 +157,46 @@ class LookupElementsCollector(
addElement(lookupElement)
}
public fun addElement(element: LookupElement, prefixMatcher: PrefixMatcher = defaultPrefixMatcher) {
if (prefixMatcher.prefixMatches(element)) {
val decorated = object : LookupElementDecorator<LookupElement>(element) {
override fun handleInsert(context: InsertionContext) {
getDelegate().handleInsert(context)
public fun addElement(element: LookupElement, prefixMatcher: PrefixMatcher = defaultPrefixMatcher, notImported: Boolean = false) {
if (!prefixMatcher.prefixMatches(element)) return
if (context.shouldAddCompletionChar() && !isJustTyping(context, this)) {
when (context.getCompletionChar()) {
',' -> WithTailInsertHandler.commaTail().postHandleInsert(context, getDelegate())
if (notImported) {
element.putUserData(NOT_IMPORTED_KEY, Unit)
if (isResultEmpty && elements.isEmpty()) { /* without these checks we may get duplicated items */
addElement(element.suppressAutoInsertion(), prefixMatcher)
}
else {
addElement(element, prefixMatcher)
}
return
}
'=' -> WithTailInsertHandler.eqTail().postHandleInsert(context, getDelegate())
val decorated = object : LookupElementDecorator<LookupElement>(element) {
override fun handleInsert(context: InsertionContext) {
getDelegate().handleInsert(context)
'!' -> {
WithExpressionPrefixInsertHandler("!").postHandleInsert(context)
context.setAddCompletionChar(false)
}
if (context.shouldAddCompletionChar() && !isJustTyping(context, this)) {
when (context.getCompletionChar()) {
',' -> WithTailInsertHandler.commaTail().postHandleInsert(context, getDelegate())
'=' -> WithTailInsertHandler.eqTail().postHandleInsert(context, getDelegate())
'!' -> {
WithExpressionPrefixInsertHandler("!").postHandleInsert(context)
context.setAddCompletionChar(false)
}
}
}
}
var result: LookupElement = decorated
for (postProcessor in postProcessors) {
result = postProcessor(result)
}
elements.getOrPut(prefixMatcher) { ArrayList() }.add(result)
}
var result: LookupElement = decorated
for (postProcessor in postProcessors) {
result = postProcessor(result)
}
elements.getOrPut(prefixMatcher) { ArrayList() }.add(result)
}
// used to avoid insertion of spaces before/after ',', '=' on just typing
@@ -201,17 +206,8 @@ class LookupElementsCollector(
return insertedText == element.getUserData(KotlinCompletionCharFilter.JUST_TYPING_PREFIX)
}
public fun addElementWithAutoInsertionSuppressed(element: LookupElement) {
if (isResultEmpty && elements.isEmpty()) { /* without these checks we may get duplicated items */
addElement(element.suppressAutoInsertion())
}
else {
addElement(element)
}
}
public fun addElements(elements: Iterable<LookupElement>) {
elements.forEach { addElement(it) }
public fun addElements(elements: Iterable<LookupElement>, prefixMatcher: PrefixMatcher = defaultPrefixMatcher, notImported: Boolean = false) {
elements.forEach { addElement(it, prefixMatcher, notImported) }
}
public fun advertiseSecondCompletion() {
@@ -19,14 +19,16 @@ package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementWeigher
import com.intellij.codeInsight.lookup.WeighingContext
import com.intellij.openapi.module.ModuleUtilCore
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.completion.smart.*
import org.jetbrains.kotlin.idea.core.SymbolUsageProximityPrioritizer
import org.jetbrains.kotlin.idea.core.ImportableFqNameClassifier
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
import org.jetbrains.kotlin.types.typeUtil.isBooleanOrNullableBoolean
@@ -37,6 +39,69 @@ object PriorityWeigher : LookupElementWeigher("kotlin.priority") {
= element.getUserData(ITEM_PRIORITY_KEY) ?: ItemPriority.DEFAULT
}
class NotImportedWeigher(private val classifier: ImportableFqNameClassifier) : LookupElementWeigher("kotlin.notImported") {
private enum class Weight {
default,
hasImportFromSamePackage,
notImported,
notToBeUsedInKotlin
}
override fun weigh(element: LookupElement): Weight {
if (element.getUserData(NOT_IMPORTED_KEY) == null) return Weight.default
val o = element.`object` as? DeclarationLookupObject
val fqName = o?.importableFqName ?: return Weight.default
return when (classifier.classify(fqName, o is PackageLookupObject)) {
ImportableFqNameClassifier.Classification.hasImportFromSamePackage -> Weight.hasImportFromSamePackage
ImportableFqNameClassifier.Classification.notImported -> Weight.notImported
ImportableFqNameClassifier.Classification.notToBeUsedInKotlin -> Weight.notToBeUsedInKotlin
else -> Weight.default
}
}
}
class ImportedWeigher(private val classifier: ImportableFqNameClassifier) : LookupElementWeigher("kotlin.imported") {
private enum class Weight {
currentPackage,
defaultImport,
preciseImport,
allUnderImport
}
override fun weigh(element: LookupElement): Weight? {
val o = element.`object` as? DeclarationLookupObject
val fqName = o?.importableFqName ?: return null
return when (classifier.classify(fqName, o is PackageLookupObject)) {
ImportableFqNameClassifier.Classification.fromCurrentPackage -> Weight.currentPackage
ImportableFqNameClassifier.Classification.defaultImport -> Weight.defaultImport
ImportableFqNameClassifier.Classification.preciseImport -> Weight.preciseImport
ImportableFqNameClassifier.Classification.allUnderImport -> Weight.allUnderImport
else -> null
}
}
}
class LocationWeigher(private val file: JetFile, private val originalFile: JetFile) : LookupElementWeigher("kotlin.location") {
private val currentModule = ModuleUtilCore.findModuleForPsiElement(originalFile)
private enum class Weight {
currentFile,
currentModule,
project,
libraries
}
override fun weigh(element: LookupElement): Weight? {
val declaration = (element.`object` as? DeclarationLookupObject)?.psiElement ?: return null
return when {
declaration.containingFile == file -> Weight.currentFile
ModuleUtilCore.findModuleForPsiElement(declaration) == currentModule -> Weight.currentModule
ProjectRootsUtil.isInProjectSource(declaration) -> Weight.project
else -> Weight.libraries
}
}
}
object SmartCompletionPriorityWeigher : LookupElementWeigher("kotlin.smartCompletionPriority") {
override fun weigh(element: LookupElement, context: WeighingContext)
= element.getUserData(SMART_COMPLETION_ITEM_PRIORITY_KEY) ?: SmartCompletionItemPriority.DEFAULT
@@ -114,16 +179,6 @@ object PreferMatchingItemWeigher : LookupElementWeigher("kotlin.preferMatching",
}
}
class SymbolUsageProximityWeigher(private val file: JetFile) : LookupElementWeigher("kotlin.symbolUsageProximity") {
private val prioritizer = SymbolUsageProximityPrioritizer(file)
override fun weigh(element: LookupElement): SymbolUsageProximityPrioritizer.Priority {
val o = element.`object` as? DeclarationLookupObject ?: return SymbolUsageProximityPrioritizer.Priority.DEFAULT
val fqName = o.importableFqName ?: return SymbolUsageProximityPrioritizer.Priority.DEFAULT
return prioritizer.priority(fqName, o.psiElement, o is PackageLookupObject)
}
}
class SmartCompletionInBasicWeigher(private val smartCompletion: SmartCompletion) : LookupElementWeigher("kotlin.smartInBasic", true, false) {
private val descriptorsToSkip = smartCompletion.descriptorsToSkip
private val expectedInfos = smartCompletion.expectedInfos
@@ -67,7 +67,7 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para
referenceVariants.forEach { collector.addElements(filter(it)) }
flushToResultSet()
processNonImported { collector.addElements(filter(it)) }
processNonImported { collector.addElements(filter(it), notImported = true) }
flushToResultSet()
if (position.getContainingFile() is JetCodeFragment) {
@@ -5,8 +5,8 @@ val some = prefix<caret>
// INVOCATION_COUNT: 2
// ORDER: prefixProp2
// ORDER: prefixFun2
// ORDER: prefixProp1
// ORDER: prefixProp3
// ORDER: prefixFun2
// ORDER: prefixFun1
// ORDER: prefixFun3
+1 -1
View File
@@ -5,6 +5,6 @@ class A {
}
// INVOCATION_COUNT: 2
// ORDER: kokoFun
// ORDER: koza
// ORDER: kotlin
// ORDER: kokoFun
@@ -0,0 +1,81 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.core
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.resolve.ImportPath
import java.util.*
class ImportableFqNameClassifier(private val file: JetFile) {
private val preciseImports = HashSet<FqName>()
private val preciseImportPackages = HashSet<FqName>()
private val allUnderImports = HashSet<FqName>()
init {
for (import in file.getImportDirectives()) {
val importPath = import.getImportPath() ?: continue
val fqName = importPath.fqnPart()
if (importPath.isAllUnder()) {
allUnderImports.add(fqName)
}
else {
preciseImports.add(fqName)
preciseImportPackages.add(fqName.parent())
}
}
}
enum class Classification {
fromCurrentPackage,
topLevelPackage,
defaultImport,
preciseImport,
allUnderImport,
hasImportFromSamePackage,
notImported,
notToBeUsedInKotlin
}
fun classify(fqName: FqName, isPackage: Boolean): Classification {
val importPath = ImportPath(fqName, false)
if (isPackage) {
return when {
isImportedWithPreciseImport(fqName) -> Classification.preciseImport
fqName.parent().isRoot -> Classification.topLevelPackage
else -> Classification.notImported
}
}
return when {
JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName).isNotEmpty() -> Classification.notToBeUsedInKotlin
fqName.parent() == file.packageFqName -> Classification.fromCurrentPackage
ImportInsertHelper.getInstance(file.project).isImportedWithDefault(importPath, file) -> Classification.defaultImport
isImportedWithPreciseImport(fqName) -> Classification.preciseImport
isImportedWithAllUnderImport(fqName) -> Classification.allUnderImport
hasPreciseImportFromPackage(fqName.parent()) -> Classification.hasImportFromSamePackage
else -> Classification.notImported
}
}
private fun isImportedWithPreciseImport(name: FqName) = name in preciseImports
private fun isImportedWithAllUnderImport(name: FqName) = name.parent() in allUnderImports
private fun hasPreciseImportFromPackage(packageName: FqName) = packageName in preciseImportPackages
}
@@ -1,120 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.core
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.resolve.ImportPath
import java.util.*
class SymbolUsageProximityPrioritizer(private val file: JetFile) {
private val importCache = ImportCache()
private val thisModule = ModuleUtilCore.findModuleForPsiElement(file)
private enum class PriorityBasedOnImports {
thisPackage,
defaultImport,
preciseImport,
allUnderImport,
default,
hasImportFromSamePackage,
notImported,
notToBeUsedInKotlin
}
private enum class PriorityBasedOnLocation {
thisFile,
thisModule,
project,
other
}
data class Priority(private val priority1: PriorityBasedOnImports, private val priority2: PriorityBasedOnLocation) : Comparable<Priority> {
override fun compareTo(other: Priority): Int {
val c1 = priority1.compareTo(other.priority1)
if (c1 != 0) return c1
return priority2.compareTo(other.priority2)
}
companion object {
val DEFAULT = Priority(PriorityBasedOnImports.default, PriorityBasedOnLocation.other)
}
}
fun priority(fqName: FqName, declaration: PsiElement?, isPackage: Boolean)
= Priority(priorityBasedOnImports(fqName, isPackage), priorityBasedOnLocation(declaration))
private fun priorityBasedOnImports(fqName: FqName, isPackage: Boolean): PriorityBasedOnImports {
val importPath = ImportPath(fqName, false)
if (isPackage) {
return when {
importCache.isImportedWithPreciseImport(fqName) -> PriorityBasedOnImports.preciseImport
else -> PriorityBasedOnImports.default
}
}
return when {
JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName).isNotEmpty() -> PriorityBasedOnImports.notToBeUsedInKotlin
fqName.parent() == file.packageFqName -> PriorityBasedOnImports.thisPackage
ImportInsertHelper.getInstance(file.project).isImportedWithDefault(importPath, file) -> PriorityBasedOnImports.defaultImport
importCache.isImportedWithPreciseImport(fqName) -> PriorityBasedOnImports.preciseImport
importCache.isImportedWithAllUnderImport(fqName) -> PriorityBasedOnImports.allUnderImport
importCache.hasPreciseImportFromPackage(fqName.parent()) -> PriorityBasedOnImports.hasImportFromSamePackage
else -> PriorityBasedOnImports.notImported
}
}
private fun priorityBasedOnLocation(declaration: PsiElement?): PriorityBasedOnLocation {
return when {
declaration == null -> PriorityBasedOnLocation.other
declaration.containingFile == file -> PriorityBasedOnLocation.thisFile
ModuleUtilCore.findModuleForPsiElement(declaration) == thisModule -> PriorityBasedOnLocation.thisModule
ProjectRootsUtil.isInProjectSource(declaration) -> PriorityBasedOnLocation.project
else -> PriorityBasedOnLocation.other
}
}
private inner class ImportCache {
private val preciseImports = HashSet<FqName>()
private val preciseImportPackages = HashSet<FqName>()
private val allUnderImports = HashSet<FqName>()
init {
for (import in file.getImportDirectives()) {
val importPath = import.getImportPath() ?: continue
val fqName = importPath.fqnPart()
if (importPath.isAllUnder()) {
allUnderImports.add(fqName)
}
else {
preciseImports.add(fqName)
preciseImportPackages.add(fqName.parent())
}
}
}
fun isImportedWithPreciseImport(name: FqName) = name in preciseImports
fun isImportedWithAllUnderImport(name: FqName) = name.parent() in allUnderImports
fun hasPreciseImportFromPackage(packageName: FqName) = packageName in preciseImportPackages
}
}
@@ -20,32 +20,30 @@ import com.intellij.codeInsight.daemon.QuickFixBundle
import com.intellij.codeInsight.daemon.impl.actions.AddImportAction
import com.intellij.codeInsight.hint.QuestionAction
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.PopupStep
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.idea.JetDescriptorIconProvider
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.ImportableFqNameClassifier
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import com.intellij.openapi.module.ModuleUtilCore
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.idea.imports.importableFqNameSafe
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.JetDescriptorIconProvider
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.SymbolUsageProximityPrioritizer
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
/**
* Automatically adds import directive to the file for resolving reference.
@@ -58,14 +56,14 @@ public class KotlinAddImportAction(
candidates: Collection<DeclarationDescriptor>
) : QuestionAction {
private val prioritizer = SymbolUsageProximityPrioritizer(element.getContainingJetFile())
private val prioritizer = Prioritizer(element.getContainingJetFile())
private inner class Variant(
val fqName: FqName,
val descriptors: Collection<DeclarationDescriptor>
) {
val priority = descriptors
.map { prioritizer.priority(fqName, DescriptorToSourceUtilsIde.getAnyDeclaration(project, it), false) }
.map { prioritizer.priority(fqName, it) }
.min()!!
val descriptorToImport: DeclarationDescriptor
@@ -151,11 +149,46 @@ public class KotlinAddImportAction(
val descriptor = selectedVariant.descriptorToImport
// for class or package we use ShortenReferences because we not necessary insert an import but may want to insert partly qualified name
if (descriptor is ClassDescriptor || descriptor is PackageViewDescriptor) {
element.mainReference.bindToFqName(descriptor.importableFqNameSafe, JetSimpleNameReference.ShorteningMode.FORCED_SHORTENING)
element.mainReference.bindToFqName(descriptor.importableFqName!!, JetSimpleNameReference.ShorteningMode.FORCED_SHORTENING)
}
else {
ImportInsertHelper.getInstance(project).importDescriptor(file, descriptor)
}
}
}
private class Prioritizer(private val file: JetFile) {
private val classifier = ImportableFqNameClassifier(file)
private val currentModule = ModuleUtilCore.findModuleForPsiElement(file)
private enum class Location {
currentModule,
project,
libraries,
unknown
}
private class Priority(private val priority1: ImportableFqNameClassifier.Classification, private val priority2: Location) : Comparable<Priority> {
override fun compareTo(other: Priority): Int {
val c1 = priority1.compareTo(other.priority1)
if (c1 != 0) return c1
return priority2.compareTo(other.priority2)
}
}
fun priority(fqName: FqName, descriptor: DeclarationDescriptor): Priority {
val classification = classifier.classify(fqName, false)
val location = location(descriptor)
return Priority(classification, location)
}
private fun location(descriptor: DeclarationDescriptor): Location {
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor) ?: return Location.unknown
return when {
ModuleUtilCore.findModuleForPsiElement(declaration) == currentModule -> Location.currentModule
ProjectRootsUtil.isInProjectSource(declaration) -> Location.project
else -> Location.libraries
}
}
}
}
@@ -41,7 +41,6 @@ import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedShortening
import org.jetbrains.kotlin.idea.conversion.copy.end
import org.jetbrains.kotlin.idea.conversion.copy.range
import org.jetbrains.kotlin.idea.conversion.copy.start
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.references.JetMultiReference
import org.jetbrains.kotlin.idea.references.JetReference
@@ -65,7 +64,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.awt.datatransfer.Transferable
import java.awt.datatransfer.UnsupportedFlavorException
import java.io.IOException
import java.util.ArrayList
import java.util.*
//NOTE: this class is based on CopyPasteReferenceProcessor and JavaCopyPasteReferenceProcessor
public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<KotlinReferenceTransferableData>() {
@@ -162,7 +161,6 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
}
val fqName = descriptor.importableFqName ?: continue
if (!descriptor.canBeReferencedViaImport()) continue
val kind = KotlinReferenceData.Kind.fromDescriptor(descriptor) ?: continue
add(KotlinReferenceData(element.range.start - startOffset, element.range.end - startOffset, fqName.asString(), kind))
@@ -44,8 +44,7 @@ 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 java.util.HashMap
import java.util.HashSet
import java.util.*
public class KotlinImportOptimizer() : ImportOptimizer {
@@ -99,9 +98,8 @@ public class KotlinImportOptimizer() : ImportOptimizer {
val targets = bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? JetReferenceExpression]?.let { listOf(it) }
?: reference.resolveToDescriptors(bindingContext)
for (target in targets) {
if (!target.canBeReferencedViaImport()) continue
val importableDescriptor = target.getImportableDescriptor()
val parentFqName = DescriptorUtils.getFqNameSafe(importableDescriptor).parent()
val importableFqName = target.importableFqName ?: continue
val parentFqName = importableFqName.parent()
if (target is PackageViewDescriptor && parentFqName == FqName.ROOT) continue // no need to import top-level packages
if (target !is PackageViewDescriptor && parentFqName == currentPackageName) continue
@@ -111,6 +109,8 @@ public class KotlinImportOptimizer() : ImportOptimizer {
if (element.getReceiverExpression() != null) continue
}
val importableDescriptor = target.getImportableDescriptor()
if (referencedName != null && importableDescriptor.name != referencedName) continue // resolved via alias
if (isAccessibleAsMember(importableDescriptor, element)) continue
@@ -153,7 +153,7 @@ public class KotlinImportOptimizer() : ImportOptimizer {
val descriptorsByPackages = HashMultimap.create<FqName, DeclarationDescriptor>()
for (descriptor in descriptorsToImport) {
val fqName = descriptor.importableFqNameSafe
val fqName = descriptor.importableFqName!!
val parentFqName = fqName.parent()
if (descriptor is PackageViewDescriptor || parentFqName.isRoot()) {
importsToGenerate.add(ImportPath(fqName, false))
@@ -169,7 +169,7 @@ public class KotlinImportOptimizer() : ImportOptimizer {
for (packageName in descriptorsByPackages.keys()) {
val descriptors = descriptorsByPackages[packageName]
val fqNames = descriptors.map { it.importableFqNameSafe }.toSet()
val fqNames = descriptors.map { it.importableFqName!! }.toSet()
val explicitImports = fqNames.size() < codeStyleSettings.NAME_COUNT_TO_USE_STAR_IMPORT
&& packageName.asString() !in codeStyleSettings.PACKAGES_TO_USE_STAR_IMPORTS
if (explicitImports) {
@@ -182,7 +182,7 @@ public class KotlinImportOptimizer() : ImportOptimizer {
else {
for (descriptor in descriptors) {
if (descriptor is ClassDescriptor) {
classNamesToCheck.add(descriptor.importableFqNameSafe)
classNamesToCheck.add(descriptor.importableFqName!!)
}
}
@@ -201,13 +201,13 @@ public class KotlinImportOptimizer() : ImportOptimizer {
val scope = fileWithImports.getResolutionFacade().getFileTopLevelScope(fileWithImports)
for (fqName in classNamesToCheck) {
if (scope.getClassifier(fqName.shortName(), NoLookupLocation.FROM_IDE)?.importableFqNameSafe != fqName) {
if (scope.getClassifier(fqName.shortName(), NoLookupLocation.FROM_IDE)?.importableFqName != fqName) {
// add explicit import if failed to import with * (or from current package)
importsToGenerate.add(ImportPath(fqName, false))
val packageName = fqName.parent()
for (descriptor in descriptorsByPackages[packageName].filter { it.importableFqNameSafe == fqName }) {
for (descriptor in descriptorsByPackages[packageName].filter { it.importableFqName == fqName }) {
descriptorsByPackages.remove(packageName, descriptor)
}
@@ -43,15 +43,14 @@ import com.intellij.util.Processor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.imports.KotlinImportOptimizer
import org.jetbrains.kotlin.idea.imports.importableFqNameSafe
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
import java.util.ArrayList
import java.util.HashSet
import java.util.*
class UnusedImportInspection : AbstractKotlinInspection() {
override fun runForWholeFile() = true
@@ -75,7 +74,7 @@ class UnusedImportInspection : AbstractKotlinInspection() {
val fqNames = HashSet<FqName>()
val parentFqNames = HashSet<FqName>()
for (descriptor in descriptorsToImport) {
val fqName = descriptor.importableFqNameSafe
val fqName = descriptor.importableFqName!!
fqNames.add(fqName)
if (fqName !in explicitlyImportedFqNames) { // we don't add parents of explicitly imported fq-names because such imports are not needed
@@ -202,7 +202,6 @@ public class DeprecatedCallableAddReplaceWithIntention : JetSelfTargetingRangeIn
val target = bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, expression]
?: bindingContext[BindingContext.REFERENCE_TARGET, expression]
?: return
if (!target.canBeReferencedViaImport()) return
if (target.isExtension || expression.getReceiverExpression() == null) {
val fqName = target.importableFqName ?: return
if (!importHelper.isImportedWithDefault(ImportPath(fqName, false), file)
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.core.asExpression
import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
@@ -46,8 +45,7 @@ import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.ArrayList
import java.util.LinkedHashSet
import java.util.*
data class ReplaceWith(val expression: String, vararg val imports: String)
@@ -116,7 +114,7 @@ object ReplaceWithAnnotationAnalyzer {
expression.forEachDescendantOfType<JetSimpleNameExpression> { expression ->
val target = bindingContext[BindingContext.REFERENCE_TARGET, expression] ?: return@forEachDescendantOfType
if (target.canBeReferencedViaImport() && (target.isExtension || expression.getReceiverExpression() == null)) {
if (target.isExtension || expression.getReceiverExpression() == null) {
importFqNames.addIfNotNull(target.importableFqName)
}
@@ -47,7 +47,7 @@ import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
import org.jetbrains.kotlin.idea.core.comparePossiblyOverridingDescriptors
import org.jetbrains.kotlin.idea.core.getResolutionScope
import org.jetbrains.kotlin.idea.core.refactoring.createTempCopy
import org.jetbrains.kotlin.idea.imports.importableFqNameSafe
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.Status
@@ -638,7 +638,7 @@ private fun ExtractionData.inferParametersInfo(
info.typeParameters, info.nonDenotableTypes, options, targetScope, false
)) continue
info.replacementMap[refInfo.offsetInBody] = FqNameReplacement(originalDescriptor.importableFqNameSafe)
info.replacementMap[refInfo.offsetInBody] = FqNameReplacement(originalDescriptor.importableFqName!!)
}
else {
val extractThis = (hasThisReceiver && refInfo.smartCast == null) || thisExpr != null
@@ -24,10 +24,8 @@ import org.jetbrains.kotlin.descriptors.*
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.imports.canBeReferencedViaImport
import org.jetbrains.kotlin.idea.imports.getImportableTargets
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.imports.importableFqNameSafe
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
import org.jetbrains.kotlin.idea.refactoring.fqName.isImported
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
@@ -46,9 +44,7 @@ import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.ArrayList
import java.util.Comparator
import java.util.LinkedHashSet
import java.util.*
public class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper() {
@@ -113,11 +109,10 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
fun importDescriptor(descriptor: DeclarationDescriptor): ImportDescriptorResult {
val target = descriptor.getImportableDescriptor()
if (!target.canBeReferencedViaImport()) return ImportDescriptorResult.FAIL
val targetFqName = target.importableFqName ?: return ImportDescriptorResult.FAIL
val name = target.getName()
val name = target.name
val topLevelScope = resolutionFacade.getFileTopLevelScope(file)
val targetFqName = target.importableFqNameSafe
// check if import is not needed
when (target) {
@@ -158,13 +153,13 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
}
if (conflict != null && imports.any {
!it.isAllUnder()
&& it.getImportPath()?.fqnPart() == conflict.importableFqNameSafe
&& it.getImportPath()?.fqnPart() == conflict.importableFqName
&& it.getImportPath()?.getImportedName() == name
}) {
return ImportDescriptorResult.FAIL
}
val fqName = target.importableFqNameSafe
val fqName = target.importableFqName!!
val packageFqName = fqName.parent()
val tryStarImport = shouldTryStarImport(packageFqName, imports)
@@ -201,7 +196,7 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
}
private fun addStarImport(target: DeclarationDescriptor): ImportDescriptorResult {
val targetFqName = target.importableFqNameSafe
val targetFqName = target.importableFqName!!
val parentFqName = targetFqName.parent()
val moduleDescriptor = resolutionFacade.moduleDescriptor
@@ -244,7 +239,7 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
// check that class is really imported
&& topLevelScope.getClassifier(importedClass.name, NoLookupLocation.FROM_IDE) == importedClass
// and not yet imported explicitly
&& imports.all { it.importPath != ImportPath(importedClass.importableFqNameSafe, false) }
&& imports.all { it.importPath != ImportPath(importedClass.importableFqName!!, false) }
}
val conflicts = detectNeededImports(conflictCandidates)
@@ -253,7 +248,7 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
if (target is ClassDescriptor) {
val newTopLevelScope = resolutionFacade.getFileTopLevelScope(file)
val resolvedTo = newTopLevelScope.getClassifier(target.name, NoLookupLocation.FROM_IDE)
if (resolvedTo?.importableFqNameSafe != targetFqName) {
if (resolvedTo?.importableFqName != targetFqName) {
addedImport.delete()
return ImportDescriptorResult.FAIL
}
@@ -293,7 +288,7 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
}
}
addImport(target.importableFqNameSafe, false)
addImport(target.importableFqName!!, false)
return ImportDescriptorResult.IMPORT_ADDED
}
@@ -309,7 +304,7 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
val targets = refExpr.resolveTargets()
if (targets.any { it is PackageViewDescriptor }) continue // do not drop import of package
val classDescriptor = targets.filterIsInstance<ClassDescriptor>().firstOrNull()
importsToCheck.addIfNotNull(classDescriptor?.importableFqNameSafe)
importsToCheck.addIfNotNull(classDescriptor?.importableFqName)
import.delete()
}
@@ -317,7 +312,7 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
val topLevelScope = resolutionFacade.getFileTopLevelScope(file)
for (classFqName in importsToCheck) {
val classifier = topLevelScope.getClassifier(classFqName.shortName(), NoLookupLocation.FROM_IDE)
if (classifier?.importableFqNameSafe != classFqName) {
if (classifier?.importableFqName != classFqName) {
addImport(classFqName, false) // restore explicit import
}
}