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
This commit is contained in:
+2
-2
@@ -21,7 +21,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
abstract class AbstractCreateDeclarationFix<D : KtNamedDeclaration>(
|
||||
declaration: D,
|
||||
protected val module: Module,
|
||||
protected val generateIt: KtPsiFactory.(Project, D) -> D?
|
||||
protected val generateIt: KtPsiFactory.(Project, TypeAccessibilityChecker, D) -> D?
|
||||
) : KotlinQuickFixAction<D>(declaration) {
|
||||
|
||||
override fun getFamilyName(): String = "Create expect / actual declaration"
|
||||
@@ -53,7 +53,7 @@ abstract class AbstractCreateDeclarationFix<D : KtNamedDeclaration>(
|
||||
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)
|
||||
|
||||
@@ -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<FunctionDescriptor>()?.let {
|
||||
generateFunction(project, false, missedDeclaration, it, element, targetModule = module)
|
||||
generateFunction(project, false, missedDeclaration, it, element, checker = checker)
|
||||
}
|
||||
is KtProperty -> missedDeclaration.toDescriptor()?.safeAs<PropertyDescriptor>()?.let {
|
||||
generateProperty(project, false, missedDeclaration, it, element, targetModule = module)
|
||||
generateProperty(project, false, missedDeclaration, it, element, checker = checker)
|
||||
}
|
||||
else -> null
|
||||
} ?: continue
|
||||
|
||||
@@ -38,7 +38,7 @@ sealed class CreateActualFix<D : KtNamedDeclaration>(
|
||||
declaration: D,
|
||||
actualModule: Module,
|
||||
private val actualPlatform: TargetPlatform,
|
||||
generateIt: KtPsiFactory.(Project, D) -> D?
|
||||
generateIt: KtPsiFactory.(Project, TypeAccessibilityChecker, D) -> D?
|
||||
) : AbstractCreateDeclarationFix<D>(declaration, actualModule, generateIt) {
|
||||
|
||||
override fun getText() =
|
||||
@@ -86,25 +86,25 @@ class CreateActualClassFix(
|
||||
klass: KtClassOrObject,
|
||||
actualModule: Module,
|
||||
actualPlatform: TargetPlatform
|
||||
) : CreateActualFix<KtClassOrObject>(klass, actualModule, actualPlatform, { project, element ->
|
||||
generateClassOrObject(project, false, element, targetModule = actualModule)
|
||||
) : CreateActualFix<KtClassOrObject>(klass, actualModule, actualPlatform, { project, checker, element ->
|
||||
generateClassOrObject(project, false, element, checker = checker)
|
||||
})
|
||||
|
||||
class CreateActualPropertyFix(
|
||||
property: KtProperty,
|
||||
actualModule: Module,
|
||||
actualPlatform: TargetPlatform
|
||||
) : CreateActualFix<KtProperty>(property, actualModule, actualPlatform, { project, element ->
|
||||
) : CreateActualFix<KtProperty>(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<KtFunction>(function, actualModule, actualPlatform, { project, element ->
|
||||
) : CreateActualFix<KtFunction>(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) }
|
||||
})
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ sealed class CreateExpectedFix<D : KtNamedDeclaration>(
|
||||
declaration: D,
|
||||
targetExpectedClass: KtClassOrObject?,
|
||||
commonModule: Module,
|
||||
generateIt: KtPsiFactory.(Project, D) -> D?
|
||||
generateIt: KtPsiFactory.(Project, TypeAccessibilityChecker, D) -> D?
|
||||
) : AbstractCreateDeclarationFix<D>(declaration, commonModule, generateIt) {
|
||||
|
||||
private val targetExpectedClassPointer = targetExpectedClass?.createSmartPointer()
|
||||
@@ -116,16 +116,16 @@ class CreateExpectedClassFix(
|
||||
klass: KtClassOrObject,
|
||||
outerExpectedClass: KtClassOrObject?,
|
||||
commonModule: Module
|
||||
) : CreateExpectedFix<KtClassOrObject>(klass, outerExpectedClass, commonModule, block@{ project, element ->
|
||||
) : CreateExpectedFix<KtClassOrObject>(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<KtNamedDeclaration>, module: Module): HashSet<String> {
|
||||
private tailrec fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collection<KtNamedDeclaration>): HashSet<String> {
|
||||
val classes = elements.filterIsInstance<KtClassOrObject>()
|
||||
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<KtNamedDeclaration>): Boolean =
|
||||
private fun showUnknownTypesDialog(project: Project, declarationsWithNonExistentClasses: Collection<KtNamedDeclaration>): Boolean =
|
||||
declarationsWithNonExistentClasses.isEmpty() || showOkNoDialog(
|
||||
"Unknown types",
|
||||
declarationsWithNonExistentClasses.joinToString(
|
||||
@@ -290,12 +292,13 @@ class CreateExpectedPropertyFix(
|
||||
property: KtNamedDeclaration,
|
||||
targetExpectedClass: KtClassOrObject?,
|
||||
commonModule: Module
|
||||
) : CreateExpectedFix<KtNamedDeclaration>(property, targetExpectedClass, commonModule, block@{ project, element ->
|
||||
if (!element.checkTypeAccessibilityInModule(commonModule)) {
|
||||
) : CreateExpectedFix<KtNamedDeclaration>(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<KtFunction>(function, targetExpectedClass, commonModule, block@{ project, element ->
|
||||
if (!element.checkTypeAccessibilityInModule(commonModule)) {
|
||||
) : CreateExpectedFix<KtFunction>(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
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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<KtClassOrObject> = emptyList(),
|
||||
existingFqNames: Set<String> = emptySet()
|
||||
checker: TypeAccessibilityChecker,
|
||||
outerClasses: List<KtClassOrObject> = 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<String>,
|
||||
checker: TypeAccessibilityChecker,
|
||||
context: BindingContext
|
||||
): Collection<String> {
|
||||
val superNames = linkedSetOf<String>()
|
||||
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<KtClassOrObject> = 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<KtClassOrObject> = emptyList(),
|
||||
existingFqNames: Set<String> = 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<KtClassOrObject> = emptyList(),
|
||||
existingFqNames: Set<String> = 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<String>,
|
||||
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<String>) {
|
||||
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<String>,
|
||||
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<KotlinSourceElement>()?.psi.safeAs<KtAnnotationEntry>() ?: continue
|
||||
addAnnotationEntry(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun AnnotationDescriptor.isValidInModule(targetModule: Module, existingFqNames: Set<String>, 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<KtClassOrObject>) {
|
||||
@@ -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<String> = emptySet()): Boolean {
|
||||
return !hasPrivateModifier() && descriptor?.checkTypeAccessibilityInModule(project, module, existingClasses) == true
|
||||
}
|
||||
|
||||
fun DeclarationDescriptor.checkTypeAccessibilityInModule(
|
||||
project: Project,
|
||||
module: Module,
|
||||
existingClasses: Set<String> = emptySet()
|
||||
): Boolean = incorrectTypesInSequence(collectAllTypes(), project, module, additionalClasses(existingClasses)).isEmpty()
|
||||
|
||||
fun KotlinType.checkTypeAccessibilityInModule(
|
||||
project: Project,
|
||||
module: Module,
|
||||
existingClasses: Set<String> = emptySet()
|
||||
): Boolean = incorrectTypesInSequence(collectAllTypes(), project, module, existingClasses).isEmpty()
|
||||
|
||||
private fun incorrectTypesInSequence(
|
||||
sequence: Sequence<FqName?>,
|
||||
project: Project,
|
||||
module: Module,
|
||||
existingClasses: Set<String>,
|
||||
lazy: Boolean = true
|
||||
): List<FqName?> {
|
||||
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<String> = emptySet()): Set<String> = 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<FqName?> {
|
||||
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<FqName?> = sequenceOf(fqName) + arguments.asSequence()
|
||||
.map(TypeProjection::getType)
|
||||
.flatMap(KotlinType::collectAllTypes)
|
||||
|
||||
fun KtNamedDeclaration.isAlwaysActual(): Boolean = safeAs<KtParameter>()?.parent?.parent?.safeAs<KtPrimaryConstructor>()
|
||||
?.mustHaveValOrVar() ?: false
|
||||
|
||||
val KotlinType.fqName: FqName? get() = constructor.declarationDescriptor?.fqNameOrNull()
|
||||
|
||||
private fun FqName.canFindClassInModule(project: Project, scope: GlobalSearchScope, existedClasses: Set<String>): Boolean {
|
||||
val name = asString()
|
||||
return name in existedClasses || KotlinFullClassNameIndex.getInstance()[name, project, scope].isNotEmpty()
|
||||
}
|
||||
@@ -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<String>
|
||||
|
||||
fun incorrectTypes(declaration: KtNamedDeclaration): Collection<FqName?>
|
||||
fun incorrectTypes(descriptor: DeclarationDescriptor): Collection<FqName?>
|
||||
fun incorrectTypes(type: KotlinType): Collection<FqName?>
|
||||
|
||||
fun checkAccessibility(declaration: KtNamedDeclaration): Boolean
|
||||
fun checkAccessibility(descriptor: DeclarationDescriptor): Boolean
|
||||
fun checkAccessibility(type: KotlinType): Boolean
|
||||
|
||||
fun <R> runInContext(fqNames: Collection<String>, block: TypeAccessibilityChecker.() -> R): R
|
||||
|
||||
companion object {
|
||||
fun create(
|
||||
project: Project,
|
||||
targetModule: Module,
|
||||
existingFqNames: Collection<String> = emptyList()
|
||||
): TypeAccessibilityChecker = TypeAccessibilityCheckerImpl(project, targetModule, existingFqNames)
|
||||
}
|
||||
}
|
||||
+111
@@ -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<String> = 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<FqName?> =
|
||||
declaration.descriptor?.let { incorrectTypesInSequence(it.collectAllTypes(), false) } ?: listOf(null)
|
||||
|
||||
override fun incorrectTypes(descriptor: DeclarationDescriptor): Collection<FqName?> =
|
||||
incorrectTypesInSequence(descriptor.collectAllTypes(), false)
|
||||
|
||||
override fun incorrectTypes(type: KotlinType): Collection<FqName?> = 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 <R> runInContext(fqNames: Collection<String>, block: TypeAccessibilityChecker.() -> R): R {
|
||||
val oldValue = existingFqNames
|
||||
existingFqNames = fqNames
|
||||
return block().also { existingFqNames = oldValue }
|
||||
}
|
||||
|
||||
private fun incorrectTypesInSequence(
|
||||
sequence: Sequence<FqName?>,
|
||||
lazy: Boolean = true
|
||||
): List<FqName?> {
|
||||
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<String> = emptySet()): Collection<String> =
|
||||
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<FqName?> {
|
||||
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<FqName?> = sequenceOf(fqName) + arguments.asSequence()
|
||||
.map(TypeProjection::getType)
|
||||
.flatMap(KotlinType::collectAllTypes)
|
||||
Reference in New Issue
Block a user