diff --git a/idea/resources/inspectionDescriptions/RemoveToStringInStringTemplate.html b/idea/resources/inspectionDescriptions/RemoveToStringInStringTemplate.html
new file mode 100644
index 00000000000..eedf504b371
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/RemoveToStringInStringTemplate.html
@@ -0,0 +1,5 @@
+
+
+This inspection reports calls to 'toString()' in String templates that can be safely removed
+
+
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 4d59a799235..58d29165166 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1670,6 +1670,14 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RemoveToStringInStringTemplateInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RemoveToStringInStringTemplateInspection.kt
new file mode 100644
index 00000000000..0ec0d4bcbae
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RemoveToStringInStringTemplateInspection.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010-2016 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.inspections
+
+import com.intellij.codeInspection.*
+import com.intellij.openapi.project.Project
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
+import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
+import org.jetbrains.kotlin.idea.intentions.toResolvedCall
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.canPlaceAfterSimpleNameEntry
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
+import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
+
+class RemoveToStringInStringTemplateInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) =
+ object : KtVisitorVoid() {
+ override fun visitCallExpression(expression: KtCallExpression) {
+ val qualifiedExpression = expression.parent as? KtDotQualifiedExpression ?: return
+ if (qualifiedExpression.parent !is KtBlockStringTemplateEntry) return
+ if (!qualifiedExpression.isToString()) return
+
+ holder.registerProblem(expression,
+ "Redundant 'toString()' call in string template",
+ ProblemHighlightType.LIKE_UNUSED_SYMBOL,
+ RemoveToStringFix())
+ }
+
+ private fun KtDotQualifiedExpression.isToString(): Boolean {
+ val resolvedCall = toResolvedCall(BodyResolveMode.PARTIAL) ?: return false
+ val callableDescriptor = resolvedCall.resultingDescriptor as? CallableMemberDescriptor ?: return false
+ return callableDescriptor.getDeepestSuperDeclarations().any { it.fqNameUnsafe.asString() == "kotlin.Any.toString" }
+ }
+ }
+}
+
+class RemoveToStringFix: LocalQuickFix {
+ override fun getName() = "Remove 'toString()' call"
+ override fun getFamilyName() = name
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val element = descriptor.psiElement.parent as? KtDotQualifiedExpression ?: return
+ val receiverExpression = element.receiverExpression
+ if (receiverExpression is KtNameReferenceExpression) {
+ val templateEntry = receiverExpression.parent.parent
+ if (templateEntry is KtBlockStringTemplateEntry && canPlaceAfterSimpleNameEntry(templateEntry.nextSibling)) {
+
+ val factory = KtPsiFactory(templateEntry)
+ templateEntry.replace(factory.createSimpleNameStringTemplateEntry(receiverExpression.getReferencedName()))
+ return
+ }
+ }
+ element.replace(receiverExpression)
+ }
+}
diff --git a/idea/testData/inspections/removeToStringInStringTemplate/inspectionData/expected.xml b/idea/testData/inspections/removeToStringInStringTemplate/inspectionData/expected.xml
new file mode 100644
index 00000000000..72788f3d77a
--- /dev/null
+++ b/idea/testData/inspections/removeToStringInStringTemplate/inspectionData/expected.xml
@@ -0,0 +1,10 @@
+
+
+ test.kt
+ 1
+ light_idea_test_case
+
+ Remove redundant call to 'toString()' in string template
+ Redundant 'toString()' call in string template
+
+
\ No newline at end of file
diff --git a/idea/testData/inspections/removeToStringInStringTemplate/inspectionData/inspections.test b/idea/testData/inspections/removeToStringInStringTemplate/inspectionData/inspections.test
new file mode 100644
index 00000000000..7acddc9ee9e
--- /dev/null
+++ b/idea/testData/inspections/removeToStringInStringTemplate/inspectionData/inspections.test
@@ -0,0 +1 @@
+// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RemoveToStringInStringTemplateInspection
\ No newline at end of file
diff --git a/idea/testData/inspections/removeToStringInStringTemplate/test.kt b/idea/testData/inspections/removeToStringInStringTemplate/test.kt
new file mode 100644
index 00000000000..29fc08261fb
--- /dev/null
+++ b/idea/testData/inspections/removeToStringInStringTemplate/test.kt
@@ -0,0 +1 @@
+val z = "a${"b".toString()}"
\ No newline at end of file
diff --git a/idea/testData/quickfix/removeToStringInStringTemplate/.inspection b/idea/testData/quickfix/removeToStringInStringTemplate/.inspection
new file mode 100644
index 00000000000..b364baa0acf
--- /dev/null
+++ b/idea/testData/quickfix/removeToStringInStringTemplate/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.RemoveToStringInStringTemplateInspection
\ No newline at end of file
diff --git a/idea/testData/quickfix/removeToStringInStringTemplate/call.kt b/idea/testData/quickfix/removeToStringInStringTemplate/call.kt
new file mode 100644
index 00000000000..7dc81084b23
--- /dev/null
+++ b/idea/testData/quickfix/removeToStringInStringTemplate/call.kt
@@ -0,0 +1,5 @@
+// "Remove 'toString()' call" "true"
+
+operator fun Any.invoke() = this
+
+fun foo(arg: Any) = "${arg().toString()}"
\ No newline at end of file
diff --git a/idea/testData/quickfix/removeToStringInStringTemplate/call.kt.after b/idea/testData/quickfix/removeToStringInStringTemplate/call.kt.after
new file mode 100644
index 00000000000..a0dbd5b8cc9
--- /dev/null
+++ b/idea/testData/quickfix/removeToStringInStringTemplate/call.kt.after
@@ -0,0 +1,5 @@
+// "Remove 'toString()' call" "true"
+
+operator fun Any.invoke() = this
+
+fun foo(arg: Any) = "${arg()}"
\ No newline at end of file
diff --git a/idea/testData/quickfix/removeToStringInStringTemplate/name.kt b/idea/testData/quickfix/removeToStringInStringTemplate/name.kt
new file mode 100644
index 00000000000..aa183ef8e69
--- /dev/null
+++ b/idea/testData/quickfix/removeToStringInStringTemplate/name.kt
@@ -0,0 +1,3 @@
+// "Remove 'toString()' call" "true"
+
+fun foo(arg: Any) = "arg = ${arg.toString()}"
\ No newline at end of file
diff --git a/idea/testData/quickfix/removeToStringInStringTemplate/name.kt.after b/idea/testData/quickfix/removeToStringInStringTemplate/name.kt.after
new file mode 100644
index 00000000000..b56d6d262fe
--- /dev/null
+++ b/idea/testData/quickfix/removeToStringInStringTemplate/name.kt.after
@@ -0,0 +1,3 @@
+// "Remove 'toString()' call" "true"
+
+fun foo(arg: Any) = "arg = $arg"
\ No newline at end of file
diff --git a/idea/testData/quickfix/removeToStringInStringTemplate/nameWithPostfix.kt b/idea/testData/quickfix/removeToStringInStringTemplate/nameWithPostfix.kt
new file mode 100644
index 00000000000..1bd0395e0eb
--- /dev/null
+++ b/idea/testData/quickfix/removeToStringInStringTemplate/nameWithPostfix.kt
@@ -0,0 +1,3 @@
+// "Remove 'toString()' call" "true"
+
+fun foo(arg: Any) = "arg = ${arg.toString()}xy"
\ No newline at end of file
diff --git a/idea/testData/quickfix/removeToStringInStringTemplate/nameWithPostfix.kt.after b/idea/testData/quickfix/removeToStringInStringTemplate/nameWithPostfix.kt.after
new file mode 100644
index 00000000000..5ba2b376c48
--- /dev/null
+++ b/idea/testData/quickfix/removeToStringInStringTemplate/nameWithPostfix.kt.after
@@ -0,0 +1,3 @@
+// "Remove 'toString()' call" "true"
+
+fun foo(arg: Any) = "arg = ${arg}xy"
\ No newline at end of file
diff --git a/idea/testData/quickfix/removeToStringInStringTemplate/simple.kt b/idea/testData/quickfix/removeToStringInStringTemplate/simple.kt
new file mode 100644
index 00000000000..9fef3cd46c1
--- /dev/null
+++ b/idea/testData/quickfix/removeToStringInStringTemplate/simple.kt
@@ -0,0 +1,5 @@
+// "Remove 'toString()' call" "true"
+
+fun foo(s: String) = s
+
+fun bar() = foo("a${"b".toString()}")
\ No newline at end of file
diff --git a/idea/testData/quickfix/removeToStringInStringTemplate/simple.kt.after b/idea/testData/quickfix/removeToStringInStringTemplate/simple.kt.after
new file mode 100644
index 00000000000..f4abe7b6109
--- /dev/null
+++ b/idea/testData/quickfix/removeToStringInStringTemplate/simple.kt.after
@@ -0,0 +1,5 @@
+// "Remove 'toString()' call" "true"
+
+fun foo(s: String) = s
+
+fun bar() = foo("a${"b"}")
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
index 1d664b98f48..4a52b4f157a 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
@@ -232,6 +232,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
doTest(fileName);
}
+ @TestMetadata("removeToStringInStringTemplate/inspectionData/inspections.test")
+ public void testRemoveToStringInStringTemplate_inspectionData_Inspections_test() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/removeToStringInStringTemplate/inspectionData/inspections.test");
+ doTest(fileName);
+ }
+
@TestMetadata("replaceCallWithComparison/inspectionData/inspections.test")
public void testReplaceCallWithComparison_inspectionData_Inspections_test() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/replaceCallWithComparison/inspectionData/inspections.test");
diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
index 9d74ed8a901..8efe786c017 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
@@ -6830,6 +6830,39 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
+ @TestMetadata("idea/testData/quickfix/removeToStringInStringTemplate")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class RemoveToStringInStringTemplate extends AbstractQuickFixTest {
+ public void testAllFilesPresentInRemoveToStringInStringTemplate() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/removeToStringInStringTemplate"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
+ }
+
+ @TestMetadata("call.kt")
+ public void testCall() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeToStringInStringTemplate/call.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("name.kt")
+ public void testName() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeToStringInStringTemplate/name.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("nameWithPostfix.kt")
+ public void testNameWithPostfix() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeToStringInStringTemplate/nameWithPostfix.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("simple.kt")
+ public void testSimple() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeToStringInStringTemplate/simple.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/quickfix/removeUnused")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)