[Kotlin] Modified KotlinCallProcessor to allow processing unresolved elements

KTIJ-29049

GitOrigin-RevId: bbadaf150875b69b444916052555bf156bdef578
This commit is contained in:
Frederik Haselmeier
2024-03-14 13:49:03 +00:00
committed by intellij-monorepo-bot
parent b4c2eb5f19
commit baafb50e56
2 changed files with 132 additions and 61 deletions
@@ -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<KtCall>()
val callInfo = element.resolveCall()
val call = callInfo?.successfulCallOrNull<KtCall>()
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<PsiElement>, processor: KtAnalysisSession.(CallTarget) -> Unit) {
process(elements, processor.toCallTargetProcessor())
}
fun KotlinCallProcessor.process(elements: Collection<PsiElement>, 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<KtExpression>().toList()
process(allExpressions, processor)
}
@@ -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<KtExpression> { expression ->
val resolvedCall = expression.resolveCall() ?: return@anyDescendantOfType false
resolvedCall.successfulCallOrNull<KtCallableMemberCall<*, *>>() == null
}
}
context(KtAnalysisSession)
private fun KtNamedFunction.hasSuspendOrUnresolvedCall(functionSymbol: KtFunctionSymbol): Boolean {
val allExpressions = descendantsOfType<KtExpression>().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
}
}