From 9326dfa048d4eeab49d8c1a4ac72f22d34e4fa4d Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Sat, 15 Oct 2016 22:46:52 +0300 Subject: [PATCH] Honor errors in code before when checking smart casts --- .../idea/intentions/loopToCallChain/utils.kt | 18 ++++++++++++++++-- .../smartCasts/errorOutsideLoop.kt | 19 +++++++++++++++++++ .../smartCasts/errorOutsideLoop.kt.after | 14 ++++++++++++++ .../smartCasts/errorOutsideLoop.kt.after2 | 15 +++++++++++++++ .../intentions/IntentionTest2Generated.java | 6 ++++++ .../intentions/IntentionTestGenerated.java | 6 ++++++ 6 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt create mode 100644 idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt.after2 diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt index ba5223ddcbc..eb15735be17 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt @@ -18,9 +18,11 @@ package org.jetbrains.kotlin.idea.intentions.loopToCallChain import com.intellij.openapi.util.Key import com.intellij.openapi.util.UserDataHolderBase +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.idea.analysis.analyzeInContext import org.jetbrains.kotlin.idea.caches.resolve.analyze @@ -244,10 +246,19 @@ fun tryChangeAndCheckErrors( // we declare these keys locally to avoid possible race-condition problems if this code is executed in 2 threads simultaneously val EXPRESSION = Key("EXPRESSION") val SCOPE_TO_EXCLUDE = Key("SCOPE_TO_EXCLUDE") + val ERRORS_BEFORE = Key>>("ERRORS_BEFORE") expressionToChange.putCopyableUserData(EXPRESSION, Unit) scopeToExclude?.putCopyableUserData(SCOPE_TO_EXCLUDE, Unit) + block.forEachDescendantOfType { element -> + val errors = bindingContext.diagnostics.forElement(element) + .filter { it.severity == Severity.ERROR } + if (errors.isNotEmpty()) { + element.putCopyableUserData(ERRORS_BEFORE, errors.map { it.factory }) + } + } + val blockCopy = block.copied() val expressionCopy: TExpression val scopeToExcludeCopy: KtElement? @@ -268,8 +279,11 @@ fun tryChangeAndCheckErrors( contextExpression = block, dataFlowInfo = bindingContext.getDataFlowInfo(block), trace = DelegatingBindingTrace(bindingContext, "Temporary trace")) - //TODO: what if there were errors before? - return newBindingContext.diagnostics.none { it.severity == Severity.ERROR && !scopeToExcludeCopy.isAncestor(it.psiElement) } + return newBindingContext.diagnostics.none { + it.severity == Severity.ERROR + && !scopeToExcludeCopy.isAncestor(it.psiElement) + && it.factory !in (it.psiElement.getCopyableUserData(ERRORS_BEFORE) ?: emptyList()) + } } private val NO_SIDE_EFFECT_STANDARD_CLASSES = setOf( diff --git a/idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt b/idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt new file mode 100644 index 00000000000..b9e39ce2179 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt @@ -0,0 +1,19 @@ +// WITH_RUNTIME +// ERROR: Unresolved reference: unresolvedFun +// INTENTION_TEXT: "Replace with 'map{}.map{}.firstOrNull{}'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.map{}.firstOrNull{}'" +fun foo(list: List, o: Any) { + if (o is CharSequence) { + unresolvedFun() + + var result: Any? = null + for (s in list) { + val a = s.length + (o as String).capitalize().hashCode() + val x = a * o.length + if (x > 1000) { + result = x + break + } + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt.after b/idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt.after new file mode 100644 index 00000000000..47dd671a90d --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt.after @@ -0,0 +1,14 @@ +// WITH_RUNTIME +// ERROR: Unresolved reference: unresolvedFun +// INTENTION_TEXT: "Replace with 'map{}.map{}.firstOrNull{}'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.map{}.firstOrNull{}'" +fun foo(list: List, o: Any) { + if (o is CharSequence) { + unresolvedFun() + + val result: Any? = list + .map { it.length + (o as String).capitalize().hashCode() } + .map { it * o.length } + .firstOrNull { it > 1000 } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt.after2 b/idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt.after2 new file mode 100644 index 00000000000..64b8b007f68 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt.after2 @@ -0,0 +1,15 @@ +// WITH_RUNTIME +// ERROR: Unresolved reference: unresolvedFun +// INTENTION_TEXT: "Replace with 'map{}.map{}.firstOrNull{}'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.map{}.firstOrNull{}'" +fun foo(list: List, o: Any) { + if (o is CharSequence) { + unresolvedFun() + + val result: Any? = list + .asSequence() + .map { it.length + (o as String).capitalize().hashCode() } + .map { it * o.length } + .firstOrNull { it > 1000 } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java index eef30d01086..efe20633d01 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java @@ -1168,6 +1168,12 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/loopToCallChain/smartCasts"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); } + @TestMetadata("errorOutsideLoop.kt") + public void testErrorOutsideLoop() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt"); + doTest(fileName); + } + @TestMetadata("smartCastNotBroken.kt") public void testSmartCastNotBroken() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCasts/smartCastNotBroken.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index e185c88e892..c13447c66cb 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -9076,6 +9076,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/loopToCallChain/smartCasts"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); } + @TestMetadata("errorOutsideLoop.kt") + public void testErrorOutsideLoop() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCasts/errorOutsideLoop.kt"); + doTest(fileName); + } + @TestMetadata("smartCastNotBroken.kt") public void testSmartCastNotBroken() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/smartCasts/smartCastNotBroken.kt");