diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityCheckerImpl.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityCheckerImpl.kt index c0020dd26de..33dbaeabe59 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityCheckerImpl.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/TypeAccessibilityCheckerImpl.kt @@ -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 { } } -private fun KotlinType.collectAllTypes(): Sequence = sequenceOf(fqName) + arguments.asSequence() +private fun KotlinType.collectAllTypes(): Sequence = if (isError) sequenceOf(null) +else sequenceOf(fqName) + arguments.asSequence() .map(TypeProjection::getType) .flatMap(KotlinType::collectAllTypes) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt index ca7e6e73e4e..f2c02fa7f51 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt @@ -57,7 +57,7 @@ abstract class AbstractCreateDeclarationFix( 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 } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt index 686ac3d4cd7..41e8361a82b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AddActualFix.kt @@ -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() 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()?.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()?.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() { diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt index 094c9d0f63b..a9aed636eba 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt @@ -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( @@ -89,7 +90,10 @@ class CreateActualClassFix( klass: KtClassOrObject, actualModule: Module, actualPlatform: TargetPlatform -) : CreateActualFix(klass, actualModule, actualPlatform, { project, checker, element -> +) : CreateActualFix(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(declaration, actualModule, actualPlatform, { project, checker, element -> +) : CreateActualFix(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) } }) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt index 399f7f1e7b6..ca764b0e6f3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt @@ -113,7 +113,7 @@ class CreateExpectedClassFix( outerExpectedClass: KtClassOrObject?, commonModule: Module ) : CreateExpectedFix(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): HashSet { - var classes = elements.filterIsInstance() - 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 @@ -231,7 +218,7 @@ private class Member(val prefix: String, element: KtElement, descriptor: Declara } } -private fun KtClassOrObject.collectDeclarations(withSelf: Boolean = true): Sequence { +fun KtClassOrObject.collectDeclarationsForAddActualModifier(withSelf: Boolean = true): Sequence { val thisSequence: Sequence = if (withSelf) sequenceOf(this) else emptySequence() val primaryConstructorSequence: Sequence = 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() } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt index b3f972531b3..3e29fae5fd8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/ExpectActualUtils.kt @@ -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) : 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()?.parent?.parent?.safeAs() @@ -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, separator: CharSequence = "\n"): String { return types.toSet().joinToString(separator = separator) { - it?.shortName()?.asString() ?: "Unknown type" + it?.shortName()?.asString() ?: "" + } +} + +fun TypeAccessibilityChecker.findAndApplyExistingClasses(elements: Collection): HashSet { + var classes = elements.filterIsInstance() + 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 } } \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/accessibilityChecker/errorType/common/foo.kt b/idea/testData/multiModuleQuickFix/accessibilityChecker/errorType/common/foo.kt new file mode 100644 index 00000000000..f9b32892ce7 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/accessibilityChecker/errorType/common/foo.kt @@ -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 { + var bar +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/accessibilityChecker/errorType/jvm/Utils.kt b/idea/testData/multiModuleQuickFix/accessibilityChecker/errorType/jvm/Utils.kt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/multiModuleQuickFix/addMissingActualMembers/membersWithIncorrectType/common/F.kt b/idea/testData/multiModuleQuickFix/addMissingActualMembers/membersWithIncorrectType/common/F.kt new file mode 100644 index 00000000000..698298e20b1 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/addMissingActualMembers/membersWithIncorrectType/common/F.kt @@ -0,0 +1,8 @@ +expect class F { + val a: List + val b: T + fun c(t: T): F + class M { + val v: K + } +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/addMissingActualMembers/membersWithIncorrectType/jvm/F.kt b/idea/testData/multiModuleQuickFix/addMissingActualMembers/membersWithIncorrectType/jvm/F.kt new file mode 100644 index 00000000000..499580924e7 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/addMissingActualMembers/membersWithIncorrectType/jvm/F.kt @@ -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 F \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/addMissingActualMembers/membersWithIncorrectType/jvm/F.kt.after b/idea/testData/multiModuleQuickFix/addMissingActualMembers/membersWithIncorrectType/jvm/F.kt.after new file mode 100644 index 00000000000..059b86ed55d --- /dev/null +++ b/idea/testData/multiModuleQuickFix/addMissingActualMembers/membersWithIncorrectType/jvm/F.kt.after @@ -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 F { + actual val a: List + 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 c(t: T): F { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/addMissingActualMembers/propertyWithIncorrectType/common/F.kt b/idea/testData/multiModuleQuickFix/addMissingActualMembers/propertyWithIncorrectType/common/F.kt new file mode 100644 index 00000000000..ce2d03f6ec3 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/addMissingActualMembers/propertyWithIncorrectType/common/F.kt @@ -0,0 +1,5 @@ +expect class F { + val a + val b: Int + val c: Gdssmem +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/addMissingActualMembers/propertyWithIncorrectType/jvm/F.kt b/idea/testData/multiModuleQuickFix/addMissingActualMembers/propertyWithIncorrectType/jvm/F.kt new file mode 100644 index 00000000000..6f7cdc50879 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/addMissingActualMembers/propertyWithIncorrectType/jvm/F.kt @@ -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 F \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/addMissingActualMembers/propertyWithIncorrectType/jvm/F.kt.after b/idea/testData/multiModuleQuickFix/addMissingActualMembers/propertyWithIncorrectType/jvm/F.kt.after new file mode 100644 index 00000000000..c000cb0ee63 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/addMissingActualMembers/propertyWithIncorrectType/jvm/F.kt.after @@ -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 F { + actual val b: Int + get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt index b57e59933cc..068fa2dd509 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt @@ -91,6 +91,7 @@ abstract class AbstractQuickFixMultiModuleTest : AbstractMultiModuleTest(), Quic TestCase.fail(getTestName(true)) } else { Assert.assertEquals("Wrong exception message", expectedErrorMessage, e.message) + compareToExpected(dirPath) } } }, "", "") diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java index aa719ea9374..f5900a36949 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java @@ -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/");