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 }