diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt index 3df59d0f4e5..a76c1bd46e1 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt @@ -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, diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt index aec54fb5896..d5535c5c60a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt @@ -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 ) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt index 5d8ee988e3a..935efe5178f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt @@ -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( @@ -136,7 +126,7 @@ class CreateActualClassFix( actualModule: Module, actualPlatform: MultiTargetPlatform.Specific ) : CreateActualFix(klass, actualModule, actualPlatform, { project, element -> - generateClassOrObjectByExpectedClass(project, element) + generateClassOrObject(project, false, element) }) class CreateActualPropertyFix( @@ -145,7 +135,7 @@ class CreateActualPropertyFix( actualPlatform: MultiTargetPlatform.Specific ) : CreateActualFix(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(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? = 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 -} - diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt index cae66cb72a1..9a39c701b19 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt @@ -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( declaration: D, @@ -151,7 +138,7 @@ class CreateExpectedClassFix( outerExpectedClass: KtClassOrObject?, commonModule: Module ) : CreateExpectedFix(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(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(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 { - 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 -): 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 -): 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) { - typeParameters.forEach { typeParameter -> - typeParameter.upperBounds.forEach { upperBound -> - upperBound.checkAccessibility(accessibleClasses) - } - } -} - -private fun KotlinType.checkAccessibility(accessibleClasses: List) { - 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" -} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt index 566fb540113..006754d64b6 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt @@ -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 = emptyList(), + // If null, all class declarations are missed (so none from them exists) + missedDeclarations: List? = 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 = 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 = 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) { + typeParameters.forEach { typeParameter -> + typeParameter.upperBounds.forEach { upperBound -> + upperBound.checkAccessibility(accessibleClasses) + } + } +} + +private fun KotlinType.checkAccessibility(accessibleClasses: List) { + 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" +}