diff --git a/idea/resources-en/messages/KotlinBundle.properties b/idea/resources-en/messages/KotlinBundle.properties
index c8dbcebd0cf..e1505b05311 100644
--- a/idea/resources-en/messages/KotlinBundle.properties
+++ b/idea/resources-en/messages/KotlinBundle.properties
@@ -2217,6 +2217,8 @@ logger.initialized.with.foreign.class=Logger initialized with foreign class ''{0
logger.factory.method.name=Logger factory method name
logger.factory.class.name=Logger factory class name
choose.logger.factory.class=Choose logger factory class
+inspection.redundant.assequence.call=Redundant 'asSequence' call
+remove.assequence.call.fix.text=Remove 'asSequence' call
codestyle.layout.import.aliases.separately=Import aliases separately
button.add.package=Add Package
listbox.import.package=Package
diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml
index 8f210092dcb..6d6a85e76b7 100644
--- a/idea/resources/META-INF/plugin-common.xml
+++ b/idea/resources/META-INF/plugin-common.xml
@@ -3324,6 +3324,14 @@
language="kotlin"
key="inspection.logger.initialized.with.foreign.class.display.name" bundle="messages.KotlinBundle"/>
+
+
diff --git a/idea/resources/inspectionDescriptions/RedundantAsSequence.html b/idea/resources/inspectionDescriptions/RedundantAsSequence.html
new file mode 100644
index 00000000000..9b8026dea25
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/RedundantAsSequence.html
@@ -0,0 +1,5 @@
+
+
+This inspection reports redundant 'asSequence()' call that can never have a positive performance effect.
+
+
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt
index 755a87db331..9ac3299aa8e 100644
--- a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt
@@ -236,10 +236,9 @@ private val transformations = listOf(
"windowed",
"withIndex",
"zipWithNext"
-).associate { it to FqName("kotlin.collections.$it") }
+).associateWith { FqName("kotlin.collections.$it") }
-@NonNls
-private val terminations = listOf(
+internal val collectionTerminationFunctionNames = listOf(
"all",
"any",
"asIterable",
@@ -303,9 +302,12 @@ private val terminations = listOf(
"toSet",
"toSortedSet",
"unzip"
-).associate {
+)
+
+@NonNls
+private val terminations = collectionTerminationFunctionNames.associateWith {
val pkg = if (it in listOf("contains", "indexOf", "lastIndexOf")) "kotlin.collections.List" else "kotlin.collections"
- it to FqName("$pkg.$it")
+ FqName("$pkg.$it")
}
private val lazyTerminations = terminations.filter { (key, _) -> key == "groupingBy" }
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RedundantAsSequenceInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RedundantAsSequenceInspection.kt
new file mode 100644
index 00000000000..8ded0684c11
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RedundantAsSequenceInspection.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010-2020 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.inspections.collections
+
+import com.intellij.codeInspection.LocalQuickFix
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.openapi.project.Project
+import org.jetbrains.kotlin.idea.KotlinBundle
+import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.idea.core.replaced
+import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
+import org.jetbrains.kotlin.idea.intentions.callExpression
+import org.jetbrains.kotlin.idea.util.CommentSaver
+import org.jetbrains.kotlin.idea.util.textRangeIn
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
+import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
+
+class RedundantAsSequenceInspection : AbstractKotlinInspection() {
+ companion object {
+ private val asSequenceFqName = FqName("kotlin.collections.asSequence")
+ private val terminations = collectionTerminationFunctionNames.associateWith { FqName("kotlin.sequences.$it") }
+ }
+
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = qualifiedExpressionVisitor(fun(qualified) {
+ val call = qualified.callExpression ?: return
+ val callee = call.calleeExpression ?: return
+ if (callee.text != "asSequence") return
+ val parentCall = qualified.getQualifiedExpressionForReceiver()?.callExpression ?: return
+
+ val context = qualified.analyze(BodyResolveMode.PARTIAL)
+ if (call.getResolvedCall(context)?.isCalling(asSequenceFqName) != true) return
+ if (!parentCall.isTermination(context)) return
+
+ holder.registerProblem(
+ qualified,
+ callee.textRangeIn(qualified),
+ KotlinBundle.message("inspection.redundant.assequence.call"),
+ RemoveAsSequenceFix()
+ )
+ })
+
+ private fun KtCallExpression.isTermination(context: BindingContext): Boolean {
+ val fqName = terminations[calleeExpression?.text] ?: return false
+ return isCalling(fqName, context)
+ }
+
+ private class RemoveAsSequenceFix : LocalQuickFix {
+ override fun getName() = KotlinBundle.message("remove.assequence.call.fix.text")
+
+ override fun getFamilyName() = name
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val qualified = descriptor.psiElement as? KtQualifiedExpression ?: return
+ val commentSaver = CommentSaver(qualified)
+ val replaced = qualified.replaced(qualified.receiverExpression)
+ commentSaver.restore(replaced)
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/.inspection b/idea/testData/inspectionsLocal/collections/redundantAsSequence/.inspection
new file mode 100644
index 00000000000..75e1018fa2e
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.collections.RedundantAsSequenceInspection
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt
new file mode 100644
index 00000000000..3bdc30c670a
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun test(list: List) {
+ list/*comment*/.asSequence().last()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt.after b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt.after
new file mode 100644
index 00000000000..a96f6ca0a73
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun test(list: List) {
+ list/*comment*/.last()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt
new file mode 100644
index 00000000000..01fd7693326
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt
@@ -0,0 +1,7 @@
+// WITH_RUNTIME
+fun test(list: List) {
+ list
+ // comment
+ .asSequence()
+ .max()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt.after b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt.after
new file mode 100644
index 00000000000..2a3acec2fa8
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt.after
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+fun test(list: List) {
+ list
+ // comment
+ .max()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/notTermination.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/notTermination.kt
new file mode 100644
index 00000000000..665a212bed4
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/notTermination.kt
@@ -0,0 +1,5 @@
+// PROBLEM: none
+// WITH_RUNTIME
+fun test(list: List) {
+ list.sorted().first()
+}
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt
new file mode 100644
index 00000000000..f529d04440b
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun test(list: List?) {
+ list?.asSequence()?.first()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt.after b/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt.after
new file mode 100644
index 00000000000..58313ab07ea
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun test(list: List?) {
+ list?.first()
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt
new file mode 100644
index 00000000000..c024f767611
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun test(list: List) {
+ list.asSequence().all { it.isBlank() }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt.after b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt.after
new file mode 100644
index 00000000000..c7bbb898d70
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun test(list: List) {
+ list.all { it.isBlank() }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt
new file mode 100644
index 00000000000..2b5fa744c0c
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun test(set: Set) {
+ set.asSequence().any { it.isBlank() }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt.after b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt.after
new file mode 100644
index 00000000000..7997d81689f
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+fun test(set: Set) {
+ set.any { it.isBlank() }
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
index 687f52f1ba9..272f5fc6678 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -1479,6 +1479,49 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
+ @TestMetadata("idea/testData/inspectionsLocal/collections/redundantAsSequence")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class RedundantAsSequence extends AbstractLocalInspectionTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
+ }
+
+ public void testAllFilesPresentInRedundantAsSequence() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/inspectionsLocal/collections/redundantAsSequence"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true);
+ }
+
+ @TestMetadata("hasComment.kt")
+ public void testHasComment() throws Exception {
+ runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt");
+ }
+
+ @TestMetadata("hasLineBreak.kt")
+ public void testHasLineBreak() throws Exception {
+ runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt");
+ }
+
+ @TestMetadata("notTermination.kt")
+ public void testNotTermination() throws Exception {
+ runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/notTermination.kt");
+ }
+
+ @TestMetadata("nullable.kt")
+ public void testNullable() throws Exception {
+ runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt");
+ }
+
+ @TestMetadata("simple.kt")
+ public void testSimple() throws Exception {
+ runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt");
+ }
+
+ @TestMetadata("simple2.kt")
+ public void testSimple2() throws Exception {
+ runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt");
+ }
+ }
+
@TestMetadata("idea/testData/inspectionsLocal/collections/simplifiableCall")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)