diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml
index e9826450f15..ac4b83c3e7e 100644
--- a/idea/resources/META-INF/plugin-common.xml
+++ b/idea/resources/META-INF/plugin-common.xml
@@ -3136,6 +3136,15 @@
language="kotlin"
/>
+
+
diff --git a/idea/resources/inspectionDescriptions/DeferredIsResult.html b/idea/resources/inspectionDescriptions/DeferredIsResult.html
new file mode 100644
index 00000000000..e878dbd543e
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/DeferredIsResult.html
@@ -0,0 +1,8 @@
+
+
+This inspection reports functions with kotlinx.coroutines.Deferred result.
+
+Functions which use Deferred as return type should have a name with suffix Async.
+Otherwise, it's recommended to turn a function into a suspend function and unwrap Deferred.
+
+
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/AbstractIsResultInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/AbstractIsResultInspection.kt
new file mode 100644
index 00000000000..1961fe5adac
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/AbstractIsResultInspection.kt
@@ -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,
+ 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) {}
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/AddCallOrUnwrapTypeFix.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/AddCallOrUnwrapTypeFix.kt
new file mode 100644
index 00000000000..b7f092bb7a2
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/AddCallOrUnwrapTypeFix.kt
@@ -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() ?: 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 {
+ 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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/DeferredIsResultInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/DeferredIsResultInspection.kt
new file mode 100644
index 00000000000..f7898e6a7e6
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/DeferredIsResultInspection.kt
@@ -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)
+ }
+)
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/DirectUseOfResultTypeInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/DirectUseOfResultTypeInspection.kt
index 068bc8d0402..685a63a561c 100644
--- a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/DirectUseOfResultTypeInspection.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/DirectUseOfResultTypeInspection.kt
@@ -5,35 +5,25 @@
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.ProblemsHolder
-import com.intellij.openapi.project.Project
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.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
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.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.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(
nameWithoutCatching: String,
@@ -76,130 +66,27 @@ class DirectUseOfResultTypeInspection : AbstractKotlinInspection() {
return false
}
- private fun analyzeFunction(function: KtFunction, toReport: PsiElement, holder: ProblemsHolder) {
- if (function is KtConstructor<*>) return
- val returnTypeText = function.getReturnTypeReference()?.text
- if (returnTypeText != null && SHORT_NAME !in returnTypeText) return
- val name = (function as? KtNamedFunction)?.nameAsName?.asString()
- // 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
+ override fun analyzeFunctionWithAllowedSuffix(
+ name: String,
+ descriptor: FunctionDescriptor,
+ toReport: PsiElement,
+ holder: ProblemsHolder
+ ) {
val returnType = descriptor.returnType ?: return
- val returnTypeClass = returnType.constructor.declarationDescriptor as? ClassDescriptor ?: return
- if (returnTypeClass.fqNameSafe.asString() != FULL_NAME) return
-
- if (name != null && name.endsWith(CATCHING)) {
- 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 {
+ 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 returning $SHORT_NAME with a name that does not end with $CATCHING",
- ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
- *listOfNotNull(
- AddGetOrThrowFix(withBody = function.hasBody()),
- name?.let { RenameToCatchingFix("$it$CATCHING") }
- ).toTypedArray()
+ "Function '$name' returning '$SHORT_NAME<$typeName>' without the corresponding " +
+ "function '$nameWithoutCatching' returning '$typeName'",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING
)
}
}
+}
- override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
- return object : KtVisitorVoid() {
- override fun visitNamedFunction(function: KtNamedFunction) {
- analyzeFunction(function, function.nameIdentifier ?: function.funKeyword ?: function, holder)
- }
+private const val SHORT_NAME = "Result"
- override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
- 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() ?: 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 {
- 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() ?: 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")
- }
-}
\ No newline at end of file
+private const val CATCHING = "Catching"
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/RedundantAsyncInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/RedundantAsyncInspection.kt
index d5c9418d2b5..d2ad40dc174 100644
--- a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/RedundantAsyncInspection.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/RedundantAsyncInspection.kt
@@ -23,7 +23,7 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class RedundantAsyncInspection : AbstractCallChainChecker() {
- private fun generateConversion(expression: KtQualifiedExpression): Conversion? {
+ fun generateConversion(expression: KtQualifiedExpression): Conversion? {
var defaultContext: Boolean? = null
var defaultStart: Boolean? = null
@@ -63,38 +63,42 @@ class RedundantAsyncInspection : AbstractCallChainChecker() {
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) =
qualifiedExpressionVisitor(fun(expression) {
val conversion = generateConversion(expression) ?: return
- val contextArgument = conversion.additionalArgument
val descriptor = holder.manager.createProblemDescriptor(
expression,
expression.firstCalleeExpression()!!.textRange.shiftRight(-expression.startOffset),
"Redundant 'async' call may be reduced to '${conversion.replacement}'",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
isOnTheFly,
- 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
- }
- }
- }
- }
- }
+ generateFix(conversion)
)
holder.registerProblem(descriptor)
})
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/RenameToFix.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/RenameToFix.kt
new file mode 100644
index 00000000000..82dbc5aaae7
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/coroutines/RenameToFix.kt
@@ -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() ?: return
+ RenameProcessor(project, function, newName, false, false).run()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/coroutines/deferredIsResult/.inspection b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/.inspection
new file mode 100644
index 00000000000..79cafa5303d
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.coroutines.DeferredIsResultInspection
diff --git a/idea/testData/inspectionsLocal/coroutines/deferredIsResult/abstract.kt b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/abstract.kt
new file mode 100644
index 00000000000..9834fd4031c
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/abstract.kt
@@ -0,0 +1,8 @@
+// WITH_RUNTIME
+// FIX: Unwrap 'Deferred' return type (breaks use-sites!)
+
+package kotlinx.coroutines
+
+interface My {
+ fun function(): Deferred
+}
diff --git a/idea/testData/inspectionsLocal/coroutines/deferredIsResult/abstract.kt.after b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/abstract.kt.after
new file mode 100644
index 00000000000..98585eb087a
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/abstract.kt.after
@@ -0,0 +1,8 @@
+// WITH_RUNTIME
+// FIX: Unwrap 'Deferred' return type (breaks use-sites!)
+
+package kotlinx.coroutines
+
+interface My {
+ suspend fun function(): Int
+}
diff --git a/idea/testData/inspectionsLocal/coroutines/deferredIsResult/complex.kt b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/complex.kt
new file mode 100644
index 00000000000..22c01700319
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/complex.kt
@@ -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 myFunction(context: CoroutineContext, switch: Int): Deferred {
+ with (GlobalScope) {
+ when (switch) {
+ 0 -> return async {
+ val x = 123
+ x * x
+ }
+ 1 -> return async(context) { -1 }
+ else -> return async() { 9 }
+ }
+ }
+}
diff --git a/idea/testData/inspectionsLocal/coroutines/deferredIsResult/complex.kt.after b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/complex.kt.after
new file mode 100644
index 00000000000..02463a7473b
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/complex.kt.after
@@ -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 }
+ }
+ }
+}
diff --git a/idea/testData/inspectionsLocal/coroutines/deferredIsResult/coroutines.lib.kt b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/coroutines.lib.kt
new file mode 100644
index 00000000000..e7a34504351
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/coroutines.lib.kt
@@ -0,0 +1,46 @@
+package kotlinx.coroutines
+
+interface Deferred {
+ 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 CoroutineScope.async(
+ context: CoroutineContext = Dispatchers.Default,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> T
+): Deferred {
+ TODO()
+}
+
+suspend fun withContext(
+ context: CoroutineContext,
+ block: suspend CoroutineScope.() -> T
+): T {
+ TODO()
+}
+
+suspend fun coroutineScope(block: suspend CoroutineScope.() -> R): R = GlobalScope.block()
+
+operator fun CoroutineContext.plus(other: CoroutineContext): CoroutineContext {
+ TODO()
+}
+
diff --git a/idea/testData/inspectionsLocal/coroutines/deferredIsResult/rename.kt b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/rename.kt
new file mode 100644
index 00000000000..f74be2956c2
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/rename.kt
@@ -0,0 +1,8 @@
+// WITH_RUNTIME
+// FIX: Rename to 'myFunctionAsync'
+
+package kotlinx.coroutines
+
+fun myFunction(): Deferred {
+ return GlobalScope.async { 42 }
+}
diff --git a/idea/testData/inspectionsLocal/coroutines/deferredIsResult/rename.kt.after b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/rename.kt.after
new file mode 100644
index 00000000000..074e1a1140d
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/rename.kt.after
@@ -0,0 +1,8 @@
+// WITH_RUNTIME
+// FIX: Rename to 'myFunctionAsync'
+
+package kotlinx.coroutines
+
+fun myFunctionAsync(): Deferred {
+ return GlobalScope.async { 42 }
+}
diff --git a/idea/testData/inspectionsLocal/coroutines/deferredIsResult/simple.kt b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/simple.kt
new file mode 100644
index 00000000000..d1d2fd6683e
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/simple.kt
@@ -0,0 +1,8 @@
+// WITH_RUNTIME
+// FIX: Add '.await()' to function result (breaks use-sites!)
+
+package kotlinx.coroutines
+
+fun myFunction(): Deferred {
+ return GlobalScope.async { 42 }
+}
diff --git a/idea/testData/inspectionsLocal/coroutines/deferredIsResult/simple.kt.after b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/simple.kt.after
new file mode 100644
index 00000000000..ba284b28de5
--- /dev/null
+++ b/idea/testData/inspectionsLocal/coroutines/deferredIsResult/simple.kt.after
@@ -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 }
+}
diff --git a/idea/testData/inspectionsLocal/coroutines/redundantAsync/coroutines.lib.kt b/idea/testData/inspectionsLocal/coroutines/redundantAsync/coroutines.lib.kt
index 0791bbff53c..e7a34504351 100644
--- a/idea/testData/inspectionsLocal/coroutines/redundantAsync/coroutines.lib.kt
+++ b/idea/testData/inspectionsLocal/coroutines/redundantAsync/coroutines.lib.kt
@@ -34,7 +34,7 @@ fun CoroutineScope.async(
suspend fun withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
-) {
+): T {
TODO()
}
diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
index 86a7e51fa4c..3572eac8b27 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -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);
}
+ @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")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)