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
}
}
}
@@ -0,0 +1,9 @@
// "class org.jetbrains.kotlin.idea.quickfix.WrapWithCollectionLiteralCallFix" "false"
// ERROR: Type mismatch: inferred type is String but MutableList<String> was expected
// WITH_RUNTIME
fun foo(a: String) {
bar(a<caret>)
}
fun bar(a: MutableList<String>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'listOf()' call" "true"
// WITH_RUNTIME
fun foo() {
bar(null<caret>)
}
fun bar(a: List<String?>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'listOf()' call" "true"
// WITH_RUNTIME
fun foo() {
bar(listOf(null))
}
fun bar(a: List<String?>) {}
@@ -0,0 +1,7 @@
// "Replace with 'emptyArray()' call" "true"
// WITH_RUNTIME
fun foo(a: String?): Array<String> {
val w = a ?: return null<caret>
return arrayOf(w)
}
@@ -0,0 +1,7 @@
// "Replace with 'emptyArray()' call" "true"
// WITH_RUNTIME
fun foo(a: String?): Array<String> {
val w = a ?: return emptyArray()<caret>
return arrayOf(w)
}
@@ -0,0 +1,7 @@
// "Replace with 'emptyList()' call" "true"
// WITH_RUNTIME
fun foo(a: String?): Collection<String> {
val w = a ?: return null<caret>
return listOf(w)
}
@@ -0,0 +1,7 @@
// "Replace with 'emptyList()' call" "true"
// WITH_RUNTIME
fun foo(a: String?): Collection<String> {
val w = a ?: return emptyList()<caret>
return listOf(w)
}
@@ -0,0 +1,7 @@
// "Replace with 'emptyList()' call" "true"
// WITH_RUNTIME
fun foo(a: String?): List<String> {
val w = a ?: return null<caret>
return listOf(w)
}
@@ -0,0 +1,7 @@
// "Replace with 'emptyList()' call" "true"
// WITH_RUNTIME
fun foo(a: String?): List<String> {
val w = a ?: return emptyList()<caret>
return listOf(w)
}
@@ -0,0 +1,7 @@
// "Replace with 'emptySequence()' call" "true"
// WITH_RUNTIME
fun foo(a: String?): Sequence<String> {
val w = a ?: return null<caret>
return sequenceOf(w)
}
@@ -0,0 +1,7 @@
// "Replace with 'emptySequence()' call" "true"
// WITH_RUNTIME
fun foo(a: String?): Sequence<String> {
val w = a ?: return emptySequence()<caret>
return sequenceOf(w)
}
@@ -0,0 +1,7 @@
// "Replace with 'emptySet()' call" "true"
// WITH_RUNTIME
fun foo(a: String?): Set<String> {
val w = a ?: return null<caret>
return setOf(w)
}
@@ -0,0 +1,7 @@
// "Replace with 'emptySet()' call" "true"
// WITH_RUNTIME
fun foo(a: String?): Set<String> {
val w = a ?: return emptySet()<caret>
return setOf(w)
}
@@ -0,0 +1,8 @@
// "Wrap element with 'arrayOf()' call" "true"
// WITH_RUNTIME
fun foo(a: String) {
bar(a<caret>)
}
fun bar(a: Array<String>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'arrayOf()' call" "true"
// WITH_RUNTIME
fun foo(a: String) {
bar(arrayOf(a)<caret>)
}
fun bar(a: Array<String>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'listOf()' call" "true"
// WITH_RUNTIME
fun foo(a: String) {
bar(a<caret>)
}
fun bar(a: Collection<String>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'listOf()' call" "true"
// WITH_RUNTIME
fun foo(a: String) {
bar(listOf(a)<caret>)
}
fun bar(a: Collection<String>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'listOf()' call" "true"
// WITH_RUNTIME
fun foo(a: String) {
bar(a<caret>)
}
fun bar(a: List<String>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'listOf()' call" "true"
// WITH_RUNTIME
fun foo(a: String) {
bar(listOf(a)<caret>)
}
fun bar(a: List<String>) {}
@@ -0,0 +1,8 @@
// "class org.jetbrains.kotlin.idea.quickfix.WrapWithCollectionLiteralCallFix" "false"
// ERROR: Type mismatch: inferred type is Int but List<String> was expected
fun foo(a: Int) {
bar(a<caret>)
}
fun bar(a: List<String>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'sequenceOf()' call" "true"
// WITH_RUNTIME
fun foo(a: String) {
bar(a<caret>)
}
fun bar(a: Sequence<String>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'sequenceOf()' call" "true"
// WITH_RUNTIME
fun foo(a: String) {
bar(sequenceOf(a)<caret>)
}
fun bar(a: Sequence<String>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'setOf()' call" "true"
// WITH_RUNTIME
fun foo(a: String) {
bar(a<caret>)
}
fun bar(a: Set<String>) {}
@@ -0,0 +1,8 @@
// "Wrap element with 'setOf()' call" "true"
// WITH_RUNTIME
fun foo(a: String) {
bar(setOf(a)<caret>)
}
fun bar(a: Set<String>) {}
@@ -3937,42 +3937,16 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
}
}
@TestMetadata("idea/testData/quickfix/typeMismatch/numberConversion")
@TestMetadata("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class NumberConversion extends AbstractQuickFixMultiFileTest {
public static class WrapWithCollectionLiteral extends AbstractQuickFixMultiFileTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInNumberConversion() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/typeMismatch/numberConversion"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
}
}
@TestMetadata("idea/testData/quickfix/typeMismatch/parameterTypeMismatch")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ParameterTypeMismatch extends AbstractQuickFixMultiFileTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInParameterTypeMismatch() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/typeMismatch/parameterTypeMismatch"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
}
}
@TestMetadata("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class TypeMismatchOnReturnedExpression extends AbstractQuickFixMultiFileTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInTypeMismatchOnReturnedExpression() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
public void testAllFilesPresentInWrapWithCollectionLiteral() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
}
}
@@ -2124,6 +2124,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
runTest("idea/testData/quickfix/typeMismatch/genericVarianceViolation/basicMultiple.before.Main.kt");
}
}
@TestMetadata("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class WrapWithCollectionLiteral extends AbstractQuickFixMultiFileTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInWrapWithCollectionLiteral() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
}
}
}
@TestMetadata("idea/testData/quickfix/variables")
@@ -11736,6 +11736,84 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@TestMetadata("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class WrapWithCollectionLiteral extends AbstractQuickFixTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInWrapWithCollectionLiteral() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("noMutableList.kt")
public void testNoMutableList() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/noMutableList.kt");
}
@TestMetadata("nullToListOfNullable.kt")
public void testNullToListOfNullable() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/nullToListOfNullable.kt");
}
@TestMetadata("returnEmptyArray.kt")
public void testReturnEmptyArray() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/returnEmptyArray.kt");
}
@TestMetadata("returnEmptyCollection.kt")
public void testReturnEmptyCollection() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/returnEmptyCollection.kt");
}
@TestMetadata("returnEmptyList.kt")
public void testReturnEmptyList() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/returnEmptyList.kt");
}
@TestMetadata("returnEmptySequence.kt")
public void testReturnEmptySequence() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/returnEmptySequence.kt");
}
@TestMetadata("returnEmptySet.kt")
public void testReturnEmptySet() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/returnEmptySet.kt");
}
@TestMetadata("toArray.kt")
public void testToArray() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/toArray.kt");
}
@TestMetadata("toCollection.kt")
public void testToCollection() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/toCollection.kt");
}
@TestMetadata("toList.kt")
public void testToList() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/toList.kt");
}
@TestMetadata("toListInapplicableType.kt")
public void testToListInapplicableType() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/toListInapplicableType.kt");
}
@TestMetadata("toSequence.kt")
public void testToSequence() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/toSequence.kt");
}
@TestMetadata("toSet.kt")
public void testToSet() throws Exception {
runTest("idea/testData/quickfix/typeMismatch/wrapWithCollectionLiteral/toSet.kt");
}
}
@TestMetadata("idea/testData/quickfix/typeMismatch/wrongPrimitive")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)