Add "Ambiguous context due to scope receiver in suspend fun" inspection
#KT-28696 Fixed
This commit is contained in:
@@ -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>
|
||||
+33
-26
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+216
@@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.coroutines.SuspendFunctionOnCoroutineScopeInspection
|
||||
+12
@@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+13
@@ -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() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -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() }
|
||||
+8
@@ -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() } }
|
||||
+15
@@ -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()
|
||||
}
|
||||
}
|
||||
+18
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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)
|
||||
}
|
||||
+11
@@ -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 })
|
||||
}
|
||||
Vendored
+10
@@ -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)
|
||||
}
|
||||
+11
@@ -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 })
|
||||
}
|
||||
Vendored
+10
@@ -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)
|
||||
}
|
||||
Vendored
+11
@@ -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 })
|
||||
}
|
||||
Vendored
+8
@@ -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() }
|
||||
Vendored
+8
@@ -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() }
|
||||
Vendored
+46
@@ -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()
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
fun calcSomething() = 42
|
||||
|
||||
suspend fun CoroutineScope.foo() {
|
||||
val deferred = <caret>async {
|
||||
calcSomething()
|
||||
}
|
||||
}
|
||||
Vendored
+13
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -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()
|
||||
}
|
||||
}
|
||||
+23
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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() }
|
||||
}
|
||||
+12
@@ -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() }
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
fun calcSomething() = 42
|
||||
|
||||
object MyCoroutineScope : CoroutineScope {
|
||||
suspend fun <caret>foo() = async { calcSomething() }
|
||||
}
|
||||
+9
@@ -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() } }
|
||||
}
|
||||
+12
@@ -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()
|
||||
}
|
||||
}
|
||||
+14
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -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()
|
||||
}
|
||||
}
|
||||
+14
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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() }
|
||||
}
|
||||
Vendored
+11
@@ -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() } }
|
||||
}
|
||||
+14
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+16
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+15
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -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()
|
||||
}
|
||||
+18
@@ -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()
|
||||
}
|
||||
+12
@@ -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()
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+108
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user