Introduce additional quick-fix for (collection) type mismatch

This fixed inserts conversion .toSequence/Array/Iterable/Collection/List
So #KT-19735 Fixed
This commit is contained in:
Kirill Rakhman
2017-09-08 14:51:08 +03:00
committed by Mikhail Glukhikh
parent 7f5b9b1760
commit 93252926ba
25 changed files with 345 additions and 1 deletions
@@ -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<KtExpression>(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) }
}
}
}
@@ -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() {
}
}
}
@@ -0,0 +1,8 @@
// "Convert expression to 'Collection' by inserting '.toList()'" "true"
// WITH_RUNTIME
fun foo(a: Array<String>) {
bar(a<caret>)
}
fun bar(a: Collection<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Collection' by inserting '.toList()'" "true"
// WITH_RUNTIME
fun foo(a: Array<String>) {
bar(a.toList()<caret>)
}
fun bar(a: Collection<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Iterable' by inserting '.toList()'" "true"
// WITH_RUNTIME
fun foo(a: Array<String>) {
bar(a<caret>)
}
fun bar(a: Iterable<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Iterable' by inserting '.toList()'" "true"
// WITH_RUNTIME
fun foo(a: Array<String>) {
bar(a.toList()<caret>)
}
fun bar(a: Iterable<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'List' by inserting '.toList()'" "true"
// WITH_RUNTIME
fun foo(a: Array<String>) {
bar(a<caret>)
}
fun bar(a: List<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'List' by inserting '.toList()'" "true"
// WITH_RUNTIME
fun foo(a: Array<String>) {
bar(a.toList()<caret>)
}
fun bar(a: List<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Sequence' by inserting '.asSequence()'" "true"
// WITH_RUNTIME
fun foo(a: Array<String>) {
bar(a<caret>)
}
fun bar(a: Sequence<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Sequence' by inserting '.asSequence()'" "true"
// WITH_RUNTIME
fun foo(a: Array<String>) {
bar(a.asSequence()<caret>)
}
fun bar(a: Sequence<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Array' by inserting '.toList().toTypedArray()'" "true"
// WITH_RUNTIME
fun foo(a: Iterable<String>) {
bar(a<caret>)
}
fun bar(a: Array<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Array' by inserting '.toList().toTypedArray()'" "true"
// WITH_RUNTIME
fun foo(a: Iterable<String>) {
bar(a.toList().toTypedArray()<caret>)
}
fun bar(a: Array<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Array' by inserting '.toTypedArray()'" "true"
// WITH_RUNTIME
fun foo(a: List<String>) {
bar(a<caret>)
}
fun bar(a: Array<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Array' by inserting '.toTypedArray()'" "true"
// WITH_RUNTIME
fun foo(a: List<String>) {
bar(a.toTypedArray()<caret>)
}
fun bar(a: Array<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Array' by inserting '.toTypedArray()'" "true"
// WITH_RUNTIME
fun foo(a: List<String>, b: List<String>) {
bar(a<caret> + b)
}
fun bar(a: Array<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Array' by inserting '.toTypedArray()'" "true"
// WITH_RUNTIME
fun foo(a: List<String>, b: List<String>) {
bar((a + b).toTypedArray()<caret>)
}
fun bar(a: Array<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'MutableList' by inserting '.toMutableList()'" "true"
// WITH_RUNTIME
fun foo(a: List<String>) {
bar(a<caret>)
}
fun bar(a: MutableList<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'MutableList' by inserting '.toMutableList()'" "true"
// WITH_RUNTIME
fun foo(a: List<String>) {
bar(a.toMutableList()<caret>)
}
fun bar(a: MutableList<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Sequence' by inserting '.asSequence()'" "true"
// WITH_RUNTIME
fun foo(a: List<String>) {
bar(a<caret>)
}
fun bar(a: Sequence<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Sequence' by inserting '.asSequence()'" "true"
// WITH_RUNTIME
fun foo(a: List<String>) {
bar(a.asSequence()<caret>)
}
fun bar(a: Sequence<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Array' by inserting '.toList().toTypedArray()'" "true"
// WITH_RUNTIME
fun foo(a: Sequence<String>) {
bar(a<caret>)
}
fun bar(a: Array<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'Array' by inserting '.toList().toTypedArray()'" "true"
// WITH_RUNTIME
fun foo(a: Sequence<String>) {
bar(a.toList().toTypedArray()<caret>)
}
fun bar(a: Array<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'List' by inserting '.toList()'" "true"
// WITH_RUNTIME
fun foo(a: Sequence<String>) {
bar(a<caret>)
}
fun bar(a: List<String>) {}
@@ -0,0 +1,8 @@
// "Convert expression to 'List' by inserting '.toList()'" "true"
// WITH_RUNTIME
fun foo(a: Sequence<String>) {
bar(a.toList()<caret>)
}
fun bar(a: List<String>) {}
@@ -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)