diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt index 2bd5278edfa..dd80f7de17f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/DestructureIntention.kt @@ -40,7 +40,6 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.utils.singletonOrEmptyList import java.util.* @@ -48,9 +47,9 @@ class DestructureInspection : IntentionBasedInspection( DestructureIntention::class, { element, inspection -> if (element is KtParameter) true - else with(inspection as DestructureInspection) { - val usagesToRemove = DestructureIntention.collectUsagesToRemove(element, element.parent as? KtForExpression)?.data - usagesToRemove != null && usagesToRemove.any { it.declarationToDrop is KtDestructuringDeclaration } + else { + val usagesToRemove = DestructureIntention.collectUsagesToRemove(element)?.data + usagesToRemove?.any { it.declarationToDrop is KtDestructuringDeclaration } ?: false } } ) @@ -60,13 +59,11 @@ class DestructureIntention : SelfTargetingRangeIntention( "Use destructuring declaration" ) { override fun applyTo(element: KtDeclaration, editor: Editor?) { - val forLoop = element.parent as? KtForExpression - val loopRange = forLoop?.loopRange - val (usagesToRemove, removeSelectorInLoopRange) = collectUsagesToRemove(element, forLoop)!! + val (usagesToRemove, removeSelectorInLoopRange) = collectUsagesToRemove(element)!! val factory = KtPsiFactory(element) val validator = NewDeclarationNameValidator( - element.parent, element, NewDeclarationNameValidator.Target.VARIABLES, + container = element.parent, anchor = element, target = NewDeclarationNameValidator.Target.VARIABLES, excludedDeclarations = usagesToRemove.map { it.declarationToDrop.singletonOrEmptyList() }.flatten() ) val names = ArrayList() @@ -82,6 +79,7 @@ class DestructureIntention : SelfTargetingRangeIntention( val joinedNames = names.joinToString() when (element) { is KtParameter -> { + val loopRange = (element.parent as? KtForExpression)?.loopRange element.replace(factory.createDestructuringParameter("($joinedNames)")) if (removeSelectorInLoopRange && loopRange is KtDotQualifiedExpression) { loopRange.replace(loopRange.receiverExpression) @@ -113,9 +111,9 @@ class DestructureIntention : SelfTargetingRangeIntention( } override fun applicabilityRange(element: KtDeclaration): TextRange? { - if (!isSuitableDeclaration(element)) return null + if (!element.isSuitableDeclaration()) return null - val usagesToRemove = collectUsagesToRemove(element, element.parent as? KtForExpression)?.data ?: return null + val usagesToRemove = collectUsagesToRemove(element)?.data ?: return null if (usagesToRemove.isEmpty()) return null return when (element) { @@ -125,33 +123,36 @@ class DestructureIntention : SelfTargetingRangeIntention( } } - private fun isSuitableDeclaration(declaration: KtDeclaration) = when (declaration) { - is KtParameter -> { - val parent = declaration.parent - when { - parent is KtForExpression -> true - parent?.parent is KtFunctionLiteral -> true - else -> false - } - } - is KtVariableDeclaration -> true - is KtFunctionLiteral -> !declaration.hasParameterSpecification() // replace implicit 'it' with destructuring declaration - else -> false - } - companion object { + internal fun KtDeclaration.isSuitableDeclaration() = getUsageScopeElement() != null + + private fun KtDeclaration.getUsageScopeElement() = when (this) { + is KtParameter -> { + val parent = parent + when { + parent is KtForExpression -> parent + parent?.parent is KtFunctionLiteral -> parent.parent + else -> null + } + } + is KtVariableDeclaration -> parent + is KtFunctionLiteral -> if (!hasParameterSpecification()) this else null + else -> null + } + internal data class UsagesToRemove(val data: List, val removeSelectorInLoopRange: Boolean) - internal fun collectUsagesToRemove(declaration: KtDeclaration, forLoop: KtForExpression?): UsagesToRemove? { + internal fun collectUsagesToRemove(declaration: KtDeclaration): UsagesToRemove? { val context = declaration.analyze() - val variableDescriptor = when (declaration) { - is KtParameter -> context.get(BindingContext.VALUE_PARAMETER, declaration) - is KtFunctionLiteral -> context.get(BindingContext.FUNCTION, declaration)?.valueParameters?.singleOrNull() - is KtVariableDeclaration -> context.get(BindingContext.VARIABLE, declaration) - else -> null - } ?: return null + val variableDescriptor = + when (declaration) { + is KtParameter -> context.get(BindingContext.VALUE_PARAMETER, declaration) + is KtFunctionLiteral -> context.get(BindingContext.FUNCTION, declaration)?.valueParameters?.singleOrNull() + is KtVariableDeclaration -> context.get(BindingContext.VARIABLE, declaration) + else -> null + } ?: return null val variableType = variableDescriptor.type if (variableType.isMarkedNullable) return null @@ -163,6 +164,7 @@ class DestructureIntention : SelfTargetingRangeIntention( val usagesToRemove = mutableListOf() var noBadUsages = true var removeSelectorInLoopRange = false + val forLoop = declaration.parent as? KtForExpression if (forLoop != null && DescriptorUtils.isSubclass(classDescriptor, mapEntryClassDescriptor)) { val loopRangeDescriptor = forLoop.loopRange.getResolvedCall(context)?.resultingDescriptor if (loopRangeDescriptor != null) { @@ -175,7 +177,7 @@ class DestructureIntention : SelfTargetingRangeIntention( } listOf("key", "value").mapTo(usagesToRemove) { - UsageData(mapEntryClassDescriptor.unsubstitutedMemberScope.getContributedVariables( + UsageData(descriptor = mapEntryClassDescriptor.unsubstitutedMemberScope.getContributedVariables( Name.identifier(it), NoLookupLocation.FROM_BUILTINS).single()) } @@ -188,14 +190,9 @@ class DestructureIntention : SelfTargetingRangeIntention( else if (classDescriptor.isData) { val valueParameters = classDescriptor.unsubstitutedPrimaryConstructor?.valueParameters ?: return null - valueParameters.mapTo(usagesToRemove) { UsageData(it) } + valueParameters.mapTo(usagesToRemove) { UsageData(descriptor = it) } - // inference bug: remove as? PsiElement when fixed - val usageScopeElement: PsiElement = forLoop as? PsiElement - ?: (declaration as? KtFunctionLiteral) - ?: (declaration.parent?.parent as? KtFunctionLiteral) - ?: (declaration as? KtVariableDeclaration)?.parent - ?: return null + val usageScopeElement = declaration.getUsageScopeElement() ?: return null val nameToSearch = when (declaration) { is KtParameter -> declaration.nameAsName @@ -253,7 +250,7 @@ class DestructureIntention : SelfTargetingRangeIntention( } cancel() - return@forEach false + false }) } @@ -325,7 +322,7 @@ class DestructureIntention : SelfTargetingRangeIntention( val descriptor: CallableDescriptor, val usagesToReplace: MutableList = mutableListOf(), var declarationToDrop: KtDeclaration? = null, - var name: String? = declarationToDrop?.name + var name: String? = null ) { // Returns true if data is successfully added, false otherwise fun add(newData: SingleUsageData, componentIndex: Int): Boolean {