From f6078b24df0cef9839901b7ef46172ce93da3797 Mon Sep 17 00:00:00 2001 From: Tianyu Geng Date: Fri, 11 Jun 2021 14:11:07 -0700 Subject: [PATCH] FIR IDE: AddToString quickfix --- .../generators/tests/idea/GenerateTests.kt | 1 + .../idea/quickfix/MainKtQuickFixRegistrar.kt | 11 ++++- .../quickfix/fixes/AddToStringFixFactories.kt | 49 +++++++++++++++++++ .../HighLevelQuickFixTestGenerated.java | 48 ++++++++++++++++++ .../api/components/KtTypeInfoProvider.kt | 2 + .../kotlin/idea/quickfix/AddToStringFix.kt | 6 +-- .../toString/{simple.kt => callArgument.kt} | 0 ...{simple.kt.after => callArgument.kt.after} | 0 .../quickfix/toString/propertyInitializer.kt | 3 ++ .../toString/propertyInitializer.kt.after | 3 ++ .../quickfix/toString/returnStatement.kt | 5 ++ .../toString/returnStatement.kt.after | 5 ++ .../quickfix/toString/variableAssignment.kt | 6 +++ .../toString/variableAssignment.kt.after | 6 +++ .../idea/quickfix/QuickFixTestGenerated.java | 21 ++++++-- 15 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/AddToStringFixFactories.kt rename idea/testData/quickfix/toString/{simple.kt => callArgument.kt} (100%) rename idea/testData/quickfix/toString/{simple.kt.after => callArgument.kt.after} (100%) create mode 100644 idea/testData/quickfix/toString/propertyInitializer.kt create mode 100644 idea/testData/quickfix/toString/propertyInitializer.kt.after create mode 100644 idea/testData/quickfix/toString/returnStatement.kt create mode 100644 idea/testData/quickfix/toString/returnStatement.kt.after create mode 100644 idea/testData/quickfix/toString/variableAssignment.kt create mode 100644 idea/testData/quickfix/toString/variableAssignment.kt.after diff --git a/generators/idea-generator/tests/org/jetbrains/kotlin/generators/tests/idea/GenerateTests.kt b/generators/idea-generator/tests/org/jetbrains/kotlin/generators/tests/idea/GenerateTests.kt index cded4987b5b..a3c4b7e1224 100644 --- a/generators/idea-generator/tests/org/jetbrains/kotlin/generators/tests/idea/GenerateTests.kt +++ b/generators/idea-generator/tests/org/jetbrains/kotlin/generators/tests/idea/GenerateTests.kt @@ -1183,6 +1183,7 @@ fun main(args: Array) { model("quickfix/wrapWithSafeLetCall", pattern = pattern, filenameStartsLowerCase = true) model("quickfix/typeMismatch/componentFunctionReturnTypeMismatch", pattern = pattern, filenameStartsLowerCase = true) model("quickfix/typeMismatch/typeMismatchOnReturnedExpression", pattern = pattern, filenameStartsLowerCase = true) + model("quickfix/toString", pattern = pattern, filenameStartsLowerCase = true) } testClass { diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt index 70d29458ca6..18f9c4eba0d 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt @@ -137,11 +137,18 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { registerPsiQuickFixes(KtFirDiagnostic.InapplicableLateinitModifier::class, RemoveNullableFix.removeForLateInitProperty) } - private val returnTypes = KtQuickFixesListBuilder.registerPsiQuickFix { + private val typeMismatch = KtQuickFixesListBuilder.registerPsiQuickFix { registerApplicator(ChangeTypeQuickFixFactories.componentFunctionReturnTypeMismatch) registerApplicator(ChangeTypeQuickFixFactories.returnTypeMismatch) + + registerApplicator(AddToStringFixFactories.typeMismatch) + registerApplicator(AddToStringFixFactories.argumentTypeMismatch) + registerApplicator(AddToStringFixFactories.assignmentTypeMismatch) + registerApplicator(AddToStringFixFactories.returnTypeMismatch) + registerApplicator(AddToStringFixFactories.initializerTypeMismatch) } + override val list: KtQuickFixesList = KtQuickFixesList.createCombined( keywords, propertyInitialization, @@ -149,6 +156,6 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { imports, mutability, expressions, - returnTypes + typeMismatch ) } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/AddToStringFixFactories.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/AddToStringFixFactories.kt new file mode 100644 index 00000000000..f9adc0a7744 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/AddToStringFixFactories.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.fixes + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic +import org.jetbrains.kotlin.idea.frontend.api.types.KtType +import org.jetbrains.kotlin.idea.quickfix.AddToStringFix +import org.jetbrains.kotlin.psi.KtExpression + +object AddToStringFixFactories { + @OptIn(ExperimentalStdlibApi::class) + private fun KtAnalysisSession.getFixes(element: PsiElement?, expectedType: KtType, actualType: KtType): List { + if (element !is KtExpression) return emptyList() + return buildList { + if (expectedType.isString || expectedType.isCharSequence) { + add(AddToStringFix(element, false)) + if (expectedType.isMarkedNullable && actualType.isMarkedNullable) { + add(AddToStringFix(element, true)) + } + } + } + } + + val typeMismatch = diagnosticFixFactory(KtFirDiagnostic.TypeMismatch::class) { diagnostic -> + getFixes(diagnostic.psi, diagnostic.expectedType, diagnostic.actualType) + } + + val argumentTypeMismatch = diagnosticFixFactory(KtFirDiagnostic.ArgumentTypeMismatch::class) { diagnostic -> + getFixes(diagnostic.psi, diagnostic.expectedType, diagnostic.actualType) + } + + val assignmentTypeMismatch = diagnosticFixFactory(KtFirDiagnostic.AssignmentTypeMismatch::class) { diagnostic -> + getFixes(diagnostic.psi, diagnostic.expectedType, diagnostic.actualType) + } + + val returnTypeMismatch = diagnosticFixFactory(KtFirDiagnostic.ReturnTypeMismatch::class) { diagnostic -> + getFixes(diagnostic.psi, diagnostic.expectedType, diagnostic.actualType) + } + + val initializerTypeMismatch = diagnosticFixFactory(KtFirDiagnostic.InitializerTypeMismatch::class) { diagnostic -> + getFixes(diagnostic.psi.initializer, diagnostic.expectedType, diagnostic.actualType) + } +} \ No newline at end of file diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/quickfix/HighLevelQuickFixTestGenerated.java b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/quickfix/HighLevelQuickFixTestGenerated.java index 44892db435a..1c106710cd6 100644 --- a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/quickfix/HighLevelQuickFixTestGenerated.java +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/quickfix/HighLevelQuickFixTestGenerated.java @@ -2334,4 +2334,52 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/typeMismatchInReturnStatement.kt"); } } + + @TestMetadata("idea/testData/quickfix/toString") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ToString extends AbstractHighLevelQuickFixTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInToString() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/toString"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true); + } + + @TestMetadata("callArgument.kt") + public void testCallArgument() throws Exception { + runTest("idea/testData/quickfix/toString/callArgument.kt"); + } + + @TestMetadata("notNullableExpectedNullable.kt") + public void testNotNullableExpectedNullable() throws Exception { + runTest("idea/testData/quickfix/toString/notNullableExpectedNullable.kt"); + } + + @TestMetadata("nullableExpectedNotNullable.kt") + public void testNullableExpectedNotNullable() throws Exception { + runTest("idea/testData/quickfix/toString/nullableExpectedNotNullable.kt"); + } + + @TestMetadata("nullableExpectedNullable.kt") + public void testNullableExpectedNullable() throws Exception { + runTest("idea/testData/quickfix/toString/nullableExpectedNullable.kt"); + } + + @TestMetadata("propertyInitializer.kt") + public void testPropertyInitializer() throws Exception { + runTest("idea/testData/quickfix/toString/propertyInitializer.kt"); + } + + @TestMetadata("returnStatement.kt") + public void testReturnStatement() throws Exception { + runTest("idea/testData/quickfix/toString/returnStatement.kt"); + } + + @TestMetadata("variableAssignment.kt") + public void testVariableAssignment() throws Exception { + runTest("idea/testData/quickfix/toString/variableAssignment.kt"); + } + } } diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt index 239cebeb869..0a1f8acdb9e 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt @@ -38,6 +38,7 @@ interface KtTypeInfoProviderMixIn : KtAnalysisSessionMixIn { val KtType.isChar: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.CHAR) val KtType.isBoolean: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.BOOLEAN) val KtType.isString: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.STRING) + val KtType.isCharSequence: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.CHAR_SEQUENCE) val KtType.isAny: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.ANY) val KtType.isUInt: Boolean get() = isClassTypeWithClassId(StandardNames.FqNames.uInt) @@ -85,6 +86,7 @@ object DefaultTypeClassIds { val CHAR = ClassId.topLevel(StandardNames.FqNames._char.toSafe()) val BOOLEAN = ClassId.topLevel(StandardNames.FqNames._boolean.toSafe()) val STRING = ClassId.topLevel(StandardNames.FqNames.string.toSafe()) + val CHAR_SEQUENCE = ClassId.topLevel(StandardNames.FqNames.charSequence.toSafe()) val ANY = ClassId.topLevel(StandardNames.FqNames.any.toSafe()) val PRIMITIVES = setOf(INT, LONG, SHORT, BYTE, FLOAT, DOUBLE, CHAR, BOOLEAN) } diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddToStringFix.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddToStringFix.kt index 7b2464a8c89..43481f5c940 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddToStringFix.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/AddToStringFix.kt @@ -17,12 +17,12 @@ import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.createExpressionByPattern import org.jetbrains.kotlin.psi.psiUtil.endOffset -class AddToStringFix(element: KtExpression, private val nullable: Boolean) : +class AddToStringFix(element: KtExpression, private val useSafeCallOperator: Boolean) : KotlinPsiOnlyQuickFixAction(element), KotlinUniversalQuickFix, LowPriorityAction { override fun getFamilyName() = KotlinBundle.message("fix.add.tostring.call.family") override fun getText(): String { - return when (nullable) { + return when (useSafeCallOperator) { true -> KotlinBundle.message("fix.add.tostring.call.text.safe") false -> KotlinBundle.message("fix.add.tostring.call.text") } @@ -30,7 +30,7 @@ class AddToStringFix(element: KtExpression, private val nullable: Boolean) : override fun invoke(project: Project, editor: Editor?, file: KtFile) { val element = element ?: return - val pattern = if (nullable) "$0?.toString()" else "$0.toString()" + val pattern = if (useSafeCallOperator) "$0?.toString()" else "$0.toString()" val expressionToInsert = KtPsiFactory(file).createExpressionByPattern(pattern, element) val newExpression = element.replaced(expressionToInsert) editor?.caretModel?.moveToOffset(newExpression.endOffset) diff --git a/idea/testData/quickfix/toString/simple.kt b/idea/testData/quickfix/toString/callArgument.kt similarity index 100% rename from idea/testData/quickfix/toString/simple.kt rename to idea/testData/quickfix/toString/callArgument.kt diff --git a/idea/testData/quickfix/toString/simple.kt.after b/idea/testData/quickfix/toString/callArgument.kt.after similarity index 100% rename from idea/testData/quickfix/toString/simple.kt.after rename to idea/testData/quickfix/toString/callArgument.kt.after diff --git a/idea/testData/quickfix/toString/propertyInitializer.kt b/idea/testData/quickfix/toString/propertyInitializer.kt new file mode 100644 index 00000000000..3f25e89e92b --- /dev/null +++ b/idea/testData/quickfix/toString/propertyInitializer.kt @@ -0,0 +1,3 @@ +// "Add 'toString()' call" "true" + +val s: String = 1 + 2 * 3 diff --git a/idea/testData/quickfix/toString/propertyInitializer.kt.after b/idea/testData/quickfix/toString/propertyInitializer.kt.after new file mode 100644 index 00000000000..7b88c815ddc --- /dev/null +++ b/idea/testData/quickfix/toString/propertyInitializer.kt.after @@ -0,0 +1,3 @@ +// "Add 'toString()' call" "true" + +val s: String = (1 + 2 * 3).toString() diff --git a/idea/testData/quickfix/toString/returnStatement.kt b/idea/testData/quickfix/toString/returnStatement.kt new file mode 100644 index 00000000000..3350754d822 --- /dev/null +++ b/idea/testData/quickfix/toString/returnStatement.kt @@ -0,0 +1,5 @@ +// "Add 'toString()' call" "true" + +fun test(): String { + return 1 + 2 * 3 +} \ No newline at end of file diff --git a/idea/testData/quickfix/toString/returnStatement.kt.after b/idea/testData/quickfix/toString/returnStatement.kt.after new file mode 100644 index 00000000000..157b17d15f0 --- /dev/null +++ b/idea/testData/quickfix/toString/returnStatement.kt.after @@ -0,0 +1,5 @@ +// "Add 'toString()' call" "true" + +fun test(): String { + return (1 + 2 * 3).toString() +} \ No newline at end of file diff --git a/idea/testData/quickfix/toString/variableAssignment.kt b/idea/testData/quickfix/toString/variableAssignment.kt new file mode 100644 index 00000000000..f18d48ae9bd --- /dev/null +++ b/idea/testData/quickfix/toString/variableAssignment.kt @@ -0,0 +1,6 @@ +// "Add 'toString()' call" "true" + +fun test() { + var s: String = "" + s = 1 + 2 * 3 +} \ No newline at end of file diff --git a/idea/testData/quickfix/toString/variableAssignment.kt.after b/idea/testData/quickfix/toString/variableAssignment.kt.after new file mode 100644 index 00000000000..a815cf03e04 --- /dev/null +++ b/idea/testData/quickfix/toString/variableAssignment.kt.after @@ -0,0 +1,6 @@ +// "Add 'toString()' call" "true" + +fun test() { + var s: String = "" + s = (1 + 2 * 3).toString() +} \ 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 6e1685e006e..2a9009216ed 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -13668,6 +13668,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/toString"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true); } + @TestMetadata("callArgument.kt") + public void testCallArgument() throws Exception { + runTest("idea/testData/quickfix/toString/callArgument.kt"); + } + @TestMetadata("notNullableExpectedNullable.kt") public void testNotNullableExpectedNullable() throws Exception { runTest("idea/testData/quickfix/toString/notNullableExpectedNullable.kt"); @@ -13683,9 +13688,19 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { runTest("idea/testData/quickfix/toString/nullableExpectedNullable.kt"); } - @TestMetadata("simple.kt") - public void testSimple() throws Exception { - runTest("idea/testData/quickfix/toString/simple.kt"); + @TestMetadata("propertyInitializer.kt") + public void testPropertyInitializer() throws Exception { + runTest("idea/testData/quickfix/toString/propertyInitializer.kt"); + } + + @TestMetadata("returnStatement.kt") + public void testReturnStatement() throws Exception { + runTest("idea/testData/quickfix/toString/returnStatement.kt"); + } + + @TestMetadata("variableAssignment.kt") + public void testVariableAssignment() throws Exception { + runTest("idea/testData/quickfix/toString/variableAssignment.kt"); } }