[kotlin] mpp: "create expect" should warn about inaccessible annotations

^KTIJ-20493 Fixed

GitOrigin-RevId: c1fbbe47db3dbe955545e1ba9169f419bba55f4a
This commit is contained in:
Dmitry Gridin
2021-12-17 16:58:06 +00:00
committed by intellij-monorepo-bot
parent 97ad13d9df
commit a43fedd524
17 changed files with 97 additions and 54 deletions
@@ -20,7 +20,7 @@ interface TypeAccessibilityChecker {
* For example, you want to move `open class A<T : A>` class to another module.
* In this case, you should add [FqName] of class `A` in [existingTypeNames].
*/
var existingTypeNames: Collection<String>
var existingTypeNames: Set<String>
fun incorrectTypes(declaration: KtNamedDeclaration): Collection<FqName?>
fun incorrectTypes(descriptor: DeclarationDescriptor): Collection<FqName?>
@@ -30,13 +30,13 @@ interface TypeAccessibilityChecker {
fun checkAccessibility(descriptor: DeclarationDescriptor): Boolean
fun checkAccessibility(type: KotlinType): Boolean
fun <R> runInContext(fqNames: Collection<String>, block: TypeAccessibilityChecker.() -> R): R
fun <R> runInContext(fqNames: Set<String>, block: TypeAccessibilityChecker.() -> R): R
companion object {
fun create(
project: Project,
targetModule: Module,
existingFqNames: Collection<String> = emptyList()
existingFqNames: Set<String> = emptySet(),
): TypeAccessibilityChecker = TypeAccessibilityCheckerImpl(project, targetModule, existingFqNames)
@get:TestOnly
@@ -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<String> = emptyList()
override var existingTypeNames: Set<String> = 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 <R> runInContext(fqNames: Collection<String>, block: TypeAccessibilityChecker.() -> R): R {
override fun <R> runInContext(fqNames: Set<String>, block: TypeAccessibilityChecker.() -> R): R {
val oldValue = existingTypeNames
existingTypeNames = fqNames
return block().also { existingTypeNames = oldValue }
@@ -62,10 +63,12 @@ class TypeAccessibilityCheckerImpl(
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()
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<String> = emptySet()): Collection<String> =
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()
})
is CallableDescriptor -> containingDeclaration.additionalClasses(
existingClasses = 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 (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<FqName?> {
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<FqName?> {
}
}
private fun KotlinType.collectAllTypes(): Sequence<FqName?> = if (isError) sequenceOf(null)
else sequenceOf(fqName) + arguments.asSequence()
.map(TypeProjection::getType)
.flatMap(KotlinType::collectAllTypes)
private fun KotlinType.collectAllTypes(): Sequence<FqName?> =
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<ParameterDescriptor>
get() = valueParameters.asSequence() + dispatchReceiverParameter?.let {
@@ -439,9 +439,9 @@ fun TypeAccessibilityChecker.Companion.typesToString(types: Collection<FqName?>,
}
}
fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collection<KtNamedDeclaration>): HashSet<String> {
fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collection<KtNamedDeclaration>): Set<String> {
var classes = elements.filterIsInstance<KtClassOrObject>()
while (true) {
while (classes.isNotEmpty()) {
val existingNames = classes.mapNotNull { it.fqName?.asString() }.toHashSet()
existingTypeNames = existingNames
@@ -450,4 +450,6 @@ fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collection<Kt
classes = newExistingClasses
}
return existingTypeNames
}
@@ -26,6 +26,11 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("annotationOnClass")
public void testAnnotationOnClass() throws Exception {
runTest("testData/multiModuleQuickFix/accessibilityChecker/annotationOnClass/");
}
@TestMetadata("classPrimaryConstructor")
public void testClassPrimaryConstructor() throws Exception {
runTest("testData/multiModuleQuickFix/accessibilityChecker/classPrimaryConstructor/");
@@ -537,6 +542,11 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM
runTest("testData/multiModuleQuickFix/createExpect/class/");
}
@TestMetadata("classWithAnnotation")
public void testClassWithAnnotation() throws Exception {
runTest("testData/multiModuleQuickFix/createExpect/classWithAnnotation/");
}
@TestMetadata("classWithSuperClassAndTypeParameter")
public void testClassWithSuperClassAndTypeParameter() throws Exception {
runTest("testData/multiModuleQuickFix/createExpect/classWithSuperClassAndTypeParameter/");
@@ -616,11 +626,6 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM
public void testFunction() throws Exception {
runTest("testData/multiModuleQuickFix/createExpect/function/");
}
@TestMetadata("function2")
public void testFunction2() throws Exception {
runTest("testData/multiModuleQuickFix/createExpect/function2/");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@@ -630,6 +635,11 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("function2")
public void testFunction2() throws Exception {
runTest("testData/multiModuleQuickFix/createExpect/function2/");
}
@TestMetadata("functionInInterface")
public void testFunctionInInterface() throws Exception {
runTest("testData/multiModuleQuickFix/createExpect/functionInInterface/");
@@ -724,11 +734,6 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM
public void testPropertyWithLateinitModifier() throws Exception {
runTest("testData/multiModuleQuickFix/createExpect/propertyWithLateinitModifier/");
}
@TestMetadata("propertyWithPrivateModifier")
public void testPropertyWithPrivateModifier() throws Exception {
runTest("testData/multiModuleQuickFix/createExpect/propertyWithPrivateModifier/");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@@ -738,6 +743,11 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("propertyWithPrivateModifier")
public void testPropertyWithPrivateModifier() throws Exception {
runTest("testData/multiModuleQuickFix/createExpect/propertyWithPrivateModifier/");
}
@TestMetadata("sealedClass")
public void testSealedClass() throws Exception {
runTest("testData/multiModuleQuickFix/createExpect/sealedClass/");
@@ -0,0 +1,10 @@
// "Create expected class in common module testModule_Common" "true"
// SHOULD_FAIL_WITH: Some types are not accessible from testModule_Common:
// SHOULD_FAIL_WITH: JvmAnnotationClass
package one.two
annotation class JvmAnnotationClass
@JvmAnnotationClass
actual class Plat<caret>form
@@ -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
@@ -1,3 +0,0 @@
// To be implemented
expect class A<T> {
}
@@ -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<T> {
class B {
@@ -1,8 +0,0 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class A<T> {
class B {
fun a(): T = TODO()
}
}
@@ -0,0 +1,3 @@
package one.two
annotation class CommonAnnotationClass
@@ -0,0 +1,4 @@
package one.two
@CommonAnnotationClass
expect class <selection><caret></selection>Platform
@@ -0,0 +1,6 @@
// "Create expected class in common module testModule_Common" "true"
package one.two
@CommonAnnotationClass
actual class Plat<caret>form
@@ -3,8 +3,6 @@
annotation class CommonAnnotation
expect class My {
tailrec fun foo(arg: Int): Int
@CommonAnnotation
fun initialize()
@@ -17,4 +17,4 @@ actual class <caret>My {
actual fun initialize() {
some = true
}
}
}
@@ -5,7 +5,7 @@ annotation class PlatformAnnotation
actual class <caret>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 <caret>My {
actual fun initialize() {
some = true
}
}
}
@@ -1,2 +1,3 @@
These declarations cannot be transformed:
fun foo(arg: Int){...}
actual lateinit var some: Boolean