From 85cbea70bfc7b5814305210b8cf6286bfc6bda2a Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Fri, 23 Apr 2021 16:44:30 +0000 Subject: [PATCH] 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. --- .../idea/quickfix/AddExclExclCallFix.kt | 40 ++++++ .../inspections/HasPlatformTypeInspection.kt | 4 +- ...formExtensionReceiverOfInlineInspection.kt | 16 ++- .../idea/quickfix/AddExclExclCallFix.kt | 128 +++++++----------- .../QuickFixFactoryForTypeMismatchError.kt | 2 +- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 6 +- .../quickfix/allowResolveInWriteAction.txt | 2 - .../nj2k/postProcessing/J2kPostProcessor.kt | 4 +- .../diagnosticBasedPostProcessing.kt | 15 -- 9 files changed, 104 insertions(+), 113 deletions(-) create mode 100644 idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt new file mode 100644 index 00000000000..0211e1e6c39 --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt @@ -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) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/HasPlatformTypeInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/HasPlatformTypeInspection.kt index 8667ab190ae..bb463d5e9a5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/HasPlatformTypeInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/HasPlatformTypeInspection.kt @@ -22,7 +22,7 @@ import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention 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.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments @@ -58,7 +58,7 @@ class HasPlatformTypeInspection( if (expression != null && (!reportPlatformArguments || !TypeUtils.makeNotNullable(type).isFlexibleRecursive()) ) { - return listOf(IntentionWrapper(AddExclExclCallFix(expression), element.containingFile)) + return listOfNotNull(getAddExclExclCallFix(expression)?.let { IntentionWrapper(it, element.containingFile) }) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/PlatformExtensionReceiverOfInlineInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/PlatformExtensionReceiverOfInlineInspection.kt index 2b0436ec83f..2232af4dc9a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/PlatformExtensionReceiverOfInlineInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/PlatformExtensionReceiverOfInlineInspection.kt @@ -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.getResolutionFacade 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.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtDotQualifiedExpression @@ -78,12 +78,14 @@ class PlatformExtensionReceiverOfInlineInspection : AbstractKotlinInspection() { val stableNullability = context.getDataFlowInfoBefore(receiverExpression).getStableNullability(dataFlow) if (!stableNullability.canBeNull()) return - holder.registerProblem( - receiverExpression, - KotlinBundle.message("call.of.inline.function.with.nullable.extension.receiver.can.provoke.npe.in.kotlin.1.2"), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - IntentionWrapper(AddExclExclCallFix(receiverExpression), receiverExpression.containingKtFile) - ) + getAddExclExclCallFix(receiverExpression)?.let { + holder.registerProblem( + receiverExpression, + KotlinBundle.message("call.of.inline.function.with.nullable.extension.receiver.can.provoke.npe.in.kotlin.1.2"), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + IntentionWrapper(it, receiverExpression.containingKtFile) + ) + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt index dcd92b08ef6..c7a775d893b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt @@ -5,11 +5,7 @@ package org.jetbrains.kotlin.idea.quickfix -import com.intellij.codeInsight.FileModificationService 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.impl.source.tree.LeafPsiElement 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.Errors 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.findModuleDescriptor 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.utils.addToStdlib.safeAs -class AddExclExclCallFix(psiElement: PsiElement, val checkImplicitReceivers: Boolean = true) : ExclExclCallFix(psiElement), - LowPriorityAction { +fun getAddExclExclCallFix(element: PsiElement?, checkImplicitReceivers: Boolean = false): AddExclExclCallFix? { + fun KtExpression?.asFix(implicitReceiver: Boolean = false) = this?.let { AddExclExclCallFix(it, implicitReceiver) } - override fun getText() = KotlinBundle.message("fix.introduce.non.null.assertion") - - override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean = - getExpressionForIntroduceCall() != null - - override fun invoke(project: Project, editor: Editor?, file: KtFile) { - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return - - val expr = getExpressionForIntroduceCall() ?: return - val modifiedExpression = expr.expression - val psiFactory = KtPsiFactory(project) - val exclExclExpression = if (expr.implicitReceiver) { - if (modifiedExpression is KtCallableReferenceExpression) { - psiFactory.createExpressionByPattern("this!!::$0", modifiedExpression.callableReference) + val psiElement = element ?: return null + if ((psiElement as? KtExpression).isNullExpression()) { + return null + } + if (psiElement is LeafPsiElement && psiElement.elementType == KtTokens.DOT) { + return (psiElement.prevSibling as? KtExpression).asFix() + } + return when (psiElement) { + is KtArrayAccessExpression -> psiElement.asFix() + is KtOperationReferenceExpression -> { + when (val parent = psiElement.parent) { + is KtUnaryExpression -> parent.baseExpression.asFix() + is KtBinaryExpression -> { + 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 { - psiFactory.createExpressionByPattern("this!!.$0", modifiedExpression) - } - } else { - psiFactory.createExpressionByPattern("$0!!", modifiedExpression) - } - modifiedExpression.replace(exclExclExpression) - } + val targetElement = parent.safeAs()?.receiverExpression ?: psiElement + context[BindingContext.EXPRESSION_TYPE_INFO, targetElement]?.let { + val type = it.type - 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) } - - private fun getExpressionForIntroduceCall(): ExpressionForCall? { - val psiElement = element ?: return null - if ((psiElement as? KtExpression).isNullExpression()) { - 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() + if (type != null) { + val nullability = it.dataFlowInfo.getStableNullability( + dataFlowValueFactory.createDataFlowValue(targetElement, type, context, targetElement.findModuleDescriptor()) + ) + if (!nullability.canBeNonNull()) return null } - 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()?.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() { - override fun createAction(diagnostic: Diagnostic): IntentionAction { - val psiElement = diagnostic.psiElement - if (diagnostic.factory == UNSAFE_CALL && psiElement is KtArrayAccessExpression) { - psiElement.arrayExpression?.let { return AddExclExclCallFix(it) } - } - return AddExclExclCallFix(psiElement) +object UnsafeCallExclExclFixFactory : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val psiElement = diagnostic.psiElement + if (diagnostic.factory == UNSAFE_CALL && psiElement is KtArrayAccessExpression) { + psiElement.arrayExpression?.let { return getAddExclExclCallFix(it) } } + return getAddExclExclCallFix(psiElement, checkImplicitReceivers = true) } } @@ -141,7 +107,7 @@ object SmartCastImpossibleExclExclFixFactory : KotlinSingleIntentionActionFactor val nullableExpectedType = TypeUtils.makeNullable(expectedType) if (!type.isSubtypeOf(nullableExpectedType)) return null - return AddExclExclCallFix(element, checkImplicitReceivers = false) + return getAddExclExclCallFix(element) } } @@ -174,6 +140,6 @@ object MissingIteratorExclExclFixFactory : KotlinSingleIntentionActionFactory() else -> return null } - return AddExclExclCallFix(element, checkImplicitReceivers = false) + return getAddExclExclCallFix(element) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt index eca380ad32c..a479cda2ea3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt @@ -166,7 +166,7 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() { val nullableExpected = expectedType.makeNullable() if (expressionType.isSubtypeOf(nullableExpected)) { val targetExpression = diagnosticElement.getTopMostQualifiedForSelectorIfAny() - actions.add(AddExclExclCallFix(targetExpression, checkImplicitReceivers = false)) + getAddExclExclCallFix(targetExpression)?.let { actions.add(it) } if (expectedType.isBoolean()) { actions.add(AddEqEqTrueFix(targetExpression)) } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 08bd80e0791..7ddddf51db6 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -262,9 +262,9 @@ class QuickFixRegistrar : QuickFixContributor { UNSAFE_OPERATOR_CALL.registerFactory(WrapWithSafeLetCallFix.UnsafeFactory) TYPE_MISMATCH.registerFactory(WrapWithSafeLetCallFix.TypeMismatchFactory) - UNSAFE_CALL.registerFactory(AddExclExclCallFix) - UNSAFE_INFIX_CALL.registerFactory(AddExclExclCallFix) - UNSAFE_OPERATOR_CALL.registerFactory(AddExclExclCallFix) + UNSAFE_CALL.registerFactory(UnsafeCallExclExclFixFactory) + UNSAFE_INFIX_CALL.registerFactory(UnsafeCallExclExclFixFactory) + UNSAFE_OPERATOR_CALL.registerFactory(UnsafeCallExclExclFixFactory) UNNECESSARY_NOT_NULL_ASSERTION.registerFactory(RemoveExclExclCallFix) UNSAFE_INFIX_CALL.registerFactory(ReplaceInfixOrOperatorCallFix) UNSAFE_OPERATOR_CALL.registerFactory(ReplaceInfixOrOperatorCallFix) diff --git a/idea/testData/quickfix/allowResolveInWriteAction.txt b/idea/testData/quickfix/allowResolveInWriteAction.txt index cf806580272..54425449711 100644 --- a/idea/testData/quickfix/allowResolveInWriteAction.txt +++ b/idea/testData/quickfix/allowResolveInWriteAction.txt @@ -20,9 +20,7 @@ org.jetbrains.kotlin.idea.intentions.MoveMemberToCompanionObjectIntention org.jetbrains.kotlin.idea.intentions.MovePropertyToConstructorIntention org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention org.jetbrains.kotlin.idea.quickfix.AddAnnotationTargetFix -org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix org.jetbrains.kotlin.idea.quickfix.AddFunctionToSupertypeFix -org.jetbrains.kotlin.idea.quickfix.AddModifierFix org.jetbrains.kotlin.idea.quickfix.AddNameToArgumentFix org.jetbrains.kotlin.idea.quickfix.AddPropertyToSupertypeFix org.jetbrains.kotlin.idea.quickfix.AddStartProjectionsForInnerClass diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/J2kPostProcessor.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/J2kPostProcessor.kt index a8c0edf099b..5d93b34c8c8 100644 --- a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/J2kPostProcessor.kt +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/J2kPostProcessor.kt @@ -101,13 +101,13 @@ private val errorsFixingDiagnosticBasedPostProcessingGroup = }, diagnosticBasedProcessing( - addExclExclFactoryNoImplicitReceiver(AddExclExclCallFix), + UnsafeCallExclExclFixFactory, Errors.UNSAFE_CALL, Errors.UNSAFE_INFIX_CALL, Errors.UNSAFE_OPERATOR_CALL ), diagnosticBasedProcessing( - addExclExclFactoryNoImplicitReceiver(MissingIteratorExclExclFixFactory), + MissingIteratorExclExclFixFactory, Errors.ITERATOR_ON_NULLABLE ), diagnosticBasedProcessing( diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/diagnosticBasedPostProcessing.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/diagnosticBasedPostProcessing.kt index 6b532712d0d..7e51d25e2fb 100644 --- a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/diagnosticBasedPostProcessing.kt +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/diagnosticBasedPostProcessing.kt @@ -5,15 +5,12 @@ package org.jetbrains.kotlin.nj2k.postProcessing -import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.editor.RangeMarker import com.intellij.psi.PsiElement import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.DiagnosticFactory 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.resolve.ResolutionFacade 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.psiUtil.elementsInRange import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics -import org.jetbrains.kotlin.utils.addToStdlib.safeAs class DiagnosticBasedPostProcessingGroup(diagnosticBasedProcessings: List) : FileBasedPostProcessing() { 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() - ?.let { - AddExclExclCallFix(diagnostic.psiElement, checkImplicitReceivers = false) - } - } -