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)