Extend "async result unused" to "Deferred result unused" #KT-15063 Fixed

This commit is contained in:
Mikhail Glukhikh
2018-05-16 12:06:21 +03:00
parent ffcfa51fbf
commit b8375d4864
15 changed files with 113 additions and 51 deletions
@@ -15,12 +15,12 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
abstract class AbstractResultUnusedChecker(
private val expressionChecker: (KtExpression) -> Boolean,
private val callChecker: (ResolvedCall<*>) -> Boolean
private val expressionChecker: (KtExpression, AbstractResultUnusedChecker) -> Boolean,
private val callChecker: (ResolvedCall<*>, AbstractResultUnusedChecker) -> Boolean
) : AbstractKotlinInspection() {
protected fun check(expression: KtExpression): Boolean {
// Check whatever possible by PSI
if (!expressionChecker(expression)) return false
if (!expressionChecker(expression, this)) return false
var parent: PsiElement? = expression.parent
while (parent != null) {
if (parent is KtBlockExpression || parent is KtFunction || parent is KtFile) break
@@ -32,6 +32,6 @@ abstract class AbstractResultUnusedChecker(
val context = expression.analyze(BodyResolveMode.PARTIAL_WITH_CFA)
if (expression.isUsedAsExpression(context)) return false
val resolvedCall = expression.getResolvedCall(context) ?: return false
return callChecker(resolvedCall)
return callChecker(resolvedCall, this)
}
}
@@ -1,6 +0,0 @@
<html>
<body>
This inspection reports <b>async</b> call that is never used,
so all actions inside <b>async</b> are never executed.
</body>
</html>
@@ -0,0 +1,6 @@
<html>
<body>
This inspection reports calls with <b>kotlinx.coroutines.experimental.Deferred</b> result that is never used,
so nobody will wait for execution of all related actions.
</body>
</html>
+2 -2
View File
@@ -2854,8 +2854,8 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.DeferredResultUnusedInspection"
displayName="Deferred result is never used"
groupPath="Kotlin"
groupName="Probable bugs"
enabledByDefault="true"
+2 -2
View File
@@ -2854,8 +2854,8 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.DeferredResultUnusedInspection"
displayName="Deferred result is never used"
groupPath="Kotlin"
groupName="Probable bugs"
enabledByDefault="true"
+2 -2
View File
@@ -2854,8 +2854,8 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.DeferredResultUnusedInspection"
displayName="Deferred result is never used"
groupPath="Kotlin"
groupName="Probable bugs"
enabledByDefault="true"
+2 -2
View File
@@ -2855,8 +2855,8 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.DeferredResultUnusedInspection"
displayName="Deferred result is never used"
groupPath="Kotlin"
groupName="Probable bugs"
enabledByDefault="true"
+2 -2
View File
@@ -2854,8 +2854,8 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.DeferredResultUnusedInspection"
displayName="Deferred result is never used"
groupPath="Kotlin"
groupName="Probable bugs"
enabledByDefault="true"
+2 -2
View File
@@ -2854,8 +2854,8 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection"
displayName="Result of 'async' is not used"
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.coroutines.DeferredResultUnusedInspection"
displayName="Deferred result is never used"
groupPath="Kotlin"
groupName="Probable bugs"
enabledByDefault="true"
@@ -1,26 +0,0 @@
/*
* 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.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.idea.inspections.AbstractResultUnusedChecker
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.callExpressionVisitor
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
class AsyncResultUnusedInspection : AbstractResultUnusedChecker(
expressionChecker = fun(expression): Boolean =
expression is KtCallExpression && expression.calleeExpression?.text == "async",
callChecker = fun(resolvedCall): Boolean =
resolvedCall.resultingDescriptor.fqNameOrNull()?.asString() == "kotlinx.coroutines.experimental.async"
) {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor =
callExpressionVisitor(fun(expression) {
if (!check(expression)) return
holder.registerProblem(expression.calleeExpression ?: expression, "Result of 'async' is never used")
})
}
@@ -0,0 +1,52 @@
/*
* 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.ProblemsHolder
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.inspections.AbstractResultUnusedChecker
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.callExpressionVisitor
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import javax.swing.JComponent
class DeferredResultUnusedInspection(@JvmField var standardOnly: Boolean = true) : AbstractResultUnusedChecker(
expressionChecker = fun(expression, inspection): Boolean =
inspection is DeferredResultUnusedInspection && expression is KtCallExpression &&
(!inspection.standardOnly || expression.calleeExpression?.text in shortNames),
callChecker = fun(resolvedCall, inspection): Boolean {
if (inspection !is DeferredResultUnusedInspection) return false
return if (inspection.standardOnly) {
resolvedCall.resultingDescriptor.fqNameOrNull() in fqNames
} else {
val returnTypeClassifier = resolvedCall.resultingDescriptor.returnType?.constructor?.declarationDescriptor
returnTypeClassifier?.importableFqName == deferred
}
}
) {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor =
callExpressionVisitor(fun(expression) {
if (!check(expression)) return
holder.registerProblem(expression.calleeExpression ?: expression, "Deferred result is never used")
})
override fun createOptionsPanel(): JComponent? {
val panel = MultipleCheckboxOptionsPanel(this)
panel.addCheckbox("Reports only function calls from kotlinx.coroutines", "standardOnly")
return panel
}
companion object {
private val shortNames = setOf("async")
private val fqNames: Set<FqName> = shortNames.mapTo(mutableSetOf()) { FqName("kotlinx.coroutines.experimental.$it") }
private val deferred = FqName("kotlinx.coroutines.experimental.Deferred")
}
}
@@ -5,7 +5,25 @@
<module>light_idea_test_case</module>
<package>&lt;default&gt;</package>
<entry_point TYPE="file" FQNAME="temp:///src/simple.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Result of 'async' is not used</problem_class>
<description>Result of 'async' is never used</description>
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Deferred result is never used</problem_class>
<description>Deferred result is never used</description>
</problem>
<problem>
<file>simple.kt</file>
<line>49</line>
<module>light_idea_test_case</module>
<package>&lt;default&gt;</package>
<entry_point TYPE="file" FQNAME="temp:///src/simple.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Deferred result is never used</problem_class>
<description>Deferred result is never used</description>
</problem>
<problem>
<file>simple.kt</file>
<line>51</line>
<module>light_idea_test_case</module>
<package>&lt;default&gt;</package>
<entry_point TYPE="file" FQNAME="temp:///src/simple.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Deferred result is never used</problem_class>
<description>Deferred result is never used</description>
</problem>
</problems>
@@ -1 +1 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.coroutines.AsyncResultUnusedInspection
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.coroutines.DeferredResultUnusedInspection
@@ -0,0 +1,3 @@
<inspection_tool class="DeferredResultUnused" enabled="true" level="WARNING" enabled_by_default="true">
<option name="standardOnly" value="false" />
</inspection_tool>
@@ -37,3 +37,18 @@ fun falsePositives() {
val res = async { 13 }
useIt(async { 7 })
}
class User
interface DbHandler {
fun getUser(id: Long): Deferred<User>
fun doStuff(): Deferred<Unit>
}
fun DbHandler.test() {
getUser(42L)
val user = getUser(42L).await()
doStuff()
doStuff().await()
}