ExpectActualUtils: add type arguments accessibility check for constructors

This commit is contained in:
Dmitry Gridin
2019-08-07 16:43:50 +03:00
parent e59571c482
commit 2b32c9d2f9
4 changed files with 44 additions and 28 deletions
@@ -16,8 +16,8 @@ import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.refactoring.ValVarExpression import org.jetbrains.kotlin.idea.refactoring.ValVarExpression
import org.jetbrains.kotlin.idea.util.allowedValOrVar
import org.jetbrains.kotlin.idea.util.isExpectDeclaration import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.idea.util.mustHaveValOrVar
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
@@ -32,7 +32,7 @@ interface AddValVarToConstructorParameterAction {
fun canInvoke(element: KtParameter): Boolean { fun canInvoke(element: KtParameter): Boolean {
return element.valOrVarKeyword == null && ((element.parent as? KtParameterList)?.parent as? KtPrimaryConstructor) return element.valOrVarKeyword == null && ((element.parent as? KtParameterList)?.parent as? KtPrimaryConstructor)
?.takeIf { it.allowedValOrVar() || !it.isExpectDeclaration() } != null ?.takeIf { it.mustHaveValOrVar() || !it.isExpectDeclaration() } != null
} }
operator fun invoke(element: KtParameter, editor: Editor?) { operator fun invoke(element: KtParameter, editor: Editor?) {
@@ -29,7 +29,6 @@ import org.jetbrains.kotlin.idea.inspections.findExistingEditor
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
import org.jetbrains.kotlin.idea.refactoring.renderTrimmed import org.jetbrains.kotlin.idea.refactoring.renderTrimmed
import org.jetbrains.kotlin.idea.util.allowedValOrVar
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.liftToExpected import org.jetbrains.kotlin.idea.util.liftToExpected
import org.jetbrains.kotlin.idea.util.module import org.jetbrains.kotlin.idea.util.module
@@ -38,6 +37,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.util.* import java.util.*
sealed class CreateExpectedFix<D : KtNamedDeclaration>( sealed class CreateExpectedFix<D : KtNamedDeclaration>(
@@ -116,28 +116,29 @@ class CreateExpectedClassFix(
outerExpectedClass: KtClassOrObject?, outerExpectedClass: KtClassOrObject?,
commonModule: Module commonModule: Module
) : CreateExpectedFix<KtClassOrObject>(klass, outerExpectedClass, commonModule, block@{ project, element -> ) : CreateExpectedFix<KtClassOrObject>(klass, outerExpectedClass, commonModule, block@{ project, element ->
val originalElements = element.collectDeclarations(false).toList() val originalElements = element.collectDeclarations(withSelf = false).toList()
val existingClasses = findClasses(originalElements + klass) val existingClasses = findClasses(originalElements + klass, commonModule)
if (!element.checkTypeAccessibilityInModule(commonModule, existingClasses)) { if (!element.checkTypeAccessibilityInModule(commonModule, existingClasses)) {
showUnknownTypesError(element) showUnknownTypesError(element)
return@block null return@block null
} }
val (members, declarationsWithNonExistentClasses) = originalElements.filterNot(KtNamedDeclaration::isAlwaysActual).partition { val (members, declarationsWithNonExistentClasses) = originalElements.partition {
it.checkTypeAccessibilityInModule(commonModule, existingClasses) it.checkTypeAccessibilityInModule(commonModule, existingClasses)
} }
if (!showUnknownTypesDialog(project, declarationsWithNonExistentClasses)) return@block null if (!showUnknownTypesDialog(project, declarationsWithNonExistentClasses)) return@block null
val membersForSelection = members.filterNot(KtNamedDeclaration::isAlwaysActual)
val selectedElements = when { val selectedElements = when {
members.all(KtDeclaration::hasActualModifier) -> originalElements - declarationsWithNonExistentClasses membersForSelection.all(KtDeclaration::hasActualModifier) -> membersForSelection
ApplicationManager.getApplication().isUnitTestMode -> members.filter(KtDeclaration::hasActualModifier) ApplicationManager.getApplication().isUnitTestMode -> membersForSelection.filter(KtDeclaration::hasActualModifier)
else -> { else -> {
val prefix = klass.fqName?.asString()?.plus(".") ?: "" val prefix = klass.fqName?.asString()?.plus(".") ?: ""
chooseMembers(project, members, prefix) ?: return@block null chooseMembers(project, membersForSelection, prefix) ?: return@block null
} }
}.let { selectedElements -> }.plus(members.filter(KtNamedDeclaration::isAlwaysActual)).let { selectedElements ->
val selectedClasses = findClasses(selectedElements + klass) val selectedClasses = findClasses(selectedElements + klass, commonModule)
if (selectedClasses != existingClasses) { if (selectedClasses != existingClasses) {
if (!element.checkTypeAccessibilityInModule(commonModule, selectedClasses)) { if (!element.checkTypeAccessibilityInModule(commonModule, selectedClasses)) {
showUnknownTypesError(element) showUnknownTypesError(element)
@@ -157,15 +158,19 @@ class CreateExpectedClassFix(
if (originalElements.isNotEmpty()) { if (originalElements.isNotEmpty()) {
project.executeWriteCommand("Repair actual members") { project.executeWriteCommand("Repair actual members") {
repairActualModifiers(originalElements, selectedElements) repairActualModifiers(originalElements, selectedElements.toSet())
} }
} }
generateClassOrObject(project, true, element, listOfNotNull(outerExpectedClass)) generateClassOrObject(project, true, element, listOfNotNull(outerExpectedClass))
}) })
private fun findClasses(elements: List<KtNamedDeclaration>): HashSet<String> { private tailrec fun findClasses(elements: List<KtNamedDeclaration>, module: Module): HashSet<String> {
return elements.filterIsInstance<KtClassOrObject>().mapNotNull { it.fqName?.asString() }.toHashSet() val classes = elements.filterIsInstance<KtClassOrObject>()
val existingNames = classes.mapNotNull { it.fqName?.asString() }.toHashSet()
val newExistingClasses = classes.filter { it.checkTypeAccessibilityInModule(module, existingNames) }
return if (classes.size == newExistingClasses.size) existingNames
else findClasses(newExistingClasses, module)
} }
private fun showUnknownTypesDialog(project: Project, declarationsWithNonExistentClasses: List<KtNamedDeclaration>): Boolean = private fun showUnknownTypesDialog(project: Project, declarationsWithNonExistentClasses: List<KtNamedDeclaration>): Boolean =
@@ -258,7 +263,7 @@ private fun repairActualModifiers(
} }
else else
for (original in originalElements) { for (original in originalElements) {
if (original.isAlwaysActual() || original in selectedElements) if (original in selectedElements)
original.makeActualWithParents() original.makeActualWithParents()
else else
original.makeNotActual() original.makeNotActual()
@@ -267,15 +272,10 @@ private fun repairActualModifiers(
private tailrec fun KtDeclaration.makeActualWithParents() { private tailrec fun KtDeclaration.makeActualWithParents() {
makeActual() makeActual()
safeAs<KtParameter>()?.parent?.parent?.safeAs<KtPrimaryConstructor>()?.takeUnless(KtDeclaration::hasActualModifier)?.makeActual()
containingClassOrObject?.takeUnless(KtDeclaration::hasActualModifier)?.makeActualWithParents() containingClassOrObject?.takeUnless(KtDeclaration::hasActualModifier)?.makeActualWithParents()
} }
private fun KtNamedDeclaration.isAlwaysActual(): Boolean = when (this) {
is KtPrimaryConstructor -> this
is KtParameter -> (parent as? KtParameterList)?.parent as? KtPrimaryConstructor
else -> null
}?.allowedValOrVar() ?: false
class CreateExpectedPropertyFix( class CreateExpectedPropertyFix(
property: KtNamedDeclaration, property: KtNamedDeclaration,
targetExpectedClass: KtClassOrObject?, targetExpectedClass: KtClassOrObject?,
@@ -325,19 +325,32 @@ fun DeclarationDescriptor.checkTypeAccessibilityInModule(
module: Module, module: Module,
existingClasses: Set<String> = emptySet() existingClasses: Set<String> = emptySet()
): Boolean { ): Boolean {
val existingClassesWithTypeParameters = if (this is ClassifierDescriptorWithTypeParameters) val existingClassesWithTypeParameters = additionalClasses(existingClasses)
existingClasses + declaredTypeParameters.map { it.fqNameOrNull()?.asString() ?: return false }.toSet()
else
existingClasses
return collectAllTypes().all { it?.canFindClassInModule(project, module, existingClassesWithTypeParameters) == true } return collectAllTypes().all { it?.canFindClassInModule(project, module, existingClassesWithTypeParameters) == true }
} }
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()
})
else ->
existingClasses
}
private fun DeclarationDescriptor.collectAllTypes(): Sequence<FqName?> { private fun DeclarationDescriptor.collectAllTypes(): Sequence<FqName?> {
return when (this) { return when (this) {
is ClassDescriptor -> declaredTypeParameters.asSequence().flatMap(DeclarationDescriptor::collectAllTypes) is ClassConstructorDescriptor -> valueParameters.asSequence().map(ValueParameterDescriptor::getType).flatMap(KotlinType::collectAllTypes)
is ClassDescriptor -> if (isInline) unsubstitutedPrimaryConstructor?.collectAllTypes().orEmpty() else {
emptySequence()
} + declaredTypeParameters.asSequence().flatMap(DeclarationDescriptor::collectAllTypes)
is CallableDescriptor -> { is CallableDescriptor -> {
val returnType = returnType ?: return sequenceOf(null) val returnType = returnType ?: return sequenceOf(null)
returnType.collectAllTypes() + allParameters.asSequence().map { it.type }.flatMap(KotlinType::collectAllTypes) returnType.collectAllTypes() + allParameters.asSequence().map(ParameterDescriptor::getType).flatMap(KotlinType::collectAllTypes)
} }
is TypeParameterDescriptor -> { is TypeParameterDescriptor -> {
val upperBounds = upperBounds val upperBounds = upperBounds
@@ -352,6 +365,9 @@ private fun KotlinType.collectAllTypes(): Sequence<FqName?> = sequenceOf(fqName)
.map(TypeProjection::getType) .map(TypeProjection::getType)
.flatMap(KotlinType::collectAllTypes) .flatMap(KotlinType::collectAllTypes)
fun KtNamedDeclaration.isAlwaysActual(): Boolean = safeAs<KtParameter>()?.parent?.parent?.safeAs<KtPrimaryConstructor>()
?.mustHaveValOrVar() ?: false
fun KtNamedDeclaration.checkTypeAccessibilityInModule(module: Module, existingClasses: Set<String> = emptySet()): Boolean { fun KtNamedDeclaration.checkTypeAccessibilityInModule(module: Module, existingClasses: Set<String> = emptySet()): Boolean {
return !hasPrivateModifier() && descriptor?.checkTypeAccessibilityInModule(project, module, existingClasses) == true return !hasPrivateModifier() && descriptor?.checkTypeAccessibilityInModule(project, module, existingClasses) == true
} }
@@ -31,7 +31,7 @@ fun KtModifierListOwner.hasInlineModifier() = hasModifier(KtTokens.INLINE_KEYWOR
fun KtModifierListOwner.hasPrivateModifier() = hasModifier(KtTokens.PRIVATE_KEYWORD) fun KtModifierListOwner.hasPrivateModifier() = hasModifier(KtTokens.PRIVATE_KEYWORD)
fun KtPrimaryConstructor.allowedValOrVar(): Boolean = containingClass()?.let { fun KtPrimaryConstructor.mustHaveValOrVar(): Boolean = containingClass()?.let {
it.isAnnotation() || it.hasInlineModifier() it.isAnnotation() || it.hasInlineModifier()
} ?: false } ?: false