KT-9950 IntelliJ IDEA does not suggest importing extension methods which have the same name as an instance method
#KT-9950 Fixed
This commit is contained in:
@@ -25,18 +25,23 @@ import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.packageDependencies.DependencyValidationManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiErrorElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.actions.KotlinAddImportAction
|
||||
import org.jetbrains.kotlin.idea.actions.createGroupedImportsAction
|
||||
import org.jetbrains.kotlin.idea.actions.createSingleImportAction
|
||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolveScope
|
||||
@@ -46,12 +51,25 @@ import org.jetbrains.kotlin.idea.core.isVisible
|
||||
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
|
||||
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
|
||||
import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.KtPsiUtil.getConventionName
|
||||
import org.jetbrains.kotlin.psi.KtPsiUtil.isSelectorInQualified
|
||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isImportDirectiveExpression
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils.isTopLevelDeclaration
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentCall
|
||||
import org.jetbrains.kotlin.resolve.calls.context.ContextDependency
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.scopes.ExplicitImportsScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.collectFunctions
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import org.jetbrains.kotlin.utils.CachedValueProperty
|
||||
@@ -123,22 +141,18 @@ internal abstract class ImportFixBase<T : KtExpression>(expression: T) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeSuggestionsForName(name: Name, callTypeAndReceiver: CallTypeAndReceiver<*, *>):
|
||||
Collection<DeclarationDescriptor> {
|
||||
private fun computeSuggestionsForName(name: Name, callTypeAndReceiver: CallTypeAndReceiver<*, *>): Collection<DeclarationDescriptor> {
|
||||
val nameStr = name.asString()
|
||||
if (nameStr.isEmpty()) return emptyList()
|
||||
|
||||
val file = element.containingFile as KtFile
|
||||
val file = element.getContainingKtFile()
|
||||
|
||||
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
|
||||
if (!checkErrorStillPresent(bindingContext)) return emptyList()
|
||||
|
||||
val searchScope = getResolveScope(file)
|
||||
|
||||
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
|
||||
|
||||
val diagnostics = bindingContext.diagnostics.forElement(element)
|
||||
|
||||
if (!diagnostics.any { it.factory in getSupportedErrors() }) return emptyList()
|
||||
|
||||
val resolutionFacade = element.getResolutionFacade()
|
||||
val resolutionFacade = file.getResolutionFacade()
|
||||
|
||||
fun isVisible(descriptor: DeclarationDescriptor): Boolean {
|
||||
if (descriptor is DeclarationDescriptorWithVisibility) {
|
||||
@@ -166,6 +180,14 @@ internal abstract class ImportFixBase<T : KtExpression>(expression: T) :
|
||||
result
|
||||
}
|
||||
|
||||
private fun checkErrorStillPresent(bindingContext: BindingContext): Boolean {
|
||||
return elementsToCheckDiagnostics()
|
||||
.flatMap { bindingContext.diagnostics.forElement(it) }
|
||||
.any { diagnostic -> diagnostic.factory in getSupportedErrors() }
|
||||
}
|
||||
|
||||
protected open fun elementsToCheckDiagnostics(): Collection<PsiElement> = listOf(element)
|
||||
|
||||
abstract fun fillCandidates(
|
||||
name: String,
|
||||
callTypeAndReceiver: CallTypeAndReceiver<*, *>,
|
||||
@@ -196,7 +218,7 @@ internal abstract class OrdinaryImportFixBase<T : KtExpression>(expression: T) :
|
||||
val expression = element
|
||||
|
||||
if (expression is KtSimpleNameExpression) {
|
||||
if (!expression.isImportDirectiveExpression() && !KtPsiUtil.isSelectorInQualified(expression)) {
|
||||
if (!expression.isImportDirectiveExpression() && !isSelectorInQualified(expression)) {
|
||||
val filterByCallType = { descriptor: DeclarationDescriptor -> callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor) }
|
||||
|
||||
if (ProjectStructureUtil.isJsKotlinModule(expression.getContainingKtFile())) {
|
||||
@@ -221,31 +243,7 @@ internal abstract class OrdinaryImportFixBase<T : KtExpression>(expression: T) :
|
||||
internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFixBase<KtSimpleNameExpression>(expression) {
|
||||
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.detect(element)
|
||||
|
||||
override val importNames: Collection<Name> = run {
|
||||
if (element.getIdentifier() == null) {
|
||||
val conventionName = KtPsiUtil.getConventionName(element)
|
||||
if (conventionName != null) {
|
||||
if (element is KtOperationReferenceExpression) {
|
||||
val elementType = element.firstChild.node.elementType
|
||||
if (elementType in OperatorConventions.ASSIGNMENT_OPERATIONS) {
|
||||
val counterpart = OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS[elementType]
|
||||
val counterpartName = OperatorConventions.BINARY_OPERATION_NAMES[counterpart]
|
||||
if (counterpartName != null) {
|
||||
return@run listOf(conventionName, counterpartName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return@run conventionName.singletonOrEmptyList()
|
||||
}
|
||||
}
|
||||
else if (Name.isValidIdentifier(element.getReferencedName())) {
|
||||
return@run Name.identifier(element.getReferencedName()).singletonOrEmptyList()
|
||||
}
|
||||
|
||||
emptyList<Name>()
|
||||
}
|
||||
|
||||
override val importNames = element.extractImportNames()
|
||||
|
||||
override fun getSupportedErrors() = ERRORS
|
||||
|
||||
@@ -259,6 +257,31 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtSimpleNameExpression.extractImportNames(): Collection<Name> {
|
||||
if (getIdentifier() == null) {
|
||||
val conventionName = getConventionName(this)
|
||||
if (conventionName != null) {
|
||||
if (this is KtOperationReferenceExpression) {
|
||||
val elementType = firstChild.node.elementType
|
||||
if (elementType in OperatorConventions.ASSIGNMENT_OPERATIONS) {
|
||||
val counterpart = OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS[elementType]
|
||||
val counterpartName = OperatorConventions.BINARY_OPERATION_NAMES[counterpart]
|
||||
if (counterpartName != null) {
|
||||
return listOf(conventionName, counterpartName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return conventionName.singletonOrEmptyList()
|
||||
}
|
||||
}
|
||||
else if (Name.isValidIdentifier(getReferencedName())) {
|
||||
return Name.identifier(getReferencedName()).singletonOrEmptyList()
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
|
||||
internal class InvokeImportFix(expression: KtExpression) : OrdinaryImportFixBase<KtExpression>(expression) {
|
||||
override val importNames = OperatorNameConventions.INVOKE.singletonList()
|
||||
@@ -275,8 +298,12 @@ internal class InvokeImportFix(expression: KtExpression) : OrdinaryImportFixBase
|
||||
}
|
||||
}
|
||||
|
||||
internal open class ArrayAccessorImportFix(element: KtArrayAccessExpression, override val importNames: Collection<Name>, private val showHint: Boolean) :
|
||||
OrdinaryImportFixBase<KtArrayAccessExpression>(element) {
|
||||
internal open class ArrayAccessorImportFix(
|
||||
element: KtArrayAccessExpression,
|
||||
override val importNames: Collection<Name>,
|
||||
private val showHint: Boolean
|
||||
) : OrdinaryImportFixBase<KtArrayAccessExpression>(element) {
|
||||
|
||||
override fun getCallTypeAndReceiver() =
|
||||
CallTypeAndReceiver.OPERATOR(element.arrayExpression!!)
|
||||
|
||||
@@ -310,8 +337,11 @@ internal open class ArrayAccessorImportFix(element: KtArrayAccessExpression, ove
|
||||
}
|
||||
|
||||
internal class DelegateAccessorsImportFix(
|
||||
element: KtExpression, override val importNames: Collection<Name>, private val solveSeveralProblems: Boolean) :
|
||||
OrdinaryImportFixBase<KtExpression>(element) {
|
||||
element: KtExpression,
|
||||
override val importNames: Collection<Name>,
|
||||
private val solveSeveralProblems: Boolean
|
||||
) : OrdinaryImportFixBase<KtExpression>(element) {
|
||||
|
||||
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.DELEGATE(element)
|
||||
|
||||
override fun createAction(project: Project, editor: Editor): KotlinAddImportAction {
|
||||
@@ -351,8 +381,12 @@ internal class DelegateAccessorsImportFix(
|
||||
}
|
||||
}
|
||||
|
||||
internal class ComponentsImportFix(element: KtExpression, override val importNames: Collection<Name>, private val solveSeveralProblems: Boolean) :
|
||||
OrdinaryImportFixBase<KtExpression>(element) {
|
||||
internal class ComponentsImportFix(
|
||||
element: KtExpression,
|
||||
override val importNames: Collection<Name>,
|
||||
private val solveSeveralProblems: Boolean
|
||||
) : OrdinaryImportFixBase<KtExpression>(element) {
|
||||
|
||||
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.OPERATOR(element)
|
||||
|
||||
override fun createAction(project: Project, editor: Editor): KotlinAddImportAction {
|
||||
@@ -397,15 +431,14 @@ internal class ImportMemberFix(expression: KtSimpleNameExpression) : ImportFixBa
|
||||
indicesHelper: KotlinIndicesHelper
|
||||
): List<DeclarationDescriptor> {
|
||||
val result = ArrayList<DeclarationDescriptor>()
|
||||
val expression = element
|
||||
if (!expression.isImportDirectiveExpression() && !KtPsiUtil.isSelectorInQualified(expression)) {
|
||||
if (!element.isImportDirectiveExpression() && !isSelectorInQualified(element)) {
|
||||
val filterByCallType = { descriptor: DeclarationDescriptor -> callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor) }
|
||||
|
||||
indicesHelper.getKotlinCallablesByName(name)
|
||||
.filter { it.canBeReferencedViaImport() && !isTopLevelDeclaration(it) }
|
||||
.filterTo(result, filterByCallType)
|
||||
|
||||
if (!ProjectStructureUtil.isJsKotlinModule(expression.getContainingKtFile())) {
|
||||
if (!ProjectStructureUtil.isJsKotlinModule(element.getContainingKtFile())) {
|
||||
indicesHelper.getJvmCallablesByName(name)
|
||||
.filter { it.canBeReferencedViaImport() }
|
||||
.filterTo(result, filterByCallType)
|
||||
@@ -436,7 +469,90 @@ internal class ImportMemberFix(expression: KtSimpleNameExpression) : ImportFixBa
|
||||
|
||||
private val ERRORS: Collection<DiagnosticFactory<*>> by lazy { QuickFixes.getInstance().getDiagnostics(this) }
|
||||
}
|
||||
}
|
||||
|
||||
internal class ImportForMismatchingArgumentsFix(expression: KtSimpleNameExpression) : ImportFixBase<KtSimpleNameExpression>(expression) {
|
||||
override fun getCallTypeAndReceiver() = CallTypeAndReceiver.detect(element)
|
||||
|
||||
override val importNames = element.extractImportNames()
|
||||
|
||||
override fun getSupportedErrors() = ERRORS
|
||||
|
||||
override fun elementsToCheckDiagnostics(): Collection<PsiElement> {
|
||||
val callExpression = element.parent as? KtCallExpression ?: return emptyList()
|
||||
return callExpression.valueArguments +
|
||||
callExpression.valueArguments.mapNotNull { it.getArgumentExpression() } +
|
||||
callExpression.valueArguments.mapNotNull { it.getArgumentName()?.referenceExpression } +
|
||||
callExpression.valueArgumentList.singletonOrEmptyList()
|
||||
}
|
||||
|
||||
override fun fillCandidates(
|
||||
name: String,
|
||||
callTypeAndReceiver: CallTypeAndReceiver<*, *>,
|
||||
bindingContext: BindingContext,
|
||||
indicesHelper: KotlinIndicesHelper
|
||||
): List<DeclarationDescriptor> {
|
||||
if (!Name.isValidIdentifier(name)) return emptyList()
|
||||
val identifier = Name.identifier(name)
|
||||
|
||||
val call = element.getParentCall(bindingContext) ?: return emptyList()
|
||||
val callElement = call.callElement as? KtExpression ?: return emptyList()
|
||||
if (callElement.anyDescendantOfType<PsiErrorElement>()) return emptyList() // incomplete call
|
||||
val elementToAnalyze = callElement.getQualifiedExpressionForSelectorOrThis()
|
||||
|
||||
val file = element.getContainingKtFile()
|
||||
val resolutionFacade = file.getResolutionFacade()
|
||||
val resolutionScope = elementToAnalyze.getResolutionScope(bindingContext, resolutionFacade)
|
||||
|
||||
fun filterFunction(descriptor: FunctionDescriptor): Boolean {
|
||||
if (!callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor)) return false
|
||||
|
||||
if (descriptor in resolutionScope.collectFunctions(identifier, NoLookupLocation.FROM_IDE)) return false // already imported
|
||||
|
||||
// check that this function matches all arguments
|
||||
val resolutionScopeWithAddedImport = resolutionScope.addImportingScope(ExplicitImportsScope(listOf(descriptor)))
|
||||
val dataFlowInfo = bindingContext.getDataFlowInfo(elementToAnalyze)
|
||||
val newBindingContext = elementToAnalyze.analyzeInContext(
|
||||
resolutionScopeWithAddedImport,
|
||||
dataFlowInfo = dataFlowInfo,
|
||||
contextDependency = ContextDependency.DEPENDENT // to not check complete inference
|
||||
)
|
||||
return newBindingContext.diagnostics.none { it.severity == Severity.ERROR }
|
||||
}
|
||||
|
||||
val result = ArrayList<FunctionDescriptor>()
|
||||
|
||||
fun processDescriptor(descriptor: CallableDescriptor) {
|
||||
if (descriptor is FunctionDescriptor && filterFunction(descriptor)) {
|
||||
result.add(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
indicesHelper
|
||||
.getCallableTopLevelExtensions(callTypeAndReceiver, element, bindingContext) { it == name }
|
||||
.forEach(::processDescriptor)
|
||||
|
||||
if (!isSelectorInQualified(element)) {
|
||||
indicesHelper
|
||||
.getTopLevelCallablesByName(name)
|
||||
.forEach(::processDescriptor)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): ImportForMismatchingArgumentsFix? {
|
||||
//TODO: not only KtCallExpression
|
||||
val callExpression = diagnostic.psiElement.getStrictParentOfType<KtCallExpression>() ?: return null
|
||||
val nameExpression = callExpression.calleeExpression as? KtNameReferenceExpression ?: return null
|
||||
return ImportForMismatchingArgumentsFix(nameExpression)
|
||||
}
|
||||
|
||||
override fun isApplicableForCodeFragment() = true
|
||||
|
||||
private val ERRORS: Collection<DiagnosticFactory<*>> by lazy { QuickFixes.getInstance().getDiagnostics(this) }
|
||||
}
|
||||
}
|
||||
|
||||
object ImportForMissingOperatorFactory : KotlinSingleIntentionActionFactory() {
|
||||
|
||||
@@ -134,6 +134,12 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
UNRESOLVED_REFERENCE.registerFactory(ImportMemberFix)
|
||||
UNRESOLVED_REFERENCE.registerFactory(ImportFix)
|
||||
|
||||
TOO_MANY_ARGUMENTS.registerFactory(ImportForMismatchingArgumentsFix)
|
||||
NO_VALUE_FOR_PARAMETER.registerFactory(ImportForMismatchingArgumentsFix)
|
||||
TYPE_MISMATCH.registerFactory(ImportForMismatchingArgumentsFix)
|
||||
CONSTANT_EXPECTED_TYPE_MISMATCH.registerFactory(ImportForMismatchingArgumentsFix)
|
||||
NAMED_PARAMETER_NOT_FOUND.registerFactory(ImportForMismatchingArgumentsFix)
|
||||
|
||||
UNRESOLVED_REFERENCE.registerFactory(AddTestLibQuickFix)
|
||||
|
||||
UNRESOLVED_REFERENCE_WRONG_RECEIVER.registerFactory(ImportFix)
|
||||
|
||||
Reference in New Issue
Block a user