ExpectActualUtils: add type accessibility check for supertypes

This commit is contained in:
Dmitry Gridin
2019-08-08 16:37:05 +03:00
parent 8e9952e203
commit 7561905676
3 changed files with 135 additions and 55 deletions
@@ -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<FunctionDescriptor>()?.let {
generateFunction(project, false, missedDeclaration, it, element)
}
@@ -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<KtNamedDeclaration>, module: Module): HashSet<String> {
@@ -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<KtClassOrObject> = emptyList()
targetModule: Module? = null,
outerClasses: List<KtClassOrObject> = emptyList(),
existingFqNames: Set<String> = 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<KtElement>()?.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<String>,
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<KtElement>()?.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<KtClassOrObject> = emptyList()
outerClasses: List<KtClassOrObject> = emptyList(),
existingFqNames: Set<String> = 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<KtClassOrObject> = emptyList()
outerClasses: List<KtClassOrObject> = emptyList(),
existingFqNames: Set<String> = emptySet()
): KtProperty {
if (generateExpect) {
val accessibleClasses = outerClasses + listOfNotNull(generatedClass)
@@ -324,9 +392,25 @@ fun DeclarationDescriptor.checkTypeAccessibilityInModule(
project: Project,
module: Module,
existingClasses: Set<String> = 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<String> = emptySet()
): Boolean = checkTypeInSequence(collectAllTypes(), project, module, existingClasses)
private fun checkTypeInSequence(sequence: Sequence<FqName?>, project: Project, module: Module, existingClasses: Set<String>): 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<String> = emptySet()): Set<String> = when (this) {
@@ -343,14 +427,14 @@ private tailrec fun DeclarationDescriptor.additionalClasses(existingClasses: Set
}
private fun DeclarationDescriptor.collectAllTypes(): Sequence<FqName?> {
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<String>): Boolean {
private fun FqName.canFindClassInModule(project: Project, scope: GlobalSearchScope, existedClasses: Set<String>): 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()
}