diff --git a/idea/resources/inspectionDescriptions/ObsoleteExperimentalCoroutinesInspection.html b/idea/resources/inspectionDescriptions/ObsoleteExperimentalCoroutinesInspection.html
new file mode 100644
index 00000000000..f0b0060fe63
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/ObsoleteExperimentalCoroutinesInspection.html
@@ -0,0 +1,5 @@
+
+
+This inspection reports experimental coroutines usages that are incompatible with Kotlin 1.3+ and should be updated.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 27a16ef9d79..357371848af 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -2918,6 +2918,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
+
+
diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172
index ecc5a17b8bc..bc4f36e37cd 100644
--- a/idea/src/META-INF/plugin.xml.172
+++ b/idea/src/META-INF/plugin.xml.172
@@ -2915,6 +2915,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
+
+
diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173
index 31ba66d7720..aa729525a53 100644
--- a/idea/src/META-INF/plugin.xml.173
+++ b/idea/src/META-INF/plugin.xml.173
@@ -2918,6 +2918,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
+
+
diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182
index d14bc560226..f551fd17f96 100644
--- a/idea/src/META-INF/plugin.xml.182
+++ b/idea/src/META-INF/plugin.xml.182
@@ -2919,6 +2919,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
+
+
diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31
index b011388df1b..f138c9ddd2f 100644
--- a/idea/src/META-INF/plugin.xml.as31
+++ b/idea/src/META-INF/plugin.xml.as31
@@ -2918,6 +2918,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
+
+
diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32
index 6903aa15dca..7ab66c7c4fc 100644
--- a/idea/src/META-INF/plugin.xml.as32
+++ b/idea/src/META-INF/plugin.xml.as32
@@ -2918,6 +2918,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt
new file mode 100644
index 00000000000..b0791d314bb
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/migration/ObsoleteExperimentalCoroutinesInspection.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2010-2018 JetBrains s.r.o. 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.migration
+
+import com.intellij.codeInspection.*
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.project.Project
+import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.kotlin.config.LanguageVersion
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
+import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
+import org.jetbrains.kotlin.idea.imports.importableFqName
+import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
+import org.jetbrains.kotlin.idea.project.languageVersionSettings
+import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
+import org.jetbrains.kotlin.idea.references.mainReference
+import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
+import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex
+import org.jetbrains.kotlin.idea.util.ImportInsertHelper
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.parents
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
+
+class ObsoleteExperimentalCoroutinesInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitorVoid {
+ return simpleNameExpressionVisitor(fun(simpleNameExpression) {
+ run {
+ val versionAtLeast13 = simpleNameExpression.languageVersionSettings.languageVersion >= LanguageVersion.KOTLIN_1_3
+ if (!versionAtLeast13 && !ApplicationManager.getApplication().isUnitTestMode) {
+ return
+ }
+ }
+
+ when (simpleNameExpression.text) {
+ RESUME_MARKER, RESUME_WITH_EXCEPTION_MARKER -> {
+ simpleNameExpression.parent as? KtCallExpression ?: return
+
+ val descriptor = simpleNameExpression.resolveMainReferenceToDescriptors().firstOrNull() ?: return
+ val callableDescriptor = descriptor as? CallableDescriptor ?: return
+
+ val resolvedToFqName = callableDescriptor.fqNameOrNull()?.asString() ?: return
+ val fixFqName = when (resolvedToFqName) {
+ RESUME_FUNCTION_FQNAME -> RESUME_FUNCTION_FIX_FQNAME
+ RESUME_WITH_EXCEPTION_FQNAME -> RESUME_WITH_EXCEPTION_FIX_FQNAME
+ else -> null
+ } ?: return
+
+ val problemDescriptor = holder.manager.createProblemDescriptor(
+ simpleNameExpression,
+ simpleNameExpression,
+ "Methods are absent in coroutines class since 1.3",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ isOnTheFly,
+ ImportExtensionFunctionFix(fixFqName)
+ )
+
+ holder.registerProblem(problemDescriptor)
+ }
+
+ EXPERIMENTAL_COROUTINES_MARKER -> {
+ val parent = simpleNameExpression.parent as? KtExpression ?: return
+ val reportExpression = parent as? KtDotQualifiedExpression ?: simpleNameExpression
+
+ findBinding(simpleNameExpression) ?: return
+
+ val problemDescriptor = holder.manager.createProblemDescriptor(
+ reportExpression,
+ reportExpression,
+ "Experimental coroutines usages are obsolete since 1.3",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ isOnTheFly,
+ ObsoleteCoroutineImportFix()
+ )
+
+ holder.registerProblem(problemDescriptor)
+ }
+ }
+ })
+ }
+
+ companion object {
+ private const val EXPERIMENTAL_COROUTINES_MARKER = "experimental"
+
+ private const val RESUME_MARKER = "resume"
+ private const val RESUME_FUNCTION_FQNAME = "kotlin.coroutines.experimental.Continuation.resume"
+ private const val RESUME_FUNCTION_FIX_FQNAME = "kotlin.coroutines.resume"
+
+ private const val RESUME_WITH_EXCEPTION_MARKER = "resumeWithException"
+ private const val RESUME_WITH_EXCEPTION_FQNAME = "kotlin.coroutines.experimental.Continuation.resumeWithException"
+ private const val RESUME_WITH_EXCEPTION_FIX_FQNAME = "kotlin.coroutines.resumeWithException"
+
+ // All quick-fixes should have same name to be executable together
+ private const val QUICK_FIX_NAME = "Fix experimental coroutines usage"
+
+ private class Binding(
+ val bindTo: FqName,
+ val shouldRemove: Boolean,
+ val importDirective: KtImportDirective
+ )
+
+ @Suppress("SpellCheckingInspection")
+ private val PACKAGE_BINDING = mapOf(
+ "kotlinx.coroutines.experimental" to "kotlinx.coroutines",
+ "kotlin.coroutines.experimental" to "kotlin.coroutines"
+ )
+
+ private val IMPORTS_TO_REMOVE = setOf(
+ "kotlin.coroutines.experimental.buildSequence"
+ )
+
+ private fun findBinding(simpleNameExpression: KtSimpleNameExpression): Binding? {
+ if (simpleNameExpression.text != EXPERIMENTAL_COROUTINES_MARKER) return null
+
+ val importDirective = simpleNameExpression.parents
+ .takeWhile { it is KtDotQualifiedExpression || it is KtImportDirective }
+ .lastOrNull() as? KtImportDirective ?: return null
+
+ val fqNameStr = importDirective.importedFqName?.asString() ?: return null
+
+ val bindEntry = PACKAGE_BINDING.entries.find { (affectedImportPrefix, _) ->
+ fqNameStr.startsWith(affectedImportPrefix)
+ } ?: return null
+
+ return Binding(FqName(bindEntry.value), fqNameStr in IMPORTS_TO_REMOVE, importDirective)
+ }
+ }
+
+ private class ObsoleteCoroutineImportFix : LocalQuickFix {
+ override fun getFamilyName() = QUICK_FIX_NAME
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val element = descriptor.psiElement
+ val simpleNameExpression = when (element) {
+ is KtSimpleNameExpression -> element
+ is KtDotQualifiedExpression -> element.selectorExpression as? KtSimpleNameExpression
+ else -> null
+ } ?: return
+
+ val binding = ObsoleteExperimentalCoroutinesInspection.findBinding(simpleNameExpression) ?: return
+
+ if (binding.shouldRemove) {
+ binding.importDirective.delete()
+ } else {
+ simpleNameExpression.mainReference.bindToFqName(
+ binding.bindTo, shorteningMode = KtSimpleNameReference.ShorteningMode.NO_SHORTENING
+ )
+ }
+ }
+ }
+
+ private class ImportExtensionFunctionFix(val fqName: String) : LocalQuickFix {
+ override fun getFamilyName() = QUICK_FIX_NAME
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val element = descriptor.psiElement
+ element as? KtSimpleNameExpression ?: return
+
+ val importFun =
+ KotlinTopLevelFunctionFqnNameIndex.getInstance().get(fqName, element.project, GlobalSearchScope.allScope(element.project))
+ .asSequence()
+ .map { it.resolveToDescriptorIfAny() }
+ .find { it != null && it.importableFqName?.asString() == fqName } ?: return
+
+ ImportInsertHelper.getInstance(element.project).importDescriptor(element.containingKtFile, importFun, false)
+ }
+ }
+}
diff --git a/idea/testData/quickfix/obsoleteCoroutines/.inspection b/idea/testData/quickfix/obsoleteCoroutines/.inspection
new file mode 100644
index 00000000000..6c77dc329c6
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.migration.ObsoleteExperimentalCoroutinesInspection
diff --git a/idea/testData/quickfix/obsoleteCoroutines/buildSequence.kt b/idea/testData/quickfix/obsoleteCoroutines/buildSequence.kt
new file mode 100644
index 00000000000..6c0269e1924
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/buildSequence.kt
@@ -0,0 +1,8 @@
+// "Fix experimental coroutines usage" "true"
+// ERROR: Unresolved reference: buildSequence
+import kotlin.coroutines.experimental.buildSequence
+
+fun main(args: Array) {
+ val lazySeq = buildSequence {
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/obsoleteCoroutines/buildSequence.kt.after b/idea/testData/quickfix/obsoleteCoroutines/buildSequence.kt.after
new file mode 100644
index 00000000000..d1b2117b0eb
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/buildSequence.kt.after
@@ -0,0 +1,7 @@
+// "Fix experimental coroutines usage" "true"
+// ERROR: Unresolved reference: buildSequence
+
+fun main(args: Array) {
+ val lazySeq = buildSequence {
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/obsoleteCoroutines/resume.kt b/idea/testData/quickfix/obsoleteCoroutines/resume.kt
new file mode 100644
index 00000000000..4ef619567ae
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/resume.kt
@@ -0,0 +1,9 @@
+// "Fix experimental coroutines usage" "true"
+// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
+// WITH_RUNTIME
+
+import kotlin.coroutines.experimental.Continuation
+
+fun test(con: Continuation) {
+ con.resume(12)
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/obsoleteCoroutines/resume.kt.after b/idea/testData/quickfix/obsoleteCoroutines/resume.kt.after
new file mode 100644
index 00000000000..4ef619567ae
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/resume.kt.after
@@ -0,0 +1,9 @@
+// "Fix experimental coroutines usage" "true"
+// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
+// WITH_RUNTIME
+
+import kotlin.coroutines.experimental.Continuation
+
+fun test(con: Continuation) {
+ con.resume(12)
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/obsoleteCoroutines/resumeWithException.kt b/idea/testData/quickfix/obsoleteCoroutines/resumeWithException.kt
new file mode 100644
index 00000000000..a0b8595520b
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/resumeWithException.kt
@@ -0,0 +1,9 @@
+// "Fix experimental coroutines usage" "true"
+// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
+// WITH_RUNTIME
+
+import kotlin.coroutines.experimental.Continuation
+
+fun test(con: Continuation) {
+ con.resumeWithException(RuntimeException("Haha"))
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/obsoleteCoroutines/resumeWithException.kt.after b/idea/testData/quickfix/obsoleteCoroutines/resumeWithException.kt.after
new file mode 100644
index 00000000000..a0b8595520b
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/resumeWithException.kt.after
@@ -0,0 +1,9 @@
+// "Fix experimental coroutines usage" "true"
+// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
+// WITH_RUNTIME
+
+import kotlin.coroutines.experimental.Continuation
+
+fun test(con: Continuation) {
+ con.resumeWithException(RuntimeException("Haha"))
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/obsoleteCoroutines/starImport.kt b/idea/testData/quickfix/obsoleteCoroutines/starImport.kt
new file mode 100644
index 00000000000..bef053100b1
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/starImport.kt
@@ -0,0 +1,4 @@
+// "Fix experimental coroutines usage" "true"
+// ERROR: Unresolved reference: coroutines
+
+import kotlin.coroutines.experimental.*
\ No newline at end of file
diff --git a/idea/testData/quickfix/obsoleteCoroutines/starImport.kt.after b/idea/testData/quickfix/obsoleteCoroutines/starImport.kt.after
new file mode 100644
index 00000000000..ab63b4f7f37
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/starImport.kt.after
@@ -0,0 +1,4 @@
+// "Fix experimental coroutines usage" "true"
+// ERROR: Unresolved reference: coroutines
+
+import kotlin.coroutines.*
\ No newline at end of file
diff --git a/idea/testData/quickfix/obsoleteCoroutines/unresolvedKotlinxImport.kt b/idea/testData/quickfix/obsoleteCoroutines/unresolvedKotlinxImport.kt
new file mode 100644
index 00000000000..74450e5e68f
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/unresolvedKotlinxImport.kt
@@ -0,0 +1,4 @@
+// "Fix experimental coroutines usage" "true"
+// ERROR: Unresolved reference: kotlinx
+
+import kotlinx.coroutines.experimental.delay
\ No newline at end of file
diff --git a/idea/testData/quickfix/obsoleteCoroutines/unresolvedKotlinxImport.kt.after b/idea/testData/quickfix/obsoleteCoroutines/unresolvedKotlinxImport.kt.after
new file mode 100644
index 00000000000..3af1ea94b1d
--- /dev/null
+++ b/idea/testData/quickfix/obsoleteCoroutines/unresolvedKotlinxImport.kt.after
@@ -0,0 +1,4 @@
+// "Fix experimental coroutines usage" "true"
+// ERROR: Unresolved reference: kotlinx
+
+import kotlinx.coroutines.delay
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java
index 209bc3896ea..f7386b0b664 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java
@@ -3026,6 +3026,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
}
}
+ @TestMetadata("idea/testData/quickfix/obsoleteCoroutines")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class ObsoleteCoroutines extends AbstractQuickFixMultiFileTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
+ }
+
+ public void testAllFilesPresentInObsoleteCoroutines() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/obsoleteCoroutines"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
+ }
+ }
+
@TestMetadata("idea/testData/quickfix/optimizeImports")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java.172 b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java.172
index d009241d5e8..f1a3cd005ff 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java.172
+++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java.172
@@ -1916,6 +1916,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
}
}
+ @TestMetadata("idea/testData/quickfix/obsoleteCoroutines")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class ObsoleteCoroutines extends AbstractQuickFixMultiFileTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
+ }
+
+ public void testAllFilesPresentInObsoleteCoroutines() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/obsoleteCoroutines"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
+ }
+ }
+
@TestMetadata("idea/testData/quickfix/optimizeImports")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
index 2411a35042c..6cd2aa7a0d0 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
@@ -8146,6 +8146,44 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
+ @TestMetadata("idea/testData/quickfix/obsoleteCoroutines")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class ObsoleteCoroutines extends AbstractQuickFixTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
+ }
+
+ public void testAllFilesPresentInObsoleteCoroutines() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/obsoleteCoroutines"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("buildSequence.kt")
+ public void testBuildSequence() throws Exception {
+ runTest("idea/testData/quickfix/obsoleteCoroutines/buildSequence.kt");
+ }
+
+ @TestMetadata("resume.kt")
+ public void testResume() throws Exception {
+ runTest("idea/testData/quickfix/obsoleteCoroutines/resume.kt");
+ }
+
+ @TestMetadata("resumeWithException.kt")
+ public void testResumeWithException() throws Exception {
+ runTest("idea/testData/quickfix/obsoleteCoroutines/resumeWithException.kt");
+ }
+
+ @TestMetadata("starImport.kt")
+ public void testStarImport() throws Exception {
+ runTest("idea/testData/quickfix/obsoleteCoroutines/starImport.kt");
+ }
+
+ @TestMetadata("unresolvedKotlinxImport.kt")
+ public void testUnresolvedKotlinxImport() throws Exception {
+ runTest("idea/testData/quickfix/obsoleteCoroutines/unresolvedKotlinxImport.kt");
+ }
+ }
+
@TestMetadata("idea/testData/quickfix/optimizeImports")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)