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 7f4d4c6d025..2873fd6cfa4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt @@ -57,7 +57,7 @@ class AddActualFix( for (missedDeclaration in missedDeclarationPointers.mapNotNull { it.element }) { val actualDeclaration = when (missedDeclaration) { - is KtClassOrObject -> factory.generateClassOrObject(project, false, missedDeclaration, listOf(element)) + is KtClassOrObject -> factory.generateClassOrObject(project, false, missedDeclaration, outerClasses = listOf(element)) is KtFunction -> missedDeclaration.toDescriptor()?.safeAs()?.let { generateFunction(project, false, missedDeclaration, it, element) } 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 9e92bfebb66..ba35d2d607a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt @@ -137,32 +137,30 @@ class CreateExpectedClassFix( val prefix = klass.fqName?.asString()?.plus(".") ?: "" chooseMembers(project, membersForSelection, prefix) ?: return@block null } - }.plus(members.filter(KtNamedDeclaration::isAlwaysActual)).let { selectedElements -> - val selectedClasses = findClasses(selectedElements + klass, commonModule) - if (selectedClasses != existingClasses) { - if (!element.checkTypeAccessibilityInModule(commonModule, selectedClasses)) { - showUnknownTypesError(element) - return@block null - } - - val (resultDeclarations, withErrors) = selectedElements.partition { - it.checkTypeAccessibilityInModule(commonModule, selectedClasses) - } - if (!showUnknownTypesDialog(project, withErrors)) return@block null - resultDeclarations - } else - selectedElements - } + }.plus(members.filter(KtNamedDeclaration::isAlwaysActual)) + val selectedClasses = findClasses(selectedElements + klass, commonModule) + val resultDeclarations = if (selectedClasses != existingClasses) { + if (!element.checkTypeAccessibilityInModule(commonModule, selectedClasses)) { + showUnknownTypesError(element) + return@block null + } + val (resultDeclarations, withErrors) = selectedElements.partition { + it.checkTypeAccessibilityInModule(commonModule, selectedClasses) + } + if (!showUnknownTypesDialog(project, withErrors)) return@block null + resultDeclarations + } else + selectedElements if (originalElements.isNotEmpty()) { project.executeWriteCommand("Repair actual members") { - repairActualModifiers(originalElements, selectedElements.toSet()) + repairActualModifiers(originalElements, resultDeclarations.toSet()) } } - generateClassOrObject(project, true, element, listOfNotNull(outerExpectedClass)) + generateClassOrObject(project, true, element, commonModule, listOfNotNull(outerExpectedClass), selectedClasses) }) private tailrec fun findClasses(elements: List, module: Module): HashSet { 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 6e1caed5650..f704d180b1d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt @@ -9,10 +9,11 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.psi.JavaDirectoryService import com.intellij.psi.search.GlobalSearchScope -import org.jetbrains.kotlin.backend.common.descriptors.allParameters +import org.jetbrains.kotlin.backend.common.descriptors.explicitParameters import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.project.implementedDescriptors import org.jetbrains.kotlin.idea.caches.project.isTestModule +import org.jetbrains.kotlin.idea.caches.project.toDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent import org.jetbrains.kotlin.idea.codeInsight.shorten.addToBeShortenedDescendantsToWaitingSet import org.jetbrains.kotlin.idea.core.KotlinNameSuggester @@ -31,6 +32,7 @@ import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex import org.jetbrains.kotlin.idea.util.* import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName @@ -115,29 +117,22 @@ internal fun KtPsiFactory.generateClassOrObject( project: Project, generateExpectClass: Boolean, originalClass: KtClassOrObject, - outerClasses: List = emptyList() + targetModule: Module? = null, + outerClasses: List = emptyList(), + existingFqNames: Set = emptySet() ): KtClassOrObject { val generatedClass = createClassHeaderCopyByText(originalClass) val context = originalClass.analyzeWithContent() + repairSuperTypeList( + generatedClass, + originalClass, + generateExpectClass, + project, + targetModule, + existingFqNames, + context + ) - 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 entryText = IdeDescriptorRenderers.SOURCE_CODE.renderType(superType) - val newGeneratedEntry = if (generateExpectClass) { - createSuperTypeEntry(entryText) - } else { - createSuperTypeCallEntry("$entryText()") - } - generatedEntry.replace(newGeneratedEntry).safeAs()?.addToBeShortenedDescendantsToWaitingSet() - } - } if (generatedClass.isAnnotation()) { generatedClass.annotationEntries.zip(originalClass.annotationEntries).forEach { (generatedEntry, originalEntry) -> val annotationDescriptor = context.get(BindingContext.ANNOTATION, originalEntry) ?: return@forEach @@ -167,14 +162,33 @@ internal fun KtPsiFactory.generateClassOrObject( val descriptor = originalDeclaration.toDescriptor() ?: continue if (generateExpectClass && !originalDeclaration.isEffectivelyActual(false)) continue val generatedDeclaration: KtDeclaration = when (originalDeclaration) { - is KtClassOrObject -> generateClassOrObject(project, generateExpectClass, originalDeclaration, existingClasses) + is KtClassOrObject -> generateClassOrObject( + project, + generateExpectClass, + originalDeclaration, + targetModule, + existingClasses, + existingFqNames + ) is KtCallableDeclaration -> { when (originalDeclaration) { is KtFunction -> generateFunction( - project, generateExpectClass, originalDeclaration, descriptor as FunctionDescriptor, generatedClass, existingClasses + project, + generateExpectClass, + originalDeclaration, + descriptor as FunctionDescriptor, + generatedClass, + existingClasses, + existingFqNames ) is KtProperty -> generateProperty( - project, generateExpectClass, originalDeclaration, descriptor as PropertyDescriptor, generatedClass, existingClasses + project, + generateExpectClass, + originalDeclaration, + descriptor as PropertyDescriptor, + generatedClass, + existingClasses, + existingFqNames ) else -> continue@declLoop } @@ -188,7 +202,13 @@ internal fun KtPsiFactory.generateClassOrObject( if (!originalProperty.hasValOrVar() || !originalProperty.hasActualModifier()) continue val descriptor = originalProperty.toDescriptor() as? PropertyDescriptor ?: continue val generatedProperty = generateProperty( - project, generateExpectClass, originalProperty, descriptor, generatedClass, outerClasses + project, + generateExpectClass, + originalProperty, + descriptor, + generatedClass, + outerClasses, + existingFqNames ) generatedClass.addDeclaration(generatedProperty) } @@ -202,7 +222,13 @@ internal fun KtPsiFactory.generateClassOrObject( val descriptor = originalPrimaryConstructor.toDescriptor() if (descriptor is FunctionDescriptor) { val expectedPrimaryConstructor = generateFunction( - project, generateExpectClass, originalPrimaryConstructor, descriptor, generatedClass, outerClasses + project, + generateExpectClass, + originalPrimaryConstructor, + descriptor, + generatedClass, + outerClasses, + existingFqNames ) generatedClass.createPrimaryConstructorIfAbsent().replace(expectedPrimaryConstructor) } @@ -211,6 +237,46 @@ internal fun KtPsiFactory.generateClassOrObject( return generatedClass } +private fun KtPsiFactory.repairSuperTypeList( + generated: KtClassOrObject, + original: KtClassOrObject, + generateExpectClass: Boolean, + project: Project, + targetModule: Module?, + existingFqNames: Set, + context: BindingContext +) { + generated.superTypeListEntries.zip(original.superTypeListEntries).forEach { (generatedEntry, originalEntry) -> + val superType = context[BindingContext.TYPE, originalEntry.typeReference] + val superClassDescriptor = superType?.constructor?.declarationDescriptor as? ClassDescriptor ?: return@forEach + if (generateExpectClass + && targetModule != null + && !superType.checkTypeAccessibilityInModule(project, targetModule, existingFqNames) + ) { + generatedEntry.delete() + return@forEach + } + + if (generateExpectClass) { + if (generatedEntry !is KtSuperTypeCallEntry) return@forEach + } else { + if (generatedEntry !is KtSuperTypeEntry) return@forEach + } + + if (superClassDescriptor.kind == ClassKind.CLASS || superClassDescriptor.kind == ClassKind.ENUM_CLASS) { + val entryText = IdeDescriptorRenderers.SOURCE_CODE.renderType(superType) + val newGeneratedEntry = if (generateExpectClass) { + createSuperTypeEntry(entryText) + } else { + createSuperTypeCallEntry("$entryText()") + } + generatedEntry.replace(newGeneratedEntry).safeAs()?.addToBeShortenedDescendantsToWaitingSet() + } + } + + if (generated.superTypeListEntries.isEmpty()) generated.getSuperTypeList()?.delete() +} + private val forbiddenAnnotationFqNames = setOf( ExpectedActualDeclarationChecker.OPTIONAL_EXPECTATION_FQ_NAME, FqName("kotlin.ExperimentalMultiplatform"), @@ -223,7 +289,8 @@ internal fun generateFunction( originalFunction: KtFunction, descriptor: FunctionDescriptor, generatedClass: KtClassOrObject? = null, - outerClasses: List = emptyList() + outerClasses: List = emptyList(), + existingFqNames: Set = emptySet() ): KtFunction { if (generateExpect) { val accessibleClasses = outerClasses + listOfNotNull(generatedClass) @@ -251,7 +318,8 @@ internal fun generateProperty( originalProperty: KtNamedDeclaration, descriptor: PropertyDescriptor, generatedClass: KtClassOrObject? = null, - outerClasses: List = emptyList() + outerClasses: List = emptyList(), + existingFqNames: Set = emptySet() ): KtProperty { if (generateExpect) { val accessibleClasses = outerClasses + listOfNotNull(generatedClass) @@ -324,9 +392,25 @@ fun DeclarationDescriptor.checkTypeAccessibilityInModule( project: Project, module: Module, existingClasses: Set = emptySet() -): Boolean { - val existingClassesWithTypeParameters = additionalClasses(existingClasses) - return collectAllTypes().all { it?.canFindClassInModule(project, module, existingClassesWithTypeParameters) == true } +): Boolean = checkTypeInSequence(collectAllTypes(), project, module, additionalClasses(existingClasses)) + +fun KotlinType.checkTypeAccessibilityInModule( + project: Project, + module: Module, + existingClasses: Set = emptySet() +): Boolean = checkTypeInSequence(collectAllTypes(), project, module, existingClasses) + +private fun checkTypeInSequence(sequence: Sequence, project: Project, module: Module, existingClasses: Set): Boolean { + val builtInsModule = module.toDescriptor() + val scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, module.isTestModule) + return sequence.all { fqName -> + if (fqName == null) return false + builtInsModule?.resolveClassByFqName(fqName, NoLookupLocation.FROM_BUILTINS) != null || fqName.canFindClassInModule( + project, + scope, + existingClasses + ) + } } private tailrec fun DeclarationDescriptor.additionalClasses(existingClasses: Set = emptySet()): Set = when (this) { @@ -343,14 +427,14 @@ private tailrec fun DeclarationDescriptor.additionalClasses(existingClasses: Set } private fun DeclarationDescriptor.collectAllTypes(): Sequence { - return when (this) { + return sequenceOf(fqNameOrNull()) + when (this) { is ClassConstructorDescriptor -> valueParameters.asSequence().map(ValueParameterDescriptor::getType).flatMap(KotlinType::collectAllTypes) is ClassDescriptor -> if (isInline) unsubstitutedPrimaryConstructor?.collectAllTypes().orEmpty() else { emptySequence() } + declaredTypeParameters.asSequence().flatMap(DeclarationDescriptor::collectAllTypes) is CallableDescriptor -> { val returnType = returnType ?: return sequenceOf(null) - returnType.collectAllTypes() + allParameters.asSequence().map(ParameterDescriptor::getType).flatMap(KotlinType::collectAllTypes) + returnType.collectAllTypes() + explicitParameters.asSequence().map(ParameterDescriptor::getType).flatMap(KotlinType::collectAllTypes) } is TypeParameterDescriptor -> { val upperBounds = upperBounds @@ -374,9 +458,7 @@ fun KtNamedDeclaration.checkTypeAccessibilityInModule(module: Module, existingCl val KotlinType.fqName: FqName? get() = constructor.declarationDescriptor?.fqNameOrNull() -private fun FqName.canFindClassInModule(project: Project, module: Module, existedClasses: Set): Boolean { +private fun FqName.canFindClassInModule(project: Project, scope: GlobalSearchScope, existedClasses: Set): Boolean { val name = asString() - return name in existedClasses || KotlinFullClassNameIndex.getInstance()[ - name, project, GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, module.isTestModule) - ].isNotEmpty() + return name in existedClasses || KotlinFullClassNameIndex.getInstance()[name, project, scope].isNotEmpty() } \ No newline at end of file