IDE: Don't do any resolution in AddExclExclExclFix (i.e., in

isAvailable and in invoke) by moving computation of element to modify
to before instantiation (i.e., to the factories or equivalent).

This lets us to move it to idea-frontend-independent and re-use it FIR.
This commit is contained in:
Mark Punzalan
2021-04-23 16:44:30 +00:00
committed by Ilya Kirillov
parent c472c9facd
commit 85cbea70bf
9 changed files with 104 additions and 113 deletions
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2021 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.FileModificationService
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.createExpressionByPattern
class AddExclExclCallFix(psiElement: PsiElement, val fixImplicitReceiver: Boolean = false) : ExclExclCallFix(psiElement),
LowPriorityAction {
override fun getText() = KotlinBundle.message("fix.introduce.non.null.assertion")
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return
val modifiedExpression = element ?: return
val psiFactory = KtPsiFactory(project)
val exclExclExpression = if (fixImplicitReceiver) {
if (modifiedExpression is KtCallableReferenceExpression) {
psiFactory.createExpressionByPattern("this!!::$0", modifiedExpression.callableReference)
} else {
psiFactory.createExpressionByPattern("this!!.$0", modifiedExpression)
}
} else {
psiFactory.createExpressionByPattern("$0!!", modifiedExpression)
}
modifiedExpression.replace(exclExclExpression)
}
}
@@ -22,7 +22,7 @@ import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel
import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
import org.jetbrains.kotlin.idea.intentions.isFlexibleRecursive import org.jetbrains.kotlin.idea.intentions.isFlexibleRecursive
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix import org.jetbrains.kotlin.idea.quickfix.getAddExclExclCallFix
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
@@ -58,7 +58,7 @@ class HasPlatformTypeInspection(
if (expression != null && if (expression != null &&
(!reportPlatformArguments || !TypeUtils.makeNotNullable(type).isFlexibleRecursive()) (!reportPlatformArguments || !TypeUtils.makeNotNullable(type).isFlexibleRecursive())
) { ) {
return listOf(IntentionWrapper(AddExclExclCallFix(expression), element.containingFile)) return listOfNotNull(getAddExclExclCallFix(expression)?.let { IntentionWrapper(it, element.containingFile) })
} }
} }
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.project.languageVersionSettings import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix import org.jetbrains.kotlin.idea.quickfix.getAddExclExclCallFix
import org.jetbrains.kotlin.idea.resolve.getDataFlowValueFactory import org.jetbrains.kotlin.idea.resolve.getDataFlowValueFactory
import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
@@ -78,12 +78,14 @@ class PlatformExtensionReceiverOfInlineInspection : AbstractKotlinInspection() {
val stableNullability = context.getDataFlowInfoBefore(receiverExpression).getStableNullability(dataFlow) val stableNullability = context.getDataFlowInfoBefore(receiverExpression).getStableNullability(dataFlow)
if (!stableNullability.canBeNull()) return if (!stableNullability.canBeNull()) return
holder.registerProblem( getAddExclExclCallFix(receiverExpression)?.let {
receiverExpression, holder.registerProblem(
KotlinBundle.message("call.of.inline.function.with.nullable.extension.receiver.can.provoke.npe.in.kotlin.1.2"), receiverExpression,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, KotlinBundle.message("call.of.inline.function.with.nullable.extension.receiver.can.provoke.npe.in.kotlin.1.2"),
IntentionWrapper(AddExclExclCallFix(receiverExpression), receiverExpression.containingKtFile) ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
) IntentionWrapper(it, receiverExpression.containingKtFile)
)
}
} }
} }
@@ -5,11 +5,7 @@
package org.jetbrains.kotlin.idea.quickfix package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -18,7 +14,6 @@ import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.Errors.UNSAFE_CALL import org.jetbrains.kotlin.diagnostics.Errors.UNSAFE_CALL
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
@@ -38,91 +33,62 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.util.isValidOperator import org.jetbrains.kotlin.util.isValidOperator
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class AddExclExclCallFix(psiElement: PsiElement, val checkImplicitReceivers: Boolean = true) : ExclExclCallFix(psiElement), fun getAddExclExclCallFix(element: PsiElement?, checkImplicitReceivers: Boolean = false): AddExclExclCallFix? {
LowPriorityAction { fun KtExpression?.asFix(implicitReceiver: Boolean = false) = this?.let { AddExclExclCallFix(it, implicitReceiver) }
override fun getText() = KotlinBundle.message("fix.introduce.non.null.assertion") val psiElement = element ?: return null
if ((psiElement as? KtExpression).isNullExpression()) {
override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean = return null
getExpressionForIntroduceCall() != null }
if (psiElement is LeafPsiElement && psiElement.elementType == KtTokens.DOT) {
override fun invoke(project: Project, editor: Editor?, file: KtFile) { return (psiElement.prevSibling as? KtExpression).asFix()
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return }
return when (psiElement) {
val expr = getExpressionForIntroduceCall() ?: return is KtArrayAccessExpression -> psiElement.asFix()
val modifiedExpression = expr.expression is KtOperationReferenceExpression -> {
val psiFactory = KtPsiFactory(project) when (val parent = psiElement.parent) {
val exclExclExpression = if (expr.implicitReceiver) { is KtUnaryExpression -> parent.baseExpression.asFix()
if (modifiedExpression is KtCallableReferenceExpression) { is KtBinaryExpression -> {
psiFactory.createExpressionByPattern("this!!::$0", modifiedExpression.callableReference) val receiver = if (KtPsiUtil.isInOrNotInOperation(parent)) parent.right else parent.left
receiver.asFix()
}
else -> null
}
}
is KtExpression -> {
val parent = psiElement.parent
val context = psiElement.analyze()
if (checkImplicitReceivers && psiElement.getResolvedCall(context)?.getImplicitReceiverValue() is ExtensionReceiver) {
val expressionToReplace = parent as? KtCallExpression ?: parent as? KtCallableReferenceExpression ?: psiElement
expressionToReplace.asFix(implicitReceiver = true)
} else { } else {
psiFactory.createExpressionByPattern("this!!.$0", modifiedExpression) val targetElement = parent.safeAs<KtCallableReferenceExpression>()?.receiverExpression ?: psiElement
} context[BindingContext.EXPRESSION_TYPE_INFO, targetElement]?.let {
} else { val type = it.type
psiFactory.createExpressionByPattern("$0!!", modifiedExpression)
}
modifiedExpression.replace(exclExclExpression)
}
private class ExpressionForCall(val expression: KtExpression, val implicitReceiver: Boolean) val dataFlowValueFactory = targetElement.getResolutionFacade().getDataFlowValueFactory()
private fun KtExpression?.expressionForCall(implicitReceiver: Boolean = false) = this?.let { ExpressionForCall(it, implicitReceiver) } if (type != null) {
val nullability = it.dataFlowInfo.getStableNullability(
private fun getExpressionForIntroduceCall(): ExpressionForCall? { dataFlowValueFactory.createDataFlowValue(targetElement, type, context, targetElement.findModuleDescriptor())
val psiElement = element ?: return null )
if ((psiElement as? KtExpression).isNullExpression()) { if (!nullability.canBeNonNull()) return null
return null
}
if (psiElement is LeafPsiElement && psiElement.elementType == KtTokens.DOT) {
return (psiElement.prevSibling as? KtExpression).expressionForCall()
}
return when (psiElement) {
is KtArrayAccessExpression -> psiElement.expressionForCall()
is KtOperationReferenceExpression -> {
when (val parent = psiElement.parent) {
is KtUnaryExpression -> parent.baseExpression.expressionForCall()
is KtBinaryExpression -> {
val receiver = if (KtPsiUtil.isInOrNotInOperation(parent)) parent.right else parent.left
receiver.expressionForCall()
} }
else -> null
} }
targetElement.asFix()
} }
is KtExpression -> {
val parent = psiElement.parent
val context = psiElement.analyze()
if (checkImplicitReceivers && psiElement.getResolvedCall(context)?.getImplicitReceiverValue() is ExtensionReceiver) {
val expressionToReplace = parent as? KtCallExpression ?: parent as? KtCallableReferenceExpression ?: psiElement
expressionToReplace.expressionForCall(implicitReceiver = true)
} else {
val targetElement = parent.safeAs<KtCallableReferenceExpression>()?.receiverExpression ?: psiElement
context[BindingContext.EXPRESSION_TYPE_INFO, targetElement]?.let {
val type = it.type
val dataFlowValueFactory = targetElement.getResolutionFacade().getDataFlowValueFactory()
if (type != null) {
val nullability = it.dataFlowInfo.getStableNullability(
dataFlowValueFactory.createDataFlowValue(targetElement, type, context, targetElement.findModuleDescriptor())
)
if (!nullability.canBeNonNull()) return null
}
}
targetElement.expressionForCall()
}
}
else -> null
} }
else -> null
} }
}
companion object : KotlinSingleIntentionActionFactory() { object UnsafeCallExclExclFixFactory : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction { override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val psiElement = diagnostic.psiElement val psiElement = diagnostic.psiElement
if (diagnostic.factory == UNSAFE_CALL && psiElement is KtArrayAccessExpression) { if (diagnostic.factory == UNSAFE_CALL && psiElement is KtArrayAccessExpression) {
psiElement.arrayExpression?.let { return AddExclExclCallFix(it) } psiElement.arrayExpression?.let { return getAddExclExclCallFix(it) }
}
return AddExclExclCallFix(psiElement)
} }
return getAddExclExclCallFix(psiElement, checkImplicitReceivers = true)
} }
} }
@@ -141,7 +107,7 @@ object SmartCastImpossibleExclExclFixFactory : KotlinSingleIntentionActionFactor
val nullableExpectedType = TypeUtils.makeNullable(expectedType) val nullableExpectedType = TypeUtils.makeNullable(expectedType)
if (!type.isSubtypeOf(nullableExpectedType)) return null if (!type.isSubtypeOf(nullableExpectedType)) return null
return AddExclExclCallFix(element, checkImplicitReceivers = false) return getAddExclExclCallFix(element)
} }
} }
@@ -174,6 +140,6 @@ object MissingIteratorExclExclFixFactory : KotlinSingleIntentionActionFactory()
else -> return null else -> return null
} }
return AddExclExclCallFix(element, checkImplicitReceivers = false) return getAddExclExclCallFix(element)
} }
} }
@@ -166,7 +166,7 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() {
val nullableExpected = expectedType.makeNullable() val nullableExpected = expectedType.makeNullable()
if (expressionType.isSubtypeOf(nullableExpected)) { if (expressionType.isSubtypeOf(nullableExpected)) {
val targetExpression = diagnosticElement.getTopMostQualifiedForSelectorIfAny() val targetExpression = diagnosticElement.getTopMostQualifiedForSelectorIfAny()
actions.add(AddExclExclCallFix(targetExpression, checkImplicitReceivers = false)) getAddExclExclCallFix(targetExpression)?.let { actions.add(it) }
if (expectedType.isBoolean()) { if (expectedType.isBoolean()) {
actions.add(AddEqEqTrueFix(targetExpression)) actions.add(AddEqEqTrueFix(targetExpression))
} }
@@ -262,9 +262,9 @@ class QuickFixRegistrar : QuickFixContributor {
UNSAFE_OPERATOR_CALL.registerFactory(WrapWithSafeLetCallFix.UnsafeFactory) UNSAFE_OPERATOR_CALL.registerFactory(WrapWithSafeLetCallFix.UnsafeFactory)
TYPE_MISMATCH.registerFactory(WrapWithSafeLetCallFix.TypeMismatchFactory) TYPE_MISMATCH.registerFactory(WrapWithSafeLetCallFix.TypeMismatchFactory)
UNSAFE_CALL.registerFactory(AddExclExclCallFix) UNSAFE_CALL.registerFactory(UnsafeCallExclExclFixFactory)
UNSAFE_INFIX_CALL.registerFactory(AddExclExclCallFix) UNSAFE_INFIX_CALL.registerFactory(UnsafeCallExclExclFixFactory)
UNSAFE_OPERATOR_CALL.registerFactory(AddExclExclCallFix) UNSAFE_OPERATOR_CALL.registerFactory(UnsafeCallExclExclFixFactory)
UNNECESSARY_NOT_NULL_ASSERTION.registerFactory(RemoveExclExclCallFix) UNNECESSARY_NOT_NULL_ASSERTION.registerFactory(RemoveExclExclCallFix)
UNSAFE_INFIX_CALL.registerFactory(ReplaceInfixOrOperatorCallFix) UNSAFE_INFIX_CALL.registerFactory(ReplaceInfixOrOperatorCallFix)
UNSAFE_OPERATOR_CALL.registerFactory(ReplaceInfixOrOperatorCallFix) UNSAFE_OPERATOR_CALL.registerFactory(ReplaceInfixOrOperatorCallFix)
-2
View File
@@ -20,9 +20,7 @@ org.jetbrains.kotlin.idea.intentions.MoveMemberToCompanionObjectIntention
org.jetbrains.kotlin.idea.intentions.MovePropertyToConstructorIntention org.jetbrains.kotlin.idea.intentions.MovePropertyToConstructorIntention
org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
org.jetbrains.kotlin.idea.quickfix.AddAnnotationTargetFix org.jetbrains.kotlin.idea.quickfix.AddAnnotationTargetFix
org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
org.jetbrains.kotlin.idea.quickfix.AddFunctionToSupertypeFix org.jetbrains.kotlin.idea.quickfix.AddFunctionToSupertypeFix
org.jetbrains.kotlin.idea.quickfix.AddModifierFix
org.jetbrains.kotlin.idea.quickfix.AddNameToArgumentFix org.jetbrains.kotlin.idea.quickfix.AddNameToArgumentFix
org.jetbrains.kotlin.idea.quickfix.AddPropertyToSupertypeFix org.jetbrains.kotlin.idea.quickfix.AddPropertyToSupertypeFix
org.jetbrains.kotlin.idea.quickfix.AddStartProjectionsForInnerClass org.jetbrains.kotlin.idea.quickfix.AddStartProjectionsForInnerClass
@@ -101,13 +101,13 @@ private val errorsFixingDiagnosticBasedPostProcessingGroup =
}, },
diagnosticBasedProcessing( diagnosticBasedProcessing(
addExclExclFactoryNoImplicitReceiver(AddExclExclCallFix), UnsafeCallExclExclFixFactory,
Errors.UNSAFE_CALL, Errors.UNSAFE_CALL,
Errors.UNSAFE_INFIX_CALL, Errors.UNSAFE_INFIX_CALL,
Errors.UNSAFE_OPERATOR_CALL Errors.UNSAFE_OPERATOR_CALL
), ),
diagnosticBasedProcessing( diagnosticBasedProcessing(
addExclExclFactoryNoImplicitReceiver(MissingIteratorExclExclFixFactory), MissingIteratorExclExclFixFactory,
Errors.ITERATOR_ON_NULLABLE Errors.ITERATOR_ON_NULLABLE
), ),
diagnosticBasedProcessing( diagnosticBasedProcessing(
@@ -5,15 +5,12 @@
package org.jetbrains.kotlin.nj2k.postProcessing package org.jetbrains.kotlin.nj2k.postProcessing
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.RangeMarker
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.idea.core.util.range import org.jetbrains.kotlin.idea.core.util.range
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.quickfix.QuickFixFactory import org.jetbrains.kotlin.idea.quickfix.QuickFixFactory
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
@@ -22,7 +19,6 @@ import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class DiagnosticBasedPostProcessingGroup(diagnosticBasedProcessings: List<DiagnosticBasedProcessing>) : FileBasedPostProcessing() { class DiagnosticBasedPostProcessingGroup(diagnosticBasedProcessings: List<DiagnosticBasedProcessing>) : FileBasedPostProcessing() {
constructor(vararg diagnosticBasedProcessings: DiagnosticBasedProcessing) : this(diagnosticBasedProcessings.toList()) constructor(vararg diagnosticBasedProcessings: DiagnosticBasedProcessing) : this(diagnosticBasedProcessings.toList())
@@ -97,14 +93,3 @@ fun diagnosticBasedProcessing(fixFactory: QuickFixFactory, vararg diagnosticFact
} }
} }
} }
fun addExclExclFactoryNoImplicitReceiver(initialFactory: KotlinSingleIntentionActionFactory) =
object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? =
initialFactory.createActions(diagnostic).singleOrNull()
?.safeAs<AddExclExclCallFix>()
?.let {
AddExclExclCallFix(diagnostic.psiElement, checkImplicitReceivers = false)
}
}