diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ConvertCollectionFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ConvertCollectionFix.kt new file mode 100644 index 00000000000..cfc8ab5f64b --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ConvertCollectionFix.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.quickfix + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.name.FqName +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.typeUtil.isSubtypeOf + +class ConvertCollectionFix(element: KtExpression, val type: CollectionType) : KotlinQuickFixAction(element) { + override fun getFamilyName(): String = "Convert to ${type.displayName}" + override fun getText() = "Convert expression to '${type.displayName}' by inserting '.${type.functionCall}'" + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val expression = element ?: return + val factory = KtPsiFactory(expression) + + val replaced = expression.replaced(factory.createExpressionByPattern("$0.$1", expression, type.functionCall)) + editor?.caretModel?.moveToOffset(replaced.endOffset) + } + + enum class CollectionType( + val functionCall: String, + val fqName: FqName, + private val nameOverride: String? = null + ) { + List("toList()", FqName("kotlin.collections.List")), + Collection("toList()", FqName("kotlin.collections.Collection")), + Iterable("toList()", FqName("kotlin.collections.Iterable")), + MutableList("toMutableList()", FqName("kotlin.collections.MutableList")), + Array("toTypedArray()", FqName("kotlin.Array")), + Sequence("asSequence()", FqName("kotlin.sequences.Sequence")), + + //specialized types must be last because iteration order is relevant for getCollectionType + ArrayViaList("toList().toTypedArray()", FqName("kotlin.Array"), "Array"), + ; + + val displayName get() = nameOverride ?: name + + fun specializeFor(sourceType: CollectionType) = when { + this == Array && sourceType == Sequence -> ArrayViaList + this == Array && sourceType == Iterable -> ArrayViaList + else -> this + } + } + + companion object { + private val TYPES = CollectionType.values() + + fun getConversionTypeOrNull(expressionType: KotlinType, expectedType: KotlinType): CollectionType? { + val expressionCollectionType = expressionType.getCollectionType() ?: return null + val expectedCollectionType = expectedType.getCollectionType() ?: return null + + val expressionTypeArg = expressionType.arguments.singleOrNull()?.type ?: return null + val expectedTypeArg = expectedType.arguments.singleOrNull()?.type ?: return null + if (!expressionTypeArg.isSubtypeOf(expectedTypeArg)) return null + + return expectedCollectionType.specializeFor(expressionCollectionType) + } + + private fun KotlinType.getCollectionType(): CollectionType? { + if (isMarkedNullable) return null + return TYPES.firstOrNull { KotlinBuiltIns.isConstructedFromGivenClass(this, it.fqName) } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt index d82049a6892..bd31eeeba42 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactoryForTypeMismatchError.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -116,6 +116,10 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() { expressionTypeDeclaration?.let { actions.add(LetImplementInterfaceFix(it, expectedType, expressionType)) } } + ConvertCollectionFix.getConversionTypeOrNull(expressionType, expectedType)?.let { + actions.add(ConvertCollectionFix(diagnosticElement, it)) + } + fun KtExpression.getTopMostQualifiedForSelectorIfAny(): KtExpression { var qualifiedOrThis = this do { @@ -245,3 +249,4 @@ class QuickFixFactoryForTypeMismatchError : KotlinIntentionActionsFactory() { } } } + diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/arrayToCollection.kt b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToCollection.kt new file mode 100644 index 00000000000..d0e8923f091 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToCollection.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'Collection' by inserting '.toList()'" "true" +// WITH_RUNTIME + +fun foo(a: Array) { + bar(a) +} + +fun bar(a: Collection) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/arrayToCollection.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToCollection.kt.after new file mode 100644 index 00000000000..4a86d1d875b --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToCollection.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'Collection' by inserting '.toList()'" "true" +// WITH_RUNTIME + +fun foo(a: Array) { + bar(a.toList()) +} + +fun bar(a: Collection) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/arrayToIterable.kt b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToIterable.kt new file mode 100644 index 00000000000..1f67967c9c0 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToIterable.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'Iterable' by inserting '.toList()'" "true" +// WITH_RUNTIME + +fun foo(a: Array) { + bar(a) +} + +fun bar(a: Iterable) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/arrayToIterable.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToIterable.kt.after new file mode 100644 index 00000000000..61ed32f3f17 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToIterable.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'Iterable' by inserting '.toList()'" "true" +// WITH_RUNTIME + +fun foo(a: Array) { + bar(a.toList()) +} + +fun bar(a: Iterable) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/arrayToList.kt b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToList.kt new file mode 100644 index 00000000000..72b65192953 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToList.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'List' by inserting '.toList()'" "true" +// WITH_RUNTIME + +fun foo(a: Array) { + bar(a) +} + +fun bar(a: List) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/arrayToList.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToList.kt.after new file mode 100644 index 00000000000..4d8c86a20a8 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToList.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'List' by inserting '.toList()'" "true" +// WITH_RUNTIME + +fun foo(a: Array) { + bar(a.toList()) +} + +fun bar(a: List) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/arrayToSequence.kt b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToSequence.kt new file mode 100644 index 00000000000..67edc122ede --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToSequence.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'Sequence' by inserting '.asSequence()'" "true" +// WITH_RUNTIME + +fun foo(a: Array) { + bar(a) +} + +fun bar(a: Sequence) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/arrayToSequence.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToSequence.kt.after new file mode 100644 index 00000000000..9b68e153cc2 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/arrayToSequence.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'Sequence' by inserting '.asSequence()'" "true" +// WITH_RUNTIME + +fun foo(a: Array) { + bar(a.asSequence()) +} + +fun bar(a: Sequence) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/iterableToArray.kt b/idea/testData/quickfix/typeMismatch/convertCollection/iterableToArray.kt new file mode 100644 index 00000000000..15dfaa5be3e --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/iterableToArray.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'Array' by inserting '.toList().toTypedArray()'" "true" +// WITH_RUNTIME + +fun foo(a: Iterable) { + bar(a) +} + +fun bar(a: Array) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/iterableToArray.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/iterableToArray.kt.after new file mode 100644 index 00000000000..008d19e3ef4 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/iterableToArray.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'Array' by inserting '.toList().toTypedArray()'" "true" +// WITH_RUNTIME + +fun foo(a: Iterable) { + bar(a.toList().toTypedArray()) +} + +fun bar(a: Array) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/listToArray.kt b/idea/testData/quickfix/typeMismatch/convertCollection/listToArray.kt new file mode 100644 index 00000000000..bb8385993aa --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/listToArray.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'Array' by inserting '.toTypedArray()'" "true" +// WITH_RUNTIME + +fun foo(a: List) { + bar(a) +} + +fun bar(a: Array) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/listToArray.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/listToArray.kt.after new file mode 100644 index 00000000000..8c8e4ff851b --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/listToArray.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'Array' by inserting '.toTypedArray()'" "true" +// WITH_RUNTIME + +fun foo(a: List) { + bar(a.toTypedArray()) +} + +fun bar(a: Array) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/listToArrayBinary.kt b/idea/testData/quickfix/typeMismatch/convertCollection/listToArrayBinary.kt new file mode 100644 index 00000000000..c1062860cee --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/listToArrayBinary.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'Array' by inserting '.toTypedArray()'" "true" +// WITH_RUNTIME + +fun foo(a: List, b: List) { + bar(a + b) +} + +fun bar(a: Array) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/listToArrayBinary.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/listToArrayBinary.kt.after new file mode 100644 index 00000000000..fa8c5b2cde3 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/listToArrayBinary.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'Array' by inserting '.toTypedArray()'" "true" +// WITH_RUNTIME + +fun foo(a: List, b: List) { + bar((a + b).toTypedArray()) +} + +fun bar(a: Array) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/listToMutableList.kt b/idea/testData/quickfix/typeMismatch/convertCollection/listToMutableList.kt new file mode 100644 index 00000000000..ddf2bde056e --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/listToMutableList.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'MutableList' by inserting '.toMutableList()'" "true" +// WITH_RUNTIME + +fun foo(a: List) { + bar(a) +} + +fun bar(a: MutableList) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/listToMutableList.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/listToMutableList.kt.after new file mode 100644 index 00000000000..4ac5aa13914 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/listToMutableList.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'MutableList' by inserting '.toMutableList()'" "true" +// WITH_RUNTIME + +fun foo(a: List) { + bar(a.toMutableList()) +} + +fun bar(a: MutableList) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/listToSequence.kt b/idea/testData/quickfix/typeMismatch/convertCollection/listToSequence.kt new file mode 100644 index 00000000000..339b6b8da7b --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/listToSequence.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'Sequence' by inserting '.asSequence()'" "true" +// WITH_RUNTIME + +fun foo(a: List) { + bar(a) +} + +fun bar(a: Sequence) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/listToSequence.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/listToSequence.kt.after new file mode 100644 index 00000000000..0afe8d82740 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/listToSequence.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'Sequence' by inserting '.asSequence()'" "true" +// WITH_RUNTIME + +fun foo(a: List) { + bar(a.asSequence()) +} + +fun bar(a: Sequence) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToArray.kt b/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToArray.kt new file mode 100644 index 00000000000..036d0eec5de --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToArray.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'Array' by inserting '.toList().toTypedArray()'" "true" +// WITH_RUNTIME + +fun foo(a: Sequence) { + bar(a) +} + +fun bar(a: Array) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToArray.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToArray.kt.after new file mode 100644 index 00000000000..3101a221165 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToArray.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'Array' by inserting '.toList().toTypedArray()'" "true" +// WITH_RUNTIME + +fun foo(a: Sequence) { + bar(a.toList().toTypedArray()) +} + +fun bar(a: Array) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToList.kt b/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToList.kt new file mode 100644 index 00000000000..9b12abf535c --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToList.kt @@ -0,0 +1,8 @@ +// "Convert expression to 'List' by inserting '.toList()'" "true" +// WITH_RUNTIME + +fun foo(a: Sequence) { + bar(a) +} + +fun bar(a: List) {} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToList.kt.after b/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToList.kt.after new file mode 100644 index 00000000000..344bce3d798 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/convertCollection/sequenceToList.kt.after @@ -0,0 +1,8 @@ +// "Convert expression to 'List' by inserting '.toList()'" "true" +// WITH_RUNTIME + +fun foo(a: Sequence) { + bar(a.toList()) +} + +fun bar(a: List) {} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 0562bbf9445..15c62e41ba2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -11138,6 +11138,81 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/typeMismatch/convertCollection") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConvertCollection extends AbstractQuickFixTest { + public void testAllFilesPresentInConvertCollection() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/typeMismatch/convertCollection"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("arrayToCollection.kt") + public void testArrayToCollection() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/arrayToCollection.kt"); + doTest(fileName); + } + + @TestMetadata("arrayToIterable.kt") + public void testArrayToIterable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/arrayToIterable.kt"); + doTest(fileName); + } + + @TestMetadata("arrayToList.kt") + public void testArrayToList() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/arrayToList.kt"); + doTest(fileName); + } + + @TestMetadata("arrayToSequence.kt") + public void testArrayToSequence() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/arrayToSequence.kt"); + doTest(fileName); + } + + @TestMetadata("iterableToArray.kt") + public void testIterableToArray() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/iterableToArray.kt"); + doTest(fileName); + } + + @TestMetadata("listToArray.kt") + public void testListToArray() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/listToArray.kt"); + doTest(fileName); + } + + @TestMetadata("listToArrayBinary.kt") + public void testListToArrayBinary() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/listToArrayBinary.kt"); + doTest(fileName); + } + + @TestMetadata("listToMutableList.kt") + public void testListToMutableList() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/listToMutableList.kt"); + doTest(fileName); + } + + @TestMetadata("listToSequence.kt") + public void testListToSequence() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/listToSequence.kt"); + doTest(fileName); + } + + @TestMetadata("sequenceToArray.kt") + public void testSequenceToArray() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/sequenceToArray.kt"); + doTest(fileName); + } + + @TestMetadata("sequenceToList.kt") + public void testSequenceToList() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/typeMismatch/convertCollection/sequenceToList.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/typeMismatch/fixOverloadedOperator") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)