From f30e8ca592b9ff5ec517aa68ef7f915ef51f5a40 Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Fri, 29 Jul 2022 19:07:52 +0200 Subject: [PATCH] 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 --- .../kotlin/idea/codeInsight/hints/HintType.kt | 2 +- .../hints/KotlinValuesHintsProvider.kt | 38 +++++++++++---- .../inspections/AbstractRangeInspection.kt | 47 +++++++------------ .../idea/inspections/EmptyRangeInspection.kt | 5 ++ .../ReplaceRangeToWithUntilInspection.kt | 17 +++++-- .../inspections/dfa/KtControlFlowBuilder.kt | 7 ++- .../kotlin/idea/inspections/dfa/util.kt | 10 ++-- 7 files changed, 73 insertions(+), 53 deletions(-) diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/HintType.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/HintType.kt index f94b06d1663a..201d46bdc44b 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/HintType.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/HintType.kt @@ -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 { val binaryExpression = e.safeAs() ?: return emptyList() diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinValuesHintsProvider.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinValuesHintsProvider.kt index 9791480849e5..3843a9eb1ad8 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinValuesHintsProvider.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinValuesHintsProvider.kt @@ -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() { @@ -63,18 +75,26 @@ class KotlinValuesHintsProvider : KotlinAbstractHintsProvider?): 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?): RangeKtExpressionType? { + val name = castSafelyTo()?.operationReference?.getReferencedNameAsName()?.asString() + ?: castSafelyTo()?.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 } diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/AbstractRangeInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/AbstractRangeInspection.kt index a3759d717c4b..97e9c13f0bf4 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/AbstractRangeInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/AbstractRangeInspection.kt @@ -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? { val c = context ?: this.analyze(BodyResolveMode.PARTIAL) diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/EmptyRangeInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/EmptyRangeInspection.kt index 94defa227357..c905c366329d 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/EmptyRangeInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/EmptyRangeInspection.kt @@ -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) -> diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeToWithUntilInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeToWithUntilInspection.kt index 1bd9613c2aa5..d173eebb382b 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeToWithUntilInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceRangeToWithUntilInspection.kt @@ -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) { diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/dfa/KtControlFlowBuilder.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/dfa/KtControlFlowBuilder.kt index c95ffe96a513..5843a6070e60 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/dfa/KtControlFlowBuilder.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/dfa/KtControlFlowBuilder.kt @@ -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 diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/dfa/util.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/dfa/util.kt index c67b654841ae..637c51fa216e 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/dfa/util.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/dfa/util.kt @@ -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 }