Create actual: add type accessibility checker
#KT-28740 Fixed
This commit is contained in:
+3
-1
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.psi.KtTypeParameter
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeProjection
|
||||
import org.jetbrains.kotlin.types.isError
|
||||
import org.jetbrains.kotlin.types.typeUtil.isNullableAny
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
@@ -126,7 +127,8 @@ private fun DeclarationDescriptor.collectAllTypes(): Sequence<FqName?> {
|
||||
}
|
||||
}
|
||||
|
||||
private fun KotlinType.collectAllTypes(): Sequence<FqName?> = sequenceOf(fqName) + arguments.asSequence()
|
||||
private fun KotlinType.collectAllTypes(): Sequence<FqName?> = if (isError) sequenceOf(null)
|
||||
else sequenceOf(fqName) + arguments.asSequence()
|
||||
.map(TypeProjection::getType)
|
||||
.flatMap(KotlinType::collectAllTypes)
|
||||
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ abstract class AbstractCreateDeclarationFix<D : KtNamedDeclaration>(
|
||||
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)
|
||||
showErrorHint(project, editor, escapeXml("Cannot generate $elementType: " + e.message), "Inaccessible type")
|
||||
}
|
||||
return@runWhenSmart
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.idea.core.toDescriptor
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
|
||||
import org.jetbrains.kotlin.idea.quickfix.TypeAccessibilityChecker
|
||||
import org.jetbrains.kotlin.idea.refactoring.getExpressionShortText
|
||||
import org.jetbrains.kotlin.idea.util.module
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
||||
@@ -58,14 +59,20 @@ class AddActualFix(
|
||||
|
||||
val module = element.module ?: return
|
||||
val checker = TypeAccessibilityChecker.create(project, module)
|
||||
val errors = linkedMapOf<KtDeclaration, KotlinTypeInaccessibleException>()
|
||||
for (missedDeclaration in missedDeclarationPointers.mapNotNull { it.element }) {
|
||||
val actualDeclaration = when (missedDeclaration) {
|
||||
is KtClassOrObject -> factory.generateClassOrObject(project, false, missedDeclaration, checker)
|
||||
is KtFunction, is KtProperty -> missedDeclaration.toDescriptor()?.safeAs<CallableMemberDescriptor>()?.let {
|
||||
generateCallable(project, false, missedDeclaration, it, element, checker = checker)
|
||||
}
|
||||
else -> null
|
||||
} ?: continue
|
||||
val actualDeclaration = try {
|
||||
when (missedDeclaration) {
|
||||
is KtClassOrObject -> factory.generateClassOrObject(project, false, missedDeclaration, checker)
|
||||
is KtFunction, is KtProperty -> missedDeclaration.toDescriptor()?.safeAs<CallableMemberDescriptor>()?.let {
|
||||
generateCallable(project, false, missedDeclaration, it, element, checker = checker)
|
||||
}
|
||||
else -> null
|
||||
} ?: continue
|
||||
} catch (e: KotlinTypeInaccessibleException) {
|
||||
errors += missedDeclaration to e
|
||||
continue
|
||||
}
|
||||
|
||||
if (actualDeclaration is KtPrimaryConstructor) {
|
||||
if (element.primaryConstructor == null)
|
||||
@@ -74,6 +81,16 @@ class AddActualFix(
|
||||
element.addDeclaration(actualDeclaration).clean()
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.isNotEmpty()) {
|
||||
val message = errors.entries.joinToString(
|
||||
separator = "\n",
|
||||
prefix = "Some types are not accessible:\n"
|
||||
) { (declaration, error) ->
|
||||
getExpressionShortText(declaration) + " -> " + error.message
|
||||
}
|
||||
showInaccessibleDeclarationError(element, message, editor)
|
||||
}
|
||||
}
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.idea.quickfix.TypeAccessibilityChecker
|
||||
import org.jetbrains.kotlin.idea.util.actualsForExpected
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getSuperNames
|
||||
import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier
|
||||
|
||||
sealed class CreateActualFix<D : KtNamedDeclaration>(
|
||||
@@ -89,7 +90,10 @@ class CreateActualClassFix(
|
||||
klass: KtClassOrObject,
|
||||
actualModule: Module,
|
||||
actualPlatform: TargetPlatform
|
||||
) : CreateActualFix<KtClassOrObject>(klass, actualModule, actualPlatform, { project, checker, element ->
|
||||
) : CreateActualFix<KtClassOrObject>(klass, actualModule, actualPlatform, block@{ project, checker, element ->
|
||||
checker.findAndApplyExistingClasses(element.collectDeclarationsForAddActualModifier().toList())
|
||||
if (!checker.isCorrectAndHaveNonPrivateModifier(element, true)) return@block null
|
||||
|
||||
generateClassOrObject(project, false, element, checker = checker)
|
||||
})
|
||||
|
||||
@@ -97,7 +101,9 @@ class CreateActualCallableMemberFix(
|
||||
declaration: KtCallableDeclaration,
|
||||
actualModule: Module,
|
||||
actualPlatform: TargetPlatform
|
||||
) : CreateActualFix<KtCallableDeclaration>(declaration, actualModule, actualPlatform, { project, checker, element ->
|
||||
) : CreateActualFix<KtCallableDeclaration>(declaration, actualModule, actualPlatform, block@{ project, checker, element ->
|
||||
if (!checker.isCorrectAndHaveNonPrivateModifier(element, true)) return@block null
|
||||
|
||||
val descriptor = element.toDescriptor() as? CallableMemberDescriptor
|
||||
descriptor?.let { generateCallable(project, false, element, descriptor, checker = checker) }
|
||||
})
|
||||
|
||||
@@ -113,7 +113,7 @@ class CreateExpectedClassFix(
|
||||
outerExpectedClass: KtClassOrObject?,
|
||||
commonModule: Module
|
||||
) : CreateExpectedFix<KtClassOrObject>(klass, outerExpectedClass, commonModule, block@{ project, checker, element ->
|
||||
val originalElements = element.collectDeclarations(withSelf = false).toList()
|
||||
val originalElements = element.collectDeclarationsForAddActualModifier(withSelf = false).toList()
|
||||
val existingClasses = checker.findAndApplyExistingClasses(originalElements + klass)
|
||||
if (!checker.isCorrectAndHaveNonPrivateModifier(element, true)) return@block null
|
||||
|
||||
@@ -155,19 +155,6 @@ class CreateExpectedClassFix(
|
||||
generateClassOrObject(project, true, element, checker)
|
||||
})
|
||||
|
||||
private fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collection<KtNamedDeclaration>): HashSet<String> {
|
||||
var classes = elements.filterIsInstance<KtClassOrObject>()
|
||||
while (true) {
|
||||
val existingNames = classes.mapNotNull { it.fqName?.asString() }.toHashSet()
|
||||
existingTypeNames = existingNames
|
||||
|
||||
val newExistingClasses = classes.filter { isCorrectAndHaveNonPrivateModifier(it) }
|
||||
if (classes.size == newExistingClasses.size) return existingNames
|
||||
|
||||
classes = newExistingClasses
|
||||
}
|
||||
}
|
||||
|
||||
private fun showUnknownTypeInDeclarationDialog(
|
||||
project: Project,
|
||||
declarationsWithNonExistentClasses: Collection<KtNamedDeclaration>
|
||||
@@ -231,7 +218,7 @@ private class Member(val prefix: String, element: KtElement, descriptor: Declara
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtClassOrObject.collectDeclarations(withSelf: Boolean = true): Sequence<KtNamedDeclaration> {
|
||||
fun KtClassOrObject.collectDeclarationsForAddActualModifier(withSelf: Boolean = true): Sequence<KtNamedDeclaration> {
|
||||
val thisSequence: Sequence<KtNamedDeclaration> = if (withSelf) sequenceOf(this) else emptySequence()
|
||||
val primaryConstructorSequence: Sequence<KtNamedDeclaration> = primaryConstructorParameters.asSequence() + primaryConstructor.let {
|
||||
if (it != null) sequenceOf(it) else emptySequence()
|
||||
@@ -240,7 +227,7 @@ private fun KtClassOrObject.collectDeclarations(withSelf: Boolean = true): Seque
|
||||
return thisSequence + primaryConstructorSequence + declarations.asSequence().flatMap {
|
||||
if (it.canAddActualModifier())
|
||||
when (it) {
|
||||
is KtClassOrObject -> it.collectDeclarations()
|
||||
is KtClassOrObject -> it.collectDeclarationsForAddActualModifier()
|
||||
is KtNamedDeclaration -> sequenceOf(it)
|
||||
else -> emptySequence()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix.expectactual
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
@@ -265,7 +266,7 @@ internal fun generateCallable(
|
||||
generatedClass: KtClassOrObject? = null,
|
||||
checker: TypeAccessibilityChecker
|
||||
): KtCallableDeclaration {
|
||||
if (generateExpect) descriptor.checkAccessibility(checker)
|
||||
descriptor.checkAccessibility(checker)
|
||||
val memberChooserObject = create(
|
||||
originalDeclaration, descriptor, descriptor,
|
||||
if (generateExpect || descriptor.modality == Modality.ABSTRACT) NO_BODY else EMPTY_OR_TEMPLATE
|
||||
@@ -372,8 +373,10 @@ private fun AnnotationDescriptor.isValidInModule(checker: TypeAccessibilityCheck
|
||||
}
|
||||
|
||||
class KotlinTypeInaccessibleException(fqNames: Collection<FqName?>) : Exception() {
|
||||
override val message: String =
|
||||
"${StringUtil.pluralize("Type", fqNames.size)} ${fqNames.joinToString()} is not accessible from common code"
|
||||
override val message: String = "${StringUtil.pluralize(
|
||||
"Type",
|
||||
fqNames.size
|
||||
)} ${TypeAccessibilityChecker.typesToString(fqNames)} is not accessible from target module"
|
||||
}
|
||||
|
||||
fun KtNamedDeclaration.isAlwaysActual(): Boolean = safeAs<KtParameter>()?.parent?.parent?.safeAs<KtPrimaryConstructor>()
|
||||
@@ -399,14 +402,27 @@ fun TypeAccessibilityChecker.isCorrectAndHaveNonPrivateModifier(declaration: KtN
|
||||
return false
|
||||
}
|
||||
|
||||
private fun showInaccessibleDeclarationError(element: KtNamedDeclaration, message: String) {
|
||||
element.findExistingEditor()?.let { editor ->
|
||||
showErrorHint(element.project, editor, message, "Inaccessible declaration")
|
||||
fun showInaccessibleDeclarationError(element: KtNamedDeclaration, message: String, editor: Editor? = element.findExistingEditor()) {
|
||||
editor?.let {
|
||||
showErrorHint(element.project, editor, escapeXml(message), "Inaccessible declaration")
|
||||
}
|
||||
}
|
||||
|
||||
fun TypeAccessibilityChecker.Companion.typesToString(types: Collection<FqName?>, separator: CharSequence = "\n"): String {
|
||||
return types.toSet().joinToString(separator = separator) {
|
||||
it?.shortName()?.asString() ?: "Unknown type"
|
||||
it?.shortName()?.asString() ?: "<Unknown>"
|
||||
}
|
||||
}
|
||||
|
||||
fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collection<KtNamedDeclaration>): HashSet<String> {
|
||||
var classes = elements.filterIsInstance<KtClassOrObject>()
|
||||
while (true) {
|
||||
val existingNames = classes.mapNotNull { it.fqName?.asString() }.toHashSet()
|
||||
existingTypeNames = existingNames
|
||||
|
||||
val newExistingClasses = classes.filter { isCorrectAndHaveNonPrivateModifier(it) }
|
||||
if (classes.size == newExistingClasses.size) return existingNames
|
||||
|
||||
classes = newExistingClasses
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Create actual class for module testModule_JVM (JVM)" "true"
|
||||
// SHOULD_FAIL_WITH: Cannot generate class: Type <Unknown> is not accessible from target module
|
||||
// DISABLE-ERRORS
|
||||
|
||||
expect class Foo<caret> {
|
||||
var bar
|
||||
}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
expect class F<T> {
|
||||
val a: List<T>
|
||||
val b: T
|
||||
fun <F> c(t: T): F
|
||||
class M<V> {
|
||||
val v: K
|
||||
}
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
// "Add missing actual members" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible:,class M {...} -> Type <Unknown> is not accessible from target module
|
||||
|
||||
// DISABLE-ERRORS
|
||||
actual class <caret>F<T>
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
// "Add missing actual members" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible:,class M {...} -> Type <Unknown> is not accessible from target module
|
||||
|
||||
// DISABLE-ERRORS
|
||||
actual class <caret>F<T> {
|
||||
actual val a: List<T>
|
||||
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
|
||||
actual val b: T
|
||||
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
|
||||
|
||||
actual fun <F> c(t: T): F {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
expect class F {
|
||||
val a
|
||||
val b: Int
|
||||
val c: Gdssmem
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
// "Add missing actual members" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible:,val a -> Type <Unknown> is not accessible from target module,val c: Gdssmem -> Type <Unknown> is not accessible from target module
|
||||
|
||||
// DISABLE-ERRORS
|
||||
actual class <caret>F
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// "Add missing actual members" "true"
|
||||
// SHOULD_FAIL_WITH: Some types are not accessible:,val a -> Type <Unknown> is not accessible from target module,val c: Gdssmem -> Type <Unknown> is not accessible from target module
|
||||
|
||||
// DISABLE-ERRORS
|
||||
actual class <caret>F {
|
||||
actual val b: Int
|
||||
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
|
||||
}
|
||||
@@ -91,6 +91,7 @@ abstract class AbstractQuickFixMultiModuleTest : AbstractMultiModuleTest(), Quic
|
||||
TestCase.fail(getTestName(true))
|
||||
} else {
|
||||
Assert.assertEquals("Wrong exception message", expectedErrorMessage, e.message)
|
||||
compareToExpected(dirPath)
|
||||
}
|
||||
}
|
||||
}, "", "")
|
||||
|
||||
+15
@@ -56,6 +56,11 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul
|
||||
runTest("idea/testData/multiModuleQuickFix/accessibilityChecker/classUpperBounds/");
|
||||
}
|
||||
|
||||
@TestMetadata("errorType")
|
||||
public void testErrorType() throws Exception {
|
||||
runTest("idea/testData/multiModuleQuickFix/accessibilityChecker/errorType/");
|
||||
}
|
||||
|
||||
@TestMetadata("memberFunction")
|
||||
public void testMemberFunction() throws Exception {
|
||||
runTest("idea/testData/multiModuleQuickFix/accessibilityChecker/memberFunction/");
|
||||
@@ -184,11 +189,21 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul
|
||||
runTest("idea/testData/multiModuleQuickFix/addMissingActualMembers/companionAbsence/");
|
||||
}
|
||||
|
||||
@TestMetadata("membersWithIncorrectType")
|
||||
public void testMembersWithIncorrectType() throws Exception {
|
||||
runTest("idea/testData/multiModuleQuickFix/addMissingActualMembers/membersWithIncorrectType/");
|
||||
}
|
||||
|
||||
@TestMetadata("primaryConstructorAbsence")
|
||||
public void testPrimaryConstructorAbsence() throws Exception {
|
||||
runTest("idea/testData/multiModuleQuickFix/addMissingActualMembers/primaryConstructorAbsence/");
|
||||
}
|
||||
|
||||
@TestMetadata("propertyWithIncorrectType")
|
||||
public void testPropertyWithIncorrectType() throws Exception {
|
||||
runTest("idea/testData/multiModuleQuickFix/addMissingActualMembers/propertyWithIncorrectType/");
|
||||
}
|
||||
|
||||
@TestMetadata("secondaryConstructorAbsence")
|
||||
public void testSecondaryConstructorAbsence() throws Exception {
|
||||
runTest("idea/testData/multiModuleQuickFix/addMissingActualMembers/secondaryConstructorAbsence/");
|
||||
|
||||
Reference in New Issue
Block a user