Add a dialog to select members for export in expect class

#KT-31476 Fixed
#KT-31446 Fixed
#KT-31033 Fixed
This commit is contained in:
Dmitry Gridin
2019-05-17 19:31:35 +07:00
parent 9a910ef144
commit b575ed390a
37 changed files with 300 additions and 21 deletions
@@ -125,15 +125,18 @@ private fun KtClassOrObject.isExpected(): Boolean {
fun KtDeclaration.hasMatchingExpected() = (toDescriptor() as? MemberDescriptor)?.expectedDescriptor() != null
fun KtDeclaration.isEffectivelyActual(): Boolean {
fun KtDeclaration.isEffectivelyActual(checkConstructor: Boolean = true): Boolean {
if (hasActualModifier()) return true
val descriptor = toDescriptor() as? MemberDescriptor ?: return false
return descriptor.isEffectivelyActual()
return descriptor.isEffectivelyActual(checkConstructor)
}
private fun MemberDescriptor.isEffectivelyActual(): Boolean =
isActual || isEnumEntryInActual() || (this is ClassConstructorDescriptor && containingDeclaration.isEffectivelyActual())
private fun MemberDescriptor.isEffectivelyActual(checkConstructor: Boolean = true): Boolean =
isActual || isEnumEntryInActual() || isConstructorInActual(checkConstructor)
private fun MemberDescriptor.isConstructorInActual(checkConstructor: Boolean) =
checkConstructor && this is ClassConstructorDescriptor && containingDeclaration.isEffectivelyActual(checkConstructor)
private fun MemberDescriptor.isEnumEntryInActual() =
(DescriptorUtils.isEnumEntry(this) && (containingDeclaration as? MemberDescriptor)?.isActual == true)
@@ -387,7 +387,11 @@ fun generateUnsupportedOrSuperCall(
}
}
fun KtNamedDeclaration.makeNotActual() {
fun KtModifierListOwner.makeNotActual() {
removeModifier(KtTokens.ACTUAL_KEYWORD)
removeModifier(KtTokens.IMPL_KEYWORD)
}
fun KtModifierListOwner.makeActual() {
addModifier(KtTokens.ACTUAL_KEYWORD)
}
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.startOffset
abstract class AbstractCreateDeclarationFix<D : KtNamedDeclaration>(
declaration: D,
@@ -81,7 +82,9 @@ abstract class AbstractCreateDeclarationFix<D : KtNamedDeclaration>(
}
val reformatted = CodeStyleManager.getInstance(project).reformat(generatedDeclaration)
val shortened = ShortenReferences.DEFAULT.process(reformatted as KtElement)
EditorHelper.openInEditor(shortened)?.caretModel?.moveToOffset(shortened.textRange.startOffset)
EditorHelper.openInEditor(shortened)?.caretModel?.moveToOffset(
(shortened as? KtNamedDeclaration)?.nameIdentifier?.startOffset ?: shortened.startOffset
)
}
}
}
@@ -6,20 +6,32 @@
package org.jetbrains.kotlin.idea.quickfix.expectactual
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.ide.util.MemberChooser
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.project.implementedModules
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.overrideImplement.makeActual
import org.jetbrains.kotlin.idea.core.overrideImplement.makeNotActual
import org.jetbrains.kotlin.idea.core.toDescriptor
import org.jetbrains.kotlin.idea.core.util.DescriptorMemberChooserObject
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.hasInlineModifier
import org.jetbrains.kotlin.idea.util.isEffectivelyActual
import org.jetbrains.kotlin.idea.util.liftToExpected
import org.jetbrains.kotlin.idea.util.module
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
@@ -87,10 +99,104 @@ class CreateExpectedClassFix(
klass: KtClassOrObject,
outerExpectedClass: KtClassOrObject?,
commonModule: Module
) : CreateExpectedFix<KtClassOrObject>(klass, outerExpectedClass, commonModule, { project, element ->
) : CreateExpectedFix<KtClassOrObject>(klass, outerExpectedClass, commonModule, block@{ project, element ->
val originalCollection = element.collectDeclarations(false).filter(KtDeclaration::canAddActualModifier).toList()
val collection = originalCollection.filterNot(KtDeclaration::isAlwaysActual)
val selectedElements = when {
ApplicationManager.getApplication().isUnitTestMode -> collection.filter { it.isEffectivelyActual(false) }
collection.any(KtDeclaration::hasActualModifier) && collection.any { !it.hasActualModifier() } -> {
val prefix = klass.fqName?.asString()?.plus(".") ?: ""
chooseMembers(project, collection, prefix) ?: return@block null
}
else -> null
}
project.executeWriteCommand("Repair actual members") {
repairActualModifiers(originalCollection, selectedElements)
}
generateClassOrObject(project, true, element, listOfNotNull(outerExpectedClass))
})
private fun KtDeclaration.canAddActualModifier() = when (this) {
is KtEnumEntry -> false
is KtParameter -> this.hasValOrVar()
else -> true
}
/***
* @return null if close without OK
*/
private fun chooseMembers(project: Project, collection: Collection<KtDeclaration>, prefixToRemove: String): List<KtDeclaration>? {
val classMembers = collection.map { Member(prefixToRemove, it, it.resolveToDescriptorIfAny()!!) }
return MemberChooser(
classMembers.toTypedArray(),
true,
true,
project
).run {
title = "Choose actual members"
setCopyJavadocVisible(false)
selectElements(classMembers.filter { (it.element as KtDeclaration).hasActualModifier() }.toTypedArray())
show()
if (!isOK) null else selectedElements?.map { it.element as KtDeclaration }.orEmpty()
}
}
private class Member(val prefix: String, element: KtElement, descriptor: DeclarationDescriptor) :
DescriptorMemberChooserObject(element, descriptor) {
override fun getText(): String {
val text = super.getText()
return if (descriptor is ClassDescriptor) text.removePrefix(prefix)
else text
}
}
private fun KtClassOrObject.collectDeclarations(withSelf: Boolean = true): Sequence<KtDeclaration> {
val thisSequence = if (withSelf) sequenceOf(this) else emptySequence()
val primaryConstructorSequence = primaryConstructorParameters.asSequence() + primaryConstructor.let {
if (it != null) sequenceOf(it) else emptySequence()
}
return thisSequence + primaryConstructorSequence + declarations.asSequence().flatMap {
if (it is KtClassOrObject) it.collectDeclarations() else sequenceOf(it)
}
}
private fun repairActualModifiers(
originalElements: Collection<KtDeclaration>,
// If null, all class declarations are actual
selectedElements: Collection<KtDeclaration>?
) {
if (selectedElements == null)
for (original in originalElements) {
original.recursivelyMakeActual()
}
else
for (original in originalElements) {
if (original.isAlwaysActual() || original in selectedElements)
original.recursivelyMakeActual()
else
original.makeNotActual()
}
}
private tailrec fun KtDeclaration.recursivelyMakeActual() {
makeActual()
containingClassOrObject?.takeUnless(KtDeclaration::hasActualModifier)?.recursivelyMakeActual()
}
private fun KtDeclaration.isAlwaysActual(): Boolean {
val primaryConstructor = when (this) {
is KtPrimaryConstructor -> this
is KtParameter -> (parent as? KtParameterList)?.parent as? KtPrimaryConstructor
else -> null
} ?: return false
return primaryConstructor.containingClass()?.let {
it.isAnnotation() || it.hasInlineModifier()
} ?: false
}
class CreateExpectedPropertyFix(
property: KtNamedDeclaration,
targetExpectedClass: KtClassOrObject?,
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.idea.project.platform
import org.jetbrains.kotlin.idea.refactoring.createKotlinFile
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.hasDeclarationOf
import org.jetbrains.kotlin.idea.util.hasInlineModifier
import org.jetbrains.kotlin.idea.util.isEffectivelyActual
import org.jetbrains.kotlin.idea.util.module
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
@@ -172,7 +173,7 @@ internal fun KtPsiFactory.generateClassOrObject(
}
declLoop@ for (originalDeclaration in originalClass.declarations.filter { !it.exists() }) {
val descriptor = originalDeclaration.toDescriptor() ?: continue
if (generateExpectClass && !originalDeclaration.isEffectivelyActual()) continue
if (generateExpectClass && !originalDeclaration.isEffectivelyActual(false)) continue
val generatedDeclaration: KtDeclaration = when (originalDeclaration) {
is KtClassOrObject -> {
generateClassOrObject(project, generateExpectClass, originalDeclaration, outerClasses + generatedClass)
@@ -192,7 +193,7 @@ internal fun KtPsiFactory.generateClassOrObject(
}
generatedClass.addDeclaration(generatedDeclaration)
}
if (!originalClass.isAnnotation()) {
if (!originalClass.isAnnotation() && !originalClass.hasInlineModifier()) {
for (originalProperty in originalClass.primaryConstructorParameters) {
if (!originalProperty.hasValOrVar() || !originalProperty.hasActualModifier()) continue
val descriptor = originalProperty.toDescriptor() as? PropertyDescriptor ?: continue
@@ -203,7 +204,12 @@ internal fun KtPsiFactory.generateClassOrObject(
}
}
val originalPrimaryConstructor = originalClass.primaryConstructor
if (generatedClass is KtClass && originalPrimaryConstructor != null && !originalPrimaryConstructor.exists()) {
if (
generatedClass is KtClass
&& originalPrimaryConstructor != null
&& (!generateExpectClass || originalClass.hasInlineModifier() || originalPrimaryConstructor.hasActualModifier())
&& !originalPrimaryConstructor.exists()
) {
val descriptor = originalPrimaryConstructor.toDescriptor()
if (descriptor is FunctionDescriptor) {
val expectedPrimaryConstructor = generateFunction(
@@ -5,7 +5,9 @@
package org.jetbrains.kotlin.idea.util
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtTypeArgumentList
@@ -15,4 +17,6 @@ fun KtCallExpression.replaceOrCreateTypeArgumentList(newTypeArgumentList: KtType
newTypeArgumentList,
calleeExpression
)
}
}
fun KtModifierListOwner.hasInlineModifier() = hasModifier(KtTokens.INLINE_KEYWORD)
@@ -1,3 +1,3 @@
// My: to be implemented
// DISABLE-ERRORS
expect annotation class My(val x: Int, val y: Double)
expect annotation class <caret>My(val x: Int, val y: Double)
@@ -1,4 +1,4 @@
// "Create expected annotation class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual annotation class <caret>My(actual val x: Int, actual val y: Double)
actual annotation class <caret>My actual constructor(actual val x: Int, actual val y: Double)
@@ -1,4 +1,4 @@
// "Create expected annotation class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual annotation class My(actual val x: Int, actual val y: Double)
actual annotation class My actual constructor(actual val x: Int, actual val y: Double)
@@ -0,0 +1,2 @@
// My: to be implemented
// DISABLE-ERRORS
@@ -0,0 +1,3 @@
// My: to be implemented
// DISABLE-ERRORS
expect annotation class <caret>My(val x: Int, val y: Double)
@@ -0,0 +1,4 @@
// "Create expected annotation class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual annotation class <caret>My(actual val x: Int, actual val y: Double)
@@ -0,0 +1,4 @@
// "Create expected annotation class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual annotation class My actual constructor(actual val x: Int, actual val y: Double)
@@ -0,0 +1 @@
// DISABLE-ERRORS
@@ -0,0 +1,5 @@
// DISABLE-ERRORS
expect class <caret>My(a: Int, b: String) {
fun foo(param: String): Int
val a: Int
}
@@ -0,0 +1,6 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My<caret> actual constructor(actual val a: Int, b: String) {
actual fun foo(param: String) = param.length
}
@@ -0,0 +1,6 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My actual constructor(actual val a: Int, b: String) {
actual fun foo(param: String) = param.length
}
@@ -0,0 +1 @@
// DISABLE-ERRORS
@@ -0,0 +1,5 @@
// DISABLE-ERRORS
expect class <caret>My {
constructor()
}
@@ -0,0 +1,8 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My<caret>(val a: Int) {
fun foo(param: String) = param.length
actual constructor(): this(42)
}
@@ -0,0 +1,8 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My(val a: Int) {
fun foo(param: String) = param.length
actual constructor(): this(42)
}
@@ -0,0 +1 @@
// DISABLE-ERRORS
@@ -0,0 +1,4 @@
// DISABLE-ERRORS
expect class <caret>My(a: Int) {
val text: String
}
@@ -0,0 +1,8 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My<caret> actual constructor(val a: Int) {
fun foo(param: String) = param.length
actual val text: String = "test"
constructor(): this(42)
}
@@ -0,0 +1,8 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My actual constructor(val a: Int) {
fun foo(param: String) = param.length
actual val text: String = "test"
constructor(): this(42)
}
@@ -1 +1 @@
<caret>expect inline class InlineMe(val x: Int)
expect inline class <caret>InlineMe(val x: Int)
@@ -1,4 +1,4 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual inline class Inline<caret>Me actual constructor(val x: Int)
actual inline class Inline<caret>Me(val x: Int)
@@ -1,4 +1,4 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual inline class InlineMe actual constructor(val x: Int)
actual inline class InlineMe actual constructor(actual val x: Int)
@@ -0,0 +1 @@
expect inline class <caret>InlineMe(val x: Int)
@@ -0,0 +1,4 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual inline class InlineMe<caret>(actual val x: Int)
@@ -0,0 +1,4 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual inline class InlineMe actual constructor(actual val x: Int)
@@ -0,0 +1,12 @@
expect class <caret>My {
class Middle {
class Inner() {
enum class MyEnum {
FOO, TEST;
fun check(): String
}
}
}
}
@@ -0,0 +1,14 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My<caret> {
class Middle() {
class Inner actual constructor() {
enum class MyEnum {
FOO, TEST;
actual fun check() = "42"
}
}
}
}
@@ -0,0 +1,14 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My<caret> {
actual class Middle() {
actual class Inner actual constructor() {
actual enum class MyEnum {
FOO, TEST;
actual fun check() = "42"
}
}
}
}
@@ -2,12 +2,12 @@
// DISABLE-ERRORS
actual class My {
actual inner class <caret>Nested(actual val s: String) {
actual inner class <caret>Nested actual constructor(actual val s: String) {
actual fun hello() = s
actual var ss = s
actual class OtherNested(actual var d: Double) {
actual class OtherNested actual constructor(actual var d: Double) {
actual val dd = d
}
}
@@ -2,12 +2,12 @@
// DISABLE-ERRORS
actual class My {
actual inner class <caret>Nested(actual val s: String) {
actual inner class <caret>Nested actual constructor(actual val s: String) {
actual fun hello() = s
actual var ss = s
actual class OtherNested(actual var d: Double) {
actual class OtherNested actual constructor(actual var d: Double) {
actual val dd = d
}
}
@@ -274,6 +274,11 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul
runTest("idea/testData/multiModuleQuickFix/expectAnnotation/");
}
@TestMetadata("expectAnnotation2")
public void testExpectAnnotation2() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectAnnotation2/");
}
@TestMetadata("expectClass")
public void testExpectClass() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClass/");
@@ -309,6 +314,11 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul
runTest("idea/testData/multiModuleQuickFix/expectClassWithAliases/");
}
@TestMetadata("expectClassWithConstructorWithParametersWithoutValVar")
public void testExpectClassWithConstructorWithParametersWithoutValVar() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClassWithConstructorWithParametersWithoutValVar/");
}
@TestMetadata("expectClassWithInitializer")
public void testExpectClassWithInitializer() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClassWithInitializer/");
@@ -319,6 +329,16 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul
runTest("idea/testData/multiModuleQuickFix/expectClassWithPlatformNested/");
}
@TestMetadata("expectClassWithSecondaryConstructor")
public void testExpectClassWithSecondaryConstructor() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClassWithSecondaryConstructor/");
}
@TestMetadata("expectClassWithSecondaryConstructor2")
public void testExpectClassWithSecondaryConstructor2() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClassWithSecondaryConstructor2/");
}
@TestMetadata("expectClassWithSupertype")
public void testExpectClassWithSupertype() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClassWithSupertype/");
@@ -399,6 +419,16 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul
runTest("idea/testData/multiModuleQuickFix/expectInlineClass/");
}
@TestMetadata("expectInlineClass2")
public void testExpectInlineClass2() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectInlineClass2/");
}
@TestMetadata("expectInnerEnum")
public void testExpectInnerEnum() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectInnerEnum/");
}
@TestMetadata("expectNestedClass")
public void testExpectNestedClass() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectNestedClass/");