EmptyRangeInspection: support floats and doubles

^KTIJ-22379 Fixed

GitOrigin-RevId: 72ed379b506bcca2c163c85fe7a452b74aaf7bac
This commit is contained in:
Nikita Bobko
2022-08-02 22:07:19 +00:00
committed by intellij-monorepo-bot
parent cb85fcc628
commit 4e1335fb04
7 changed files with 80 additions and 60 deletions
@@ -1310,6 +1310,7 @@ add.replacewith.argument.to.specify.replacement.pattern=Add 'replaceWith' argume
deprecated.annotation.without.replacewith.argument='@Deprecated' annotation without a 'replaceWith' argument
variable.name.0.matches.the.name.of.a.different.component=Variable name ''{0}'' matches the name of a different component
this.range.is.empty.did.you.mean.to.use.0=This range is empty. Did you mean to use ''{0}''?
this.range.is.empty=This range is empty.
equals.hashcode.in.object.declaration='equals()'/'hashCode()' in object declaration
hash.code.text=Generate 'hashCode()'
equals.text=Generate 'equals()'
@@ -7,13 +7,15 @@ import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.inspections.collections.isIterable
import org.jetbrains.kotlin.idea.intentions.getArguments
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.createExpressionByPattern
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.constants.*
import org.jetbrains.kotlin.resolve.calls.util.getType
/**
* Tests:
@@ -21,46 +23,61 @@ import org.jetbrains.kotlin.resolve.constants.*
* [org.jetbrains.kotlin.idea.inspections.LocalInspectionTestGenerated.EmptyRange]
*/
class EmptyRangeInspection : AbstractRangeInspection() {
override fun visitRangeTo(expression: KtExpression, context: BindingContext, holder: ProblemsHolder) {
expression.startAndEndValueSignedOrNull(context)?.let { (startValue, endValue) ->
if (startValue > endValue) holder.registerProblem(expression, downTo = true)
}
expression.startAndEndValueUnSignedOrNull(context)?.let { (startValue, endValue) ->
if (startValue > endValue) holder.registerProblem(expression, downTo = true)
override fun visitRangeTo(expression: KtExpression, context: BindingContext, holder: ProblemsHolder) =
visitRangeToImpl<Nothing>(expression, context, holder)
override fun visitUntilOrRangeUntil(expression: KtExpression, context: BindingContext, holder: ProblemsHolder) =
visitUntilOrRangeUntilImpl<Nothing>(expression, context, holder)
override fun visitDownTo(expression: KtExpression, context: BindingContext, holder: ProblemsHolder) =
visitDownToImpl<Nothing>(expression, context, holder)
private fun <T> visitRangeToImpl(
expression: KtExpression,
context: BindingContext,
holder: ProblemsHolder
) where T : Comparable<T> {
expression.getComparableArguments<T>(context)?.let { (startValue, endValue) ->
if (startValue > endValue) holder.registerProblem(expression, context, downTo = true)
}
}
override fun visitUntilOrRangeUntil(expression: KtExpression, context: BindingContext, holder: ProblemsHolder) {
expression.startAndEndValueSignedOrNull(context)?.let { (startValue, endValue) ->
private fun <T> visitUntilOrRangeUntilImpl(
expression: KtExpression,
context: BindingContext,
holder: ProblemsHolder
) where T : Comparable<T> {
expression.getComparableArguments<T>(context)?.let { (startValue, endValue) ->
when {
startValue > endValue -> holder.registerProblem(expression, downTo = true)
startValue == endValue -> holder.registerProblem(expression, downTo = false)
}
}
expression.startAndEndValueUnSignedOrNull(context)?.let { (startValue, endValue) ->
when {
startValue > endValue -> holder.registerProblem(expression, downTo = true)
startValue == endValue -> holder.registerProblem(expression, downTo = false)
startValue > endValue -> holder.registerProblem(expression, context, downTo = true)
startValue == endValue -> holder.registerProblem(expression, context, downTo = false)
}
}
}
override fun visitDownTo(expression: KtExpression, context: BindingContext, holder: ProblemsHolder) {
expression.startAndEndValueSignedOrNull(context)?.let { (startValue, endValue) ->
if (startValue < endValue) holder.registerProblem(expression, downTo = false)
}
expression.startAndEndValueUnSignedOrNull(context)?.let { (startValue, endValue) ->
if (startValue < endValue) holder.registerProblem(expression, downTo = false)
private fun <T> visitDownToImpl(
expression: KtExpression,
context: BindingContext,
holder: ProblemsHolder
) where T : Number, T : Comparable<T> {
expression.getComparableArguments<T>(context)?.let { (startValue, endValue) ->
if (startValue < endValue) holder.registerProblem(expression, context, downTo = false)
}
}
private fun ProblemsHolder.registerProblem(expression: KtExpression, downTo: Boolean) {
val (functionName, operator) = if (downTo) "downTo" to "downTo" else "rangeTo" to ".."
private fun ProblemsHolder.registerProblem(expression: KtExpression, context: BindingContext, downTo: Boolean) {
val (msg, fixes) =
if (!downTo || expression.getType(context)?.isIterable(DefaultBuiltIns.Instance) == true) {
val (functionName, operator) = if (downTo) "downTo" to "downTo" else "rangeTo" to ".."
KotlinBundle.message("this.range.is.empty.did.you.mean.to.use.0", functionName) to arrayOf(ReplaceFix(operator))
} else {
KotlinBundle.message("this.range.is.empty") to emptyArray<LocalQuickFix>()
}
registerProblem(
expression,
KotlinBundle.message("this.range.is.empty.did.you.mean.to.use.0", functionName),
msg,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
ReplaceFix(operator)
*fixes
)
}
@@ -78,39 +95,16 @@ class EmptyRangeInspection : AbstractRangeInspection() {
}
}
private fun KtExpression.startAndEndValueSignedOrNull(context: BindingContext): Pair<Long, Long>? {
private fun <T> KtExpression.getComparableArguments(context: BindingContext): Pair<T, T>? where T : Comparable<T> {
val (start, end) = getArguments() ?: return null
if (start?.isSignedValueConstant(context) == false) return null
val startValue = start?.longValueOrNull(context) ?: return null
val endValue = end?.longValueOrNull(context) ?: return null
@Suppress("UNCHECKED_CAST")
fun KtExpression.value() = constantValueOrNull(context)?.boxedValue()
// Because it's possible to write such things `2L..0`
?.let { if (it is Number && it !is Double && it !is Float) it.toLong() else it } as? T
val startValue = start?.value() ?: return null
val endValue = end?.value() ?: return null
if (startValue::class != endValue::class) return null
return startValue to endValue
}
private fun KtExpression.startAndEndValueUnSignedOrNull(context: BindingContext): Pair<ULong, ULong>? {
val (start, end) = getArguments() ?: return null
if (start?.isSignedValueConstant(context) == true) return null
val startValue = start?.uLongValueOrNull(context) ?: return null
val endValue = end?.uLongValueOrNull(context) ?: return null
return startValue to endValue
}
private fun KtExpression.isSignedValueConstant(context: BindingContext) = constantValueOrNull(context) is IntegerValueConstant<*>
private fun KtExpression.longValueOrNull(context: BindingContext): Long? {
return when (val constantValue = constantValueOrNull(context)?.value) {
is Number -> constantValue.toLong()
is Char -> constantValue.code.toLong()
else -> null
}
}
private fun KtExpression.uLongValueOrNull(context: BindingContext): ULong? {
return when (val constantValue = constantValueOrNull(context)) {
is UByteValue -> constantValue.value.toUByte().toULong()
is UShortValue -> constantValue.value.toUShort().toULong()
is UIntValue -> constantValue.value.toUInt().toULong()
is ULongValue -> constantValue.value.toULong()
else -> null
}
}
}
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getType
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.typeUtil.isDouble
import org.jetbrains.kotlin.types.typeUtil.isFloat
@@ -73,7 +74,7 @@ sealed class AbstractReplaceRangeToWithRangeUntilInspection : AbstractRangeInspe
private fun isApplicable(expression: KtExpression, context: BindingContext, useRangeUntil: Boolean): Boolean {
val (left, right) = expression.getArguments() ?: return false
// `until` isn't available for floating point numbers
fun KtExpression.isRangeUntilOrUntilApplicable() = context.getType(this)
fun KtExpression.isRangeUntilOrUntilApplicable() = getType(context)
?.let { it.isPrimitiveNumberType() && (useRangeUntil || !it.isDouble() && !it.isFloat()) }
return right?.deparenthesize()?.isMinusOne() == true &&
left?.isRangeUntilOrUntilApplicable() == true && right.isRangeUntilOrUntilApplicable() == true
@@ -4650,6 +4650,11 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti
runTest("testData/inspectionsLocal/emptyRange/char.kt");
}
@TestMetadata("double.kt")
public void testDouble() throws Exception {
runTest("testData/inspectionsLocal/emptyRange/double.kt");
}
@TestMetadata("downTo.kt")
public void testDownTo() throws Exception {
runTest("testData/inspectionsLocal/emptyRange/downTo.kt");
@@ -4670,6 +4675,11 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti
runTest("testData/inspectionsLocal/emptyRange/rangeUntil2.kt");
}
@TestMetadata("rangeUntilFloat.kt")
public void testRangeUntilFloat() throws Exception {
runTest("testData/inspectionsLocal/emptyRange/rangeUntilFloat.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("testData/inspectionsLocal/emptyRange/simple.kt");
@@ -0,0 +1,4 @@
// PROBLEM: This range is empty.
// FIX: none
// WITH_STDLIB
val range = 1.0<caret>..0.0
@@ -0,0 +1,5 @@
// PROBLEM: This range is empty. Did you mean to use 'rangeTo'?
// WITH_STDLIB
fun test() {
<caret>0f..<0f
}
@@ -0,0 +1,5 @@
// PROBLEM: This range is empty. Did you mean to use 'rangeTo'?
// WITH_STDLIB
fun test() {
<caret>0f..0f
}