From a43fedd524289767e86ecbf7ffc394ddef39a6d4 Mon Sep 17 00:00:00 2001 From: Dmitry Gridin Date: Fri, 17 Dec 2021 19:27:54 +0700 Subject: [PATCH] [kotlin] mpp: "create expect" should warn about inaccessible annotations ^KTIJ-20493 Fixed GitOrigin-RevId: c1fbbe47db3dbe955545e1ba9169f419bba55f4a --- .../idea/quickfix/TypeAccessibilityChecker.kt | 6 +- .../quickfix/TypeAccessibilityCheckerImpl.kt | 61 ++++++++++++------- .../expectactual/ExpectActualUtils.kt | 6 +- .../QuickFixMultiModuleTestGenerated.java | 30 ++++++--- .../annotationOnClass/common/one/two/txt.txt | 0 .../annotationOnClass/jvm/one/two/Platform.kt | 10 +++ .../classUpperBounds/jvm/Utils.kt | 2 +- .../common/A.kt.after | 3 - .../memberFunctionParentType2/jvm/Utils.kt | 3 +- .../jvm/Utils.kt.after | 8 --- .../common/one/two/CommonAnnotationClass.kt | 3 + .../common/one/two/Platform.kt.after | 4 ++ .../jvm/one/two/Platform.kt | 6 ++ .../withAnnotations/header/My.kt.after | 2 - .../createExpect/withAnnotations/jvm/My.kt | 2 +- .../withAnnotations/jvm/My.kt.after | 4 +- .../createExpect/withAnnotations/log.log | 1 + 17 files changed, 97 insertions(+), 54 deletions(-) create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/annotationOnClass/common/one/two/txt.txt create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/annotationOnClass/jvm/one/two/Platform.kt delete mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/common/A.kt.after delete mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/jvm/Utils.kt.after create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/common/one/two/CommonAnnotationClass.kt create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/common/one/two/Platform.kt.after create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/jvm/one/two/Platform.kt diff --git a/plugins/kotlin/analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityChecker.kt b/plugins/kotlin/analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityChecker.kt index 49d09e73bbca..2a7e26fa05ec 100644 --- a/plugins/kotlin/analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityChecker.kt +++ b/plugins/kotlin/analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityChecker.kt @@ -20,7 +20,7 @@ interface TypeAccessibilityChecker { * For example, you want to move `open class A` class to another module. * In this case, you should add [FqName] of class `A` in [existingTypeNames]. */ - var existingTypeNames: Collection + var existingTypeNames: Set fun incorrectTypes(declaration: KtNamedDeclaration): Collection fun incorrectTypes(descriptor: DeclarationDescriptor): Collection @@ -30,13 +30,13 @@ interface TypeAccessibilityChecker { fun checkAccessibility(descriptor: DeclarationDescriptor): Boolean fun checkAccessibility(type: KotlinType): Boolean - fun runInContext(fqNames: Collection, block: TypeAccessibilityChecker.() -> R): R + fun runInContext(fqNames: Set, block: TypeAccessibilityChecker.() -> R): R companion object { fun create( project: Project, targetModule: Module, - existingFqNames: Collection = emptyList() + existingFqNames: Set = emptySet(), ): TypeAccessibilityChecker = TypeAccessibilityCheckerImpl(project, targetModule, existingFqNames) @get:TestOnly diff --git a/plugins/kotlin/analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityCheckerImpl.kt b/plugins/kotlin/analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityCheckerImpl.kt index b8e063d8b2b0..2ecc212d580a 100644 --- a/plugins/kotlin/analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityCheckerImpl.kt +++ b/plugins/kotlin/analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityCheckerImpl.kt @@ -6,6 +6,7 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.psi.search.GlobalSearchScope import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.idea.caches.project.isTestModule import org.jetbrains.kotlin.idea.caches.project.toDescriptor import org.jetbrains.kotlin.idea.refactoring.fqName.fqName @@ -27,7 +28,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs class TypeAccessibilityCheckerImpl( override val project: Project, override val targetModule: Module, - override var existingTypeNames: Collection = emptyList() + override var existingTypeNames: Set = emptySet() ) : TypeAccessibilityChecker { private val scope by lazy { GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(targetModule, targetModule.isTestModule) } private var builtInsModule: ModuleDescriptor? = targetModule.toDescriptor() @@ -52,7 +53,7 @@ class TypeAccessibilityCheckerImpl( override fun checkAccessibility(type: KotlinType): Boolean = incorrectTypesInSequence(type.collectAllTypes(), true).isEmpty() - override fun runInContext(fqNames: Collection, block: TypeAccessibilityChecker.() -> R): R { + override fun runInContext(fqNames: Set, block: TypeAccessibilityChecker.() -> R): R { val oldValue = existingTypeNames existingTypeNames = fqNames return block().also { existingTypeNames = oldValue } @@ -62,10 +63,12 @@ class TypeAccessibilityCheckerImpl( 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() + val uniqueSequence = sequence.distinct().filter { !it.canFindClassInModule() } + return when { + uniqueSequence.none() -> emptyList() + lazy -> listOf(uniqueSequence.first()) + else -> uniqueSequence.toList() + } } private fun incorrectTypesInDescriptor(descriptor: DeclarationDescriptor, lazy: Boolean) = @@ -81,33 +84,44 @@ class TypeAccessibilityCheckerImpl( } } -private tailrec fun DeclarationDescriptor.additionalClasses(existingClasses: Collection = emptySet()): Collection = +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() - }) + + is CallableDescriptor -> containingDeclaration.additionalClasses( + existingClasses = 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 (isInlineClass()) unsubstitutedPrimaryConstructor?.collectAllTypes().orEmpty() else { - emptySequence() - } + declaredTypeParameters.asSequence().flatMap(DeclarationDescriptor::collectAllTypes) + sequenceOf(fqNameOrNull()) + val annotations = annotations.asSequence().map(AnnotationDescriptor::type).flatMap(KotlinType::collectAllTypes) + return annotations + when (this) { + is ClassConstructorDescriptor -> valueParameters.asSequence().flatMap(DeclarationDescriptor::collectAllTypes) + is ClassDescriptor -> { + val primaryConstructorTypes = if (isInlineClass()) + unsubstitutedPrimaryConstructor?.collectAllTypes().orEmpty() + else + emptySequence() + + primaryConstructorTypes + + declaredTypeParameters.asSequence().flatMap(DeclarationDescriptor::collectAllTypes) + + sequenceOf(fqNameOrNull()) + } + is CallableDescriptor -> { val returnType = returnType ?: return sequenceOf(null) returnType.collectAllTypes() + - explicitParameters.map(ParameterDescriptor::getType).flatMap(KotlinType::collectAllTypes) + + explicitParameters.asSequence().flatMap(DeclarationDescriptor::collectAllTypes) + typeParameters.asSequence().flatMap(DeclarationDescriptor::collectAllTypes) } + is TypeParameterDescriptor -> { val upperBounds = upperBounds val singleUpperBound = upperBounds.singleOrNull() @@ -118,6 +132,7 @@ private fun DeclarationDescriptor.collectAllTypes(): Sequence { if (extendBoundText == null || extendBoundText == "Any?") sequenceOf(singleUpperBound.fqName) else sequenceOf(null) } + upperBounds.isEmpty() -> sequenceOf(fqNameOrNull()) else -> upperBounds.asSequence().flatMap(KotlinType::collectAllTypes) } @@ -126,10 +141,14 @@ private fun DeclarationDescriptor.collectAllTypes(): Sequence { } } -private fun KotlinType.collectAllTypes(): Sequence = if (isError) sequenceOf(null) -else sequenceOf(fqName) + arguments.asSequence() - .map(TypeProjection::getType) - .flatMap(KotlinType::collectAllTypes) +private fun KotlinType.collectAllTypes(): Sequence = + if (isError) { + sequenceOf(null) + } else { + sequenceOf(fqName) + + arguments.asSequence().map(TypeProjection::getType).flatMap(KotlinType::collectAllTypes) + + annotations.asSequence().map(AnnotationDescriptor::type).flatMap(KotlinType::collectAllTypes) + } private val CallableDescriptor.explicitParameters: Sequence get() = valueParameters.asSequence() + dispatchReceiverParameter?.let { diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt index 1f9584f55834..f25e02ddb66a 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt @@ -439,9 +439,9 @@ fun TypeAccessibilityChecker.Companion.typesToString(types: Collection, } } -fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collection): HashSet { +fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collection): Set { var classes = elements.filterIsInstance() - while (true) { + while (classes.isNotEmpty()) { val existingNames = classes.mapNotNull { it.fqName?.asString() }.toHashSet() existingTypeNames = existingNames @@ -450,4 +450,6 @@ fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collectionform \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/classUpperBounds/jvm/Utils.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/classUpperBounds/jvm/Utils.kt index bd23a757dd8e..e965c176cc4c 100644 --- a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/classUpperBounds/jvm/Utils.kt +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/classUpperBounds/jvm/Utils.kt @@ -1,5 +1,5 @@ // "Create expected class in common module testModule_Common" "true" -// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,Some,A +// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:,Some // DISABLE-ERRORS interface Some diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/common/A.kt.after b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/common/A.kt.after deleted file mode 100644 index 756e49a59435..000000000000 --- a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/common/A.kt.after +++ /dev/null @@ -1,3 +0,0 @@ -// To be implemented -expect class A { -} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/jvm/Utils.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/jvm/Utils.kt index 1441aa315936..afa89aaed394 100644 --- a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/jvm/Utils.kt +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/jvm/Utils.kt @@ -1,5 +1,6 @@ // "Create expected class in common module testModule_Common" "true" -// DISABLE-ERRORS +// ERROR: Unresolved reference: T +// ERROR: Unresolved reference: TODO actual class A { class B { diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/jvm/Utils.kt.after b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/jvm/Utils.kt.after deleted file mode 100644 index 2ca2dde257d7..000000000000 --- a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/accessibilityChecker/memberFunctionParentType2/jvm/Utils.kt.after +++ /dev/null @@ -1,8 +0,0 @@ -// "Create expected class in common module testModule_Common" "true" -// DISABLE-ERRORS - -actual class A { - class B { - fun a(): T = TODO() - } -} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/common/one/two/CommonAnnotationClass.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/common/one/two/CommonAnnotationClass.kt new file mode 100644 index 000000000000..07e812fd2b7d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/common/one/two/CommonAnnotationClass.kt @@ -0,0 +1,3 @@ +package one.two + +annotation class CommonAnnotationClass diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/common/one/two/Platform.kt.after b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/common/one/two/Platform.kt.after new file mode 100644 index 000000000000..5e5b6c0fd667 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/common/one/two/Platform.kt.after @@ -0,0 +1,4 @@ +package one.two + +@CommonAnnotationClass +expect class Platform diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/jvm/one/two/Platform.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/jvm/one/two/Platform.kt new file mode 100644 index 000000000000..9c68b8f48df5 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/classWithAnnotation/jvm/one/two/Platform.kt @@ -0,0 +1,6 @@ +// "Create expected class in common module testModule_Common" "true" + +package one.two + +@CommonAnnotationClass +actual class Platform \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/header/My.kt.after b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/header/My.kt.after index f759a2c760a6..db746c3e227e 100644 --- a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/header/My.kt.after +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/header/My.kt.after @@ -3,8 +3,6 @@ annotation class CommonAnnotation expect class My { - tailrec fun foo(arg: Int): Int - @CommonAnnotation fun initialize() diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/jvm/My.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/jvm/My.kt index 7b8cc8e59361..f9434312b036 100644 --- a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/jvm/My.kt +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/jvm/My.kt @@ -17,4 +17,4 @@ actual class My { actual fun initialize() { some = true } -} \ No newline at end of file +} diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/jvm/My.kt.after b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/jvm/My.kt.after index b5b08f2381fe..b59a1d85c63e 100644 --- a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/jvm/My.kt.after +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/jvm/My.kt.after @@ -5,7 +5,7 @@ annotation class PlatformAnnotation actual class My { @PlatformAnnotation - actual tailrec fun foo(arg: Int): Int { + tailrec fun foo(arg: Int): Int { if (arg <= 1) return 1 return foo(arg - 1) } @@ -17,4 +17,4 @@ actual class My { actual fun initialize() { some = true } -} \ No newline at end of file +} diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/log.log b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/log.log index 5c9f4b38176c..f0ee851a4816 100644 --- a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/log.log +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/createExpect/withAnnotations/log.log @@ -1,2 +1,3 @@ These declarations cannot be transformed: +fun foo(arg: Int){...} actual lateinit var some: Boolean