Commit f6f37ee0 authored by Dmitry Gridin's avatar Dmitry Gridin Committed by intellij-monorepo-bot
Browse files

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

^KTIJ-20493 Fixed

(cherry picked from commit c1fbbe47db3dbe955545e1ba9169f419bba55f4a)

GitOrigin-RevId: 9e1a69f72f317c6d57773c31f69bac844033c6eb
parent 4fe7a66f
Showing with 87 additions and 36 deletions
+87 -36
......@@ -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/");
......@@ -550,6 +555,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/");
......
// "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
\ No newline at end of file
// "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
......
// To be implemented
expect class A<T> {
}
\ No newline at end of file
// "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 {
......
package one.two
annotation class CommonAnnotationClass
package one.two
@CommonAnnotationClass
expect class <selection><caret></selection>Platform
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class A<T> {
class B {
fun a(): T = TODO()
}
}
\ No newline at end of file
package one.two
@CommonAnnotationClass
actual class Plat<caret>form
\ No newline at end of file
......@@ -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
}
}
\ No newline at end of file
}
......@@ -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
}
}
\ No newline at end of file
}
These declarations cannot be transformed:
fun foo(arg: Int){...}
actual lateinit var some: Boolean
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment