Fixed freezes with add unambiguous imports on the fly

We still had backup way to recalculate things for the case when fix
is not available. So if error is not importable, or we have second
error, which will be outdated after adding first import, we could get
freezes. 2019.2 and 2019.1 have different APIs, so it's implemented in
different way.

#KT-30863 Fixed
This commit is contained in:
Alexander Podkhalyuzin
2019-09-17 14:09:52 -07:00
parent 24d620657b
commit 7b8fbb5fea
5 changed files with 193 additions and 70 deletions
@@ -261,7 +261,8 @@ internal abstract class OrdinaryImportFixBase<T : KtExpression>(expression: T, f
}
}
internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFixBase<KtSimpleNameExpression>(expression, MyFactory) {
// This is required to be abstract to reduce bunch file size
internal abstract class AbstractImportFix(expression: KtSimpleNameExpression, factory: Factory) : OrdinaryImportFixBase<KtSimpleNameExpression>(expression, factory) {
override fun getCallTypeAndReceiver() = element?.let { CallTypeAndReceiver.detect(it) }
private fun importNamesForMembers(): Collection<Name> {
@@ -390,11 +391,6 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix
indicesHelper
)
}
companion object MyFactory : Factory() {
override fun createImportAction(diagnostic: Diagnostic) =
(diagnostic.psiElement as? KtSimpleNameExpression)?.let(::ImportFix)
}
}
internal class ImportConstructorReferenceFix(expression: KtSimpleNameExpression) :
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
internal class ImportFix(expression: KtSimpleNameExpression): AbstractImportFix(expression, MyFactory) {
override fun fixSilently(editor: Editor): Boolean {
if (!CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY) return false
if (isOutdated()) return false
val element = element ?: return false
val addImportAction = createAction(element.project, editor, element)
if (addImportAction.isUnambiguous()) {
addImportAction.execute()
return true
}
return false
}
companion object MyFactory : Factory() {
override fun createImportAction(diagnostic: Diagnostic) =
(diagnostic.psiElement as? KtSimpleNameExpression)?.let(::ImportFix)
}
}
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.quickfix
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
internal class ImportFix(expression: KtSimpleNameExpression): AbstractImportFix(expression) {
companion object MyFactory : Factory() {
override fun createImportAction(diagnostic: Diagnostic) =
(diagnostic.psiElement as? KtSimpleNameExpression)?.let(::ImportFix)
}
}
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.daemon.ReferenceImporter
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
import com.intellij.codeInsight.daemon.impl.DaemonListeners
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.editor.Editor
@@ -30,7 +29,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightWorkspaceSettings
import org.jetbrains.kotlin.idea.core.targetDescriptors
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
@@ -39,77 +37,19 @@ import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class KotlinReferenceImporter : ReferenceImporter {
override fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile)
= autoImportReferenceAtCursor(editor, file, allowCaretNearRef = false)
override fun autoImportReferenceAt(editor: Editor, file: PsiFile, offset: Int): Boolean {
override fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile): Boolean {
if (file !is KtFile) return false
val nameExpression = file.findElementAt(offset)?.parent as? KtSimpleNameExpression ?: return false
if (!KotlinCodeInsightWorkspaceSettings.getInstance(file.project).addUnambiguousImportsOnTheFly) return false
val importFix: ImportFixBase<out KtExpression>? = findImportFixAt(editor, file, offset)
if (importFix != null && !importFix.isOutdated()) {
val addImportAction = importFix.createAction(file.project, editor, nameExpression)
if (addImportAction.isUnambiguous()) {
addImportAction.execute()
}
return true
}
return nameExpression.autoImport(editor, file)
}
private fun findImportFixAt(
editor: Editor,
file: KtFile,
offset: Int
): ImportFixBase<out KtExpression>? {
var importFix: ImportFixBase<out KtExpression>? = null
DaemonCodeAnalyzerEx.processHighlights(editor.document, file.project, null, offset, offset) { info ->
importFix = info.quickFixActionRanges?.asSequence()
?.map { it.first.action }?.filterIsInstance<ImportFixBase<*>>()?.firstOrNull()
importFix == null
}
return importFix
}
companion object {
// TODO: use in table cell
fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile, allowCaretNearRef: Boolean): Boolean {
if (file !is KtFile) return false
val caretOffset = editor.caretModel.offset
val document = editor.document
val lineNumber = document.getLineNumber(caretOffset)
val startOffset = document.getLineStartOffset(lineNumber)
val endOffset = document.getLineEndOffset(lineNumber)
val elements = file.elementsInRange(TextRange(startOffset, endOffset))
.flatMap { it.collectDescendantsOfType<KtSimpleNameExpression>() }
for (element in elements) {
if (!allowCaretNearRef && element.endOffset == caretOffset) continue
if (element.autoImport(editor, file)) {
return true
}
}
return false
}
private fun hasUnresolvedImportWhichCanImport(file: KtFile, name: String): Boolean {
fun hasUnresolvedImportWhichCanImport(name: String): Boolean {
return file.importDirectives.any {
it.targetDescriptors().isEmpty() && (it.isAllUnder || it.importPath?.importedName?.asString() == name)
}
}
private fun KtSimpleNameExpression.autoImport(editor: Editor, file: KtFile): Boolean {
fun KtSimpleNameExpression.autoImport(): Boolean {
if (!KotlinCodeInsightWorkspaceSettings.getInstance(project).addUnambiguousImportsOnTheFly) return false
if (!DaemonListeners.canChangeFileSilently(file)) return false
if (hasUnresolvedImportWhichCanImport(file, getReferencedName())) return false
if (hasUnresolvedImportWhichCanImport(getReferencedName())) return false
val bindingContext = analyze(BodyResolveMode.PARTIAL)
if (mainReference.resolveToDescriptors(bindingContext).isNotEmpty()) return false
@@ -127,5 +67,15 @@ class KotlinReferenceImporter : ReferenceImporter {
}
return result
}
val caretOffset = editor.caretModel.offset
val document = editor.document
val lineNumber = document.getLineNumber(caretOffset)
val startOffset = document.getLineStartOffset(lineNumber)
val endOffset = document.getLineEndOffset(lineNumber)
return file.elementsInRange(TextRange(startOffset, endOffset))
.flatMap { it.collectDescendantsOfType<KtSimpleNameExpression>() }
.any { it.endOffset != caretOffset && it.autoImport() }
}
}
@@ -0,0 +1,131 @@
/*
* 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.quickfix
import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.codeInsight.daemon.ReferenceImporter
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
import com.intellij.codeInsight.daemon.impl.DaemonListeners
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.actions.createSingleImportAction
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.core.targetDescriptors
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class KotlinReferenceImporter : ReferenceImporter {
override fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile)
= autoImportReferenceAtCursor(editor, file, allowCaretNearRef = false)
override fun autoImportReferenceAt(editor: Editor, file: PsiFile, offset: Int): Boolean {
if (file !is KtFile) return false
val nameExpression = file.findElementAt(offset)?.parent as? KtSimpleNameExpression ?: return false
if (!CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY) return false
val importFix: ImportFixBase<out KtExpression>? = findImportFixAt(editor, file, offset)
if (importFix != null && !importFix.isOutdated()) {
val addImportAction = importFix.createAction(file.project, editor, nameExpression)
if (addImportAction.isUnambiguous()) {
addImportAction.execute()
}
return true
}
return false
}
private fun findImportFixAt(
editor: Editor,
file: KtFile,
offset: Int
): ImportFixBase<out KtExpression>? {
var importFix: ImportFixBase<out KtExpression>? = null
DaemonCodeAnalyzerEx.processHighlights(editor.document, file.project, null, offset, offset) { info ->
importFix = info.quickFixActionRanges?.asSequence()
?.map { it.first.action }?.filterIsInstance<ImportFixBase<*>>()?.firstOrNull()
importFix == null
}
return importFix
}
companion object {
// TODO: use in table cell
fun autoImportReferenceAtCursor(editor: Editor, file: PsiFile, allowCaretNearRef: Boolean): Boolean {
if (file !is KtFile) return false
val caretOffset = editor.caretModel.offset
val document = editor.document
val lineNumber = document.getLineNumber(caretOffset)
val startOffset = document.getLineStartOffset(lineNumber)
val endOffset = document.getLineEndOffset(lineNumber)
val elements = file.elementsInRange(TextRange(startOffset, endOffset))
.flatMap { it.collectDescendantsOfType<KtSimpleNameExpression>() }
for (element in elements) {
if (!allowCaretNearRef && element.endOffset == caretOffset) continue
if (element.autoImport(editor, file)) {
return true
}
}
return false
}
private fun hasUnresolvedImportWhichCanImport(file: KtFile, name: String): Boolean {
return file.importDirectives.any {
it.targetDescriptors().isEmpty() && (it.isAllUnder || it.importPath?.importedName?.asString() == name)
}
}
private fun KtSimpleNameExpression.autoImport(editor: Editor, file: KtFile): Boolean {
if (!CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY) return false
if (!DaemonListeners.canChangeFileSilently(file)) return false
if (hasUnresolvedImportWhichCanImport(file, getReferencedName())) return false
val bindingContext = analyze(BodyResolveMode.PARTIAL)
if (mainReference.resolveToDescriptors(bindingContext).isNotEmpty()) return false
val suggestions = ImportFix(this).collectSuggestions()
if (suggestions.size != 1) return false
val descriptors = file.resolveImportReference(suggestions.single())
// we do not auto-import nested classes because this will probably add qualification into the text and this will confuse the user
if (descriptors.any { it is ClassDescriptor && it.containingDeclaration is ClassDescriptor }) return false
var result = false
CommandProcessor.getInstance().runUndoTransparentAction {
result = createSingleImportAction(project, editor, this, suggestions).execute()
}
return result
}
}
}