mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[Kotlin] Replaced custom logic to test if an expression suspends with a pre-existing implementation
KTIJ-29049 GitOrigin-RevId: 687086e40a0d777ab9d2433c9522afbd55abde68
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5e0cc70bbb
commit
b4c2eb5f19
+22
-60
@@ -5,14 +5,19 @@ 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.*
|
||||
import org.jetbrains.kotlin.analysis.api.calls.KtCallableMemberCall
|
||||
import org.jetbrains.kotlin.analysis.api.calls.successfulCallOrNull
|
||||
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.KotlinCallProcessor
|
||||
import org.jetbrains.kotlin.idea.base.codeInsight.process
|
||||
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
|
||||
@@ -20,7 +25,9 @@ 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.*
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.namedFunctionVisitor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||
|
||||
internal class RedundantSuspendModifierInspection : AbstractKotlinInspection() {
|
||||
@@ -36,6 +43,7 @@ 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(
|
||||
@@ -51,77 +59,31 @@ internal class RedundantSuspendModifierInspection : AbstractKotlinInspection() {
|
||||
|
||||
context(KtAnalysisSession)
|
||||
private fun KtCallableSymbol.isSuspendSymbol(): Boolean {
|
||||
|
||||
// Currently, Kotlin does not support suspending properties except for accessing the coroutineContext
|
||||
if (getFqNameIfPackageOrNonLocal() == coroutineContextFqName) {
|
||||
if (this is KtKotlinPropertySymbol && getFqNameIfPackageOrNonLocal() == coroutineContextFqName) {
|
||||
return true
|
||||
}
|
||||
|
||||
return this is KtFunctionSymbol && isSuspend
|
||||
}
|
||||
|
||||
context(KtAnalysisSession)
|
||||
private fun KtExpression.resolveMemberFunction(
|
||||
name: String,
|
||||
psiFactory: KtPsiFactory,
|
||||
context: KtExpression = this
|
||||
): KtFunctionCall<*>? {
|
||||
val newExpression = psiFactory.createExpressionByPattern("$0.$1()", this, name)
|
||||
val fragment = KtPsiFactory(project).createExpressionCodeFragment(newExpression.text, context)
|
||||
val expression = fragment.firstChild as? KtExpression ?: return null
|
||||
return expression.resolveCall()?.successfulFunctionCallOrNull()
|
||||
}
|
||||
|
||||
context(KtAnalysisSession)
|
||||
private fun KtForExpression.isSuspendingLoopOrUnresolved(): Boolean {
|
||||
val loopRangeExpression = loopRange ?: return true
|
||||
val psiFactory = KtPsiFactory(project)
|
||||
val iteratorFunction = loopRangeExpression.resolveMemberFunction("iterator", psiFactory) ?: return true
|
||||
if (iteratorFunction.partiallyAppliedSymbol.symbol.isSuspendSymbol()) {
|
||||
return true
|
||||
}
|
||||
val functionsToCheck = listOf("hasNext", "next")
|
||||
for (f in functionsToCheck) {
|
||||
val iteratorExpression = psiFactory.createExpressionByPattern("$0.iterator()", loopRangeExpression)
|
||||
val resolvedFunction = iteratorExpression.resolveMemberFunction(f, psiFactory, loopRangeExpression) ?: return true
|
||||
if (resolvedFunction.partiallyAppliedSymbol.symbol.isSuspendSymbol()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
context(KtAnalysisSession)
|
||||
private fun KtCallInfo.isExternalSuspendOrUnresolved(selfSymbol: KtFunctionSymbol): Boolean {
|
||||
val functionCall = successfulCallOrNull<KtCallableMemberCall<*, *>>() ?: return true
|
||||
val symbol = functionCall.partiallyAppliedSymbol.symbol // Recursive call to itself, ignore
|
||||
if (symbol == selfSymbol) return false
|
||||
if (symbol.isSuspendSymbol()) return true
|
||||
|
||||
return if (functionCall is KtCompoundVariableAccessCall) {
|
||||
val compoundAccessSymbol = functionCall.compoundAccess.operationPartiallyAppliedSymbol.symbol
|
||||
if (compoundAccessSymbol == selfSymbol) return false
|
||||
compoundAccessSymbol.isSuspendSymbol()
|
||||
} else {
|
||||
false
|
||||
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 {
|
||||
return anyDescendantOfType<KtExpression> { expression ->
|
||||
if (expression == this) return@anyDescendantOfType false
|
||||
if (expression is KtForExpression) {
|
||||
return@anyDescendantOfType expression.isSuspendingLoopOrUnresolved()
|
||||
val allExpressions = descendantsOfType<KtExpression>().toList()
|
||||
var hasSuspendCall = false
|
||||
val selfCallableId = functionSymbol.callableIdIfNonLocal
|
||||
KotlinCallProcessor.process(allExpressions) { target ->
|
||||
if (target.symbol.isSuspendSymbol() && target.symbol.callableIdIfNonLocal != selfCallableId) {
|
||||
hasSuspendCall = true
|
||||
}
|
||||
// If resolveCall returns null, we skip it (likely block/function/etc., not an actual expression we want to analyze)
|
||||
val resolvedCall = expression.resolveCall()
|
||||
?: return@anyDescendantOfType false
|
||||
// If we cannot resolve to anything or a singular call, then we do not know if this might be suspending or not
|
||||
if (resolvedCall is KtErrorCallInfo) {
|
||||
return@anyDescendantOfType true
|
||||
}
|
||||
resolvedCall.isExternalSuspendOrUnresolved(functionSymbol)
|
||||
}
|
||||
return hasSuspendCall
|
||||
}
|
||||
}
|
||||
+10
@@ -1113,6 +1113,16 @@ public abstract class SharedK1LocalInspectionTestGenerated extends AbstractShare
|
||||
public void testSuspendingLoopLocalExtensionProperty() throws Exception {
|
||||
runTest("../testData/inspectionsLocal/redundantSuspend/suspendingLoopLocalExtensionProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unresolvedFunction.kt")
|
||||
public void testUnresolvedFunction() throws Exception {
|
||||
runTest("../testData/inspectionsLocal/redundantSuspend/unresolvedFunction.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unresolvedProperty.kt")
|
||||
public void testUnresolvedProperty() throws Exception {
|
||||
runTest("../testData/inspectionsLocal/redundantSuspend/unresolvedProperty.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
+10
@@ -1113,6 +1113,16 @@ public abstract class SharedK2LocalInspectionTestGenerated extends AbstractShare
|
||||
public void testSuspendingLoopLocalExtensionProperty() throws Exception {
|
||||
runTest("../testData/inspectionsLocal/redundantSuspend/suspendingLoopLocalExtensionProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unresolvedFunction.kt")
|
||||
public void testUnresolvedFunction() throws Exception {
|
||||
runTest("../testData/inspectionsLocal/redundantSuspend/unresolvedFunction.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unresolvedProperty.kt")
|
||||
public void testUnresolvedProperty() throws Exception {
|
||||
runTest("../testData/inspectionsLocal/redundantSuspend/unresolvedProperty.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// PROBLEM: none
|
||||
|
||||
class SIterable {
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// PROBLEM: none
|
||||
|
||||
class OtherIterator {
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// PROBLEM: none
|
||||
|
||||
class SIterable {
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// PROBLEM: none
|
||||
<caret>suspend fun foo() {
|
||||
test()
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// PROBLEM: none
|
||||
<caret>suspend fun foo() {
|
||||
test
|
||||
}
|
||||
Reference in New Issue
Block a user