1/2 Refactor: reuse kotlin ranges parsing code in AbstractRangeInspection

Why:
1. To support `rangeUntil` operator in the next commit. Because now
   it's easier to implement the support.
2. To get rid of "Range parsing logic" duplicate

This commit doesn't change any user visible behaviour (at least I tried
to make it this way)

GitOrigin-RevId: 9713b8eb18f6ff3647df98c0a5072883bd3f13d3
This commit is contained in:
Nikita Bobko
2022-08-02 22:07:07 +00:00
committed by intellij-monorepo-bot
parent 71e07cb22c
commit f30e8ca592
7 changed files with 73 additions and 53 deletions
@@ -168,7 +168,7 @@ enum class HintType(
KotlinBundle.message("hints.settings.dont.show.ranges"),
true
) {
override fun isApplicable(e: PsiElement): Boolean = e is KtBinaryExpression && e.isRangeExpression()
override fun isApplicable(e: PsiElement): Boolean = e is KtBinaryExpression && e.isRangeExpression(context = null)
override fun provideHintDetails(e: PsiElement): List<InlayInfoDetails> {
val binaryExpression = e.safeAs<KtBinaryExpression>() ?: return emptyList()
@@ -1,10 +1,22 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.codeInsight.hints
import com.intellij.codeInsight.hints.*
import com.intellij.codeInsight.hints.ChangeListener
import com.intellij.codeInsight.hints.ImmediateConfigurable
import com.intellij.codeInsight.hints.InlayGroup
import com.intellij.codeInsight.hints.SettingsKey
import com.intellij.ui.layout.*
import com.intellij.util.castSafelyTo
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.safeAnalyze
import org.jetbrains.kotlin.idea.intentions.callExpression
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import javax.swing.JComponent
class KotlinValuesHintsProvider : KotlinAbstractHintsProvider<KotlinValuesHintsProvider.Settings>() {
@@ -63,18 +75,26 @@ class KotlinValuesHintsProvider : KotlinAbstractHintsProvider<KotlinValuesHintsP
get() = KotlinBundle.message("inlay.kotlin.values.hints")
}
internal fun KtBinaryExpression.isRangeExpression(): Boolean = getRangeBinaryExpressionType() != null
internal fun KtExpression.isRangeExpression(context: Lazy<BindingContext>?): Boolean = getRangeBinaryExpressionType(context) != null
fun KtBinaryExpression.getRangeBinaryExpressionType() =
when (operationReference.getReferencedNameAsName().asString()) {
".." -> RangeBinaryKtExpressionType.rangeTo
"..<" -> RangeBinaryKtExpressionType.rangeUntil
"downTo" -> RangeBinaryKtExpressionType.downTo
"until" -> RangeBinaryKtExpressionType.until
internal fun KtExpression.getRangeBinaryExpressionType(context: Lazy<BindingContext>?): RangeKtExpressionType? {
val name = castSafelyTo<KtBinaryExpression>()?.operationReference?.getReferencedNameAsName()?.asString()
?: castSafelyTo<KtDotQualifiedExpression>()?.callExpression?.calleeExpression?.text
return when (name) {
".." -> RangeKtExpressionType.rangeTo
"rangeTo" -> RangeKtExpressionType.rangeTo
"..<" -> RangeKtExpressionType.rangeUntil
"rangeUntil" -> RangeKtExpressionType.rangeUntil
"downTo" -> RangeKtExpressionType.downTo
"until" -> RangeKtExpressionType.until
else -> null
}?.takeIf {
val notNullContext = context?.value ?: safeAnalyze(BodyResolveMode.PARTIAL)
getResolvedCall(notNullContext)?.resultingDescriptor?.fqNameOrNull()?.asString()?.startsWith("kotlin.") == true
}
}
@Suppress("EnumEntryName")
enum class RangeBinaryKtExpressionType {
enum class RangeKtExpressionType {
rangeTo, rangeUntil, downTo, until
}
@@ -3,40 +3,39 @@
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.util.castSafelyTo
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.intentions.callExpression
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.idea.codeInsight.hints.RangeKtExpressionType
import org.jetbrains.kotlin.idea.codeInsight.hints.getRangeBinaryExpressionType
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtVisitorVoid
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.util.getType
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
abstract class AbstractRangeInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() {
override fun visitBinaryExpression(binaryExpression: KtBinaryExpression) {
val operator = binaryExpression.operationReference.text
visitRange(binaryExpression, operator, holder)
visitRange(binaryExpression, holder)
}
override fun visitDotQualifiedExpression(expression: KtDotQualifiedExpression) {
val callee = expression.callExpression?.calleeExpression?.text ?: return
visitRange(expression, callee, holder)
visitRange(expression, holder)
}
}
private fun visitRange(expression: KtExpression, operator: String, holder: ProblemsHolder) {
if (operator !in rangeFunctions) return
val context = expression.analyze(BodyResolveMode.PARTIAL)
val fqName = expression.getResolvedCall(context)?.resultingDescriptor?.fqNameOrNull() ?: return
when (fqName) {
in rangeToFqNames -> visitRangeTo(expression, context, holder)
untilFqName -> visitUntil(expression, context, holder)
downToFqNames -> visitDownTo(expression, context, holder)
private fun visitRange(expression: KtExpression, holder: ProblemsHolder) {
val context = lazy { expression.analyze(BodyResolveMode.PARTIAL) }
when (expression.getRangeBinaryExpressionType(context) ?: return) {
RangeKtExpressionType.rangeTo -> visitRangeTo(expression, context.value, holder)
RangeKtExpressionType.rangeUntil -> Unit
RangeKtExpressionType.downTo -> visitDownTo(expression, context.value, holder)
RangeKtExpressionType.until -> visitUntil(expression, context.value, holder)
}
}
@@ -47,18 +46,6 @@ abstract class AbstractRangeInspection : AbstractKotlinInspection() {
abstract fun visitDownTo(expression: KtExpression, context: BindingContext, holder: ProblemsHolder)
companion object {
private val rangeFunctions = listOf("..", "rangeTo", "until", "downTo")
private val rangeToFqNames = listOf(
"Char",
"Byte", "Short", "Int", "Long",
"UByte", "UShort", "UInt", "ULong"
).map { FqName("kotlin.$it.rangeTo") }
private val untilFqName = FqName("kotlin.ranges.until")
private val downToFqNames = FqName("kotlin.ranges.downTo")
fun KtExpression.constantValueOrNull(context: BindingContext? = null): ConstantValue<Any?>? {
val c = context ?: this.analyze(BodyResolveMode.PARTIAL)
@@ -15,6 +15,11 @@ import org.jetbrains.kotlin.psi.createExpressionByPattern
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.constants.*
/**
* Tests:
* [org.jetbrains.kotlin.idea.codeInsight.InspectionTestGenerated.Inspections.testEmptyRange_inspectionData_Inspections_test]
* [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) ->
@@ -8,14 +8,19 @@ import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.intentions.getArguments
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.typeUtil.isDouble
import org.jetbrains.kotlin.types.typeUtil.isFloat
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType
class ReplaceRangeToWithUntilInspection : AbstractRangeInspection() {
override fun visitRangeTo(expression: KtExpression, context: BindingContext, holder: ProblemsHolder) {
if (!isApplicable(expression)) return
if (!isApplicable(expression, context)) return
holder.registerProblem(
expression,
KotlinBundle.message("inspection.replace.range.to.with.until.display.name"),
@@ -43,11 +48,15 @@ class ReplaceRangeToWithUntilInspection : AbstractRangeInspection() {
companion object {
fun applyFixIfApplicable(expression: KtExpression) {
if (isApplicable(expression)) applyFix(expression)
if (isApplicable(expression, expression.analyze(BodyResolveMode.PARTIAL_NO_ADDITIONAL))) applyFix(expression)
}
private fun isApplicable(expression: KtExpression): Boolean {
return expression.getArguments()?.second?.deparenthesize()?.isMinusOne() == true
private fun isApplicable(expression: KtExpression, context: BindingContext): Boolean {
val (left, right) = expression.getArguments() ?: return false
// `until` isn't available for floating point numbers
fun KtExpression.isIntegerType() = context.getType(this)
?.let { it.isPrimitiveNumberType() && !it.isDouble() && !it.isFloat() }
return right?.deparenthesize()?.isMinusOne() == true && left?.isIntegerType() == true && right.isIntegerType() == true
}
private fun applyFix(element: KtExpression) {
@@ -41,8 +41,7 @@ import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.caches.resolve.safeAnalyzeNonSourceRootCode
import org.jetbrains.kotlin.idea.codeInsight.hints.RangeBinaryKtExpressionType
import org.jetbrains.kotlin.idea.codeInsight.hints.RangeBinaryKtExpressionType.*
import org.jetbrains.kotlin.idea.codeInsight.hints.RangeKtExpressionType.*
import org.jetbrains.kotlin.idea.codeInsight.hints.getRangeBinaryExpressionType
import org.jetbrains.kotlin.idea.core.resolveType
import org.jetbrains.kotlin.idea.inspections.dfa.KotlinAnchor.*
@@ -960,7 +959,7 @@ class KtControlFlowBuilder(val factory: DfaValueFactory, val context: KtExpressi
}
is KtBinaryExpression -> {
val (leftRelation, rightRelation) =
range.getRangeBinaryExpressionType()?.getRelationType() ?: (null to null)
range.getRangeBinaryExpressionType(context = null)?.getRelationType() ?: (null to null)
if (leftRelation != null && rightRelation != null) {
val left = range.left
val right = range.right
@@ -1277,7 +1276,7 @@ class KtControlFlowBuilder(val factory: DfaValueFactory, val context: KtExpressi
private fun processInCheck(kotlinType: KotlinType?, range: KtExpression?, anchor: KotlinAnchor, negated: Boolean) {
if (kotlinType != null && (kotlinType.isInt() || kotlinType.isLong())) {
if (range is KtBinaryExpression) {
val type = range.getRangeBinaryExpressionType()
val type = range.getRangeBinaryExpressionType(context = null)
val pair = when (type) {
rangeTo, until, rangeUntil -> range.left to range.right
null, downTo -> null
@@ -2,11 +2,11 @@
package org.jetbrains.kotlin.idea.inspections.dfa
import com.intellij.codeInspection.dataFlow.value.RelationType
import org.jetbrains.kotlin.idea.codeInsight.hints.RangeBinaryKtExpressionType
import org.jetbrains.kotlin.idea.codeInsight.hints.RangeKtExpressionType
fun RangeBinaryKtExpressionType.getRelationType() =
fun RangeKtExpressionType.getRelationType() =
when (this) {
RangeBinaryKtExpressionType.rangeTo -> RelationType.GE to RelationType.LE
RangeBinaryKtExpressionType.rangeUntil, RangeBinaryKtExpressionType.until -> RelationType.GE to RelationType.LT
RangeBinaryKtExpressionType.downTo -> RelationType.LE to RelationType.GE
RangeKtExpressionType.rangeTo -> RelationType.GE to RelationType.LE
RangeKtExpressionType.rangeUntil, RangeKtExpressionType.until -> RelationType.GE to RelationType.LT
RangeKtExpressionType.downTo -> RelationType.LE to RelationType.GE
}