Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OptionalUnit] Allow a function to declare a Unit return type when it uses a generic function initializer #4371

Merged
Expand Up @@ -9,6 +9,7 @@ import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.rules.isOverride
import org.jetbrains.kotlin.cfg.WhenChecker
import org.jetbrains.kotlin.js.translate.callTranslator.getReturnType
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea why this is in a JavaScript package, but it appears to work in JVM code.

import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtIfExpression
Expand All @@ -18,8 +19,10 @@ import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.types.typeUtil.isNothing
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull

Expand Down Expand Up @@ -109,7 +112,7 @@ class OptionalUnit(config: Config = Config.empty) : Rule(config) {
val typeReference = function.typeReference
val typeElementText = typeReference?.typeElement?.text
if (typeElementText == UNIT) {
if (function.initializer.isNothingType()) return
if (function.initializer.isGenericOrNothingType()) return
report(CodeSmell(issue, Entity.from(typeReference), createMessage(function)))
}
}
Expand All @@ -124,8 +127,12 @@ class OptionalUnit(config: Config = Config.empty) : Rule(config) {
private fun createMessage(function: KtNamedFunction) = "The function ${function.name} " +
"defines a return type of Unit. This is unnecessary and can safely be removed."

private fun KtExpression?.isNothingType() =
bindingContext != BindingContext.EMPTY && this?.getType(bindingContext)?.isNothing() == true
private fun KtExpression?.isGenericOrNothingType(): Boolean {
if (bindingContext == BindingContext.EMPTY) return false
val isGenericType = this?.getResolvedCall(bindingContext)?.getReturnType()?.isTypeParameter() == true
val isNothingType = this?.getType(bindingContext)?.isNothing() == true
return isGenericType || isNothingType
}

companion object {
private const val UNIT = "Unit"
Expand Down
Expand Up @@ -305,5 +305,19 @@ class OptionalUnitSpec : Spek({
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).isEmpty()
}

it("should not report when the function initializer requires a type") {
val code = """
fun <T> foo(block: (List<T>) -> Unit): T {
val list = listOf<T>()
block(list)
return list.first()
}

fun doFoo(): Unit = foo {}
""".trimIndent()
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).isEmpty()
}
}
})