diff --git a/plugins/kotlin/code-insight/fixes-k2/tests/test/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/AbstractHighLevelQuickFixMultiModuleTest.kt b/plugins/kotlin/code-insight/fixes-k2/tests/test/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/AbstractHighLevelQuickFixMultiModuleTest.kt index 46800a7b4144..69857c8fd351 100644 --- a/plugins/kotlin/code-insight/fixes-k2/tests/test/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/AbstractHighLevelQuickFixMultiModuleTest.kt +++ b/plugins/kotlin/code-insight/fixes-k2/tests/test/org/jetbrains/kotlin/idea/k2/codeinsight/fixes/AbstractHighLevelQuickFixMultiModuleTest.kt @@ -1,8 +1,15 @@ // 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 com.intellij.psi.PsiFile import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixMultiModuleTest +import org.jetbrains.kotlin.psi.KtFile abstract class AbstractHighLevelQuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() { override fun isFirPlugin(): Boolean = true + override fun findAfterFile(editedFile: KtFile): PsiFile? { + val firAfter = editedFile.containingDirectory?.findFile(editedFile.name + ".fir.after") + if (firAfter != null) return firAfter + return editedFile.containingDirectory?.findFile(editedFile.name + ".after") + } } \ No newline at end of file diff --git a/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt b/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt index 9618d2fb7126..6072c4e6b827 100644 --- a/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt +++ b/plugins/kotlin/code-insight/override-implement-k2/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.analysis.api.renderer.declarations.modifiers.rendere import org.jetbrains.kotlin.analysis.api.renderer.declarations.modifiers.renderers.KtRendererOtherModifiersProvider import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtPossibleMultiplatformSymbol import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer import org.jetbrains.kotlin.idea.base.util.names.FqNames import org.jetbrains.kotlin.idea.core.TemplateKind @@ -132,11 +133,63 @@ fun generateMember( modalityProvider = modalityProvider.onlyIf { s -> s != symbol } - otherModifiersProvider = otherModifiersProvider and object : KtRendererOtherModifiersProvider { + val containingSymbol = targetClass?.getSymbol() as? KtClassOrObjectSymbol + otherModifiersProvider = object : KtRendererOtherModifiersProvider { + //copy from KtRendererOtherModifiersProvider.ALL with `actual` and `override` specifics context(KtAnalysisSession) - override fun getOtherModifiers(symbol: KtDeclarationSymbol): List = - listOf(KtTokens.OVERRIDE_KEYWORD) - }.onlyIf { s -> mode == MemberGenerateMode.OVERRIDE && s == symbol } + override fun getOtherModifiers(s: KtDeclarationSymbol): List = buildList { + if (mode == MemberGenerateMode.OVERRIDE && s is KtPossibleMultiplatformSymbol && containingSymbol?.isActual == true) { + //include actual modifier explicitly when containing class has modifier + if (s.isActual) add(KtTokens.ACTUAL_KEYWORD) + } + + if (s is KtFunctionSymbol) { + if (s.isExternal) add(KtTokens.EXTERNAL_KEYWORD) + if (s.isOverride) add(KtTokens.OVERRIDE_KEYWORD) + if (s.isInline) add(KtTokens.INLINE_KEYWORD) + if (s.isInfix) add(KtTokens.INFIX_KEYWORD) + if (s.isOperator) add(KtTokens.OPERATOR_KEYWORD) + if (s.isSuspend) add(KtTokens.SUSPEND_KEYWORD) + } + + if (s is KtPropertySymbol) { + if (s.isOverride) add(KtTokens.OVERRIDE_KEYWORD) + } + + if (s is KtValueParameterSymbol) { + if (s.isVararg) add(KtTokens.VARARG_KEYWORD) + if (s.isCrossinline) add(KtTokens.CROSSINLINE_KEYWORD) + if (s.isNoinline) add(KtTokens.NOINLINE_KEYWORD) + } + + if (s is KtKotlinPropertySymbol) { + if (s.isConst) add(KtTokens.CONST_KEYWORD) + if (s.isLateInit) add(KtTokens.LATEINIT_KEYWORD) + } + + if (s is KtNamedClassOrObjectSymbol) { + if (s.isExternal) add(KtTokens.EXTERNAL_KEYWORD) + if (s.isInline) add(KtTokens.INLINE_KEYWORD) + if (s.isData) add(KtTokens.DATA_KEYWORD) + if (s.isFun) add(KtTokens.FUN_KEYWORD) + if (s.isInner) add(KtTokens.INNER_KEYWORD) + } + + if (s is KtTypeParameterSymbol) { + if (s.isReified) add(KtTokens.REIFIED_KEYWORD) + when (s.variance) { + Variance.INVARIANT -> {} + Variance.IN_VARIANCE -> add(KtTokens.IN_KEYWORD) + Variance.OUT_VARIANCE -> add(KtTokens.OUT_KEYWORD) + } + } + + if (s == symbol && mode == MemberGenerateMode.OVERRIDE) { + //include additional override modifier + add(KtTokens.OVERRIDE_KEYWORD) + } + } + } } } @@ -158,15 +211,13 @@ fun generateMember( } MemberGenerateMode.OVERRIDE -> { - // TODO: add `actual` keyword to the generated member if the target class has `actual` and the generated member corresponds to // an `expect` member. } } if (copyDoc) { val kDoc = when (val originalOverriddenPsi = symbol.unwrapFakeOverrides.psi) { - is KtDeclaration -> - findDocComment(originalOverriddenPsi) + is KtDeclaration -> findDocComment(originalOverriddenPsi) is PsiDocCommentOwner -> { val kDocText = originalOverriddenPsi.docComment?.let { IdeaDocCommentConverter.convertDocComment(it) } @@ -243,25 +294,21 @@ private fun generateProperty( val returnType = symbol.returnType val returnsNotUnit = !returnType.isUnit - val body = - if (bodyType != BodyType.NoBody) { - buildString { - append("\nget()") - append(" = ") - append(generateUnsupportedOrSuperCall(project, symbol, bodyType, !returnsNotUnit)) - if (!symbol.isVal) { - append("\nset(value) {}") - } + val body = if (bodyType != BodyType.NoBody) { + buildString { + append("\nget()") + append(" = ") + append(generateUnsupportedOrSuperCall(project, symbol, bodyType, !returnsNotUnit)) + if (!symbol.isVal) { + append("\nset(value) {}") } - } else "" + } + } else "" return KtPsiFactory(project).createProperty(symbol.render(renderer) + body) } private fun KtAnalysisSession.generateUnsupportedOrSuperCall( - project: Project, - symbol: T, - bodyType: BodyType, - canBeEmpty: Boolean = true + project: Project, symbol: T, bodyType: BodyType, canBeEmpty: Boolean = true ): String where T : KtNamedSymbol, T : KtCallableSymbol { when (bodyType.effectiveBodyType(canBeEmpty)) { BodyType.EmptyOrTemplate -> return "" @@ -272,11 +319,7 @@ private fun KtAnalysisSession.generateUnsupportedOrSuperCall( else -> throw IllegalArgumentException("$symbol must be either a function or a property") } return getFunctionBodyTextFromTemplate( - project, - templateKind, - symbol.name.asString(), - symbol.returnType.render(position = Variance.OUT_VARIANCE), - null + project, templateKind, symbol.name.asString(), symbol.returnType.render(position = Variance.OUT_VARIANCE), null ) } diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt index 34c7f3461c16..a2d3393346db 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.TestDialog import com.intellij.openapi.ui.TestDialogManager import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiFile import com.intellij.rt.execution.junit.FileComparisonData import com.intellij.testFramework.IdeaTestUtil import com.intellij.testFramework.UsefulTestCase @@ -154,7 +155,7 @@ abstract class AbstractQuickFixMultiModuleTest : AbstractMultiModuleTest(), Quic val afterFiles = projectDirectory.walkTopDown().filter { it.path.endsWith(".after") }.toList() for (editedFile in project.allKotlinFiles()) { - val afterFileInTmpProject = editedFile.containingDirectory?.findFile(editedFile.name + ".after") ?: continue + val afterFileInTmpProject = findAfterFile(editedFile) ?: continue val afterFileInTestData = afterFiles.filter { it.name == afterFileInTmpProject.name }.single { it.readText() == File(afterFileInTmpProject.virtualFile.path).readText() } @@ -169,6 +170,8 @@ abstract class AbstractQuickFixMultiModuleTest : AbstractMultiModuleTest(), Quic } } + protected open fun findAfterFile(editedFile: KtFile): PsiFile? = editedFile.containingDirectory?.findFile(editedFile.name + ".after") + private val availableActions: List get() { doHighlighting() diff --git a/plugins/kotlin/idea/tests/testData/editor/quickDoc/OnActualDeclaration.kt b/plugins/kotlin/idea/tests/testData/editor/quickDoc/OnActualDeclaration.kt index 52a0d7d9053b..6d0807dc9892 100644 --- a/plugins/kotlin/idea/tests/testData/editor/quickDoc/OnActualDeclaration.kt +++ b/plugins/kotlin/idea/tests/testData/editor/quickDoc/OnActualDeclaration.kt @@ -8,5 +8,5 @@ expect class Foo */ actual class Foo -//K2_INFO:
public final class Foo

Doc for actual class Foo

 OnActualDeclaration.kt
+//K2_INFO:
public actual final class Foo

Doc for actual class Foo

 OnActualDeclaration.kt
//INFO:
public final actual class Foo

Doc for actual class Foo

 OnActualDeclaration.kt
diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/other/implementMembersInActualClassNoExpectMember/jvm/jvm.kt.fir.after b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/other/implementMembersInActualClassNoExpectMember/jvm/jvm.kt.fir.after new file mode 100644 index 000000000000..e70911c8cb38 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/other/implementMembersInActualClassNoExpectMember/jvm/jvm.kt.fir.after @@ -0,0 +1,12 @@ +// "Implement members" "true" +// DISABLE-ERRORS + +actual interface ExpInterface { + actual fun first() +} + +actual class ExpImpl : ExpInterface { + actual override fun first() { + TODO("Not yet implemented") + } +} \ No newline at end of file