[kotlin] Ported ReplaceJvmFieldWithConstFix to K2

Also fixed not taking into account nullable types in MayBeConstantInspection to align with the behavior of the quick-fix.

^KTIJ-29535 fixed

GitOrigin-RevId: 5a97bc73b328ef1e3cd840d9f96ba0299c8acc57
This commit is contained in:
Frederik Haselmeier
2024-08-02 10:11:13 +00:00
committed by intellij-monorepo-bot
parent 3dafa1eaf2
commit 0e7e9fba23
17 changed files with 175 additions and 71 deletions
@@ -440,6 +440,8 @@ class KotlinK2QuickFixRegistrar : KotlinQuickFixRegistrar() {
registerFactory(DeprecationFixFactory.deprecatedAliasWarning)
registerFactory(ChangeMemberFunctionSignatureFixFactory.nothingToOverrideFixFactory)
registerFactory(ReplaceJvmFieldWithConstFixFactory.inapplicableJvmField)
}
private val optIn = KtQuickFixesListBuilder.registerPsiQuickFix {
@@ -0,0 +1,33 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.k2.codeinsight.fixes
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaFirDiagnostic
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinQuickFixFactory
import org.jetbrains.kotlin.idea.codeinsight.utils.checkMayBeConstantByFields
import org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
object ReplaceJvmFieldWithConstFixFactory {
private fun KaSession.createQuickFix(annotation: KtAnnotationEntry): ReplaceJvmFieldWithConstFix? {
val property = annotation.getParentOfType<KtProperty>(false) ?: return null
val initializer = property.initializer ?: return null
if (!property.checkMayBeConstantByFields()) return null
val returnType = property.returnType
if (returnType.isMarkedNullable) return null
if (!returnType.isPrimitive && !returnType.isStringType) return null
if (initializer.evaluate() == null) {
return null
}
return ReplaceJvmFieldWithConstFix(annotation)
}
val inapplicableJvmField = KotlinQuickFixFactory.ModCommandBased { diagnostic: KaFirDiagnostic.InapplicableJvmField ->
listOfNotNull(createQuickFix(diagnostic.psi))
}
}
@@ -10046,7 +10046,64 @@ public abstract class HighLevelQuickFixTestGenerated extends AbstractHighLevelQu
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../../idea/tests/testData/quickfix/replaceJvmFieldWithConst")
public static class ReplaceJvmFieldWithConst extends AbstractHighLevelQuickFixTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K2;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("class.kt")
public void testClass() throws Exception {
runTest("../../../idea/tests/testData/quickfix/replaceJvmFieldWithConst/class.kt");
}
@TestMetadata("companionInInterface.kt")
public void testCompanionInInterface() throws Exception {
runTest("../../../idea/tests/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt");
}
@TestMetadata("getter.kt")
public void testGetter() throws Exception {
runTest("../../../idea/tests/testData/quickfix/replaceJvmFieldWithConst/getter.kt");
}
@TestMetadata("nonConstantInitializer.kt")
public void testNonConstantInitializer() throws Exception {
runTest("../../../idea/tests/testData/quickfix/replaceJvmFieldWithConst/nonConstantInitializer.kt");
}
@TestMetadata("nullable.kt")
public void testNullable() throws Exception {
runTest("../../../idea/tests/testData/quickfix/replaceJvmFieldWithConst/nullable.kt");
}
@TestMetadata("object.kt")
public void testObject() throws Exception {
runTest("../../../idea/tests/testData/quickfix/replaceJvmFieldWithConst/object.kt");
}
@TestMetadata("stringTemplateWithConstants.kt")
public void testStringTemplateWithConstants() throws Exception {
runTest("../../../idea/tests/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt");
}
@TestMetadata("stringTemplateWithVal.kt")
public void testStringTemplateWithVal() throws Exception {
runTest("../../../idea/tests/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithVal.kt");
}
@TestMetadata("toplevel.kt")
public void testToplevel() throws Exception {
runTest("../../../idea/tests/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt");
}
}
@@ -11,19 +11,17 @@ import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.base.KaConstantValue
import org.jetbrains.kotlin.analysis.api.components.KaDiagnosticCheckerFilter
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaFirDiagnostic
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.CleanupFix
import org.jetbrains.kotlin.idea.codeinsight.utils.*
import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.MayBeConstantInspectionBase
import org.jetbrains.kotlin.idea.codeinsight.utils.checkMayBeConstantByFields
import org.jetbrains.kotlin.idea.codeinsight.utils.replaceReferencesToGetterByReferenceToField
import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.MayBeConstantInspectionBase
import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.matchStatus
import org.jetbrains.kotlin.idea.quickfix.AddModifierFix
import org.jetbrains.kotlin.idea.util.application.runWriteActionIfPhysical
import org.jetbrains.kotlin.idea.util.findAnnotation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtProperty
@@ -38,7 +36,7 @@ class MayBeConstantInspection : MayBeConstantInspectionBase() {
val initializer = initializer
analyze(this) {
if (!hasPrimitiveOrStringType()) return Status.NONE
if (!hasNonNullablePrimitiveOrStringType()) return Status.NONE
}
val withJvmField = findAnnotation(ClassId.fromString(JVM_FIELD_CLASS_ID)) != null
@@ -60,8 +58,9 @@ class MayBeConstantInspection : MayBeConstantInspectionBase() {
}
context(KaSession)
private fun KtProperty.hasPrimitiveOrStringType(): Boolean {
private fun KtProperty.hasNonNullablePrimitiveOrStringType(): Boolean {
val type = this.returnType
if (type.isMarkedNullable) return false
return type.isPrimitive || type.isStringType
}
@@ -0,0 +1,25 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.modcommand.PsiUpdateModCommandAction
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
class ReplaceJvmFieldWithConstFix(annotation: KtAnnotationEntry) : PsiUpdateModCommandAction<KtAnnotationEntry>(annotation) {
override fun getFamilyName(): String = KotlinBundle.message("replace.jvmfield.with.const")
override fun invoke(
context: ActionContext,
element: KtAnnotationEntry,
updater: ModPsiUpdater
) {
val property = element.getParentOfType<KtProperty>(false) ?: return
element.delete()
property.addModifier(KtTokens.CONST_KEYWORD)
}
}
@@ -645,7 +645,7 @@ class QuickFixRegistrar : QuickFixContributor {
ILLEGAL_INLINE_PARAMETER_MODIFIER.registerFactory(AddInlineToFunctionFix)
INAPPLICABLE_JVM_FIELD.registerFactory(ReplaceJvmFieldWithConstFix, RemoveAnnotationFix.JvmField)
INAPPLICABLE_JVM_FIELD.registerFactory(ReplaceJvmFieldWithConstFixFactory, RemoveAnnotationFix.JvmField)
CONFLICTING_OVERLOADS.registerFactory(ChangeSuspendInHierarchyFix)
@@ -1,56 +0,0 @@
// 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.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.checkers.ConstModifierChecker
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class ReplaceJvmFieldWithConstFix(annotation: KtAnnotationEntry) : KotlinQuickFixAction<KtAnnotationEntry>(annotation) {
override fun getText(): String = KotlinBundle.message("replace.jvmfield.with.const")
override fun getFamilyName(): String = text
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val property = element?.getParentOfType<KtProperty>(false) ?: return
element?.delete()
property.addModifier(KtTokens.CONST_KEYWORD)
}
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val annotation = diagnostic.psiElement as? KtAnnotationEntry ?: return null
val property = annotation.getParentOfType<KtProperty>(false) ?: return null
val propertyDescriptor = property.descriptor as? PropertyDescriptor ?: return null
if (!ConstModifierChecker.canBeConst(property, property, propertyDescriptor)) {
return null
}
val initializer = property.initializer ?: return null
if (!initializer.isConstantExpression()) {
return null
}
return ReplaceJvmFieldWithConstFix(annotation)
}
private fun KtExpression.isConstantExpression() =
ConstantExpressionEvaluator.getConstant(this, analyze(BodyResolveMode.PARTIAL))?.let { !it.usesNonConstValAsConstant } ?: false
}
}
@@ -0,0 +1,36 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.checkers.ConstModifierChecker
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
internal object ReplaceJvmFieldWithConstFixFactory : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val annotation = diagnostic.psiElement as? KtAnnotationEntry ?: return null
val property = annotation.getParentOfType<KtProperty>(false) ?: return null
val propertyDescriptor = property.descriptor as? PropertyDescriptor ?: return null
if (!ConstModifierChecker.canBeConst(property, property, propertyDescriptor)) {
return null
}
val initializer = property.initializer ?: return null
if (!initializer.isConstantExpression()) {
return null
}
return ReplaceJvmFieldWithConstFix(annotation).asIntention()
}
private fun KtExpression.isConstantExpression() =
ConstantExpressionEvaluator.getConstant(this, analyze(BodyResolveMode.PARTIAL))?.let { !it.usesNonConstValAsConstant } ?: false
}
@@ -6,4 +6,5 @@ interface IFace {
<caret>@JvmField val a = "Lorem ipsum"
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
@@ -6,4 +6,5 @@ interface IFace {
const val a = "Lorem ipsum"
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
@@ -4,4 +4,5 @@ object Foo {
<caret>@JvmField private val a = "Lorem ipsum"
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
@@ -4,4 +4,5 @@ object Foo {
private const val a = "Lorem ipsum"
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
@@ -2,4 +2,5 @@
// WITH_STDLIB
const val three = 3
<caret>@JvmField private val text = "${2 + three}"
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
@@ -2,4 +2,5 @@
// WITH_STDLIB
const val three = 3
private const val text = "${2 + three}"
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
@@ -1,4 +1,5 @@
// "Replace '@JvmField' with 'const'" "true"
// WITH_STDLIB
<caret>@JvmField private val number: Int = 42
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
@@ -1,4 +1,5 @@
// "Replace '@JvmField' with 'const'" "true"
// WITH_STDLIB
private const val number: Int = 42
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ReplaceJvmFieldWithConstFix
@@ -179,7 +179,7 @@ internal fun MutableTWorkspace.generateK2FixTests() {
model("$idea/quickfix/renameToUnderscore", pattern = pattern, isIgnored = true)
model("$idea/quickfix/renameUnresolvedReference", pattern = pattern, isIgnored = true)
model("$idea/quickfix/reorderParameters", pattern = pattern)
model("$idea/quickfix/replaceJvmFieldWithConst", pattern = pattern, isIgnored = true)
model("$idea/quickfix/replaceJvmFieldWithConst", pattern = pattern)
model("$idea/quickfix/restrictedRetentionForExpressionAnnotation", pattern = pattern, isIgnored = true)
model("$idea/quickfix/simplifyComparison", pattern = pattern)
model("$idea/quickfix/smartCastImpossibleInIfThen", pattern = pattern, isIgnored = true)