ExpectActualUtils: add type arguments accessibility check for constructors
This commit is contained in:
+2
-2
@@ -16,8 +16,8 @@ import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
|
||||
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.mustHaveValOrVar
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
@@ -32,7 +32,7 @@ interface AddValVarToConstructorParameterAction {
|
||||
|
||||
fun canInvoke(element: KtParameter): Boolean {
|
||||
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?) {
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.jetbrains.kotlin.idea.inspections.findExistingEditor
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
|
||||
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.liftToExpected
|
||||
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.createSmartPointer
|
||||
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import java.util.*
|
||||
|
||||
sealed class CreateExpectedFix<D : KtNamedDeclaration>(
|
||||
@@ -116,28 +116,29 @@ class CreateExpectedClassFix(
|
||||
outerExpectedClass: KtClassOrObject?,
|
||||
commonModule: Module
|
||||
) : CreateExpectedFix<KtClassOrObject>(klass, outerExpectedClass, commonModule, block@{ project, element ->
|
||||
val originalElements = element.collectDeclarations(false).toList()
|
||||
val existingClasses = findClasses(originalElements + klass)
|
||||
val originalElements = element.collectDeclarations(withSelf = false).toList()
|
||||
val existingClasses = findClasses(originalElements + klass, commonModule)
|
||||
if (!element.checkTypeAccessibilityInModule(commonModule, existingClasses)) {
|
||||
showUnknownTypesError(element)
|
||||
return@block null
|
||||
}
|
||||
|
||||
val (members, declarationsWithNonExistentClasses) = originalElements.filterNot(KtNamedDeclaration::isAlwaysActual).partition {
|
||||
val (members, declarationsWithNonExistentClasses) = originalElements.partition {
|
||||
it.checkTypeAccessibilityInModule(commonModule, existingClasses)
|
||||
}
|
||||
|
||||
if (!showUnknownTypesDialog(project, declarationsWithNonExistentClasses)) return@block null
|
||||
|
||||
val membersForSelection = members.filterNot(KtNamedDeclaration::isAlwaysActual)
|
||||
val selectedElements = when {
|
||||
members.all(KtDeclaration::hasActualModifier) -> originalElements - declarationsWithNonExistentClasses
|
||||
ApplicationManager.getApplication().isUnitTestMode -> members.filter(KtDeclaration::hasActualModifier)
|
||||
membersForSelection.all(KtDeclaration::hasActualModifier) -> membersForSelection
|
||||
ApplicationManager.getApplication().isUnitTestMode -> membersForSelection.filter(KtDeclaration::hasActualModifier)
|
||||
else -> {
|
||||
val prefix = klass.fqName?.asString()?.plus(".") ?: ""
|
||||
chooseMembers(project, members, prefix) ?: return@block null
|
||||
chooseMembers(project, membersForSelection, prefix) ?: return@block null
|
||||
}
|
||||
}.let { selectedElements ->
|
||||
val selectedClasses = findClasses(selectedElements + klass)
|
||||
}.plus(members.filter(KtNamedDeclaration::isAlwaysActual)).let { selectedElements ->
|
||||
val selectedClasses = findClasses(selectedElements + klass, commonModule)
|
||||
if (selectedClasses != existingClasses) {
|
||||
if (!element.checkTypeAccessibilityInModule(commonModule, selectedClasses)) {
|
||||
showUnknownTypesError(element)
|
||||
@@ -157,15 +158,19 @@ class CreateExpectedClassFix(
|
||||
|
||||
if (originalElements.isNotEmpty()) {
|
||||
project.executeWriteCommand("Repair actual members") {
|
||||
repairActualModifiers(originalElements, selectedElements)
|
||||
repairActualModifiers(originalElements, selectedElements.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
generateClassOrObject(project, true, element, listOfNotNull(outerExpectedClass))
|
||||
})
|
||||
|
||||
private fun findClasses(elements: List<KtNamedDeclaration>): HashSet<String> {
|
||||
return elements.filterIsInstance<KtClassOrObject>().mapNotNull { it.fqName?.asString() }.toHashSet()
|
||||
private tailrec fun findClasses(elements: List<KtNamedDeclaration>, module: Module): HashSet<String> {
|
||||
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 =
|
||||
@@ -258,7 +263,7 @@ private fun repairActualModifiers(
|
||||
}
|
||||
else
|
||||
for (original in originalElements) {
|
||||
if (original.isAlwaysActual() || original in selectedElements)
|
||||
if (original in selectedElements)
|
||||
original.makeActualWithParents()
|
||||
else
|
||||
original.makeNotActual()
|
||||
@@ -267,15 +272,10 @@ private fun repairActualModifiers(
|
||||
|
||||
private tailrec fun KtDeclaration.makeActualWithParents() {
|
||||
makeActual()
|
||||
safeAs<KtParameter>()?.parent?.parent?.safeAs<KtPrimaryConstructor>()?.takeUnless(KtDeclaration::hasActualModifier)?.makeActual()
|
||||
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(
|
||||
property: KtNamedDeclaration,
|
||||
targetExpectedClass: KtClassOrObject?,
|
||||
|
||||
@@ -325,19 +325,32 @@ fun DeclarationDescriptor.checkTypeAccessibilityInModule(
|
||||
module: Module,
|
||||
existingClasses: Set<String> = emptySet()
|
||||
): Boolean {
|
||||
val existingClassesWithTypeParameters = if (this is ClassifierDescriptorWithTypeParameters)
|
||||
existingClasses + declaredTypeParameters.map { it.fqNameOrNull()?.asString() ?: return false }.toSet()
|
||||
else
|
||||
existingClasses
|
||||
val existingClassesWithTypeParameters = additionalClasses(existingClasses)
|
||||
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?> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
val upperBounds = upperBounds
|
||||
@@ -352,6 +365,9 @@ private fun KotlinType.collectAllTypes(): Sequence<FqName?> = sequenceOf(fqName)
|
||||
.map(TypeProjection::getType)
|
||||
.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 {
|
||||
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 KtPrimaryConstructor.allowedValOrVar(): Boolean = containingClass()?.let {
|
||||
fun KtPrimaryConstructor.mustHaveValOrVar(): Boolean = containingClass()?.let {
|
||||
it.isAnnotation() || it.hasInlineModifier()
|
||||
} ?: false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user