diff --git a/plugins/kotlin/base/code-insight/src/org/jetbrains/kotlin/idea/base/codeInsight/KotlinCallProcessor.kt b/plugins/kotlin/base/code-insight/src/org/jetbrains/kotlin/idea/base/codeInsight/KotlinCallProcessor.kt index ca50bf4dbeef..bb4c5b86646b 100644 --- a/plugins/kotlin/base/code-insight/src/org/jetbrains/kotlin/idea/base/codeInsight/KotlinCallProcessor.kt +++ b/plugins/kotlin/base/code-insight/src/org/jetbrains/kotlin/idea/base/codeInsight/KotlinCallProcessor.kt @@ -6,6 +6,7 @@ import com.intellij.psi.PsiComment import com.intellij.psi.PsiElement import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.descendantsOfType import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.analyze import org.jetbrains.kotlin.analysis.api.calls.* @@ -61,6 +62,34 @@ class FunctionCallTarget( get() = partiallyAppliedSymbol.symbol } +interface KotlinCallTargetProcessor { + /** + * Processes a successfully resolved [CallTarget]. + * If false is returned from this function, no further elements will be processed. + */ + fun KtAnalysisSession.processCallTarget(target: CallTarget): Boolean + + /** + * Processes a call that resolved as an error. + * If false is returned from this function, no further elements will be processed. + */ + fun KtAnalysisSession.processUnresolvedCall(element: KtElement, callInfo: KtCallInfo?): Boolean +} + +private fun (KtAnalysisSession.(CallTarget) -> Unit).toCallTargetProcessor(): KotlinCallTargetProcessor { + val processor = this + return object : KotlinCallTargetProcessor { + override fun KtAnalysisSession.processCallTarget(target: CallTarget): Boolean { + processor(target) + return true + } + + override fun KtAnalysisSession.processUnresolvedCall(element: KtElement, callInfo: KtCallInfo?): Boolean { + return true + } + } +} + object KotlinCallProcessor { private val NAME_REFERENCE_IGNORED_PARENTS = arrayOf( KtUserType::class.java, @@ -72,7 +101,11 @@ object KotlinCallProcessor { ) fun process(element: PsiElement, processor: KtAnalysisSession.(CallTarget) -> Unit) { - when (element) { + process(element, processor.toCallTargetProcessor()) + } + + fun process(element: PsiElement, processor: KotlinCallTargetProcessor): Boolean { + return when (element) { is KtArrayAccessExpression -> handle(element, processor) is KtCallExpression -> handle(element, processor) is KtUnaryExpression -> handle(element, processor) @@ -83,8 +116,12 @@ object KotlinCallProcessor { is KtNameReferenceExpression -> { if (shouldHandleNameReference(element)) { handle(element, processor) + } else { + true } } + + else -> true } } @@ -117,57 +154,75 @@ object KotlinCallProcessor { return current } - private fun handle(element: KtElement, processor: KtAnalysisSession.(CallTarget) -> Unit) { + private fun handle(element: KtElement, processor: KotlinCallTargetProcessor): Boolean { analyze(element) { - fun handleSpecial(element: KtElement, filter: (KtSymbol) -> Boolean) { - val symbols = element.mainReference?.resolveToSymbols() ?: return + fun handleSpecial(element: KtElement, filter: (KtSymbol) -> Boolean): Boolean { + val symbols = element.mainReference?.resolveToSymbols() ?: return true for (symbol in symbols) { if (!filter(symbol)) { continue } - if (symbol is KtFunctionLikeSymbol) { - val signature = symbol.asSignature() - val partiallyAppliedSymbol = KtPartiallyAppliedFunctionSymbol(signature, null, null) - val call = KtSimpleFunctionCall(partiallyAppliedSymbol, linkedMapOf(), mapOf(), _isImplicitInvoke = false) - processor(FunctionCallTarget(element, call, partiallyAppliedSymbol)) - } else if (symbol is KtVariableLikeSymbol) { - val signature = symbol.asSignature() - val partiallyAppliedSymbol = KtPartiallyAppliedVariableSymbol(signature, null, null) - val call = KtSimpleVariableAccessCall(partiallyAppliedSymbol, linkedMapOf(), KtSimpleVariableAccess.Read) - processor(VariableCallTarget(element, call, partiallyAppliedSymbol)) + val shouldContinue = with(processor) { + when (symbol) { + is KtFunctionLikeSymbol -> { + val signature = symbol.asSignature() + val partiallyAppliedSymbol = KtPartiallyAppliedFunctionSymbol(signature, null, null) + val call = KtSimpleFunctionCall(partiallyAppliedSymbol, linkedMapOf(), mapOf(), _isImplicitInvoke = false) + processCallTarget(FunctionCallTarget(element, call, partiallyAppliedSymbol)) + } + + is KtVariableLikeSymbol -> { + val signature = symbol.asSignature() + val partiallyAppliedSymbol = KtPartiallyAppliedVariableSymbol(signature, null, null) + val call = KtSimpleVariableAccessCall(partiallyAppliedSymbol, linkedMapOf(), KtSimpleVariableAccess.Read) + processCallTarget(VariableCallTarget(element, call, partiallyAppliedSymbol)) + } + + else -> true + } + } + if (!shouldContinue) { + return false } } + return true } if (element is KtForExpression) { - handleSpecial(element) { it is KtFunctionLikeSymbol } - return + return handleSpecial(element) { it is KtFunctionLikeSymbol } } if (element is KtDestructuringDeclarationEntry) { - handleSpecial(element) { !(it is KtLocalVariableSymbol && it.psi == element) } - return + return handleSpecial(element) { !(it is KtLocalVariableSymbol && it.psi == element) } } - val call = element.resolveCall()?.successfulCallOrNull() + val callInfo = element.resolveCall() + val call = callInfo?.successfulCallOrNull() - if (call != null) { - when (call) { - is KtDelegatedConstructorCall -> processor(FunctionCallTarget(element, call, call.partiallyAppliedSymbol)) - is KtSimpleFunctionCall -> processor(FunctionCallTarget(element, call, call.partiallyAppliedSymbol)) - is KtCompoundVariableAccessCall -> { - processor(VariableCallTarget(element, call, call.partiallyAppliedSymbol)) - processor(FunctionCallTarget(element, call, call.compoundAccess.operationPartiallyAppliedSymbol)) + return with(processor) { + if (call != null) { + when (call) { + is KtDelegatedConstructorCall -> processCallTarget(FunctionCallTarget(element, call, call.partiallyAppliedSymbol)) + is KtSimpleFunctionCall -> processCallTarget(FunctionCallTarget(element, call, call.partiallyAppliedSymbol)) + is KtCompoundVariableAccessCall -> { + processCallTarget(VariableCallTarget(element, call, call.partiallyAppliedSymbol)) + processCallTarget(FunctionCallTarget(element, call, call.compoundAccess.operationPartiallyAppliedSymbol)) + } + + is KtSimpleVariableAccessCall -> { + processCallTarget(VariableCallTarget(element, call, call.partiallyAppliedSymbol)) + } + + is KtCompoundArrayAccessCall -> { + processCallTarget(FunctionCallTarget(element, call, call.getPartiallyAppliedSymbol)) + processCallTarget(FunctionCallTarget(element, call, call.setPartiallyAppliedSymbol)) + } + + else -> true } - is KtSimpleVariableAccessCall -> { - processor(VariableCallTarget(element, call, call.partiallyAppliedSymbol)) - } - is KtCompoundArrayAccessCall -> { - processor(FunctionCallTarget(element, call, call.getPartiallyAppliedSymbol)) - processor(FunctionCallTarget(element, call, call.setPartiallyAppliedSymbol)) - } - else -> {} + } else { + processUnresolvedCall(element, callInfo) } } } @@ -175,8 +230,23 @@ object KotlinCallProcessor { } fun KotlinCallProcessor.process(elements: Collection, processor: KtAnalysisSession.(CallTarget) -> Unit) { + process(elements, processor.toCallTargetProcessor()) +} + +fun KotlinCallProcessor.process(elements: Collection, processor: KotlinCallTargetProcessor) { for (element in elements) { ProgressManager.checkCanceled() - process(element, processor) + if (!process(element, processor)) { + return + } } +} + +fun KotlinCallProcessor.processExpressionsRecursively(element: PsiElement, processor: KtAnalysisSession.(CallTarget) -> Unit) { + processExpressionsRecursively(element, processor.toCallTargetProcessor()) +} + +fun KotlinCallProcessor.processExpressionsRecursively(element: PsiElement, processor: KotlinCallTargetProcessor) { + val allExpressions = element.descendantsOfType().toList() + process(allExpressions, processor) } \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/RedundantSuspendModifierInspection.kt b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/RedundantSuspendModifierInspection.kt index 273bd36becca..688962fb23c1 100644 --- a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/RedundantSuspendModifierInspection.kt +++ b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/RedundantSuspendModifierInspection.kt @@ -5,36 +5,31 @@ import com.intellij.codeInspection.IntentionWrapper import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.PsiElementVisitor -import com.intellij.psi.util.descendantsOfType import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.analyze -import org.jetbrains.kotlin.analysis.api.calls.KtCallableMemberCall -import org.jetbrains.kotlin.analysis.api.calls.successfulCallOrNull +import org.jetbrains.kotlin.analysis.api.calls.KtCallInfo import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtKotlinPropertySymbol import org.jetbrains.kotlin.builtins.StandardNames -import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.idea.base.codeInsight.CallTarget import org.jetbrains.kotlin.idea.base.codeInsight.KotlinCallProcessor -import org.jetbrains.kotlin.idea.base.codeInsight.process -import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinCallTargetProcessor +import org.jetbrains.kotlin.idea.base.codeInsight.processExpressionsRecursively import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection import org.jetbrains.kotlin.idea.codeinsight.utils.getFqNameIfPackageOrNonLocal import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFixBase import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.namedFunctionVisitor -import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType internal class RedundantSuspendModifierInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { return namedFunctionVisitor(fun(function) { - if (!function.languageVersionSettings.supportsFeature(LanguageFeature.Coroutines)) return - val suspendModifier = function.modifierList?.getModifier(KtTokens.SUSPEND_KEYWORD) ?: return if (!function.hasBody()) return if (function.hasModifier(KtTokens.OVERRIDE_KEYWORD) || function.hasModifier(KtTokens.ACTUAL_KEYWORD)) return @@ -43,7 +38,6 @@ internal class RedundantSuspendModifierInspection : AbstractKotlinInspection() { val functionSymbol = function.getFunctionLikeSymbol() as? KtFunctionSymbol ?: return if (functionSymbol.modality == Modality.OPEN) return - if (function.hasUnresolvedCalls()) return if (function.hasSuspendOrUnresolvedCall(functionSymbol)) return holder.registerProblem( @@ -66,24 +60,31 @@ internal class RedundantSuspendModifierInspection : AbstractKotlinInspection() { return this is KtFunctionSymbol && isSuspend } - context(KtAnalysisSession) - private fun KtNamedFunction.hasUnresolvedCalls(): Boolean { - return anyDescendantOfType { expression -> - val resolvedCall = expression.resolveCall() ?: return@anyDescendantOfType false - resolvedCall.successfulCallOrNull>() == null - } - } - context(KtAnalysisSession) private fun KtNamedFunction.hasSuspendOrUnresolvedCall(functionSymbol: KtFunctionSymbol): Boolean { - val allExpressions = descendantsOfType().toList() - var hasSuspendCall = false + var hasSuspendOrUnresolvedCall = false val selfCallableId = functionSymbol.callableIdIfNonLocal - KotlinCallProcessor.process(allExpressions) { target -> - if (target.symbol.isSuspendSymbol() && target.symbol.callableIdIfNonLocal != selfCallableId) { - hasSuspendCall = true + + KotlinCallProcessor.processExpressionsRecursively(this, object : KotlinCallTargetProcessor { + override fun KtAnalysisSession.processCallTarget(target: CallTarget): Boolean { + if (target.symbol.isSuspendSymbol() && target.symbol.callableIdIfNonLocal != selfCallableId) { + hasSuspendOrUnresolvedCall = true + return false + } + return true } - } - return hasSuspendCall + + override fun KtAnalysisSession.processUnresolvedCall(element: KtElement, callInfo: KtCallInfo?): Boolean { + if (callInfo != null) { + // A callInfo of null means that the element could not be resolved at all, so it is not even unresolved. + // For example, if the element is not an expression, so we ignore those cases. + hasSuspendOrUnresolvedCall = true + return false + } + return true + } + }) + + return hasSuspendOrUnresolvedCall } } \ No newline at end of file