From 46fefde09c29a65db7804c15481a569f581db342 Mon Sep 17 00:00:00 2001 From: Dmitry Gridin Date: Tue, 20 Aug 2019 14:41:24 +0700 Subject: [PATCH] ExpectActualUtils: introduce TypeAccessibilityChecker #KT-28537 Fixed #KT-28538 Fixed #KT-28549 Fixed #KT-28620 Fixed #KT-31433 Fixed #KT-31475 Fixed #KT-32642 Fixed #KT-32768 Fixed #KT-33150 Fixed --- .../AbstractCreateDeclarationFix.kt | 4 +- .../quickfix/expectactual/AddActualFix.kt | 7 +- .../quickfix/expectactual/CreateActualFix.kt | 14 +- .../expectactual/CreateExpectedFix.kt | 44 +-- .../expectactual/ExpectActualUtils.kt | 252 ++++++------------ .../expectactual/TypeAccessibilityChecker.kt | 37 +++ .../TypeAccessibilityCheckerImpl.kt | 111 ++++++++ 7 files changed, 271 insertions(+), 198 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/TypeAccessibilityChecker.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/TypeAccessibilityCheckerImpl.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt index 9c4cbfcb250..f9369e76694 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset abstract class AbstractCreateDeclarationFix( declaration: D, protected val module: Module, - protected val generateIt: KtPsiFactory.(Project, D) -> D? + protected val generateIt: KtPsiFactory.(Project, TypeAccessibilityChecker, D) -> D? ) : KotlinQuickFixAction(declaration) { override fun getFamilyName(): String = "Create expect / actual declaration" @@ -53,7 +53,7 @@ abstract class AbstractCreateDeclarationFix( val factory = KtPsiFactory(project) DumbService.getInstance(project).runWhenSmart { val generated = try { - factory.generateIt(project, element) ?: return@runWhenSmart + factory.generateIt(project, TypeAccessibilityChecker.create(project, module), element) ?: return@runWhenSmart } catch (e: KotlinTypeInaccessibleException) { if (editor != null) { showErrorHint(project, editor, "Cannot generate expected $elementType: " + e.message, e.message) 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 380808cf4c7..db5bd3c2ee1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt @@ -57,14 +57,15 @@ class AddActualFix( } val module = element.module ?: return + val checker = TypeAccessibilityChecker.create(project, module) for (missedDeclaration in missedDeclarationPointers.mapNotNull { it.element }) { val actualDeclaration = when (missedDeclaration) { - is KtClassOrObject -> factory.generateClassOrObject(project, false, missedDeclaration, module, listOf(element)) + is KtClassOrObject -> factory.generateClassOrObject(project, false, missedDeclaration, checker, listOf(element)) is KtFunction -> missedDeclaration.toDescriptor()?.safeAs()?.let { - generateFunction(project, false, missedDeclaration, it, element, targetModule = module) + generateFunction(project, false, missedDeclaration, it, element, checker = checker) } is KtProperty -> missedDeclaration.toDescriptor()?.safeAs()?.let { - generateProperty(project, false, missedDeclaration, it, element, targetModule = module) + generateProperty(project, false, missedDeclaration, it, element, checker = checker) } else -> null } ?: continue 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 f31630c4437..3c9659780f7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt @@ -38,7 +38,7 @@ sealed class CreateActualFix( declaration: D, actualModule: Module, private val actualPlatform: TargetPlatform, - generateIt: KtPsiFactory.(Project, D) -> D? + generateIt: KtPsiFactory.(Project, TypeAccessibilityChecker, D) -> D? ) : AbstractCreateDeclarationFix(declaration, actualModule, generateIt) { override fun getText() = @@ -86,25 +86,25 @@ class CreateActualClassFix( klass: KtClassOrObject, actualModule: Module, actualPlatform: TargetPlatform -) : CreateActualFix(klass, actualModule, actualPlatform, { project, element -> - generateClassOrObject(project, false, element, targetModule = actualModule) +) : CreateActualFix(klass, actualModule, actualPlatform, { project, checker, element -> + generateClassOrObject(project, false, element, checker = checker) }) class CreateActualPropertyFix( property: KtProperty, actualModule: Module, actualPlatform: TargetPlatform -) : CreateActualFix(property, actualModule, actualPlatform, { project, element -> +) : CreateActualFix(property, actualModule, actualPlatform, { project, checker, element -> val descriptor = element.toDescriptor() as? PropertyDescriptor - descriptor?.let { generateProperty(project, false, element, descriptor, targetModule = actualModule) } + descriptor?.let { generateProperty(project, false, element, descriptor, checker = checker) } }) class CreateActualFunctionFix( function: KtFunction, actualModule: Module, actualPlatform: TargetPlatform -) : CreateActualFix(function, actualModule, actualPlatform, { project, element -> +) : CreateActualFix(function, actualModule, actualPlatform, { project, checker, element -> val descriptor = element.toDescriptor() as? FunctionDescriptor - descriptor?.let { generateFunction(project, false, element, descriptor, targetModule = actualModule) } + descriptor?.let { generateFunction(project, false, element, descriptor, checker = checker) } }) 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 8d1749853dd..66f4311adb0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt @@ -45,7 +45,7 @@ sealed class CreateExpectedFix( declaration: D, targetExpectedClass: KtClassOrObject?, commonModule: Module, - generateIt: KtPsiFactory.(Project, D) -> D? + generateIt: KtPsiFactory.(Project, TypeAccessibilityChecker, D) -> D? ) : AbstractCreateDeclarationFix(declaration, commonModule, generateIt) { private val targetExpectedClassPointer = targetExpectedClass?.createSmartPointer() @@ -116,16 +116,16 @@ class CreateExpectedClassFix( klass: KtClassOrObject, outerExpectedClass: KtClassOrObject?, commonModule: Module -) : CreateExpectedFix(klass, outerExpectedClass, commonModule, block@{ project, element -> +) : CreateExpectedFix(klass, outerExpectedClass, commonModule, block@{ project, checker, element -> val originalElements = element.collectDeclarations(withSelf = false).toList() - val existingClasses = findClasses(originalElements + klass, commonModule) - if (!element.checkTypeAccessibilityInModule(commonModule, existingClasses)) { + val existingClasses = checker.findAndApplyExistingClasses(originalElements + klass) + if (!checker.checkAccessibility(element)) { showUnknownTypesError(element) return@block null } val (members, declarationsWithNonExistentClasses) = originalElements.partition { - it.checkTypeAccessibilityInModule(commonModule, existingClasses) + checker.checkAccessibility(it) } if (!showUnknownTypesDialog(project, declarationsWithNonExistentClasses)) return@block null @@ -143,15 +143,15 @@ class CreateExpectedClassFix( } }.asSequence().plus(klass).plus(members.filter(KtNamedDeclaration::isAlwaysActual)).flatMap(KtNamedDeclaration::selected).toSet() - val selectedClasses = findClasses(selectedElements, commonModule) + val selectedClasses = checker.findAndApplyExistingClasses(selectedElements) val resultDeclarations = if (selectedClasses != existingClasses) { - if (!element.checkTypeAccessibilityInModule(commonModule, selectedClasses)) { + if (!checker.checkAccessibility(element)) { showUnknownTypesError(element) return@block null } val (resultDeclarations, withErrors) = selectedElements.partition { - it.checkTypeAccessibilityInModule(commonModule, selectedClasses) + checker.checkAccessibility(it) } if (!showUnknownTypesDialog(project, withErrors)) return@block null resultDeclarations @@ -164,18 +164,20 @@ class CreateExpectedClassFix( } } - generateClassOrObject(project, true, element, commonModule, listOfNotNull(outerExpectedClass), selectedClasses) + generateClassOrObject(project, true, element, checker, listOfNotNull(outerExpectedClass)) }) -private tailrec fun findClasses(elements: Collection, module: Module): HashSet { +private tailrec fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collection): HashSet { val classes = elements.filterIsInstance() val existingNames = classes.mapNotNull { it.fqName?.asString() }.toHashSet() - val newExistingClasses = classes.filter { it.checkTypeAccessibilityInModule(module, existingNames) } + existingFqNames = existingNames + + val newExistingClasses = classes.filter { checkAccessibility(it) } return if (classes.size == newExistingClasses.size) existingNames - else findClasses(newExistingClasses, module) + else findAndApplyExistingClasses(newExistingClasses) } -private fun showUnknownTypesDialog(project: Project, declarationsWithNonExistentClasses: List): Boolean = +private fun showUnknownTypesDialog(project: Project, declarationsWithNonExistentClasses: Collection): Boolean = declarationsWithNonExistentClasses.isEmpty() || showOkNoDialog( "Unknown types", declarationsWithNonExistentClasses.joinToString( @@ -290,12 +292,13 @@ class CreateExpectedPropertyFix( property: KtNamedDeclaration, targetExpectedClass: KtClassOrObject?, commonModule: Module -) : CreateExpectedFix(property, targetExpectedClass, commonModule, block@{ project, element -> - if (!element.checkTypeAccessibilityInModule(commonModule)) { +) : CreateExpectedFix(property, targetExpectedClass, commonModule, block@{ project, checker, element -> + if (!checker.checkAccessibility(element)) { showUnknownTypesError(element) return@block null } val descriptor = element.toDescriptor() as? PropertyDescriptor + checker.existingFqNames = targetExpectedClass?.getSuperNames()?.toSet().orEmpty() descriptor?.let { generateProperty( project, @@ -303,8 +306,7 @@ class CreateExpectedPropertyFix( element, descriptor, targetExpectedClass, - existingFqNames = targetExpectedClass?.getSuperNames()?.toSet().orEmpty(), - targetModule = commonModule + checker = checker ) } }) @@ -313,12 +315,13 @@ class CreateExpectedFunctionFix( function: KtFunction, targetExpectedClass: KtClassOrObject?, commonModule: Module -) : CreateExpectedFix(function, targetExpectedClass, commonModule, block@{ project, element -> - if (!element.checkTypeAccessibilityInModule(commonModule)) { +) : CreateExpectedFix(function, targetExpectedClass, commonModule, block@{ project, checker, element -> + if (!checker.checkAccessibility(element)) { showUnknownTypesError(element) return@block null } val descriptor = element.toDescriptor() as? FunctionDescriptor + checker.existingFqNames = targetExpectedClass?.getSuperNames()?.toSet().orEmpty() descriptor?.let { generateFunction( project, @@ -326,8 +329,7 @@ class CreateExpectedFunctionFix( element, descriptor, targetExpectedClass, - existingFqNames = targetExpectedClass?.getSuperNames()?.toSet().orEmpty(), - targetModule = commonModule + checker = checker ) } }) 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 11e85349f37..b58dd9003bf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt @@ -8,13 +8,9 @@ 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 com.intellij.psi.search.GlobalSearchScope -import org.jetbrains.kotlin.backend.common.descriptors.explicitParameters import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 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 @@ -29,11 +25,8 @@ import org.jetbrains.kotlin.idea.core.overrideImplement.makeNotActual import org.jetbrains.kotlin.idea.core.toDescriptor import org.jetbrains.kotlin.idea.project.platform import org.jetbrains.kotlin.idea.refactoring.createKotlinFile -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 @@ -50,7 +43,6 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.source.KotlinSourceElement import org.jetbrains.kotlin.types.AbbreviatedType import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.TypeProjection import org.jetbrains.kotlin.utils.addToStdlib.safeAs fun createFileForDeclaration(module: Module, declaration: KtNamedDeclaration): KtFile? { @@ -119,9 +111,8 @@ internal fun KtPsiFactory.generateClassOrObject( project: Project, generateExpectClass: Boolean, originalClass: KtClassOrObject, - targetModule: Module, - outerClasses: List = emptyList(), - existingFqNames: Set = emptySet() + checker: TypeAccessibilityChecker, + outerClasses: List = emptyList() ): KtClassOrObject { val generatedClass = createClassHeaderCopyByText(originalClass) val context = originalClass.analyzeWithContent() @@ -129,15 +120,13 @@ internal fun KtPsiFactory.generateClassOrObject( generatedClass, originalClass, generateExpectClass, - project, - targetModule, - existingFqNames, + checker, context ) generatedClass.annotationEntries.zip(originalClass.annotationEntries).forEach { (generatedEntry, originalEntry) -> val annotationDescriptor = context.get(BindingContext.ANNOTATION, originalEntry) - if (annotationDescriptor?.isValidInModule(targetModule, existingFqNames, project) != true) { + if (annotationDescriptor?.isValidInModule(checker) != true) { generatedEntry.delete() } } @@ -159,6 +148,7 @@ internal fun KtPsiFactory.generateClassOrObject( it.isEffectivelyActual(false) }.toList() + generatedClass + outerClasses + val existingFqNamesWithSuperTypes = (checker.existingFqNames + superNames).toSet() declLoop@ for (originalDeclaration in originalClass.declarations) { val descriptor = originalDeclaration.toDescriptor() ?: continue if (generateExpectClass && !originalDeclaration.isEffectivelyActual(false)) continue @@ -167,30 +157,20 @@ internal fun KtPsiFactory.generateClassOrObject( project, generateExpectClass, originalDeclaration, - targetModule, - existingClasses, - existingFqNames - ) - is KtFunction -> generateFunction( - project, - generateExpectClass, - originalDeclaration, - descriptor as FunctionDescriptor, - generatedClass, - existingClasses, - existingFqNames + superNames, - targetModule - ) - is KtProperty -> generateProperty( - project, - generateExpectClass, - originalDeclaration, - descriptor as PropertyDescriptor, - generatedClass, - existingClasses, - existingFqNames + superNames, - targetModule + checker, + existingClasses ) + is KtFunction, is KtProperty -> checker.runInContext(existingFqNamesWithSuperTypes) { + generateCallable( + project, + generateExpectClass, + originalDeclaration as KtCallableDeclaration, + descriptor as CallableDescriptor, + generatedClass, + existingClasses, + this + ) + } else -> continue@declLoop } generatedClass.addDeclaration(generatedDeclaration) @@ -199,17 +179,18 @@ internal fun KtPsiFactory.generateClassOrObject( 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, - existingFqNames + superNames, - targetModule - ) - generatedClass.addDeclaration(generatedProperty) + checker.runInContext(existingFqNamesWithSuperTypes) { + val generatedProperty = generateProperty( + project, + generateExpectClass, + originalProperty, + descriptor, + generatedClass, + outerClasses, + this + ) + generatedClass.addDeclaration(generatedProperty) + } } } val originalPrimaryConstructor = originalClass.primaryConstructor @@ -220,17 +201,18 @@ internal fun KtPsiFactory.generateClassOrObject( ) { val descriptor = originalPrimaryConstructor.toDescriptor() if (descriptor is FunctionDescriptor) { - val expectedPrimaryConstructor = generateFunction( - project, - generateExpectClass, - originalPrimaryConstructor, - descriptor, - generatedClass, - outerClasses, - existingFqNames + superNames, - targetModule - ) - generatedClass.createPrimaryConstructorIfAbsent().replace(expectedPrimaryConstructor) + checker.runInContext(existingFqNamesWithSuperTypes) { + val expectedPrimaryConstructor = generateFunction( + project, + generateExpectClass, + originalPrimaryConstructor, + descriptor, + generatedClass, + outerClasses, + this + ) + generatedClass.createPrimaryConstructorIfAbsent().replace(expectedPrimaryConstructor) + } } } @@ -241,19 +223,14 @@ private fun KtPsiFactory.repairSuperTypeList( generated: KtClassOrObject, original: KtClassOrObject, generateExpectClass: Boolean, - project: Project, - targetModule: Module?, - existingFqNames: Set, + checker: TypeAccessibilityChecker, context: BindingContext ): Collection { val superNames = linkedSetOf() 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) - ) { + if (generateExpectClass && !checker.checkAccessibility(superType)) { generatedEntry.delete() return@forEach } @@ -286,6 +263,36 @@ private val forbiddenAnnotationFqNames = setOf( ExperimentalUsageChecker.USE_EXPERIMENTAL_FQ_NAME ) +internal fun generateCallable( + project: Project, + generateExpect: Boolean, + originalDeclaration: KtCallableDeclaration, + descriptor: CallableDescriptor, + generatedClass: KtClassOrObject? = null, + outerClasses: List = emptyList(), + checker: TypeAccessibilityChecker +) = when (originalDeclaration) { + is KtFunction -> generateFunction( + project, + generateExpect, + originalDeclaration, + descriptor as FunctionDescriptor, + generatedClass, + outerClasses, + checker + ) + is KtProperty -> generateProperty( + project, + generateExpect, + originalDeclaration, + descriptor as PropertyDescriptor, + generatedClass, + outerClasses, + checker + ) + else -> error("Unsupported operation for $originalDeclaration") +} + internal fun generateFunction( project: Project, generateExpect: Boolean, @@ -293,8 +300,7 @@ internal fun generateFunction( descriptor: FunctionDescriptor, generatedClass: KtClassOrObject? = null, outerClasses: List = emptyList(), - existingFqNames: Set = emptySet(), - targetModule: Module + checker: TypeAccessibilityChecker ): KtFunction { if (generateExpect) { val accessibleClasses = outerClasses + listOfNotNull(generatedClass) @@ -314,7 +320,7 @@ internal fun generateFunction( project = project, mode = if (generateExpect) MemberGenerateMode.EXPECT else MemberGenerateMode.ACTUAL ).apply { - repair(generatedClass, descriptor, existingFqNames, targetModule, project) + repair(generatedClass, descriptor, checker) } as KtFunction } @@ -325,8 +331,7 @@ internal fun generateProperty( descriptor: PropertyDescriptor, generatedClass: KtClassOrObject? = null, outerClasses: List = emptyList(), - existingFqNames: Set = emptySet(), - targetModule: Module + checker: TypeAccessibilityChecker ): KtProperty { if (generateExpect) { val accessibleClasses = outerClasses + listOfNotNull(generatedClass) @@ -343,46 +348,39 @@ internal fun generateProperty( project = project, mode = if (generateExpect) MemberGenerateMode.EXPECT else MemberGenerateMode.ACTUAL ).apply { - repair(generatedClass, descriptor, existingFqNames, targetModule, project) + repair(generatedClass, descriptor, checker) } as KtProperty } private fun KtCallableDeclaration.repair( generatedClass: KtClassOrObject?, descriptor: CallableDescriptor, - existingFqNames: Set, - targetModule: Module, - project: Project + checker: TypeAccessibilityChecker ) { - if (generatedClass != null) repairOverride(descriptor, existingFqNames) - repairAnnotationEntries(descriptor, existingFqNames, targetModule, project) + if (generatedClass != null) repairOverride(descriptor, checker) + repairAnnotationEntries(descriptor, checker) } -private fun KtCallableDeclaration.repairOverride(descriptor: CallableDescriptor, existingFqNames: Set) { +private fun KtCallableDeclaration.repairOverride(descriptor: CallableDescriptor, checker: TypeAccessibilityChecker) { if (!hasModifier(KtTokens.OVERRIDE_KEYWORD)) return val superDescriptor = descriptor.overriddenDescriptors.firstOrNull()?.containingDeclaration - if (superDescriptor?.fqNameOrNull()?.shortName()?.asString() !in existingFqNames) { + if (superDescriptor?.fqNameOrNull()?.shortName()?.asString() !in checker.existingFqNames) { removeModifier(KtTokens.OVERRIDE_KEYWORD) } } -private fun KtCallableDeclaration.repairAnnotationEntries( - descriptor: CallableDescriptor, - existingFqNames: Set, - module: Module, - project: Project -) { +private fun KtCallableDeclaration.repairAnnotationEntries(descriptor: CallableDescriptor, checker: TypeAccessibilityChecker) { for (annotation in descriptor.annotations) { - if (annotation.isValidInModule(module, existingFqNames, project)) { + if (annotation.isValidInModule(checker)) { val entry = annotation.source.safeAs()?.psi.safeAs() ?: continue addAnnotationEntry(entry) } } } -private fun AnnotationDescriptor.isValidInModule(targetModule: Module, existingFqNames: Set, project: Project): Boolean { - return fqName !in forbiddenAnnotationFqNames && type.checkTypeAccessibilityInModule(project, targetModule, existingFqNames) +private fun AnnotationDescriptor.isValidInModule(checker: TypeAccessibilityChecker): Boolean { + return fqName !in forbiddenAnnotationFqNames && checker.checkAccessibility(type) } private fun CallableMemberDescriptor.checkTypeParameterBoundsAccessibility(accessibleClasses: List) { @@ -435,83 +433,7 @@ class KotlinTypeInaccessibleException(val type: KotlinType) : Exception() { get() = "Type ${type.getJetTypeFqName(true)} is not accessible from common code" } -fun KtNamedDeclaration.checkTypeAccessibilityInModule(module: Module, existingClasses: Set = emptySet()): Boolean { - return !hasPrivateModifier() && descriptor?.checkTypeAccessibilityInModule(project, module, existingClasses) == true -} - -fun DeclarationDescriptor.checkTypeAccessibilityInModule( - project: Project, - module: Module, - existingClasses: Set = emptySet() -): Boolean = incorrectTypesInSequence(collectAllTypes(), project, module, additionalClasses(existingClasses)).isEmpty() - -fun KotlinType.checkTypeAccessibilityInModule( - project: Project, - module: Module, - existingClasses: Set = emptySet() -): Boolean = incorrectTypesInSequence(collectAllTypes(), project, module, existingClasses).isEmpty() - -private fun incorrectTypesInSequence( - sequence: Sequence, - project: Project, - module: Module, - existingClasses: Set, - lazy: Boolean = true -): List { - val builtInsModule = module.toDescriptor() - val scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, module.isTestModule) - fun predicate(fqName: FqName?): Boolean = fqName != null - && (builtInsModule?.resolveClassByFqName(fqName, NoLookupLocation.FROM_BUILTINS) != null - || fqName.canFindClassInModule(project, scope, existingClasses)) - - return if (lazy) { - for (fqName in sequence) if (!predicate(fqName)) return listOf(fqName) - emptyList() - } else sequence.filterNot(::predicate).toList() -} - -private tailrec fun DeclarationDescriptor.additionalClasses(existingClasses: Set = emptySet()): Set = when (this) { - is ClassifierDescriptorWithTypeParameters -> { - val myParameters = existingClasses + declaredTypeParameters.map { it.fqNameOrNull()?.asString() ?: return emptySet() } - val containingDeclaration = containingDeclaration - if (isInner) containingDeclaration.additionalClasses(myParameters) else myParameters - } - is CallableDescriptor -> containingDeclaration.additionalClasses(existingClasses + typeParameters.map { - it.fqNameOrNull()?.asString() ?: return emptySet() - }) - else -> - existingClasses -} - -private fun DeclarationDescriptor.collectAllTypes(): Sequence { - return 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) + sequenceOf(fqNameOrNull()) - is CallableDescriptor -> { - val returnType = returnType ?: return sequenceOf(null) - returnType.collectAllTypes() + explicitParameters.asSequence().map(ParameterDescriptor::getType).flatMap(KotlinType::collectAllTypes) - } - is TypeParameterDescriptor -> { - val upperBounds = upperBounds - if (upperBounds.isEmpty()) sequenceOf(fqNameOrNull()) - else upperBounds.asSequence().flatMap(KotlinType::collectAllTypes) - } - else -> emptySequence() - } -} - -private fun KotlinType.collectAllTypes(): Sequence = sequenceOf(fqName) + arguments.asSequence() - .map(TypeProjection::getType) - .flatMap(KotlinType::collectAllTypes) - fun KtNamedDeclaration.isAlwaysActual(): Boolean = safeAs()?.parent?.parent?.safeAs() ?.mustHaveValOrVar() ?: false val KotlinType.fqName: FqName? get() = constructor.declarationDescriptor?.fqNameOrNull() - -private fun FqName.canFindClassInModule(project: Project, scope: GlobalSearchScope, existedClasses: Set): Boolean { - val name = asString() - return name in existedClasses || KotlinFullClassNameIndex.getInstance()[name, project, scope].isNotEmpty() -} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/TypeAccessibilityChecker.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/TypeAccessibilityChecker.kt new file mode 100644 index 00000000000..bd8dd2c5e47 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/TypeAccessibilityChecker.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.quickfix.expectactual + +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.types.KotlinType + +interface TypeAccessibilityChecker { + val project: Project + val targetModule: Module + var existingFqNames: Collection + + fun incorrectTypes(declaration: KtNamedDeclaration): Collection + fun incorrectTypes(descriptor: DeclarationDescriptor): Collection + fun incorrectTypes(type: KotlinType): Collection + + fun checkAccessibility(declaration: KtNamedDeclaration): Boolean + fun checkAccessibility(descriptor: DeclarationDescriptor): Boolean + fun checkAccessibility(type: KotlinType): Boolean + + fun runInContext(fqNames: Collection, block: TypeAccessibilityChecker.() -> R): R + + companion object { + fun create( + project: Project, + targetModule: Module, + existingFqNames: Collection = emptyList() + ): TypeAccessibilityChecker = TypeAccessibilityCheckerImpl(project, targetModule, existingFqNames) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/TypeAccessibilityCheckerImpl.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/TypeAccessibilityCheckerImpl.kt new file mode 100644 index 00000000000..a9507598254 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/TypeAccessibilityCheckerImpl.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.quickfix.expectactual + +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.backend.common.descriptors.explicitParameters +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.idea.caches.project.isTestModule +import org.jetbrains.kotlin.idea.caches.project.toDescriptor +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor +import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex +import org.jetbrains.kotlin.idea.util.hasPrivateModifier +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeProjection + +class TypeAccessibilityCheckerImpl( + override val project: Project, + override val targetModule: Module, + override var existingFqNames: Collection = emptyList() +) : TypeAccessibilityChecker { + private val scope by lazy { GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(targetModule, targetModule.isTestModule) } + private val builtInsModule by lazy { targetModule.toDescriptor() } + + override fun incorrectTypes(declaration: KtNamedDeclaration): Collection = + declaration.descriptor?.let { incorrectTypesInSequence(it.collectAllTypes(), false) } ?: listOf(null) + + override fun incorrectTypes(descriptor: DeclarationDescriptor): Collection = + incorrectTypesInSequence(descriptor.collectAllTypes(), false) + + override fun incorrectTypes(type: KotlinType): Collection = incorrectTypesInSequence(type.collectAllTypes(), false) + + override fun checkAccessibility(declaration: KtNamedDeclaration): Boolean = + !declaration.hasPrivateModifier() && declaration.descriptor?.let { checkAccessibility(it) } == true + + override fun checkAccessibility(descriptor: DeclarationDescriptor): Boolean = + runInContext(descriptor.additionalClasses(existingFqNames)) { + incorrectTypesInSequence(descriptor.collectAllTypes(), true).isEmpty() + } + + override fun checkAccessibility(type: KotlinType): Boolean = + incorrectTypesInSequence(type.collectAllTypes(), true).isEmpty() + + override fun runInContext(fqNames: Collection, block: TypeAccessibilityChecker.() -> R): R { + val oldValue = existingFqNames + existingFqNames = fqNames + return block().also { existingFqNames = oldValue } + } + + private fun incorrectTypesInSequence( + sequence: Sequence, + lazy: Boolean = true + ): List { + return if (lazy) { + for (fqName in sequence) if (!fqName.canFindClassInModule()) return listOf(fqName) + emptyList() + } else sequence.filter { !it.canFindClassInModule() }.toList() + } + + private fun FqName?.canFindClassInModule(): Boolean { + val name = this?.asString() ?: return false + return name in existingFqNames + || builtInsModule?.resolveClassByFqName(this, NoLookupLocation.FROM_BUILTINS) != null + || KotlinFullClassNameIndex.getInstance()[name, project, scope].isNotEmpty() + } +} + +private tailrec fun DeclarationDescriptor.additionalClasses(existingClasses: Collection = emptySet()): Collection = + when (this) { + is ClassifierDescriptorWithTypeParameters -> { + val myParameters = existingClasses + declaredTypeParameters.map { it.fqNameOrNull()?.asString() ?: return emptySet() } + val containingDeclaration = containingDeclaration + if (isInner) containingDeclaration.additionalClasses(myParameters) else myParameters + } + is CallableDescriptor -> containingDeclaration.additionalClasses(existingClasses + typeParameters.map { + it.fqNameOrNull()?.asString() ?: return emptySet() + }) + else -> + existingClasses + } + +private fun DeclarationDescriptor.collectAllTypes(): Sequence { + return 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) + sequenceOf(fqNameOrNull()) + is CallableDescriptor -> { + val returnType = returnType ?: return sequenceOf(null) + returnType.collectAllTypes() + explicitParameters.asSequence().map(ParameterDescriptor::getType).flatMap(KotlinType::collectAllTypes) + } + is TypeParameterDescriptor -> { + val upperBounds = upperBounds + if (upperBounds.isEmpty()) sequenceOf(fqNameOrNull()) + else upperBounds.asSequence().flatMap(KotlinType::collectAllTypes) + } + else -> emptySequence() + } +} + +private fun KotlinType.collectAllTypes(): Sequence = sequenceOf(fqName) + arguments.asSequence() + .map(TypeProjection::getType) + .flatMap(KotlinType::collectAllTypes)