diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/illegalSuspendCallsForDelegated.kt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/illegalSuspendCallsForDelegated.kt new file mode 100644 index 00000000000..3deb4c35b4e --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/coroutines/illegalSuspendCallsForDelegated.kt @@ -0,0 +1,8 @@ +fun bar(d: Delegate): String { + val x: String by d + return x +} + +class Delegate { + suspend operator fun getValue(thisRef: Any?, property: Any?): String = "" +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/illegalSuspendCallsForDelegated.txt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/illegalSuspendCallsForDelegated.txt new file mode 100644 index 00000000000..2c482556bae --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/coroutines/illegalSuspendCallsForDelegated.txt @@ -0,0 +1,11 @@ +package + +public fun bar(/*0*/ d: Delegate): kotlin.String + +public final class Delegate { + public constructor Delegate() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator suspend fun getValue(/*0*/ thisRef: kotlin.Any?, /*1*/ property: kotlin.Any?): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java index becd7e5370a..41422bc4d0f 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java @@ -1295,6 +1295,12 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW doTest(fileName); } + @TestMetadata("illegalSuspendCallsForDelegated.kt") + public void testIllegalSuspendCallsForDelegated() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/illegalSuspendCallsForDelegated.kt"); + doTest(fileName); + } + @TestMetadata("irrelevantSuspendDeclarations.kt") public void testIrrelevantSuspendDeclarations() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/irrelevantSuspendDeclarations.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java index 16a331e6ac2..869611dd39b 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java @@ -1295,6 +1295,12 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno doTest(fileName); } + @TestMetadata("illegalSuspendCallsForDelegated.kt") + public void testIllegalSuspendCallsForDelegated() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/illegalSuspendCallsForDelegated.kt"); + doTest(fileName); + } + @TestMetadata("irrelevantSuspendDeclarations.kt") public void testIrrelevantSuspendDeclarations() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/irrelevantSuspendDeclarations.kt"); diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinSuspendCallLineMarkerProvider.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinSuspendCallLineMarkerProvider.kt index f6aa4ddd26d..6fa3172973b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinSuspendCallLineMarkerProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/KotlinSuspendCallLineMarkerProvider.kt @@ -24,11 +24,14 @@ import com.intellij.openapi.editor.markup.GutterIconRenderer import com.intellij.openapi.progress.ProgressManager import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors +import org.jetbrains.kotlin.descriptors.accessors import org.jetbrains.kotlin.idea.KotlinIcons import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.refactoring.getLineNumber import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.BindingContext.* import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode @@ -76,7 +79,7 @@ class KotlinSuspendCallLineMarkerProvider : LineMarkerProvider { } private fun KtExpression.isValidCandidateExpression(): Boolean { - if (this is KtOperationReferenceExpression || this is KtForExpression) return true + if (this is KtOperationReferenceExpression || this is KtForExpression || this is KtProperty) return true val parent = parent if (parent is KtCallExpression && parent.calleeExpression == this) return true return false @@ -87,13 +90,26 @@ fun KtExpression.hasSuspendCalls(bindingContext: BindingContext = analyze(BodyRe return when (this) { is KtForExpression -> { - val iteratorResolvedCall = bindingContext[BindingContext.LOOP_RANGE_ITERATOR_RESOLVED_CALL, loopRange] - val loopRangeHasNextResolvedCall = bindingContext[BindingContext.LOOP_RANGE_HAS_NEXT_RESOLVED_CALL, loopRange] - val loopRangeNextResolvedCall = bindingContext[BindingContext.LOOP_RANGE_NEXT_RESOLVED_CALL, loopRange] + val iteratorResolvedCall = bindingContext[LOOP_RANGE_ITERATOR_RESOLVED_CALL, loopRange] + val loopRangeHasNextResolvedCall = bindingContext[LOOP_RANGE_HAS_NEXT_RESOLVED_CALL, loopRange] + val loopRangeNextResolvedCall = bindingContext[LOOP_RANGE_NEXT_RESOLVED_CALL, loopRange] listOf(iteratorResolvedCall, loopRangeHasNextResolvedCall, loopRangeNextResolvedCall).any { it?.resultingDescriptor?.isSuspend == true } } + is KtProperty -> { + if (hasDelegateExpression()) { + val variableDescriptor = bindingContext[DECLARATION_TO_DESCRIPTOR, this] as? VariableDescriptorWithAccessors + val accessors = variableDescriptor?.accessors ?: emptyList() + accessors.any { accessor -> + val delegatedFunctionDescriptor = bindingContext[DELEGATED_PROPERTY_RESOLVED_CALL, accessor]?.resultingDescriptor + delegatedFunctionDescriptor?.isSuspend == true + } + } + else { + false + } + } else -> { val resolvedCall = getResolvedCall(bindingContext) (resolvedCall?.resultingDescriptor as? FunctionDescriptor)?.isSuspend == true diff --git a/idea/testData/inspectionsLocal/redundantSuspend/.inspection b/idea/testData/inspectionsLocal/redundantSuspend/.inspection new file mode 100644 index 00000000000..434360ce1f6 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSuspend/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.RedundantSuspendModifierInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantSuspend/getterDelegate.kt b/idea/testData/inspectionsLocal/redundantSuspend/getterDelegate.kt new file mode 100644 index 00000000000..2e142da4426 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSuspend/getterDelegate.kt @@ -0,0 +1,13 @@ +// PROBLEM: none +// WITH_RUNTIME + +import kotlin.reflect.KProperty + +suspend fun bar(): String { + val x: String by Delegate() + return x +} + +class Delegate { + suspend operator fun getValue(thisRef: Any?, property: KProperty<*>): String = "" +} diff --git a/idea/testData/inspectionsLocal/redundantSuspend/setterDelegate.kt b/idea/testData/inspectionsLocal/redundantSuspend/setterDelegate.kt new file mode 100644 index 00000000000..4507f6d0ae6 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSuspend/setterDelegate.kt @@ -0,0 +1,16 @@ +// PROBLEM: none +// WITH_RUNTIME + +import kotlin.reflect.KProperty + +suspend fun bar(): String { + var x: String by Delegate() + x = "Hello" + return x +} + +class Delegate { + operator fun getValue(thisRef: Any?, property: KProperty<*>): String = "" + + suspend operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {} +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 544c223f50a..b298ceb9ff0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -1977,6 +1977,27 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/redundantSuspend") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RedundantSuspend extends AbstractLocalInspectionTest { + public void testAllFilesPresentInRedundantSuspend() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/redundantSuspend"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("getterDelegate.kt") + public void testGetterDelegate() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantSuspend/getterDelegate.kt"); + doTest(fileName); + } + + @TestMetadata("setterDelegate.kt") + public void testSetterDelegate() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantSuspend/setterDelegate.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/redundantUnitExpression") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)