[Kotlin] Fix quick fixes in IDE for annotations on actual after changes in compiler

Now checker supports annotations on type parameters, value parameters,
getters. After the change actual PsiElement, where annotation must be
added, is stored in `actualAnnotationTargetElement` field of
diagnostic. Corresponding tests were added.

^KT-60671
^KTIJ-26762

GitOrigin-RevId: 021ff6d8640a2a1cadab13943f699c03b5ff3347
This commit is contained in:
Roman Efremov
2023-09-15 14:42:00 +00:00
committed by intellij-monorepo-bot
parent 8bafbe14c8
commit b0c12d52e2
18 changed files with 119 additions and 14 deletions
@@ -41,6 +41,7 @@ internal object ActualAnnotationsNotMatchExpectFixFactory {
expectDeclaration,
actualDeclaration,
expectAnnotationEntry,
diagnostic.actualAnnotationTargetSourceElement,
mappedIncompatibilityType,
annotationClassIdProvider = { expectAnnotationEntry.getAnnotationClassId() }
)
@@ -139,6 +139,11 @@ public abstract class HighLevelQuickFixMultiModuleTestGenerated extends Abstract
runTest("../idea/tests/testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualConstExpression/");
}
@TestMetadata("copyToActualGetter")
public void testCopyToActualGetter() throws Exception {
runTest("../idea/tests/testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualGetter/");
}
@TestMetadata("copyToActualNoArg")
public void testCopyToActualNoArg() throws Exception {
runTest("../idea/tests/testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualNoArg/");
@@ -159,6 +164,16 @@ public abstract class HighLevelQuickFixMultiModuleTestGenerated extends Abstract
runTest("../idea/tests/testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualSingleArg/");
}
@TestMetadata("copyToActualTypeParameter")
public void testCopyToActualTypeParameter() throws Exception {
runTest("../idea/tests/testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualTypeParameter/");
}
@TestMetadata("copyToActualValueParameter")
public void testCopyToActualValueParameter() throws Exception {
runTest("../idea/tests/testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualValueParameter/");
}
@TestMetadata("copyToActualWithImport")
public void testCopyToActualWithImport() throws Exception {
runTest("../idea/tests/testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualWithImport/");
@@ -1,6 +1,7 @@
// Copyright 2000-2023 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.psi.PsiElement
import org.jetbrains.kotlin.idea.base.psi.callableIdIfNotLocal
import org.jetbrains.kotlin.idea.base.psi.classIdIfNonLocal
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
@@ -8,10 +9,7 @@ import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickF
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.QuickFixActionBase
import org.jetbrains.kotlin.idea.inspections.RemoveAnnotationFix
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType
object ActualAnnotationsNotMatchExpectFixFactoryCommon {
@@ -27,6 +25,7 @@ object ActualAnnotationsNotMatchExpectFixFactoryCommon {
expectDeclaration: KtNamedDeclaration,
actualDeclaration: KtNamedDeclaration,
expectAnnotationEntry: KtAnnotationEntry,
actualAnnotationTargetElement: PsiElement?,
incompatibilityType: ExpectActualAnnotationsIncompatibilityType<KtAnnotationEntry?>,
annotationClassIdProvider: () -> ClassId?,
): List<KotlinQuickFixAction<*>> {
@@ -40,7 +39,7 @@ object ActualAnnotationsNotMatchExpectFixFactoryCommon {
}
if (actualAnnotationEntry == null) {
val copyFromExpect = createCopyFromExpectToActualFix(expectAnnotationEntry, actualDeclaration, annotationClassIdProvider)
val copyFromExpect = createCopyFromExpectToActualFix(expectAnnotationEntry, actualAnnotationTargetElement, annotationClassIdProvider)
return listOfNotNull(copyFromExpect)
}
@@ -59,12 +58,15 @@ object ActualAnnotationsNotMatchExpectFixFactoryCommon {
}
private fun createCopyFromExpectToActualFix(
expectAnnotationEntry: KtAnnotationEntry, actualDeclaration: KtNamedDeclaration, annotationClassIdProvider: () -> ClassId?
expectAnnotationEntry: KtAnnotationEntry, actualAnnotationTargetElement: PsiElement?, annotationClassIdProvider: () -> ClassId?
): KotlinQuickFixAction<*>? {
if (actualAnnotationTargetElement !is KtModifierListOwner) {
return null
}
val annotationClassId = annotationClassIdProvider.invoke() ?: return null
return CopyAnnotationFromExpectToActualFix(
actualDeclaration,
actualAnnotationTargetElement,
expectAnnotationEntry,
annotationClassId,
)
@@ -10,13 +10,13 @@ import org.jetbrains.kotlin.idea.util.addAnnotation
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtModifierListOwner
internal class CopyAnnotationFromExpectToActualFix(
actualDeclaration: KtNamedDeclaration,
actualElement: KtModifierListOwner,
@SafeFieldForPreview private val expectAnnotationEntry: KtAnnotationEntry,
@SafeFieldForPreview private val annotationClassId: ClassId,
) : KotlinQuickFixAction<KtNamedDeclaration>(actualDeclaration) {
) : KotlinQuickFixAction<KtModifierListOwner>(actualElement) {
private val expectAnnotationShortName: String = expectAnnotationEntry.shortName?.toString() ?: "<unknown>"
@@ -27,9 +27,9 @@ internal class CopyAnnotationFromExpectToActualFix(
override fun getFamilyName(): String = text
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val actualDeclaration = element ?: return
val actualElement = element ?: return
val innerText = expectAnnotationEntry.valueArguments.joinToString { it.asElement().text }
actualDeclaration.addAnnotation(annotationClassId, innerText.takeIf { it.isNotEmpty() }, searchForExistingEntry = false)
actualElement.addAnnotation(annotationClassId, innerText.takeIf { it.isNotEmpty() }, searchForExistingEntry = false)
}
}
@@ -6,6 +6,7 @@ import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.calls.singleConstructorCallOrNull
import org.jetbrains.kotlin.analysis.api.calls.symbol
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
@@ -19,11 +20,12 @@ import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType
import org.jetbrains.kotlin.resolve.source.getPsi
import java.util.*
internal object ActualAnnotationsNotMatchExpectFixFactory : KotlinIntentionActionsFactory() {
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val castedDiagnostic = DiagnosticFactory.cast(diagnostic, Errors.ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT)
val incompatibilityType = castedDiagnostic.c
val incompatibilityType = castedDiagnostic.d
val expectAnnotationEntry = incompatibilityType.expectAnnotation.source.getPsi() as? KtAnnotationEntry
?: return emptyList()
@@ -32,13 +34,15 @@ internal object ActualAnnotationsNotMatchExpectFixFactory : KotlinIntentionActio
ActualAnnotationsNotMatchExpectFixFactoryCommon.createRemoveAnnotationFromExpectFix(expectAnnotationEntry)
return listOfNotNull(removeAnnotationFix) +
createCopyAndReplaceAnnotationFixes(expectAnnotationEntry, castedDiagnostic.a, castedDiagnostic.b, incompatibilityType)
createCopyAndReplaceAnnotationFixes(expectAnnotationEntry, castedDiagnostic.a, castedDiagnostic.b,
castedDiagnostic.c, incompatibilityType)
}
private fun createCopyAndReplaceAnnotationFixes(
expectAnnotationEntry: KtAnnotationEntry,
expectDeclarationDescriptor: DeclarationDescriptor,
actualDeclarationDescriptor: DeclarationDescriptor,
actualAnnotationTargetSourceElement: Optional<SourceElement>,
incompatibilityType: ExpectActualAnnotationsIncompatibilityType<AnnotationDescriptor>,
): List<QuickFixActionBase<*>> {
val expectDeclaration = expectDeclarationDescriptor.toSourceElement.getPsi() as? KtNamedDeclaration ?: return emptyList()
@@ -51,6 +55,7 @@ internal object ActualAnnotationsNotMatchExpectFixFactory : KotlinIntentionActio
expectDeclaration,
actualDeclaration,
expectAnnotationEntry,
actualAnnotationTargetSourceElement.orElse(null)?.getPsi(),
mappedIncompatibilityType,
annotationClassIdProvider = { getAnnotationClassId(expectAnnotationEntry) }
)
@@ -139,6 +139,11 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM
runTest("testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualConstExpression/");
}
@TestMetadata("copyToActualGetter")
public void testCopyToActualGetter() throws Exception {
runTest("testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualGetter/");
}
@TestMetadata("copyToActualNoArg")
public void testCopyToActualNoArg() throws Exception {
runTest("testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualNoArg/");
@@ -159,6 +164,16 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM
runTest("testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualSingleArg/");
}
@TestMetadata("copyToActualTypeParameter")
public void testCopyToActualTypeParameter() throws Exception {
runTest("testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualTypeParameter/");
}
@TestMetadata("copyToActualValueParameter")
public void testCopyToActualValueParameter() throws Exception {
runTest("testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualValueParameter/");
}
@TestMetadata("copyToActualWithImport")
public void testCopyToActualWithImport() throws Exception {
runTest("testData/multiModuleQuickFix/actualAnnotationsNotMatchExpect/copyToActualWithImport/");
@@ -0,0 +1,6 @@
// DISABLE-ERRORS
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class Ann
expect val foo: Any?
@Ann get
@@ -0,0 +1,6 @@
MODULE common { platform=[JVM, JS, Native]; root=common }
MODULE jvm { platform=[JVM]; root=jvm }
common -> STDLIB_COMMON { kind=DEPENDENCY }
jvm -> common { kind=DEPENDS_ON }
jvm -> STDLIB_JVM { kind=DEPENDENCY }
@@ -0,0 +1,6 @@
// "Copy mismatched annotation 'Ann' from expect to actual declaration (may change semantics)" "true"
// DISABLE-ERRORS
// FIR_COMPARISON
actual val foo<caret>: Any?
get() = null
@@ -0,0 +1,7 @@
// "Copy mismatched annotation 'Ann' from expect to actual declaration (may change semantics)" "true"
// DISABLE-ERRORS
// FIR_COMPARISON
actual val foo: Any?
@Ann
get() = null
@@ -0,0 +1,5 @@
// DISABLE-ERRORS
@Target(AnnotationTarget.TYPE_PARAMETER)
annotation class Ann
expect fun <@Ann T> foo()
@@ -0,0 +1,6 @@
MODULE common { platform=[JVM, JS, Native]; root=common }
MODULE jvm { platform=[JVM]; root=jvm }
common -> STDLIB_COMMON { kind=DEPENDENCY }
jvm -> common { kind=DEPENDS_ON }
jvm -> STDLIB_JVM { kind=DEPENDENCY }
@@ -0,0 +1,5 @@
// "Copy mismatched annotation 'Ann' from expect to actual declaration (may change semantics)" "true"
// DISABLE-ERRORS
// FIR_COMPARISON
actual fun <T> foo<caret>() {}
@@ -0,0 +1,5 @@
// "Copy mismatched annotation 'Ann' from expect to actual declaration (may change semantics)" "true"
// DISABLE-ERRORS
// FIR_COMPARISON
actual fun <@Ann T> foo() {}
@@ -0,0 +1,5 @@
// DISABLE-ERRORS
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Ann
expect fun foo(@Ann p: Any)
@@ -0,0 +1,6 @@
MODULE common { platform=[JVM, JS, Native]; root=common }
MODULE jvm { platform=[JVM]; root=jvm }
common -> STDLIB_COMMON { kind=DEPENDENCY }
jvm -> common { kind=DEPENDS_ON }
jvm -> STDLIB_JVM { kind=DEPENDENCY }
@@ -0,0 +1,5 @@
// "Copy mismatched annotation 'Ann' from expect to actual declaration (may change semantics)" "true"
// DISABLE-ERRORS
// FIR_COMPARISON
actual fun foo<caret>(p: Any) {}
@@ -0,0 +1,5 @@
// "Copy mismatched annotation 'Ann' from expect to actual declaration (may change semantics)" "true"
// DISABLE-ERRORS
// FIR_COMPARISON
actual fun foo(@Ann p: Any) {}