Create expect / actual refactoring: extract common part to utilities
This commit is contained in:
+1
-21
@@ -119,32 +119,12 @@ interface OverrideMemberChooserObject : ClassMember {
|
||||
}
|
||||
}
|
||||
|
||||
fun OverrideMemberChooserObject.generateTopLevelActual(
|
||||
project: Project,
|
||||
copyDoc: Boolean
|
||||
) = generateMember(null, copyDoc, project, forceActual = true, forceExpect = false)
|
||||
|
||||
fun OverrideMemberChooserObject.generateActualMember(
|
||||
targetClass: KtClassOrObject,
|
||||
copyDoc: Boolean
|
||||
) = generateMember(targetClass, copyDoc, targetClass.project, forceActual = true, forceExpect = false)
|
||||
|
||||
fun OverrideMemberChooserObject.generateTopLevelExpect(
|
||||
project: Project,
|
||||
copyDoc: Boolean
|
||||
) = generateMember(null, copyDoc, project, forceActual = false, forceExpect = true)
|
||||
|
||||
fun OverrideMemberChooserObject.generateExpectMember(
|
||||
targetClass: KtClassOrObject,
|
||||
copyDoc: Boolean
|
||||
) = generateMember(targetClass, copyDoc, targetClass.project, forceActual = false, forceExpect = true)
|
||||
|
||||
fun OverrideMemberChooserObject.generateMember(
|
||||
targetClass: KtClassOrObject,
|
||||
copyDoc: Boolean
|
||||
) = generateMember(targetClass, copyDoc, targetClass.project, forceActual = false, forceExpect = false)
|
||||
|
||||
private fun OverrideMemberChooserObject.generateMember(
|
||||
fun OverrideMemberChooserObject.generateMember(
|
||||
targetClass: KtClassOrObject?,
|
||||
copyDoc: Boolean,
|
||||
project: Project,
|
||||
|
||||
@@ -52,8 +52,8 @@ class AddActualFix(
|
||||
val missedDeclarations = missedDeclarationPointers.mapNotNull { it.element }
|
||||
if (missedDeclarations.isEmpty()) return
|
||||
val factory = KtPsiFactory(element)
|
||||
val pureActualClass = factory.generateClassOrObjectByExpectedClass(
|
||||
project, expectedClass,
|
||||
val pureActualClass = factory.generateClassOrObject(
|
||||
project, false, expectedClass,
|
||||
missedDeclarations = missedDeclarations
|
||||
)
|
||||
|
||||
|
||||
@@ -24,31 +24,21 @@ import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
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.ModuleSourceInfo
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.BodyType.EMPTY_OR_TEMPLATE
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.BodyType.NO_BODY
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.Companion.create
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.generateActualMember
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.generateTopLevelActual
|
||||
import org.jetbrains.kotlin.idea.core.toDescriptor
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
|
||||
import org.jetbrains.kotlin.idea.util.actualsForExpected
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.MultiTargetPlatform
|
||||
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker
|
||||
import org.jetbrains.kotlin.resolve.getMultiTargetPlatform
|
||||
|
||||
sealed class CreateActualFix<out D : KtNamedDeclaration>(
|
||||
@@ -136,7 +126,7 @@ class CreateActualClassFix(
|
||||
actualModule: Module,
|
||||
actualPlatform: MultiTargetPlatform.Specific
|
||||
) : CreateActualFix<KtClassOrObject>(klass, actualModule, actualPlatform, { project, element ->
|
||||
generateClassOrObjectByExpectedClass(project, element)
|
||||
generateClassOrObject(project, false, element)
|
||||
})
|
||||
|
||||
class CreateActualPropertyFix(
|
||||
@@ -145,7 +135,7 @@ class CreateActualPropertyFix(
|
||||
actualPlatform: MultiTargetPlatform.Specific
|
||||
) : CreateActualFix<KtProperty>(property, actualModule, actualPlatform, { project, element ->
|
||||
val descriptor = element.toDescriptor() as? PropertyDescriptor
|
||||
descriptor?.let { generateProperty(project, element, descriptor) }
|
||||
descriptor?.let { generateProperty(project, false, element, descriptor) }
|
||||
})
|
||||
|
||||
class CreateActualFunctionFix(
|
||||
@@ -154,120 +144,6 @@ class CreateActualFunctionFix(
|
||||
actualPlatform: MultiTargetPlatform.Specific
|
||||
) : CreateActualFix<KtFunction>(function, actualModule, actualPlatform, { project, element ->
|
||||
val descriptor = element.toDescriptor() as? FunctionDescriptor
|
||||
descriptor?.let { generateFunction(project, element, descriptor) }
|
||||
descriptor?.let { generateFunction(project, false, element, descriptor) }
|
||||
})
|
||||
|
||||
internal fun KtPsiFactory.generateClassOrObjectByExpectedClass(
|
||||
project: Project,
|
||||
expectedClass: KtClassOrObject,
|
||||
// If null, all expect class declarations are missed (so none from them exists)
|
||||
missedDeclarations: List<KtDeclaration>? = null
|
||||
): KtClassOrObject {
|
||||
fun areCompatible(first: KtFunction, second: KtFunction) =
|
||||
first.valueParameters.size == second.valueParameters.size &&
|
||||
first.valueParameters.zip(second.valueParameters).all { (firstParam, secondParam) ->
|
||||
firstParam.name == secondParam.name && firstParam.typeReference?.text == secondParam.typeReference?.text
|
||||
}
|
||||
|
||||
fun KtDeclaration.exists() =
|
||||
missedDeclarations != null && missedDeclarations.none {
|
||||
name == it.name && when (this) {
|
||||
is KtConstructor<*> -> it is KtConstructor<*> && areCompatible(this, it)
|
||||
is KtNamedFunction -> it is KtNamedFunction && areCompatible(this, it)
|
||||
is KtProperty -> it is KtProperty || it is KtParameter && it.hasValOrVar()
|
||||
else -> this.javaClass == it.javaClass
|
||||
}
|
||||
}
|
||||
|
||||
val actualClass = createClassHeaderCopyByText(expectedClass)
|
||||
|
||||
val context = expectedClass.analyzeWithContent()
|
||||
actualClass.superTypeListEntries.zip(expectedClass.superTypeListEntries).forEach { (actualEntry, expectedEntry) ->
|
||||
if (actualEntry !is KtSuperTypeEntry) return@forEach
|
||||
val superType = context[BindingContext.TYPE, expectedEntry.typeReference]
|
||||
val superClassDescriptor = superType?.constructor?.declarationDescriptor as? ClassDescriptor ?: return@forEach
|
||||
if (superClassDescriptor.kind == ClassKind.CLASS || superClassDescriptor.kind == ClassKind.ENUM_CLASS) {
|
||||
actualEntry.replace(createSuperTypeCallEntry("${actualEntry.typeReference!!.text}()"))
|
||||
}
|
||||
}
|
||||
if (actualClass.isAnnotation()) {
|
||||
actualClass.annotationEntries.zip(expectedClass.annotationEntries).forEach { (actualEntry, expectedEntry) ->
|
||||
val annotationDescriptor = context.get(BindingContext.ANNOTATION, expectedEntry) ?: return@forEach
|
||||
if (annotationDescriptor.fqName in forbiddenAnnotationFqNames) {
|
||||
actualEntry.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (actualClass !is KtEnumEntry) {
|
||||
actualClass.addModifier(KtTokens.ACTUAL_KEYWORD)
|
||||
}
|
||||
|
||||
declLoop@ for (expectedDeclaration in expectedClass.declarations.filter { !it.exists() }) {
|
||||
val descriptor = expectedDeclaration.toDescriptor() ?: continue
|
||||
val actualDeclaration: KtDeclaration = when (expectedDeclaration) {
|
||||
is KtClassOrObject -> {
|
||||
generateClassOrObjectByExpectedClass(project, expectedDeclaration)
|
||||
}
|
||||
is KtCallableDeclaration -> {
|
||||
when (expectedDeclaration) {
|
||||
is KtFunction -> generateFunction(project, expectedDeclaration, descriptor as FunctionDescriptor, actualClass)
|
||||
is KtProperty -> generateProperty(project, expectedDeclaration, descriptor as PropertyDescriptor, actualClass)
|
||||
else -> continue@declLoop
|
||||
}
|
||||
}
|
||||
else -> continue@declLoop
|
||||
}
|
||||
actualClass.addDeclaration(actualDeclaration)
|
||||
}
|
||||
val expectedPrimaryConstructor = expectedClass.primaryConstructor
|
||||
if (actualClass is KtClass && expectedPrimaryConstructor?.exists() == false) {
|
||||
val descriptor = expectedPrimaryConstructor.toDescriptor()
|
||||
if (descriptor is FunctionDescriptor) {
|
||||
val actualPrimaryConstructor = generateFunction(project, expectedPrimaryConstructor, descriptor, actualClass)
|
||||
actualClass.createPrimaryConstructorIfAbsent().replace(actualPrimaryConstructor)
|
||||
}
|
||||
}
|
||||
|
||||
return actualClass
|
||||
}
|
||||
|
||||
private val forbiddenAnnotationFqNames = setOf(
|
||||
ExpectedActualDeclarationChecker.OPTIONAL_EXPECTATION_FQ_NAME,
|
||||
FqName("kotlin.ExperimentalMultiplatform"),
|
||||
ExperimentalUsageChecker.USE_EXPERIMENTAL_FQ_NAME
|
||||
)
|
||||
|
||||
private fun generateFunction(
|
||||
project: Project,
|
||||
expectedFunction: KtFunction,
|
||||
descriptor: FunctionDescriptor,
|
||||
targetClass: KtClassOrObject? = null
|
||||
): KtFunction {
|
||||
val memberChooserObject = create(
|
||||
expectedFunction, descriptor, descriptor,
|
||||
if (descriptor.modality == Modality.ABSTRACT) NO_BODY else EMPTY_OR_TEMPLATE
|
||||
)
|
||||
return if (targetClass != null) {
|
||||
memberChooserObject.generateActualMember(targetClass = targetClass, copyDoc = true)
|
||||
} else {
|
||||
memberChooserObject.generateTopLevelActual(project = project, copyDoc = true)
|
||||
} as KtFunction
|
||||
}
|
||||
|
||||
private fun generateProperty(
|
||||
project: Project,
|
||||
expectedProperty: KtProperty,
|
||||
descriptor: PropertyDescriptor,
|
||||
targetClass: KtClassOrObject? = null
|
||||
): KtProperty {
|
||||
val memberChooserObject = create(
|
||||
expectedProperty, descriptor, descriptor,
|
||||
if (descriptor.modality == Modality.ABSTRACT) NO_BODY else EMPTY_OR_TEMPLATE
|
||||
)
|
||||
return if (targetClass != null) {
|
||||
memberChooserObject.generateActualMember(targetClass = targetClass, copyDoc = true)
|
||||
} else {
|
||||
memberChooserObject.generateTopLevelActual(project = project, copyDoc = true)
|
||||
} as KtProperty
|
||||
}
|
||||
|
||||
|
||||
@@ -12,37 +12,24 @@ import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
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.implementedDescriptors
|
||||
import org.jetbrains.kotlin.idea.caches.project.implementedModules
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.*
|
||||
import org.jetbrains.kotlin.idea.core.toDescriptor
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory
|
||||
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.idea.util.hasDeclarationOf
|
||||
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.js.descriptorUtils.getJetTypeFqName
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
||||
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.MultiTargetPlatform
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.getMultiTargetPlatform
|
||||
import org.jetbrains.kotlin.types.AbbreviatedType
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
sealed class CreateExpectedFix<out D : KtNamedDeclaration>(
|
||||
declaration: D,
|
||||
@@ -151,7 +138,7 @@ class CreateExpectedClassFix(
|
||||
outerExpectedClass: KtClassOrObject?,
|
||||
commonModule: Module
|
||||
) : CreateExpectedFix<KtClassOrObject>(klass, outerExpectedClass, commonModule, { project, element ->
|
||||
generateClassOrObjectByActualClass(project, element, listOfNotNull(outerExpectedClass))
|
||||
generateClassOrObject(project, true, element, listOfNotNull(outerExpectedClass))
|
||||
})
|
||||
|
||||
class CreateExpectedPropertyFix(
|
||||
@@ -160,7 +147,7 @@ class CreateExpectedPropertyFix(
|
||||
commonModule: Module
|
||||
) : CreateExpectedFix<KtNamedDeclaration>(property, targetExpectedClass, commonModule, { project, element ->
|
||||
val descriptor = element.toDescriptor() as? PropertyDescriptor
|
||||
descriptor?.let { generateProperty(project, element, descriptor, targetExpectedClass, emptyList()) }
|
||||
descriptor?.let { generateProperty(project, true, element, descriptor, targetExpectedClass) }
|
||||
})
|
||||
|
||||
class CreateExpectedFunctionFix(
|
||||
@@ -169,160 +156,6 @@ class CreateExpectedFunctionFix(
|
||||
commonModule: Module
|
||||
) : CreateExpectedFix<KtFunction>(function, targetExpectedClass, commonModule, { project, element ->
|
||||
val descriptor = element.toDescriptor() as? FunctionDescriptor
|
||||
descriptor?.let { generateFunction(project, element, descriptor, targetExpectedClass, emptyList()) }
|
||||
descriptor?.let { generateFunction(project, true, element, descriptor, targetExpectedClass) }
|
||||
})
|
||||
|
||||
private fun KtPsiFactory.generateClassOrObjectByActualClass(
|
||||
project: Project,
|
||||
actualClass: KtClassOrObject,
|
||||
outerExpectedClasses: List<KtClassOrObject>
|
||||
): KtClassOrObject {
|
||||
val expectedClass = createClassHeaderCopyByText(actualClass)
|
||||
|
||||
val context = actualClass.analyzeWithContent()
|
||||
expectedClass.superTypeListEntries.zip(actualClass.superTypeListEntries).forEach { (expectedEntry, actualEntry) ->
|
||||
if (expectedEntry !is KtSuperTypeCallEntry) return@forEach
|
||||
val superType = context[BindingContext.TYPE, actualEntry.typeReference]
|
||||
val superClassDescriptor = superType?.constructor?.declarationDescriptor as? ClassDescriptor ?: return@forEach
|
||||
if (superClassDescriptor.kind == ClassKind.CLASS || superClassDescriptor.kind == ClassKind.ENUM_CLASS) {
|
||||
expectedEntry.replace(createSuperTypeEntry(expectedEntry.typeReference!!.text))
|
||||
}
|
||||
}
|
||||
if (outerExpectedClasses.isEmpty()) {
|
||||
expectedClass.addModifier(KtTokens.EXPECT_KEYWORD)
|
||||
} else {
|
||||
expectedClass.makeNotActual()
|
||||
}
|
||||
expectedClass.removeModifier(KtTokens.DATA_KEYWORD)
|
||||
|
||||
declLoop@ for (actualDeclaration in actualClass.declarations) {
|
||||
val descriptor = actualDeclaration.toDescriptor() ?: continue
|
||||
if (!actualDeclaration.isEffectivelyActual()) continue@declLoop
|
||||
val expectedDeclaration: KtDeclaration = when (actualDeclaration) {
|
||||
is KtClassOrObject -> {
|
||||
generateClassOrObjectByActualClass(project, actualDeclaration, outerExpectedClasses + expectedClass)
|
||||
}
|
||||
is KtCallableDeclaration -> {
|
||||
when (actualDeclaration) {
|
||||
is KtFunction ->
|
||||
generateFunction(project, actualDeclaration, descriptor as FunctionDescriptor, expectedClass, outerExpectedClasses)
|
||||
is KtProperty ->
|
||||
generateProperty(project, actualDeclaration, descriptor as PropertyDescriptor, expectedClass, outerExpectedClasses)
|
||||
else -> continue@declLoop
|
||||
}
|
||||
}
|
||||
else -> continue@declLoop
|
||||
}
|
||||
expectedClass.addDeclaration(expectedDeclaration)
|
||||
}
|
||||
if (!actualClass.isAnnotation()) {
|
||||
for (actualProperty in actualClass.primaryConstructorParameters) {
|
||||
if (!actualProperty.hasValOrVar() || !actualProperty.hasActualModifier()) continue
|
||||
val descriptor = actualProperty.toDescriptor() as? PropertyDescriptor ?: continue
|
||||
val expectedProperty =
|
||||
generateProperty(project, actualProperty, descriptor, expectedClass, outerExpectedClasses)
|
||||
expectedClass.addDeclaration(expectedProperty)
|
||||
}
|
||||
}
|
||||
val actualPrimaryConstructor = actualClass.primaryConstructor
|
||||
if (expectedClass is KtClass && actualPrimaryConstructor != null) {
|
||||
val descriptor = actualPrimaryConstructor.toDescriptor()
|
||||
if (descriptor is FunctionDescriptor) {
|
||||
val expectedPrimaryConstructor =
|
||||
generateFunction(project, actualPrimaryConstructor, descriptor, expectedClass, outerExpectedClasses)
|
||||
expectedClass.createPrimaryConstructorIfAbsent().replace(expectedPrimaryConstructor)
|
||||
}
|
||||
}
|
||||
|
||||
return expectedClass
|
||||
}
|
||||
|
||||
private fun generateFunction(
|
||||
project: Project,
|
||||
actualFunction: KtFunction,
|
||||
descriptor: FunctionDescriptor,
|
||||
targetClass: KtClassOrObject?,
|
||||
outerExpectedClasses: List<KtClassOrObject>
|
||||
): KtFunction {
|
||||
val accessibleClasses = outerExpectedClasses + listOfNotNull(targetClass)
|
||||
descriptor.checkTypeParameterBoundsAccessibility(accessibleClasses)
|
||||
descriptor.returnType?.checkAccessibility(accessibleClasses)
|
||||
descriptor.valueParameters.forEach {
|
||||
it.type.checkAccessibility(accessibleClasses)
|
||||
}
|
||||
val memberChooserObject = OverrideMemberChooserObject.create(
|
||||
actualFunction, descriptor, descriptor,
|
||||
OverrideMemberChooserObject.BodyType.NO_BODY
|
||||
)
|
||||
return if (targetClass != null) {
|
||||
memberChooserObject.generateExpectMember(targetClass = targetClass, copyDoc = true)
|
||||
} else {
|
||||
memberChooserObject.generateTopLevelExpect(project = project, copyDoc = true)
|
||||
} as KtFunction
|
||||
}
|
||||
|
||||
private fun generateProperty(
|
||||
project: Project,
|
||||
actualProperty: KtNamedDeclaration,
|
||||
descriptor: PropertyDescriptor,
|
||||
targetClass: KtClassOrObject?,
|
||||
outerExpectedClasses: List<KtClassOrObject>
|
||||
): KtProperty {
|
||||
val accessibleClasses = outerExpectedClasses + listOfNotNull(targetClass)
|
||||
descriptor.checkTypeParameterBoundsAccessibility(accessibleClasses)
|
||||
descriptor.type.checkAccessibility(accessibleClasses)
|
||||
val memberChooserObject = OverrideMemberChooserObject.create(
|
||||
actualProperty, descriptor, descriptor,
|
||||
OverrideMemberChooserObject.BodyType.NO_BODY
|
||||
)
|
||||
return if (targetClass != null) {
|
||||
memberChooserObject.generateExpectMember(targetClass = targetClass, copyDoc = true)
|
||||
} else {
|
||||
memberChooserObject.generateTopLevelExpect(project = project, copyDoc = true)
|
||||
} as KtProperty
|
||||
}
|
||||
|
||||
private fun CallableMemberDescriptor.checkTypeParameterBoundsAccessibility(accessibleClasses: List<KtClassOrObject>) {
|
||||
typeParameters.forEach { typeParameter ->
|
||||
typeParameter.upperBounds.forEach { upperBound ->
|
||||
upperBound.checkAccessibility(accessibleClasses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KotlinType.checkAccessibility(accessibleClasses: List<KtClassOrObject>) {
|
||||
for (argument in arguments) {
|
||||
if (argument.isStarProjection) continue
|
||||
argument.type.checkAccessibility(accessibleClasses)
|
||||
}
|
||||
val classifierDescriptor = constructor.declarationDescriptor as? ClassifierDescriptorWithTypeParameters ?: return
|
||||
val moduleDescriptor = classifierDescriptor.module
|
||||
if (moduleDescriptor.getMultiTargetPlatform() == MultiTargetPlatform.Common) {
|
||||
// Common classes are Ok
|
||||
return
|
||||
}
|
||||
val implementedDescriptors = moduleDescriptor.implementedDescriptors
|
||||
if (implementedDescriptors.isEmpty()) {
|
||||
// This happens now if we are not in sources, in this case yet we cannot answer question about accessibility
|
||||
// Very rude check about JDK classes
|
||||
if (!classifierDescriptor.fqNameSafe.toString().startsWith("java.")) return
|
||||
}
|
||||
if (implementedDescriptors.any { it.hasDeclarationOf(classifierDescriptor) }) {
|
||||
// Platform classes with expected class are also Ok
|
||||
return
|
||||
}
|
||||
accessibleClasses.forEach {
|
||||
if (classifierDescriptor.name == it.nameAsName) return
|
||||
}
|
||||
if (this is AbbreviatedType) {
|
||||
// For type aliases without expected class, check expansions instead
|
||||
expandedType.checkAccessibility(accessibleClasses)
|
||||
} else {
|
||||
throw KotlinTypeInaccessibleException(this)
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinTypeInaccessibleException(val type: KotlinType) : Exception() {
|
||||
override val message: String
|
||||
get() = "Type ${type.getJetTypeFqName(true)} is not accessible from common code"
|
||||
}
|
||||
@@ -6,13 +6,37 @@
|
||||
package org.jetbrains.kotlin.idea.quickfix.expectactual
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.JavaDirectoryService
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.caches.project.implementedDescriptors
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.findOrCreateDirectoryForPackage
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.*
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.BodyType.EMPTY_OR_TEMPLATE
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.BodyType.NO_BODY
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.Companion.create
|
||||
import org.jetbrains.kotlin.idea.core.toDescriptor
|
||||
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.isEffectivelyActual
|
||||
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.MultiTargetPlatform
|
||||
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.getMultiTargetPlatform
|
||||
import org.jetbrains.kotlin.types.AbbreviatedType
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
fun createFileForDeclaration(module: Module, declaration: KtNamedDeclaration): KtFile? {
|
||||
val fileName = declaration.name ?: return null
|
||||
@@ -75,3 +99,219 @@ fun KtNamedDeclaration?.getTypeDescription(): String = when (this) {
|
||||
is KtFunction -> "function"
|
||||
else -> "declaration"
|
||||
}
|
||||
|
||||
internal fun KtPsiFactory.generateClassOrObject(
|
||||
project: Project,
|
||||
generateExpectClass: Boolean,
|
||||
originalClass: KtClassOrObject,
|
||||
outerClasses: List<KtClassOrObject> = emptyList(),
|
||||
// If null, all class declarations are missed (so none from them exists)
|
||||
missedDeclarations: List<KtDeclaration>? = null
|
||||
): KtClassOrObject {
|
||||
fun areCompatible(first: KtFunction, second: KtFunction) =
|
||||
first.valueParameters.size == second.valueParameters.size &&
|
||||
first.valueParameters.zip(second.valueParameters).all { (firstParam, secondParam) ->
|
||||
firstParam.name == secondParam.name && firstParam.typeReference?.text == secondParam.typeReference?.text
|
||||
}
|
||||
|
||||
fun KtDeclaration.exists() =
|
||||
missedDeclarations != null && missedDeclarations.none {
|
||||
name == it.name && when (this) {
|
||||
is KtConstructor<*> -> it is KtConstructor<*> && areCompatible(this, it)
|
||||
is KtNamedFunction -> it is KtNamedFunction && areCompatible(this, it)
|
||||
is KtProperty -> it is KtProperty || it is KtParameter && it.hasValOrVar()
|
||||
else -> this.javaClass == it.javaClass
|
||||
}
|
||||
}
|
||||
|
||||
val generatedClass = createClassHeaderCopyByText(originalClass)
|
||||
val context = originalClass.analyzeWithContent()
|
||||
|
||||
generatedClass.superTypeListEntries.zip(originalClass.superTypeListEntries).forEach { (generatedEntry, originalEntry) ->
|
||||
if (generateExpectClass) {
|
||||
if (generatedEntry !is KtSuperTypeCallEntry) return@forEach
|
||||
} else {
|
||||
if (generatedEntry !is KtSuperTypeEntry) return@forEach
|
||||
}
|
||||
val superType = context[BindingContext.TYPE, originalEntry.typeReference]
|
||||
val superClassDescriptor = superType?.constructor?.declarationDescriptor as? ClassDescriptor ?: return@forEach
|
||||
if (superClassDescriptor.kind == ClassKind.CLASS || superClassDescriptor.kind == ClassKind.ENUM_CLASS) {
|
||||
val newGeneratedEntry = if (generateExpectClass) {
|
||||
createSuperTypeEntry(generatedEntry.typeReference!!.text)
|
||||
} else {
|
||||
createSuperTypeCallEntry(generatedEntry.typeReference!!.text + "()")
|
||||
}
|
||||
generatedEntry.replace(newGeneratedEntry)
|
||||
}
|
||||
}
|
||||
if (generatedClass.isAnnotation()) {
|
||||
generatedClass.annotationEntries.zip(originalClass.annotationEntries).forEach { (generatedEntry, originalEntry) ->
|
||||
val annotationDescriptor = context.get(BindingContext.ANNOTATION, originalEntry) ?: return@forEach
|
||||
if (annotationDescriptor.fqName in forbiddenAnnotationFqNames) {
|
||||
generatedEntry.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (generateExpectClass) {
|
||||
if (outerClasses.isEmpty()) {
|
||||
generatedClass.addModifier(KtTokens.EXPECT_KEYWORD)
|
||||
} else {
|
||||
generatedClass.makeNotActual()
|
||||
}
|
||||
generatedClass.removeModifier(KtTokens.DATA_KEYWORD)
|
||||
} else {
|
||||
if (generatedClass !is KtEnumEntry) {
|
||||
generatedClass.addModifier(KtTokens.ACTUAL_KEYWORD)
|
||||
}
|
||||
}
|
||||
declLoop@ for (originalDeclaration in originalClass.declarations.filter { !it.exists() }) {
|
||||
val descriptor = originalDeclaration.toDescriptor() ?: continue
|
||||
if (generateExpectClass && !originalDeclaration.isEffectivelyActual()) continue
|
||||
val generatedDeclaration: KtDeclaration = when (originalDeclaration) {
|
||||
is KtClassOrObject -> {
|
||||
generateClassOrObject(project, generateExpectClass, originalDeclaration, outerClasses + generatedClass)
|
||||
}
|
||||
is KtCallableDeclaration -> {
|
||||
when (originalDeclaration) {
|
||||
is KtFunction -> generateFunction(
|
||||
project, generateExpectClass, originalDeclaration, descriptor as FunctionDescriptor, generatedClass, outerClasses
|
||||
)
|
||||
is KtProperty -> generateProperty(
|
||||
project, generateExpectClass, originalDeclaration, descriptor as PropertyDescriptor, generatedClass, outerClasses
|
||||
)
|
||||
else -> continue@declLoop
|
||||
}
|
||||
}
|
||||
else -> continue@declLoop
|
||||
}
|
||||
generatedClass.addDeclaration(generatedDeclaration)
|
||||
}
|
||||
if (!originalClass.isAnnotation()) {
|
||||
for (originalProperty in originalClass.primaryConstructorParameters) {
|
||||
if (!originalProperty.hasValOrVar() || !originalProperty.hasActualModifier()) continue
|
||||
val descriptor = originalProperty.toDescriptor() as? PropertyDescriptor ?: continue
|
||||
val generatedProperty = generateProperty(
|
||||
project, generateExpectClass, originalProperty, descriptor, generatedClass, outerClasses
|
||||
)
|
||||
generatedClass.addDeclaration(generatedProperty)
|
||||
}
|
||||
}
|
||||
val originalPrimaryConstructor = originalClass.primaryConstructor
|
||||
if (generatedClass is KtClass && originalPrimaryConstructor != null && !originalPrimaryConstructor.exists()) {
|
||||
val descriptor = originalPrimaryConstructor.toDescriptor()
|
||||
if (descriptor is FunctionDescriptor) {
|
||||
val expectedPrimaryConstructor = generateFunction(
|
||||
project, generateExpectClass, originalPrimaryConstructor, descriptor, generatedClass, outerClasses
|
||||
)
|
||||
generatedClass.createPrimaryConstructorIfAbsent().replace(expectedPrimaryConstructor)
|
||||
}
|
||||
}
|
||||
|
||||
return generatedClass
|
||||
}
|
||||
|
||||
private val forbiddenAnnotationFqNames = setOf(
|
||||
ExpectedActualDeclarationChecker.OPTIONAL_EXPECTATION_FQ_NAME,
|
||||
FqName("kotlin.ExperimentalMultiplatform"),
|
||||
ExperimentalUsageChecker.USE_EXPERIMENTAL_FQ_NAME
|
||||
)
|
||||
|
||||
internal fun generateFunction(
|
||||
project: Project,
|
||||
generateExpect: Boolean,
|
||||
originalFunction: KtFunction,
|
||||
descriptor: FunctionDescriptor,
|
||||
generatedClass: KtClassOrObject? = null,
|
||||
outerClasses: List<KtClassOrObject> = emptyList()
|
||||
): KtFunction {
|
||||
if (generateExpect) {
|
||||
val accessibleClasses = outerClasses + listOfNotNull(generatedClass)
|
||||
descriptor.checkTypeParameterBoundsAccessibility(accessibleClasses)
|
||||
descriptor.returnType?.checkAccessibility(accessibleClasses)
|
||||
descriptor.valueParameters.forEach {
|
||||
it.type.checkAccessibility(accessibleClasses)
|
||||
}
|
||||
}
|
||||
val memberChooserObject = create(
|
||||
originalFunction, descriptor, descriptor,
|
||||
if (generateExpect || descriptor.modality == Modality.ABSTRACT) NO_BODY else EMPTY_OR_TEMPLATE
|
||||
)
|
||||
return memberChooserObject.generateMember(
|
||||
targetClass = generatedClass,
|
||||
copyDoc = true,
|
||||
project = project,
|
||||
forceActual = !generateExpect,
|
||||
forceExpect = generateExpect
|
||||
) as KtFunction
|
||||
}
|
||||
|
||||
internal fun generateProperty(
|
||||
project: Project,
|
||||
generateExpect: Boolean,
|
||||
originalProperty: KtNamedDeclaration,
|
||||
descriptor: PropertyDescriptor,
|
||||
generatedClass: KtClassOrObject? = null,
|
||||
outerClasses: List<KtClassOrObject> = emptyList()
|
||||
): KtProperty {
|
||||
if (generateExpect) {
|
||||
val accessibleClasses = outerClasses + listOfNotNull(generatedClass)
|
||||
descriptor.checkTypeParameterBoundsAccessibility(accessibleClasses)
|
||||
descriptor.type.checkAccessibility(accessibleClasses)
|
||||
}
|
||||
val memberChooserObject = create(
|
||||
originalProperty, descriptor, descriptor,
|
||||
if (generateExpect || descriptor.modality == Modality.ABSTRACT) NO_BODY else EMPTY_OR_TEMPLATE
|
||||
)
|
||||
return memberChooserObject.generateMember(
|
||||
targetClass = generatedClass,
|
||||
copyDoc = true,
|
||||
project = project,
|
||||
forceActual = !generateExpect,
|
||||
forceExpect = generateExpect
|
||||
) as KtProperty
|
||||
}
|
||||
|
||||
private fun CallableMemberDescriptor.checkTypeParameterBoundsAccessibility(accessibleClasses: List<KtClassOrObject>) {
|
||||
typeParameters.forEach { typeParameter ->
|
||||
typeParameter.upperBounds.forEach { upperBound ->
|
||||
upperBound.checkAccessibility(accessibleClasses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KotlinType.checkAccessibility(accessibleClasses: List<KtClassOrObject>) {
|
||||
for (argument in arguments) {
|
||||
if (argument.isStarProjection) continue
|
||||
argument.type.checkAccessibility(accessibleClasses)
|
||||
}
|
||||
val classifierDescriptor = constructor.declarationDescriptor as? ClassifierDescriptorWithTypeParameters ?: return
|
||||
val moduleDescriptor = classifierDescriptor.module
|
||||
if (moduleDescriptor.getMultiTargetPlatform() == MultiTargetPlatform.Common) {
|
||||
// Common classes are Ok
|
||||
return
|
||||
}
|
||||
val implementedDescriptors = moduleDescriptor.implementedDescriptors
|
||||
if (implementedDescriptors.isEmpty()) {
|
||||
// This happens now if we are not in sources, in this case yet we cannot answer question about accessibility
|
||||
// Very rude check about JDK classes
|
||||
if (!classifierDescriptor.fqNameSafe.toString().startsWith("java.")) return
|
||||
}
|
||||
if (implementedDescriptors.any { it.hasDeclarationOf(classifierDescriptor) }) {
|
||||
// Platform classes with expected class are also Ok
|
||||
return
|
||||
}
|
||||
accessibleClasses.forEach {
|
||||
if (classifierDescriptor.name == it.nameAsName) return
|
||||
}
|
||||
if (this is AbbreviatedType) {
|
||||
// For type aliases without expected class, check expansions instead
|
||||
expandedType.checkAccessibility(accessibleClasses)
|
||||
} else {
|
||||
throw KotlinTypeInaccessibleException(this)
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinTypeInaccessibleException(val type: KotlinType) : Exception() {
|
||||
override val message: String
|
||||
get() = "Type ${type.getJetTypeFqName(true)} is not accessible from common code"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user