Add "Ambiguous context due to scope receiver in suspend fun" inspection

#KT-28696 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-12-04 16:16:22 +03:00
parent 44da3c89b7
commit a15b47c93c
43 changed files with 883 additions and 26 deletions
@@ -3173,6 +3173,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.SuspendFunctionOnCoroutineScopeInspection"
displayName="Ambiguous coroutineContext due to CoroutineScope receiver of suspend function"
groupPath="Kotlin"
groupName="Probable bugs"
enabledByDefault="true"
level="WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -0,0 +1,17 @@
<html>
<body>
This inspection reports calls & accesses of <b>CoroutineScope</b> extensions or members
inside suspend functions with <b>CoroutineScope</b> receiver.
Both suspend functions and <b>CoroutineScope</b> members & extensions have access to <b>coroutineContext</b>.
When some function is simultaneously suspend <b>and</b> has <b>CoroutineScope</b> receiver,
it has ambiguous access to <b>CoroutineContext</b>:
first via <b>kotlin.coroutines.coroutineContext</b> and second via <b>CoroutineScope.coroutineContext</b>,
and two these contexts are different in general case.
So when we call some <b>CoroutineScope</b> extension or access <b>coroutineContext</b> from such a function,
it's unclear which from these two context do we have in mind.
Normal ways to fix this are to wrap suspicious call inside <b>coroutineScope { ... }</b> or
to get rid of <b>CoroutineScope</b> function receiver.
</body>
</html>
@@ -36,7 +36,6 @@ import org.jetbrains.kotlin.idea.refactoring.changeSignature.modify
import org.jetbrains.kotlin.idea.refactoring.changeSignature.runChangeSignature
import org.jetbrains.kotlin.idea.refactoring.explicateAsTextForReceiver
import org.jetbrains.kotlin.idea.refactoring.getThisLabelName
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.getThisReceiverOwner
import org.jetbrains.kotlin.lexer.KtTokens
@@ -119,45 +118,53 @@ class UnusedReceiverParameterInspection : AbstractKotlinInspection() {
receiverTypeReference,
KotlinBundle.message("unused.receiver.parameter"),
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
MyQuickFix(inSameClass)
RemoveReceiverFix(inSameClass)
)
}
}
}
private class MyQuickFix(private val inSameClass: Boolean) : LocalQuickFix {
override fun getName(): String = KotlinBundle.message("unused.receiver.parameter.remove")
private fun configureChangeSignature() = object : KotlinChangeSignatureConfiguration {
override fun performSilently(affectedFunctions: Collection<PsiElement>) = true
override fun configure(originalDescriptor: KotlinMethodDescriptor) = originalDescriptor.modify { it.removeParameter(0) }
}
class RemoveReceiverFix(private val inSameClass: Boolean) : LocalQuickFix {
override fun getName(): String = actionName
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement as? KtTypeReference ?: return
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return
val function = element.parent as? KtCallableDeclaration ?: return
val callableDescriptor = function.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? CallableDescriptor ?: return
val typeParameters = RemoveUnusedFunctionParameterFix.typeParameters(element)
if (inSameClass) {
runWriteAction {
val explicateAsTextForReceiver = callableDescriptor.explicateAsTextForReceiver()
function.forEachDescendantOfType<KtThisExpression> {
if (it.text == explicateAsTextForReceiver) it.labelQualifier?.delete()
}
function.setReceiverTypeReference(null)
}
} else {
runChangeSignature(project, callableDescriptor, configureChangeSignature(), element, name)
}
RemoveUnusedFunctionParameterFix.runRemoveUnusedTypeParameters(typeParameters)
apply(element, project, inSameClass)
}
override fun getFamilyName(): String = name
override fun getFamilyName(): String = actionName
override fun startInWriteAction() = false
companion object {
private val actionName = KotlinBundle.message("unused.receiver.parameter.remove")
private fun configureChangeSignature() = object : KotlinChangeSignatureConfiguration {
override fun performSilently(affectedFunctions: Collection<PsiElement>) = true
override fun configure(originalDescriptor: KotlinMethodDescriptor) = originalDescriptor.modify { it.removeParameter(0) }
}
fun apply(element: KtTypeReference, project: Project, inSameClass: Boolean = false) {
val function = element.parent as? KtCallableDeclaration ?: return
val callableDescriptor = function.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? CallableDescriptor ?: return
val typeParameters = RemoveUnusedFunctionParameterFix.typeParameters(element)
if (inSameClass) {
runWriteAction {
val explicateAsTextForReceiver = callableDescriptor.explicateAsTextForReceiver()
function.forEachDescendantOfType<KtThisExpression> {
if (it.text == explicateAsTextForReceiver) it.labelQualifier?.delete()
}
function.setReceiverTypeReference(null)
}
} else {
runChangeSignature(project, callableDescriptor, configureChangeSignature(), element, actionName)
}
RemoveUnusedFunctionParameterFix.runRemoveUnusedTypeParameters(typeParameters)
}
}
}
}
@@ -0,0 +1,216 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.inspections.coroutines
import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInspection.*
import com.intellij.codeInspection.ProblemHighlightType.*
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.inspections.UnusedReceiverParameterInspection
import org.jetbrains.kotlin.idea.intentions.ConvertReceiverToParameterIntention
import org.jetbrains.kotlin.idea.intentions.MoveMemberToCompanionObjectIntention
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.KotlinType
class SuspendFunctionOnCoroutineScopeInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return namedFunctionVisitor(fun(function: KtNamedFunction) {
if (!function.hasModifier(KtTokens.SUSPEND_KEYWORD)) return
if (!function.hasBody()) return
val context = function.analyzeWithContent()
val descriptor = context[BindingContext.FUNCTION, function] ?: return
val (extensionOfCoroutineScope, memberOfCoroutineScope) = with(descriptor) {
extensionReceiverParameter.ofCoroutineScopeType() to dispatchReceiverParameter.ofCoroutineScopeType()
}
if (!extensionOfCoroutineScope && !memberOfCoroutineScope) return
fun DeclarationDescriptor.isReceiverOfAnalyzedFunction(): Boolean {
if (extensionOfCoroutineScope && this == descriptor) return true
if (memberOfCoroutineScope && this == descriptor.containingDeclaration) return true
return false
}
fun checkSuspiciousReceiver(receiver: ReceiverValue, problemExpression: KtExpression) {
when (receiver) {
is ImplicitReceiver -> if (!receiver.declarationDescriptor.isReceiverOfAnalyzedFunction()) return
is ExpressionReceiver -> {
val receiverThisExpression = receiver.expression as? KtThisExpression ?: return
if (receiverThisExpression.getTargetLabel() != null) {
val instanceReference = receiverThisExpression.instanceReference
if (context[BindingContext.REFERENCE_TARGET, instanceReference]?.isReceiverOfAnalyzedFunction() != true) return
}
}
}
val fixes = mutableListOf<LocalQuickFix>()
val reportElement = (problemExpression as? KtCallExpression)?.calleeExpression ?: problemExpression
holder.registerProblem(
reportElement,
MESSAGE,
GENERIC_ERROR_OR_WARNING,
WrapWithCoroutineScopeFix(removeReceiver = false, wrapCallOnly = true)
)
fixes += WrapWithCoroutineScopeFix(removeReceiver = extensionOfCoroutineScope, wrapCallOnly = false)
val file = function.containingKtFile
if (extensionOfCoroutineScope) {
fixes += IntentionWrapper(ConvertReceiverToParameterIntention(), file)
}
if (memberOfCoroutineScope) {
val containingDeclaration = function.containingClassOrObject
if (containingDeclaration is KtClass && !containingDeclaration.isInterface() && function.hasBody()) {
fixes += IntentionWrapper(MoveMemberToCompanionObjectIntention(), file)
}
}
holder.registerProblem(
with(function) { receiverTypeReference ?: nameIdentifier ?: funKeyword ?: this },
MESSAGE,
GENERIC_ERROR_OR_WARNING,
*fixes.toTypedArray()
)
}
function.forEachDescendantOfType(fun(callExpression: KtCallExpression) {
val resolvedCall = callExpression.getResolvedCall(context) ?: return
val extensionReceiverParameter = resolvedCall.resultingDescriptor.extensionReceiverParameter ?: return
if (!extensionReceiverParameter.type.isCoroutineScope()) return
val extensionReceiver = resolvedCall.extensionReceiver ?: return
checkSuspiciousReceiver(extensionReceiver, callExpression)
})
function.forEachDescendantOfType(fun(nameReferenceExpression: KtNameReferenceExpression) {
if (nameReferenceExpression.getReferencedName() != COROUTINE_CONTEXT) return
val resolvedCall = nameReferenceExpression.getResolvedCall(context) ?: return
if (resolvedCall.resultingDescriptor.fqNameSafe.asString() == "$COROUTINE_SCOPE.$COROUTINE_CONTEXT") {
val dispatchReceiver = resolvedCall.dispatchReceiver ?: return
checkSuspiciousReceiver(dispatchReceiver, nameReferenceExpression)
}
})
})
}
private class WrapWithCoroutineScopeFix(
private val removeReceiver: Boolean,
private val wrapCallOnly: Boolean
) : LocalQuickFix {
override fun getFamilyName(): String = "Wrap with coroutineScope"
override fun getName(): String =
when {
removeReceiver && !wrapCallOnly -> "Remove receiver & wrap with 'coroutineScope { ... }'"
wrapCallOnly -> "Wrap call with 'coroutineScope { ... }'"
else -> "Wrap function body with 'coroutineScope { ... }'"
}
override fun startInWriteAction() = false
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val problemElement = descriptor.psiElement ?: return
val function = problemElement.getNonStrictParentOfType<KtNamedFunction>() ?: return
val functionDescriptor = function.resolveToDescriptorIfAny()
if (!FileModificationService.getInstance().preparePsiElementForWrite(function)) return
val bodyExpression = function.bodyExpression
fun getExpressionToWrapCall(): KtExpression? {
var result = problemElement as? KtExpression ?: return null
while (result.parent is KtQualifiedExpression || result.parent is KtCallExpression) {
result = result.parent as KtExpression
}
return result
}
var expressionToWrap = when {
wrapCallOnly -> getExpressionToWrapCall()
else -> bodyExpression
} ?: return
if (functionDescriptor?.extensionReceiverParameter.ofCoroutineScopeType()) {
val context = function.analyzeWithContent()
expressionToWrap.forEachDescendantOfType<KtDotQualifiedExpression> {
val receiverExpression = it.receiverExpression as? KtThisExpression
val selectorExpression = it.selectorExpression
if (receiverExpression?.getTargetLabel() != null && selectorExpression != null) {
if (context[BindingContext.REFERENCE_TARGET, receiverExpression.instanceReference] == functionDescriptor) {
if (it === expressionToWrap) {
expressionToWrap = it.replaced(selectorExpression)
} else {
it.replace(selectorExpression)
}
}
}
}
}
val factory = KtPsiFactory(function)
val blockExpression = function.bodyBlockExpression
project.executeWriteCommand(name, this) {
val result = when {
expressionToWrap != bodyExpression -> expressionToWrap.replaced(
factory.createExpressionByPattern("$COROUTINE_SCOPE_WRAPPER { $0 }", expressionToWrap)
)
blockExpression == null -> bodyExpression.replaced(
factory.createExpressionByPattern("$COROUTINE_SCOPE_WRAPPER { $0 }", bodyExpression)
)
else -> {
val bodyText = buildString {
for (statement in blockExpression.statements) {
append(statement.text)
}
}
blockExpression.replaced(
factory.createBlock("$COROUTINE_SCOPE_WRAPPER { $bodyText }")
)
}
}
val reformatted = CodeStyleManager.getInstance(project).reformat(result)
ShortenReferences.DEFAULT.process(reformatted as KtElement)
}
val receiverTypeReference = function.receiverTypeReference
if (removeReceiver && !wrapCallOnly && receiverTypeReference != null) {
UnusedReceiverParameterInspection.RemoveReceiverFix.apply(receiverTypeReference, project)
}
}
}
companion object {
private const val COROUTINE_SCOPE = "kotlinx.coroutines.CoroutineScope"
private const val COROUTINE_SCOPE_WRAPPER = "kotlinx.coroutines.coroutineScope"
private const val COROUTINE_CONTEXT = "coroutineContext"
private const val MESSAGE = "Ambiguous coroutineContext due to CoroutineScope receiver of suspend function"
private fun KotlinType.isCoroutineScope(): Boolean =
constructor.declarationDescriptor?.fqNameSafe?.asString() == COROUTINE_SCOPE
private fun ReceiverParameterDescriptor?.ofCoroutineScopeType(): Boolean {
if (this == null) return false
if (type.isCoroutineScope()) return true
return type.constructor.supertypes.reversed().any { it.isCoroutineScope() }
}
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.coroutines.SuspendFunctionOnCoroutineScopeInspection
@@ -0,0 +1,12 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun doSomething() {}
fun foo() {
val scope = object : CoroutineScope {
suspend fun foo() {
<caret>async { doSomething() }
}
}
}
@@ -0,0 +1,13 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun doSomething() {}
fun foo() {
val scope = object : CoroutineScope {
suspend fun foo() {
coroutineScope { async { doSomething() } }
}
}
}
@@ -0,0 +1,8 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() = 42
suspend fun <caret>CoroutineScope.foo() = async { calcSomething() }
@@ -0,0 +1,8 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() = 42
suspend fun foo() = coroutineScope { async { calcSomething() } }
@@ -0,0 +1,15 @@
// FIX: Wrap call with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() {}
suspend fun CoroutineScope.foo() {
async {
calcSomething()
}
<caret>async {
calcSomething()
}
}
@@ -0,0 +1,18 @@
// FIX: Wrap call with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() {}
suspend fun CoroutineScope.foo() {
async {
calcSomething()
}
coroutineScope {
async {
calcSomething()
}
}
}
@@ -0,0 +1,10 @@
// FIX: Wrap call with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineContext
fun use(context: CoroutineContext) {}
suspend fun CoroutineScope.foo() {
use(<caret>coroutineContext)
}
@@ -0,0 +1,11 @@
// FIX: Wrap call with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineContext
import kotlinx.coroutines.coroutineScope
fun use(context: CoroutineContext) {}
suspend fun CoroutineScope.foo() {
use(coroutineScope { coroutineContext })
}
@@ -0,0 +1,10 @@
// FIX: Wrap call with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineContext
fun use(context: CoroutineContext) {}
suspend fun CoroutineScope.foo() {
use(this@foo.<caret>coroutineContext)
}
@@ -0,0 +1,11 @@
// FIX: Wrap call with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineContext
import kotlinx.coroutines.coroutineScope
fun use(context: CoroutineContext) {}
suspend fun CoroutineScope.foo() {
use(coroutineScope { coroutineContext })
}
@@ -0,0 +1,10 @@
// FIX: Wrap call with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineContext
fun use(context: CoroutineContext) {}
suspend fun CoroutineScope.foo() {
use(this.<caret>coroutineContext)
}
@@ -0,0 +1,11 @@
// FIX: Wrap call with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineContext
import kotlinx.coroutines.coroutineScope
fun use(context: CoroutineContext) {}
suspend fun CoroutineScope.foo() {
use(coroutineScope { this.coroutineContext })
}
@@ -0,0 +1,8 @@
// FIX: Convert receiver to parameter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() = 42
suspend fun <caret>CoroutineScope.foo() = async { calcSomething() }
@@ -0,0 +1,8 @@
// FIX: Convert receiver to parameter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() = 42
suspend fun foo(coroutineScope: CoroutineScope) = coroutineScope.async { calcSomething() }
@@ -0,0 +1,46 @@
package kotlinx.coroutines
interface Deferred<T> {
suspend fun await(): T
}
interface CoroutineContext
object Dispatchers {
object Default : CoroutineContext
}
enum class CoroutineStart {
DEFAULT,
LAZY,
ATOMIC,
UNDISPATCHED
}
interface CoroutineScope {
val coroutineContext: CoroutineContext get() = Dispatchers.Default
}
object GlobalScope : CoroutineScope
fun <T> CoroutineScope.async(
context: CoroutineContext = Dispatchers.Default,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
TODO()
}
suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T {
TODO()
}
suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R = GlobalScope.block()
operator fun CoroutineContext.plus(other: CoroutineContext): CoroutineContext {
TODO()
}
@@ -0,0 +1,10 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() = 42
suspend fun CoroutineScope.foo() {
val deferred = <caret>async {
calcSomething()
}
}
@@ -0,0 +1,13 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() = 42
suspend fun CoroutineScope.foo() {
val deferred = coroutineScope {
async {
calcSomething()
}
}
}
@@ -0,0 +1,15 @@
// PROBLEM: none
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineContext
fun use(context: CoroutineContext) {}
abstract class MyCoroutineScope : CoroutineScope {
inner class Inner {
suspend fun foo() {
// Does not work yet (could work)
use(<caret>coroutineContext)
}
}
}
@@ -0,0 +1,21 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() {}
suspend fun <caret>CoroutineScope.foo() {
async {
calcSomething()
calcSomething() // ...
/* ... */
calcSomething() /* ... */
calcSomething()
if (true) // ...
calcSomething()
}
}
@@ -0,0 +1,23 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() {}
suspend fun foo() {
coroutineScope {
async {
calcSomething()
calcSomething() // ...
/* ... */
calcSomething() /* ... */
calcSomething()
if (true) // ...
calcSomething()
}
}
}
@@ -0,0 +1,10 @@
// FIX: Move to companion object
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() = 42
abstract class MyCoroutineScope : CoroutineScope {
suspend fun <caret>foo() = async { calcSomething() }
}
@@ -0,0 +1,12 @@
// FIX: Move to companion object
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() = 42
abstract class MyCoroutineScope : CoroutineScope {
companion object {
suspend fun foo(myCoroutineScope: MyCoroutineScope) = myCoroutineScope.async { calcSomething() }
}
}
@@ -0,0 +1,8 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() = 42
object MyCoroutineScope : CoroutineScope {
suspend fun <caret>foo() = async { calcSomething() }
}
@@ -0,0 +1,9 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() = 42
object MyCoroutineScope : CoroutineScope {
suspend fun foo() = coroutineScope { async { calcSomething() } }
}
@@ -0,0 +1,12 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() {}
suspend fun <caret>CoroutineScope.foo() {
async {
calcSomething()
}
}
@@ -0,0 +1,14 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() {}
suspend fun foo() {
coroutineScope {
async {
calcSomething()
}
}
}
@@ -0,0 +1,12 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() {}
suspend fun <caret>CoroutineScope.foo() {
this.async {
calcSomething()
}
}
@@ -0,0 +1,14 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() {}
suspend fun foo() {
coroutineScope {
this.async {
calcSomething()
}
}
}
@@ -0,0 +1,10 @@
// FIX: Wrap function body with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() = 42
abstract class MyCoroutineScope : CoroutineScope {
suspend fun <caret>foo() = this.async { calcSomething() }
}
@@ -0,0 +1,11 @@
// FIX: Wrap function body with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() = 42
abstract class MyCoroutineScope : CoroutineScope {
suspend fun foo() = coroutineScope { this.async { calcSomething() } }
}
@@ -0,0 +1,14 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() {}
class My {
suspend fun <caret>CoroutineScope.foo() {
this@foo.async {
calcSomething()
}
}
}
@@ -0,0 +1,16 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() {}
class My {
suspend fun foo() {
coroutineScope {
async {
calcSomething()
}
}
}
}
@@ -0,0 +1,15 @@
// PROBLEM: none
// DISABLE-ERRORS
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() {}
class My {
suspend fun <caret>CoroutineScope.foo() {
this@My.async {
calcSomething()
}
}
}
@@ -0,0 +1,17 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
fun calcSomething() {}
suspend fun <caret>CoroutineScope.foo() {
async {
calcSomething()
}
}
suspend fun test() {
GlobalScope.foo()
}
@@ -0,0 +1,18 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() {}
suspend fun foo() {
coroutineScope {
async {
calcSomething()
}
}
}
suspend fun test() {
foo()
}
@@ -0,0 +1,12 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
fun calcSomething() = 42
suspend fun <caret>CoroutineScope.foo() {
val deferred = async {
calcSomething()
}
}
@@ -0,0 +1,14 @@
// FIX: Remove receiver & wrap with 'coroutineScope { ... }'
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
fun calcSomething() = 42
suspend fun foo() {
coroutineScope {
val deferred = async {
calcSomething()
}
}
}
@@ -2239,6 +2239,114 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/coroutines/redundantRunCatching/simple.kt");
}
}
@TestMetadata("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class SuspendFunctionOnCoroutineScope extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInSuspendFunctionOnCoroutineScope() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("anonymous.kt")
public void testAnonymous() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/anonymous.kt");
}
@TestMetadata("body.kt")
public void testBody() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/body.kt");
}
@TestMetadata("call.kt")
public void testCall() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/call.kt");
}
@TestMetadata("context.kt")
public void testContext() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/context.kt");
}
@TestMetadata("contextWithLabeledThis.kt")
public void testContextWithLabeledThis() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithLabeledThis.kt");
}
@TestMetadata("contextWithThis.kt")
public void testContextWithThis() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/contextWithThis.kt");
}
@TestMetadata("convertReceiver.kt")
public void testConvertReceiver() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/convertReceiver.kt");
}
@TestMetadata("declaration.kt")
public void testDeclaration() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/declaration.kt");
}
@TestMetadata("inner.kt")
public void testInner() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/inner.kt");
}
@TestMetadata("lots.kt")
public void testLots() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/lots.kt");
}
@TestMetadata("member.kt")
public void testMember() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/member.kt");
}
@TestMetadata("object.kt")
public void testObject() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/object.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/simple.kt");
}
@TestMetadata("this.kt")
public void testThis() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/this.kt");
}
@TestMetadata("thisInMember.kt")
public void testThisInMember() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisInMember.kt");
}
@TestMetadata("thisLabeled.kt")
public void testThisLabeled() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisLabeled.kt");
}
@TestMetadata("thisWrongLabeled.kt")
public void testThisWrongLabeled() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/thisWrongLabeled.kt");
}
@TestMetadata("useSite.kt")
public void testUseSite() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/useSite.kt");
}
@TestMetadata("withVariable.kt")
public void testWithVariable() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/suspendFunctionOnCoroutineScope/withVariable.kt");
}
}
}
@TestMetadata("idea/testData/inspectionsLocal/delegationToVarProperty")