Implement quickfix wrapping elements in collection literal calls

#KT-25238 Fixed
This commit is contained in:
Denis Zharkov
2018-07-05 14:27:04 +03:00
parent 189260c013
commit 6f4aa2296d
30 changed files with 375 additions and 43 deletions
@@ -42,19 +42,22 @@ class ConvertCollectionFix(element: KtExpression, val type: CollectionType) : Ko
}
enum class CollectionType(
val functionCall: String,
val fqName: FqName,
private val nameOverride: String? = null
val functionCall: String,
val fqName: FqName,
val literalFunctionName: String? = null,
val emptyCollectionFunction: String? = null,
private val nameOverride: String? = null
) {
List("toList()", FqName("kotlin.collections.List")),
Collection("toList()", FqName("kotlin.collections.Collection")),
Iterable("toList()", FqName("kotlin.collections.Iterable")),
List("toList()", FqName("kotlin.collections.List"), "listOf", "emptyList"),
Collection("toList()", FqName("kotlin.collections.Collection"), "listOf", "emptyList"),
Iterable("toList()", FqName("kotlin.collections.Iterable"), "listOf", "emptyList"),
MutableList("toMutableList()", FqName("kotlin.collections.MutableList")),
Array("toTypedArray()", FqName("kotlin.Array")),
Sequence("asSequence()", FqName("kotlin.sequences.Sequence")),
Array("toTypedArray()", FqName("kotlin.Array"), "arrayOf", "emptyArray"),
Sequence("asSequence()", FqName("kotlin.sequences.Sequence"), "sequenceOf", "emptySequence"),
Set("toSet()", FqName("kotlin.collections.Set"), "setOf", "emptySet"),
//specialized types must be last because iteration order is relevant for getCollectionType
ArrayViaList("toList().toTypedArray()", FqName("kotlin.Array"), "Array"),
ArrayViaList("toList().toTypedArray()", FqName("kotlin.Array"), nameOverride = "Array"),
;
val displayName get() = nameOverride ?: name
@@ -80,9 +83,9 @@ class ConvertCollectionFix(element: KtExpression, val type: CollectionType) : Ko
return expectedCollectionType.specializeFor(expressionCollectionType)
}
private fun KotlinType.getCollectionType(): CollectionType? {
if (isMarkedNullable) return null
fun KotlinType.getCollectionType(acceptNullableTypes: Boolean = false): CollectionType? {
if (isMarkedNullable && !acceptNullableTypes) return null
return TYPES.firstOrNull { KotlinBuiltIns.isConstructedFromGivenClass(this, it.fqName) }
}
}
}
}
@@ -19,8 +19,9 @@ package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.diagnostic.Logger
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.builtins.*
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.functions.FunctionClassDescriptor
import org.jetbrains.kotlin.builtins.getFunctionalClassKind
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
@@ -122,6 +123,9 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() {
expressionTypeDeclaration?.let { actions.add(LetImplementInterfaceFix(it, expectedType, expressionType)) }
}
actions.addAll(WrapWithCollectionLiteralCallFix.create(expectedType, expressionType, diagnosticElement))
ConvertCollectionFix.getConversionTypeOrNull(expressionType, expectedType)?.let {
actions.add(ConvertCollectionFix(diagnosticElement, it))
}
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.createExpressionByPattern
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
class WrapWithCollectionLiteralCallFix private constructor(
element: KtExpression,
private val functionName: String,
private val wrapInitialElement: Boolean
) : KotlinQuickFixAction<KtExpression>(element) {
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val expression = element ?: return
val factory = KtPsiFactory(expression)
val replaced =
if (wrapInitialElement)
expression.replaced(factory.createExpressionByPattern("$functionName($0)", expression))
else
expression.replaced(factory.createExpression("$functionName()"))
editor?.caretModel?.moveToOffset(replaced.endOffset)
}
override fun getFamilyName(): String = "Wrap with collection literal call"
override fun getText() =
if (wrapInitialElement)
"Wrap element with '$functionName()' call"
else
"Replace with '$functionName()' call"
companion object {
fun create(expectedType: KotlinType, expressionType: KotlinType, element: KtExpression): List<WrapWithCollectionLiteralCallFix> {
val collectionType =
with(ConvertCollectionFix) {
expectedType.getCollectionType(acceptNullableTypes = true)
} ?: return emptyList()
val expectedArgumentType =
expectedType
.arguments.singleOrNull()
?.takeIf { it.projectionKind != Variance.IN_VARIANCE }
?.type
?: return emptyList()
val result = mutableListOf<WrapWithCollectionLiteralCallFix>()
val isNullExpression = element.isNullExpression()
if ((expressionType.isSubtypeOf(expectedArgumentType) || isNullExpression) && collectionType.literalFunctionName != null) {
result += WrapWithCollectionLiteralCallFix(element, collectionType.literalFunctionName, wrapInitialElement = true)
}
// Replace "null" with emptyList()
if (isNullExpression && collectionType.emptyCollectionFunction != null) {
result += WrapWithCollectionLiteralCallFix(element, collectionType.emptyCollectionFunction, wrapInitialElement = false)
}
return result
}
}
}