[Kotlin] Ported redundant suspend modifier inspection to analysis API

^KTIJ-29049 fixed

GitOrigin-RevId: 29be464acca9da87da63bce08d2701ea6187d8d4
This commit is contained in:
Frederik Haselmeier
2024-03-14 13:49:03 +00:00
committed by intellij-monorepo-bot
parent 6577acb998
commit 5e0cc70bbb
52 changed files with 642 additions and 223 deletions
@@ -97,6 +97,14 @@
language="kotlin"
key="inspection.redundant.setter.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.codeInsight.inspections.shared.RedundantSuspendModifierInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.redundant.constructs"
enabledByDefault="true"
level="WARNING"
language="kotlin" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES"
key="inspection.redundant.suspend.modifier.display.name" bundle="messages.KotlinBundle"/>
<localInspection
implementationClass="org.jetbrains.kotlin.idea.codeInsight.inspections.shared.SimplifyNegatedBinaryExpressionInspection"
groupPath="Kotlin"
@@ -0,0 +1,127 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.codeInsight.inspections.shared
import com.intellij.codeInspection.IntentionWrapper
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
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.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.Modality
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
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.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
analyze(function) {
val functionSymbol = function.getFunctionLikeSymbol() as? KtFunctionSymbol ?: return
if (functionSymbol.modality == Modality.OPEN) return
if (function.hasSuspendOrUnresolvedCall(functionSymbol)) return
holder.registerProblem(
suspendModifier, KotlinBundle.message("redundant.suspend.modifier"), IntentionWrapper(
RemoveModifierFixBase(function, KtTokens.SUSPEND_KEYWORD, isRedundant = true).asIntention()
)
)
}
})
}
private val coroutineContextFqName = StandardNames.COROUTINES_PACKAGE_FQ_NAME.child(Name.identifier("coroutineContext"))
context(KtAnalysisSession)
private fun KtCallableSymbol.isSuspendSymbol(): Boolean {
// Currently, Kotlin does not support suspending properties except for accessing the coroutineContext
if (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
}
}
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()
}
// 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)
}
}
}
@@ -229,6 +229,23 @@ public abstract class SharedK1InspectionTestGenerated extends AbstractSharedK1In
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspections/redundantSuspendModifier")
public abstract static class RedundantSuspendModifier extends AbstractSharedK1InspectionTest {
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspections/redundantSuspendModifier/inspectionData")
public static class InspectionData extends AbstractSharedK1InspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("inspections.test")
public void testInspections_test() throws Exception {
runTest("../testData/inspections/redundantSuspendModifier/inspectionData/inspections.test");
}
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspections/removeSetterParameterType")
public abstract static class RemoveSetterParameterType extends AbstractSharedK1InspectionTest {
@@ -987,6 +987,134 @@ public abstract class SharedK1LocalInspectionTestGenerated extends AbstractShare
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspectionsLocal/redundantSuspend")
public static class RedundantSuspend extends AbstractSharedK1LocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("actual.kt")
public void testActual() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/actual.kt");
}
@TestMetadata("coroutineContext.kt")
public void testCoroutineContext() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/coroutineContext.kt");
}
@TestMetadata("interface.kt")
public void testInterface() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/interface.kt");
}
@TestMetadata("interfaceNoBody.kt")
public void testInterfaceNoBody() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/interfaceNoBody.kt");
}
@TestMetadata("invokeOperator.kt")
public void testInvokeOperator() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/invokeOperator.kt");
}
@TestMetadata("invokingByQualified.kt")
public void testInvokingByQualified() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/invokingByQualified.kt");
}
@TestMetadata("lambdaCall.kt")
public void testLambdaCall() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/lambdaCall.kt");
}
@TestMetadata("nonSuspendingLoop.kt")
public void testNonSuspendingLoop() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/nonSuspendingLoop.kt");
}
@TestMetadata("nonSuspendingLoopExtensionProperty.kt")
public void testNonSuspendingLoopExtensionProperty() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/nonSuspendingLoopExtensionProperty.kt");
}
@TestMetadata("nonSuspendingLoopIteratorOtherClass.kt")
public void testNonSuspendingLoopIteratorOtherClass() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/nonSuspendingLoopIteratorOtherClass.kt");
}
@TestMetadata("nonSuspendingLoopLocalExtensionProperty.kt")
public void testNonSuspendingLoopLocalExtensionProperty() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/nonSuspendingLoopLocalExtensionProperty.kt");
}
@TestMetadata("nullableFunctionType.kt")
public void testNullableFunctionType() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/nullableFunctionType.kt");
}
@TestMetadata("operator.kt")
public void testOperator() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/operator.kt");
}
@TestMetadata("operatorAssignment.kt")
public void testOperatorAssignment() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/operatorAssignment.kt");
}
@TestMetadata("override.kt")
public void testOverride() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/override.kt");
}
@TestMetadata("parenthesized.kt")
public void testParenthesized() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/parenthesized.kt");
}
@TestMetadata("recursiveCall.kt")
public void testRecursiveCall() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/recursiveCall.kt");
}
@TestMetadata("recursiveOperator.kt")
public void testRecursiveOperator() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/recursiveOperator.kt");
}
@TestMetadata("recursiveOperatorAssignment.kt")
public void testRecursiveOperatorAssignment() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/recursiveOperatorAssignment.kt");
}
@TestMetadata("selfCallWithDifferentReceiver.kt")
public void testSelfCallWithDifferentReceiver() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/selfCallWithDifferentReceiver.kt");
}
@TestMetadata("suspendingLoop.kt")
public void testSuspendingLoop() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/suspendingLoop.kt");
}
@TestMetadata("suspendingLoopExtensionProperty.kt")
public void testSuspendingLoopExtensionProperty() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/suspendingLoopExtensionProperty.kt");
}
@TestMetadata("suspendingLoopIteratorOtherClass.kt")
public void testSuspendingLoopIteratorOtherClass() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/suspendingLoopIteratorOtherClass.kt");
}
@TestMetadata("suspendingLoopLocalExtensionProperty.kt")
public void testSuspendingLoopLocalExtensionProperty() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/suspendingLoopLocalExtensionProperty.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspectionsLocal/redundantValueArgument")
public static class RedundantValueArgument extends AbstractSharedK1LocalInspectionTest {
@@ -229,6 +229,23 @@ public abstract class SharedK2InspectionTestGenerated extends AbstractSharedK2In
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspections/redundantSuspendModifier")
public abstract static class RedundantSuspendModifier extends AbstractSharedK2InspectionTest {
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspections/redundantSuspendModifier/inspectionData")
public static class InspectionData extends AbstractSharedK2InspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("inspections.test")
public void testInspections_test() throws Exception {
runTest("../testData/inspections/redundantSuspendModifier/inspectionData/inspections.test");
}
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspections/removeSetterParameterType")
public abstract static class RemoveSetterParameterType extends AbstractSharedK2InspectionTest {
@@ -987,6 +987,134 @@ public abstract class SharedK2LocalInspectionTestGenerated extends AbstractShare
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspectionsLocal/redundantSuspend")
public static class RedundantSuspend extends AbstractSharedK2LocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("actual.kt")
public void testActual() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/actual.kt");
}
@TestMetadata("coroutineContext.kt")
public void testCoroutineContext() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/coroutineContext.kt");
}
@TestMetadata("interface.kt")
public void testInterface() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/interface.kt");
}
@TestMetadata("interfaceNoBody.kt")
public void testInterfaceNoBody() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/interfaceNoBody.kt");
}
@TestMetadata("invokeOperator.kt")
public void testInvokeOperator() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/invokeOperator.kt");
}
@TestMetadata("invokingByQualified.kt")
public void testInvokingByQualified() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/invokingByQualified.kt");
}
@TestMetadata("lambdaCall.kt")
public void testLambdaCall() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/lambdaCall.kt");
}
@TestMetadata("nonSuspendingLoop.kt")
public void testNonSuspendingLoop() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/nonSuspendingLoop.kt");
}
@TestMetadata("nonSuspendingLoopExtensionProperty.kt")
public void testNonSuspendingLoopExtensionProperty() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/nonSuspendingLoopExtensionProperty.kt");
}
@TestMetadata("nonSuspendingLoopIteratorOtherClass.kt")
public void testNonSuspendingLoopIteratorOtherClass() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/nonSuspendingLoopIteratorOtherClass.kt");
}
@TestMetadata("nonSuspendingLoopLocalExtensionProperty.kt")
public void testNonSuspendingLoopLocalExtensionProperty() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/nonSuspendingLoopLocalExtensionProperty.kt");
}
@TestMetadata("nullableFunctionType.kt")
public void testNullableFunctionType() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/nullableFunctionType.kt");
}
@TestMetadata("operator.kt")
public void testOperator() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/operator.kt");
}
@TestMetadata("operatorAssignment.kt")
public void testOperatorAssignment() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/operatorAssignment.kt");
}
@TestMetadata("override.kt")
public void testOverride() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/override.kt");
}
@TestMetadata("parenthesized.kt")
public void testParenthesized() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/parenthesized.kt");
}
@TestMetadata("recursiveCall.kt")
public void testRecursiveCall() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/recursiveCall.kt");
}
@TestMetadata("recursiveOperator.kt")
public void testRecursiveOperator() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/recursiveOperator.kt");
}
@TestMetadata("recursiveOperatorAssignment.kt")
public void testRecursiveOperatorAssignment() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/recursiveOperatorAssignment.kt");
}
@TestMetadata("selfCallWithDifferentReceiver.kt")
public void testSelfCallWithDifferentReceiver() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/selfCallWithDifferentReceiver.kt");
}
@TestMetadata("suspendingLoop.kt")
public void testSuspendingLoop() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/suspendingLoop.kt");
}
@TestMetadata("suspendingLoopExtensionProperty.kt")
public void testSuspendingLoopExtensionProperty() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/suspendingLoopExtensionProperty.kt");
}
@TestMetadata("suspendingLoopIteratorOtherClass.kt")
public void testSuspendingLoopIteratorOtherClass() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/suspendingLoopIteratorOtherClass.kt");
}
@TestMetadata("suspendingLoopLocalExtensionProperty.kt")
public void testSuspendingLoopLocalExtensionProperty() throws Exception {
runTest("../testData/inspectionsLocal/redundantSuspend/suspendingLoopLocalExtensionProperty.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/inspectionsLocal/redundantValueArgument")
public static class RedundantValueArgument extends AbstractSharedK2LocalInspectionTest {
@@ -0,0 +1,2 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.codeInsight.inspections.shared.RedundantSuspendModifierInspection
// K2_INSPECTION_CLASS: org.jetbrains.kotlin.idea.codeInsight.inspections.shared.RedundantSuspendModifierInspection
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.codeInsight.inspections.shared.RedundantSuspendModifierInspection
@@ -0,0 +1,7 @@
// PROBLEM: none
interface Suspended {
<caret>suspend fun bar() {
}
}
@@ -0,0 +1,6 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// PROBLEM: none
interface Suspended {
<caret>suspend fun bar()
}
@@ -0,0 +1,11 @@
class Iterable {
operator fun iterator() = this
operator fun hasNext(): Boolean = false
operator fun next(): Int = 0
}
<caret>suspend fun foo() {
val iterable = Iterable()
for (x in iterable) {
}
}
@@ -0,0 +1,11 @@
class Iterable {
operator fun iterator() = this
operator fun hasNext(): Boolean = false
operator fun next(): Int = 0
}
fun foo() {
val iterable = Iterable()
for (x in iterable) {
}
}
@@ -0,0 +1,12 @@
class SIterable {
}
operator fun SIterable.iterator() = this
operator fun SIterable.hasNext(): Boolean = false
operator fun SIterable.next(): Int = 0
<caret>suspend fun foo() {
val iterable = SIterable()
for (x in iterable) {
}
}
@@ -0,0 +1,12 @@
class SIterable {
}
operator fun SIterable.iterator() = this
operator fun SIterable.hasNext(): Boolean = false
operator fun SIterable.next(): Int = 0
fun foo() {
val iterable = SIterable()
for (x in iterable) {
}
}
@@ -0,0 +1,14 @@
class OtherIterator {
operator fun hasNext(): Boolean = false
operator fun next(): Int = 0
}
class SIterable {
operator fun iterator() = OtherIterator()
}
<caret>suspend fun foo() {
val iterable = SIterable()
for (x in iterable) {
}
}
@@ -0,0 +1,14 @@
class OtherIterator {
operator fun hasNext(): Boolean = false
operator fun next(): Int = 0
}
class SIterable {
operator fun iterator() = OtherIterator()
}
fun foo() {
val iterable = SIterable()
for (x in iterable) {
}
}
@@ -0,0 +1,12 @@
class SIterable {
}
<caret>suspend fun foo() {
operator fun SIterable.iterator() = this
operator fun SIterable.hasNext(): Boolean = false
operator fun SIterable.next(): Int = 0
val iterable = SIterable()
for (x in iterable) {
}
}
@@ -0,0 +1,12 @@
class SIterable {
}
fun foo() {
operator fun SIterable.iterator() = this
operator fun SIterable.hasNext(): Boolean = false
operator fun SIterable.next(): Int = 0
val iterable = SIterable()
for (x in iterable) {
}
}
@@ -0,0 +1,9 @@
// PROBLEM: none
class A(val x: Int) {
suspend operator fun plus(a: A): A = A(1)
}
<caret>suspend fun foo(a1: A, a2: A): A {
return a1 + a2
}
@@ -0,0 +1,10 @@
// PROBLEM: none
class A(val x: Int) {
suspend operator fun plus(a: A): A = A(1)
}
<caret>suspend fun foo(a1: A, a2: A) {
var a = a1
a += a2
}
@@ -0,0 +1,5 @@
class A(val x: Int) {
<caret>suspend operator fun plus(a: A): A {
return a + a
}
}
@@ -0,0 +1,5 @@
class A(val x: Int) {
operator fun plus(a: A): A {
return a + a
}
}
@@ -0,0 +1,7 @@
class A(val x: Int) {
<caret>suspend operator fun plus(a: A): A {
var a = a
a += a
return a
}
}
@@ -0,0 +1,7 @@
class A(val x: Int) {
operator fun plus(a: A): A {
var a = a
a += a
return a
}
}
@@ -0,0 +1,13 @@
// PROBLEM: none
class SIterable {
operator fun iterator() = this
suspend operator fun hasNext(): Boolean = false
suspend operator fun next(): Int = 0
}
<caret>suspend fun foo() {
val iterable = SIterable()
for (x in iterable) {
}
}
@@ -0,0 +1,15 @@
// 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 {
}
operator fun SIterable.iterator() = this
suspend operator fun SIterable.hasNext(): Boolean = false
suspend operator fun SIterable.next(): Int = 0
<caret>suspend fun foo() {
val iterable = SIterable()
for (x in iterable) {
}
}
@@ -0,0 +1,17 @@
// 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 {
suspend operator fun hasNext(): Boolean = false
suspend operator fun next(): Int = 0
}
class SIterable {
operator fun iterator() = OtherIterator()
}
<caret>suspend fun foo() {
val iterable = SIterable()
for (x in iterable) {
}
}
@@ -0,0 +1,15 @@
// 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 {
}
<caret>suspend fun foo() {
operator fun SIterable.iterator() = this
suspend operator fun SIterable.hasNext(): Boolean = false
suspend operator fun SIterable.next(): Int = 0
val iterable = SIterable()
for (x in iterable) {
}
}
@@ -1,83 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.IntentionWrapper
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
import org.jetbrains.kotlin.idea.highlighter.SuspendCallKind
import org.jetbrains.kotlin.idea.highlighter.getSuspendCallKind
import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFixBase
import org.jetbrains.kotlin.lexer.KtTokens
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
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
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
val context = function.analyzeWithContent()
val descriptor = context[BindingContext.FUNCTION, function] ?: return
if (descriptor.modality == Modality.OPEN) return
if (function.hasSuspendCalls(context)) return
if (function.hasAnyUnresolvedCalls(context)) return
holder.registerProblem(
suspendModifier,
KotlinBundle.message("redundant.suspend.modifier"),
IntentionWrapper(RemoveModifierFixBase(function, KtTokens.SUSPEND_KEYWORD, isRedundant = true).asIntention())
)
})
}
private fun KtNamedFunction.hasAnyUnresolvedCalls(context: BindingContext): Boolean {
return context.diagnostics.any {
it.factory == Errors.UNRESOLVED_REFERENCE && this.isAncestor(it.psiElement)
}
}
private fun KtNamedFunction.hasSuspendCalls(bindingContext: BindingContext): Boolean {
val selfDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, this] ?: return false
return anyDescendantOfType<KtExpression> { expression ->
val kind = getSuspendCallKind(expression, bindingContext) ?: return@anyDescendantOfType false
if (kind is SuspendCallKind.FunctionCall) {
val resolvedCall = kind.element.getResolvedCall(bindingContext)
if (resolvedCall != null) {
val isSelfCall = when (resolvedCall) {
is VariableAsFunctionResolvedCall -> selfDescriptor == resolvedCall.functionCall.candidateDescriptor.original
else -> selfDescriptor == resolvedCall.candidateDescriptor.original
}
if (isSelfCall) {
return@anyDescendantOfType false
}
}
}
return@anyDescendantOfType true
}
}
}
@@ -273,11 +273,6 @@ public abstract class InspectionTestGenerated extends AbstractInspectionTest {
runTest("testData/inspections/redundantSamConstructor/inspectionData/inspections.test");
}
@TestMetadata("redundantSuspendModifier/inspectionData/inspections.test")
public void testRedundantSuspendModifier_inspectionData_Inspections_test() throws Exception {
runTest("testData/inspections/redundantSuspendModifier/inspectionData/inspections.test");
}
@TestMetadata("redundantUnitReturnType/inspectionData/inspections.test")
public void testRedundantUnitReturnType_inspectionData_Inspections_test() throws Exception {
runTest("testData/inspections/redundantUnitReturnType/inspectionData/inspections.test");
@@ -10492,64 +10492,6 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/inspectionsLocal/redundantSuspend")
public static class RedundantSuspend extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("actual.kt")
public void testActual() throws Exception {
runTest("testData/inspectionsLocal/redundantSuspend/actual.kt");
}
@TestMetadata("coroutineContext.kt")
public void testCoroutineContext() throws Exception {
runTest("testData/inspectionsLocal/redundantSuspend/coroutineContext.kt");
}
@TestMetadata("invokeOperator.kt")
public void testInvokeOperator() throws Exception {
runTest("testData/inspectionsLocal/redundantSuspend/invokeOperator.kt");
}
@TestMetadata("invokingByQualified.kt")
public void testInvokingByQualified() throws Exception {
runTest("testData/inspectionsLocal/redundantSuspend/invokingByQualified.kt");
}
@TestMetadata("lambdaCall.kt")
public void testLambdaCall() throws Exception {
runTest("testData/inspectionsLocal/redundantSuspend/lambdaCall.kt");
}
@TestMetadata("nullableFunctionType.kt")
public void testNullableFunctionType() throws Exception {
runTest("testData/inspectionsLocal/redundantSuspend/nullableFunctionType.kt");
}
@TestMetadata("override.kt")
public void testOverride() throws Exception {
runTest("testData/inspectionsLocal/redundantSuspend/override.kt");
}
@TestMetadata("parenthesized.kt")
public void testParenthesized() throws Exception {
runTest("testData/inspectionsLocal/redundantSuspend/parenthesized.kt");
}
@TestMetadata("recursiveCall.kt")
public void testRecursiveCall() throws Exception {
runTest("testData/inspectionsLocal/redundantSuspend/recursiveCall.kt");
}
@TestMetadata("selfCallWithDifferentReceiver.kt")
public void testSelfCallWithDifferentReceiver() throws Exception {
runTest("testData/inspectionsLocal/redundantSuspend/selfCallWithDifferentReceiver.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/inspectionsLocal/redundantUnitExpression")
public static class RedundantUnitExpression extends AbstractLocalInspectionTest {
@@ -1 +0,0 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantSuspendModifierInspection
@@ -1 +0,0 @@
org.jetbrains.kotlin.idea.inspections.RedundantSuspendModifierInspection
@@ -215,14 +215,6 @@
language="kotlin"
key="inspection.redundant.object.type.check.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantSuspendModifierInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.redundant.constructs"
enabledByDefault="true"
level="WARNING"
language="kotlin" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES"
key="inspection.redundant.suspend.modifier.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantExplicitTypeInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.redundant.constructs"
@@ -1561,64 +1561,6 @@ public abstract class Fe10BindingLocalInspectionTestGenerated extends AbstractFe
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../idea/tests/testData/inspectionsLocal/redundantSuspend")
public static class RedundantSuspend extends AbstractFe10BindingLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("actual.kt")
public void testActual() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/redundantSuspend/actual.kt");
}
@TestMetadata("coroutineContext.kt")
public void testCoroutineContext() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/redundantSuspend/coroutineContext.kt");
}
@TestMetadata("invokeOperator.kt")
public void testInvokeOperator() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/redundantSuspend/invokeOperator.kt");
}
@TestMetadata("invokingByQualified.kt")
public void testInvokingByQualified() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/redundantSuspend/invokingByQualified.kt");
}
@TestMetadata("lambdaCall.kt")
public void testLambdaCall() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/redundantSuspend/lambdaCall.kt");
}
@TestMetadata("nullableFunctionType.kt")
public void testNullableFunctionType() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/redundantSuspend/nullableFunctionType.kt");
}
@TestMetadata("override.kt")
public void testOverride() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/redundantSuspend/override.kt");
}
@TestMetadata("parenthesized.kt")
public void testParenthesized() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/redundantSuspend/parenthesized.kt");
}
@TestMetadata("recursiveCall.kt")
public void testRecursiveCall() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/redundantSuspend/recursiveCall.kt");
}
@TestMetadata("selfCallWithDifferentReceiver.kt")
public void testSelfCallWithDifferentReceiver() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/redundantSuspend/selfCallWithDifferentReceiver.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../idea/tests/testData/inspectionsLocal/redundantExplicitType")
public static class RedundantExplicitType extends AbstractFe10BindingLocalInspectionTest {
@@ -1916,14 +1916,6 @@
language="kotlin"
key="inspection.replace.put.with.assignment.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantSuspendModifierInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.redundant.constructs"
enabledByDefault="true"
level="WARNING"
language="kotlin" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES"
key="inspection.redundant.suspend.modifier.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.MayBeConstantInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.style.issues"
@@ -38,7 +38,6 @@ internal fun MutableTWorkspace.generateK2Fe10BindingsTests() {
model("testData/inspectionsLocal/mapGetWithNotNullAssertionOperator", pattern = pattern)
model("testData/inspectionsLocal/memberVisibilityCanBePrivate", pattern = pattern)
model("testData/inspectionsLocal/redundantObjectTypeCheck", pattern = pattern)
model("testData/inspectionsLocal/redundantSuspend", pattern = pattern)
model("testData/inspectionsLocal/redundantExplicitType", pattern = pattern)
model("testData/inspectionsLocal/replaceArrayEqualityOpWithArraysEquals", pattern = pattern)
model("testData/inspectionsLocal/replaceAssociateFunction", pattern = pattern)