Create expect / actual refactoring: extract common part to utilities

This commit is contained in:
Mikhail Glukhikh
2018-11-21 16:33:18 +03:00
parent 17e0e529e0
commit 222939efe7
5 changed files with 253 additions and 324 deletions
@@ -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"
}