Introduce DeferredIsResultInspection #KT-25620 Fixed

This commit is contained in:
Mikhail Glukhikh
2018-11-23 18:09:25 +03:00
parent f4aad70b36
commit ca87e53f04
20 changed files with 440 additions and 161 deletions
@@ -3136,6 +3136,15 @@
language="kotlin" language="kotlin"
/> />
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.DeferredIsResultInspection"
displayName="Function returning Deferred directly"
groupPath="Kotlin"
groupName="Style issues"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/> <referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/> <fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -0,0 +1,8 @@
<html>
<body>
This inspection reports functions with <b>kotlinx.coroutines.Deferred</b> result.
Functions which use <b>Deferred</b> as return type should have a name with suffix <b>Async</b>.
Otherwise, it's recommended to turn a function into a suspend function and unwrap <b>Deferred</b>.
</body>
</html>
@@ -0,0 +1,86 @@
/*
* 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.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
abstract class AbstractIsResultInspection(
private val typeShortName: String,
private val typeFullName: String,
private val allowedSuffix: String,
private val allowedNames: Set<String>,
private val suggestedFunctionNameToCall: String,
private val shouldMakeSuspend: Boolean = false,
private val simplify: (KtExpression) -> Unit = {}
) : AbstractKotlinInspection() {
protected fun analyzeFunction(function: KtFunction, toReport: PsiElement, holder: ProblemsHolder) {
if (function is KtConstructor<*>) return
val returnTypeText = function.getReturnTypeReference()?.text
if (returnTypeText != null && typeShortName !in returnTypeText) return
val name = (function as? KtNamedFunction)?.nameAsName?.asString()
if (name in allowedNames) return
if (function is KtNamedFunction) {
val receiverTypeReference = function.receiverTypeReference
// Filter given type extensions
if (receiverTypeReference != null && typeShortName in receiverTypeReference.text) return
}
if (function is KtFunctionLiteral || returnTypeText == null) {
// Heuristics to save performance: check if something creates given type in function text
val text = function.bodyExpression?.text
if (text != null && allowedNames.none { it in text } && typeShortName !in text && allowedSuffix !in text) return
}
val descriptor = function.resolveToDescriptorIfAny() as? FunctionDescriptor ?: return
val returnType = descriptor.returnType ?: return
val returnTypeClass = returnType.constructor.declarationDescriptor as? ClassDescriptor ?: return
if (returnTypeClass.fqNameSafe.asString() != typeFullName) return
if (name != null && name.endsWith(allowedSuffix)) {
analyzeFunctionWithAllowedSuffix(name, descriptor, toReport, holder)
} else {
holder.registerProblem(
toReport,
"Function returning $typeShortName with a name that does not end with $allowedSuffix",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
*listOfNotNull(
name?.let { RenameToFix("$it$allowedSuffix") },
AddCallOrUnwrapTypeFix(
withBody = function.hasBody(),
functionName = suggestedFunctionNameToCall,
typeName = typeShortName,
shouldMakeSuspend = shouldMakeSuspend,
simplify = simplify
)
).toTypedArray()
)
}
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : KtVisitorVoid() {
override fun visitNamedFunction(function: KtNamedFunction) {
analyzeFunction(function, function.nameIdentifier ?: function.funKeyword ?: function, holder)
}
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
analyzeFunction(lambdaExpression.functionLiteral, lambdaExpression.functionLiteral.lBrace, holder)
}
}
}
open fun analyzeFunctionWithAllowedSuffix(name: String, descriptor: FunctionDescriptor, toReport: PsiElement, holder: ProblemsHolder) {}
}
@@ -0,0 +1,71 @@
/*
* 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.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.core.setType
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
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.bindingContextUtil.getTargetFunctionDescriptor
class AddCallOrUnwrapTypeFix(
val withBody: Boolean,
val functionName: String,
val typeName: String,
val shouldMakeSuspend: Boolean,
val simplify: (KtExpression) -> Unit
) : LocalQuickFix {
override fun getName(): String =
if (withBody) "Add '.$functionName()' to function result (breaks use-sites!)"
else "Unwrap '$typeName' return type (breaks use-sites!)"
override fun getFamilyName(): String = name
private fun KtExpression.addCallAndSimplify(factory: KtPsiFactory) {
val newCallExpression = factory.createExpressionByPattern("$0.$functionName()", this)
val result = replaced(newCallExpression)
simplify(result)
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val function = descriptor.psiElement.getNonStrictParentOfType<KtFunction>() ?: return
val returnTypeReference = function.getReturnTypeReference()
val context = function.analyzeWithContent()
val functionDescriptor = context[BindingContext.FUNCTION, function] ?: return
if (shouldMakeSuspend) {
function.addModifier(KtTokens.SUSPEND_KEYWORD)
}
if (returnTypeReference != null) {
val returnType = functionDescriptor.returnType ?: return
val returnTypeArgument = returnType.arguments.firstOrNull()?.type ?: return
function.setType(returnTypeArgument)
}
if (!withBody) return
val factory = KtPsiFactory(project)
val bodyExpression = function.bodyExpression
bodyExpression?.forEachDescendantOfType<KtReturnExpression> {
if (it.getTargetFunctionDescriptor(context) == functionDescriptor) {
it.returnedExpression?.addCallAndSimplify(factory)
}
}
if (function is KtFunctionLiteral) {
val lastStatement = function.bodyExpression?.statements?.lastOrNull()
if (lastStatement != null && lastStatement !is KtReturnExpression) {
lastStatement.addCallAndSimplify(factory)
}
} else if (!function.hasBlockBody()) {
bodyExpression?.addCallAndSimplify(factory)
}
}
}
@@ -0,0 +1,24 @@
/*
* 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 org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtQualifiedExpression
class DeferredIsResultInspection : AbstractIsResultInspection(
typeShortName = "Deferred",
typeFullName = "kotlinx.coroutines.Deferred",
allowedSuffix = "Async",
allowedNames = setOf("async"),
suggestedFunctionNameToCall = "await",
shouldMakeSuspend = true,
simplify = fun(expression: KtExpression) {
val qualifiedExpression = expression as? KtQualifiedExpression ?: return
val redundantAsyncInspection = RedundantAsyncInspection()
val conversion = redundantAsyncInspection.generateConversion(qualifiedExpression) ?: return
redundantAsyncInspection.generateFix(conversion).apply(qualifiedExpression)
}
)
@@ -5,35 +5,25 @@
package org.jetbrains.kotlin.idea.inspections.coroutines package org.jetbrains.kotlin.idea.inspections.coroutines
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.refactoring.rename.RenameProcessor
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.setType
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
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.bindingContextUtil.getTargetFunctionDescriptor
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
class DirectUseOfResultTypeInspection : AbstractKotlinInspection() { class DirectUseOfResultTypeInspection : AbstractIsResultInspection(
typeShortName = SHORT_NAME,
typeFullName = "kotlin.Result",
allowedSuffix = CATCHING,
allowedNames = setOf("success", "failure", "runCatching"),
suggestedFunctionNameToCall = "getOrThrow"
) {
private fun MemberScope.hasCorrespondingNonCatchingFunction( private fun MemberScope.hasCorrespondingNonCatchingFunction(
nameWithoutCatching: String, nameWithoutCatching: String,
@@ -76,130 +66,27 @@ class DirectUseOfResultTypeInspection : AbstractKotlinInspection() {
return false return false
} }
private fun analyzeFunction(function: KtFunction, toReport: PsiElement, holder: ProblemsHolder) { override fun analyzeFunctionWithAllowedSuffix(
if (function is KtConstructor<*>) return name: String,
val returnTypeText = function.getReturnTypeReference()?.text descriptor: FunctionDescriptor,
if (returnTypeText != null && SHORT_NAME !in returnTypeText) return toReport: PsiElement,
val name = (function as? KtNamedFunction)?.nameAsName?.asString() holder: ProblemsHolder
// Filter names from stdlib ) {
if (name in ALLOWED_NAMES) return
if (function is KtNamedFunction) {
val receiverTypeReference = function.receiverTypeReference
// Filter Result extensions
if (receiverTypeReference != null && SHORT_NAME in receiverTypeReference.text) return
}
if (function is KtFunctionLiteral || returnTypeText == null) {
// Heuristics to save performance
val text = function.bodyExpression?.text
// Check there is something creating Result in function text
if (text != null && ALLOWED_NAMES.none { it in text } && SHORT_NAME !in text && CATCHING !in text) return
}
val descriptor = function.resolveToDescriptorIfAny() as? FunctionDescriptor ?: return
val returnType = descriptor.returnType ?: return val returnType = descriptor.returnType ?: return
val returnTypeClass = returnType.constructor.declarationDescriptor as? ClassDescriptor ?: return val nameWithoutCatching = name.substringBeforeLast(CATCHING)
if (returnTypeClass.fqNameSafe.asString() != FULL_NAME) return if (descriptor.hasCorrespondingNonCatchingFunction(returnType, nameWithoutCatching) == false) {
val returnTypeArgument = returnType.arguments.firstOrNull()?.type
if (name != null && name.endsWith(CATCHING)) { val typeName = returnTypeArgument?.constructor?.declarationDescriptor?.name?.asString() ?: "T"
val nameWithoutCatching = name.substringBeforeLast(CATCHING)
if (descriptor.hasCorrespondingNonCatchingFunction(returnType, nameWithoutCatching) == false) {
val returnTypeArgument = returnType.arguments.firstOrNull()?.type
val typeName = returnTypeArgument?.constructor?.declarationDescriptor?.name?.asString() ?: "T"
holder.registerProblem(
toReport,
"Function '$name' returning '$SHORT_NAME<$typeName>' without the corresponding " +
"function '$nameWithoutCatching' returning '$typeName'",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING
)
}
} else {
holder.registerProblem( holder.registerProblem(
toReport, toReport,
"Function returning $SHORT_NAME with a name that does not end with $CATCHING", "Function '$name' returning '$SHORT_NAME<$typeName>' without the corresponding " +
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, "function '$nameWithoutCatching' returning '$typeName'",
*listOfNotNull( ProblemHighlightType.GENERIC_ERROR_OR_WARNING
AddGetOrThrowFix(withBody = function.hasBody()),
name?.let { RenameToCatchingFix("$it$CATCHING") }
).toTypedArray()
) )
} }
} }
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { private const val SHORT_NAME = "Result"
return object : KtVisitorVoid() {
override fun visitNamedFunction(function: KtNamedFunction) {
analyzeFunction(function, function.nameIdentifier ?: function.funKeyword ?: function, holder)
}
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) { private const val CATCHING = "Catching"
analyzeFunction(lambdaExpression.functionLiteral, lambdaExpression.functionLiteral.lBrace, holder)
}
}
}
private class AddGetOrThrowFix(val withBody: Boolean) : LocalQuickFix {
override fun getName(): String =
if (withBody) "Add '.$GET_OR_THROW()' to function result (breaks use-sites!)"
else "Unwrap '$SHORT_NAME' return type (breaks use-sites!)"
override fun getFamilyName(): String = name
private fun KtExpression.addGetOrThrow(factory: KtPsiFactory) {
val getOrThrowExpression = factory.createExpressionByPattern("$0.$GET_OR_THROW()", this)
replace(getOrThrowExpression)
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val function = descriptor.psiElement.getNonStrictParentOfType<KtFunction>() ?: return
val returnTypeReference = function.getReturnTypeReference()
val context = function.analyze()
val functionDescriptor = context[BindingContext.FUNCTION, function] ?: return
if (returnTypeReference != null) {
val returnType = functionDescriptor.returnType ?: return
val returnTypeArgument = returnType.arguments.firstOrNull()?.type ?: return
function.setType(returnTypeArgument)
}
if (!withBody) return
val factory = KtPsiFactory(project)
val bodyExpression = function.bodyExpression
bodyExpression?.forEachDescendantOfType<KtReturnExpression> {
if (it.getTargetFunctionDescriptor(context) == functionDescriptor) {
it.returnedExpression?.addGetOrThrow(factory)
}
}
if (function is KtFunctionLiteral) {
val lastStatement = function.bodyExpression?.statements?.lastOrNull()
if (lastStatement != null && lastStatement !is KtReturnExpression) {
lastStatement.addGetOrThrow(factory)
}
} else if (!function.hasBlockBody()) {
bodyExpression?.addGetOrThrow(factory)
}
}
}
private class RenameToCatchingFix(val newName: String) : LocalQuickFix {
override fun getName(): String = "Rename to '$newName'"
override fun getFamilyName(): String = name
override fun startInWriteAction(): Boolean = false
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val function = descriptor.psiElement.getNonStrictParentOfType<KtFunction>() ?: return
RenameProcessor(project, function, newName, false, false).run()
}
}
companion object {
private const val SHORT_NAME = "Result"
private const val FULL_NAME = "kotlin.$SHORT_NAME"
private const val CATCHING = "Catching"
private const val GET_OR_THROW = "getOrThrow"
private val ALLOWED_NAMES = setOf("success", "failure", "runCatching")
}
}
@@ -23,7 +23,7 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class RedundantAsyncInspection : AbstractCallChainChecker() { class RedundantAsyncInspection : AbstractCallChainChecker() {
private fun generateConversion(expression: KtQualifiedExpression): Conversion? { fun generateConversion(expression: KtQualifiedExpression): Conversion? {
var defaultContext: Boolean? = null var defaultContext: Boolean? = null
var defaultStart: Boolean? = null var defaultStart: Boolean? = null
@@ -63,38 +63,42 @@ class RedundantAsyncInspection : AbstractCallChainChecker() {
return conversion return conversion
} }
fun generateFix(conversion: Conversion): SimplifyCallChainFix {
val contextArgument = conversion.additionalArgument
return SimplifyCallChainFix(conversion, removeReceiverOfFirstCall = true, runOptimizeImports = true) { callExpression ->
if (contextArgument != null) {
val call = callExpression.resolveToCall()
if (call != null) {
for (argument in callExpression.valueArguments) {
val mapping = call.getArgumentMapping(argument) as? ArgumentMatch ?: continue
if (mapping.valueParameter.name.asString() == CONTEXT_ARGUMENT_NAME) {
val name = argument.getArgumentName()?.asName
val expressionText = contextArgument + " + " + argument.getArgumentExpression()!!.text
argument.replace(
if (name == null) {
createArgument(expressionText)
} else {
createArgument("$name = $expressionText")
}
)
break
}
}
}
}
}
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) =
qualifiedExpressionVisitor(fun(expression) { qualifiedExpressionVisitor(fun(expression) {
val conversion = generateConversion(expression) ?: return val conversion = generateConversion(expression) ?: return
val contextArgument = conversion.additionalArgument
val descriptor = holder.manager.createProblemDescriptor( val descriptor = holder.manager.createProblemDescriptor(
expression, expression,
expression.firstCalleeExpression()!!.textRange.shiftRight(-expression.startOffset), expression.firstCalleeExpression()!!.textRange.shiftRight(-expression.startOffset),
"Redundant 'async' call may be reduced to '${conversion.replacement}'", "Redundant 'async' call may be reduced to '${conversion.replacement}'",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
isOnTheFly, isOnTheFly,
SimplifyCallChainFix(conversion, removeReceiverOfFirstCall = true, runOptimizeImports = true) { callExpression -> generateFix(conversion)
if (contextArgument != null) {
val call = callExpression.resolveToCall()
if (call != null) {
for (argument in callExpression.valueArguments) {
val mapping = call.getArgumentMapping(argument) as? ArgumentMatch ?: continue
if (mapping.valueParameter.name.asString() == CONTEXT_ARGUMENT_NAME) {
val name = argument.getArgumentName()?.asName
val expressionText = contextArgument + " + " + argument.getArgumentExpression()!!.text
argument.replace(
if (name == null) {
createArgument(expressionText)
} else {
createArgument("$name = $expressionText")
}
)
break
}
}
}
}
}
) )
holder.registerProblem(descriptor) holder.registerProblem(descriptor)
}) })
@@ -0,0 +1,26 @@
/*
* 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.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import com.intellij.refactoring.rename.RenameProcessor
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
class RenameToFix(val newName: String) : LocalQuickFix {
override fun getName(): String = "Rename to '$newName'"
override fun getFamilyName(): String = name
override fun startInWriteAction(): Boolean = false
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val function = descriptor.psiElement.getNonStrictParentOfType<KtFunction>() ?: return
RenameProcessor(project, function, newName, false, false).run()
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.coroutines.DeferredIsResultInspection
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// FIX: Unwrap 'Deferred' return type (breaks use-sites!)
package kotlinx.coroutines
interface My {
fun <caret>function(): Deferred<Int>
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// FIX: Unwrap 'Deferred' return type (breaks use-sites!)
package kotlinx.coroutines
interface My {
suspend fun <caret>function(): Int
}
@@ -0,0 +1,18 @@
// WITH_RUNTIME
// FIX: Add '.await()' to function result (breaks use-sites!)
package kotlinx.coroutines
// TODO: this test contains strange formatting bug (see 0 -> return in *.after file). To be fixed.
fun <caret>myFunction(context: CoroutineContext, switch: Int): Deferred<Int> {
with (GlobalScope) {
when (switch) {
0 -> return async {
val x = 123
x * x
}
1 -> return async(context) { -1 }
else -> return async() { 9 }
}
}
}
@@ -0,0 +1,18 @@
// WITH_RUNTIME
// FIX: Add '.await()' to function result (breaks use-sites!)
package kotlinx.coroutines
// TODO: this test contains strange formatting bug (see 0 -> return in *.after file). To be fixed.
suspend fun myFunction(context: CoroutineContext, switch: Int): Int {
with (GlobalScope) {
when (switch) {
0 -> return withContext(Dispatchers.Default) {
val x = 123
x * x
}
1 -> return withContext(context) { -1 }
else -> return withContext(Dispatchers.Default) { 9 }
}
}
}
@@ -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,8 @@
// WITH_RUNTIME
// FIX: Rename to 'myFunctionAsync'
package kotlinx.coroutines
fun <caret>myFunction(): Deferred<Int> {
return GlobalScope.async { 42 }
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// FIX: Rename to 'myFunctionAsync'
package kotlinx.coroutines
fun myFunctionAsync(): Deferred<Int> {
return GlobalScope.async { 42 }
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// FIX: Add '.await()' to function result (breaks use-sites!)
package kotlinx.coroutines
fun <caret>myFunction(): Deferred<Int> {
return GlobalScope.async { 42 }
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// FIX: Add '.await()' to function result (breaks use-sites!)
package kotlinx.coroutines
suspend fun myFunction(): Int {
return withContext(Dispatchers.Default) { 42 }
}
@@ -34,7 +34,7 @@ fun <T> CoroutineScope.async(
suspend fun <T> withContext( suspend fun <T> withContext(
context: CoroutineContext, context: CoroutineContext,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
) { ): T {
TODO() TODO()
} }
@@ -1990,6 +1990,39 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/coroutines"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/coroutines"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
} }
@TestMetadata("idea/testData/inspectionsLocal/coroutines/deferredIsResult")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class DeferredIsResult extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
@TestMetadata("abstract.kt")
public void testAbstract() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/deferredIsResult/abstract.kt");
}
public void testAllFilesPresentInDeferredIsResult() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/coroutines/deferredIsResult"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("complex.kt")
public void testComplex() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/deferredIsResult/complex.kt");
}
@TestMetadata("rename.kt")
public void testRename() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/deferredIsResult/rename.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("idea/testData/inspectionsLocal/coroutines/deferredIsResult/simple.kt");
}
}
@TestMetadata("idea/testData/inspectionsLocal/coroutines/directUseOfResultType") @TestMetadata("idea/testData/inspectionsLocal/coroutines/directUseOfResultType")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)