diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueKindUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueKindUtils.kt index ee3aee9c8ee..484ea9341f3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueKindUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowValueKindUtils.kt @@ -17,32 +17,6 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.types.expressions.AssignedVariablesSearcher import org.jetbrains.kotlin.types.expressions.PreliminaryDeclarationVisitor -/** - * Determines whether a variable with a given descriptor is stable or not at the given usage place. - * - * - * Stable means that the variable value cannot change. The simple (non-property) variable is considered stable if it's immutable (val). - * - * - * If the variable is a property, it's considered stable if it's immutable (val) AND it's final (not open) AND - * the default getter is in use (otherwise nobody can guarantee that a getter is consistent) AND - * (it's private OR internal OR used at the same module where it's defined). - * The last check corresponds to a risk of changing property definition in another module, e.g. from "val" to "var". - - * @param variableDescriptor descriptor of a considered variable - * * - * @param usageModule a module with a considered usage place, or null if it's not known (not recommended) - * * - * @return true if variable is stable, false otherwise - */ -fun isStableValue( - variableDescriptor: VariableDescriptor, - usageModule: ModuleDescriptor? -): Boolean { - if (variableDescriptor.isVar) return false - return variableDescriptor !is PropertyDescriptor || variableDescriptor.propertyKind(usageModule) === DataFlowValue.Kind.STABLE_VALUE -} - internal fun PropertyDescriptor.propertyKind(usageModule: ModuleDescriptor?): DataFlowValue.Kind { if (isVar) return DataFlowValue.Kind.MUTABLE_PROPERTY if (isOverridable) return DataFlowValue.Kind.PROPERTY_WITH_GETTER diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/NullChecksToSafeCallInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/NullChecksToSafeCallInspection.kt index 586ca7b8eba..5b4c5513ea9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/NullChecksToSafeCallInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/NullChecksToSafeCallInspection.kt @@ -10,7 +10,7 @@ import com.intellij.codeInspection.* import com.intellij.openapi.project.Project import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.core.replaced -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStable +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStableSimpleExpression import org.jetbrains.kotlin.lexer.KtToken import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* @@ -94,8 +94,8 @@ class NullChecksToSafeCallInspection : AbstractKotlinInspection() { } private fun KtExpression.isChainStable(context: BindingContext): Boolean = when (this) { - is KtReferenceExpression -> isStable(context) - is KtQualifiedExpression -> selectorExpression?.isStable(context) == true && receiverExpression.isChainStable(context) + is KtReferenceExpression -> isStableSimpleExpression(context) + is KtQualifiedExpression -> selectorExpression?.isStableSimpleExpression(context) == true && receiverExpression.isChainStable(context) else -> false } } diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt index e667dd2d948..7158fe1496e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt @@ -33,7 +33,7 @@ class IfThenToSafeAccessInspection : AbstractApplicabilityBasedInspection() + return dataFlowValueFactory.createDataFlowValue(this, expressionType, context, findModuleDescriptor()) } data class IfThenToSelectData( diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/DoubleBangToIfThenIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/DoubleBangToIfThenIntention.kt index a8e614fe79a..230c906872e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/DoubleBangToIfThenIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/DoubleBangToIfThenIntention.kt @@ -31,7 +31,7 @@ import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention import org.jetbrains.kotlin.idea.intentions.branchedTransformations.convertToIfNotNullExpression import org.jetbrains.kotlin.idea.intentions.branchedTransformations.convertToIfNullExpression import org.jetbrains.kotlin.idea.intentions.branchedTransformations.introduceValueForCondition -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStable +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStableSimpleExpression import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtPostfixExpression import org.jetbrains.kotlin.psi.KtPsiFactory @@ -56,7 +56,7 @@ class DoubleBangToIfThenIntention : SelfTargetingRangeIntention(K val newReceiver = leftSafeCastReceiver.left val typeReference = leftSafeCastReceiver.right!! val factory = KtPsiFactory(element) - newReceiver.isStable(context) to element.convertToIfStatement( + newReceiver.isStableSimpleExpression(context) to element.convertToIfStatement( factory.createExpressionByPattern("$0 is $1", newReceiver, typeReference), left.buildExpressionWithReplacedReceiver(factory, newReceiver), right ) } else { - left.isStable(context) to element.convertToIfNotNullExpression(left, left, right) + left.isStableSimpleExpression(context) to element.convertToIfNotNullExpression(left, left, right) } if (!leftIsStable) { diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToDoubleBangIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToDoubleBangIntention.kt index bc07690f60a..86a4ad5133d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToDoubleBangIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToDoubleBangIntention.kt @@ -38,7 +38,7 @@ class IfThenToDoubleBangIntention : SelfTargetingRangeIntention( val matchesAsStatement = element.isUsedAsStatement(context) && (baseClause?.isNullExpressionOrEmptyBlock() ?: true) if (!matchesAsStatement && - !(baseClause?.evaluatesTo(receiverExpression) ?: false && receiverExpression.isStable())) return null + !(baseClause?.evaluatesTo(receiverExpression) ?: false && receiverExpression.isStableSimpleExpression())) return null var text = "Replace 'if' expression with '!!' expression" if (!throwExpression.throwsNullPointerExceptionWithNoArguments()) { diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt index 73ec598575b..da49f9cf9de 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt @@ -57,7 +57,7 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention(KtWhenE val subject1 = element.subjectExpression val subject2 = next.subjectExpression if (!subject1.matches(subject2)) return null - if (subject1 != null && !subject1.isStable()) return null + if (subject1 != null && !subject1.isStableVal()) return null val entries1 = element.entries val entries2 = next.entries diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/SafeAccessToIfThenIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/SafeAccessToIfThenIntention.kt index bbd88693e39..160e1e1ff40 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/SafeAccessToIfThenIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/SafeAccessToIfThenIntention.kt @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention import org.jetbrains.kotlin.idea.intentions.branchedTransformations.convertToIfNotNullExpression import org.jetbrains.kotlin.idea.intentions.branchedTransformations.introduceValueForCondition -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStable +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStableSimpleExpression import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType @@ -40,7 +40,7 @@ class SafeAccessToIfThenIntention : SelfTargetingRangeIntention return null } as? KtReferenceExpression ?: return null - if (!nullableExpression.isStable(context)) return null + if (!nullableExpression.isStableSimpleExpression(context)) return null val expressionTarget = expressionParent.getParentOfTypesAndPredicate(strict = false, parentClasses = KtExpression::class.java) { !it.isUsedAsExpression(context) && it.hasAcceptableParent() @@ -91,7 +91,7 @@ class SurroundWithNullCheckFix( val forExpression = nullableExpression.parent.parent as? KtForExpression ?: return null if (forExpression.parent !is KtBlockExpression) return null - if (!nullableExpression.isStable()) return null + if (!nullableExpression.isStableSimpleExpression()) return null return SurroundWithNullCheckFix(forExpression, nullableExpression) } @@ -110,7 +110,7 @@ class SurroundWithNullCheckFix( if (!isNullabilityMismatch(expected = typeMismatch.a, actual = typeMismatch.b)) return null - if (!nullableExpression.isStable()) return null + if (!nullableExpression.isStableSimpleExpression()) return null return SurroundWithNullCheckFix(rootCall, nullableExpression) } diff --git a/idea/testData/intentions/branched/doubleBangToIfThen/localVar.kt.after b/idea/testData/intentions/branched/doubleBangToIfThen/localVar.kt.after index 64fee01cf86..0e67b49961d 100644 --- a/idea/testData/intentions/branched/doubleBangToIfThen/localVar.kt.after +++ b/idea/testData/intentions/branched/doubleBangToIfThen/localVar.kt.after @@ -1,8 +1,7 @@ // WITH_RUNTIME fun main(args: Array) { var a: String? = "A" - val a1 = a - doSomething(if (a1 != null) a1 else throw NullPointerException("Expression 'a' must not be null")) + doSomething(if (a != null) a else throw NullPointerException("Expression 'a' must not be null")) } fun doSomething(a: Any){} diff --git a/idea/testData/intentions/branched/elvisToIfThen/fakeSafeCast.kt.after b/idea/testData/intentions/branched/elvisToIfThen/fakeSafeCast.kt.after index 48dbffa2bbe..a4b4e14c63b 100644 --- a/idea/testData/intentions/branched/elvisToIfThen/fakeSafeCast.kt.after +++ b/idea/testData/intentions/branched/elvisToIfThen/fakeSafeCast.kt.after @@ -1,4 +1,5 @@ class My(val x: Int?) fun foo(arg: Any) { - val y = if ((arg as? My)?.x != null) (arg as? My)?.x else 42 + val x = (arg as? My)?.x + val y = if (x != null) x else 42 } \ No newline at end of file diff --git a/idea/testData/intentions/branched/elvisToIfThen/localVarLhs.kt.after b/idea/testData/intentions/branched/elvisToIfThen/localVarLhs.kt.after index 8bc4e7a50c0..3eb729392ab 100644 --- a/idea/testData/intentions/branched/elvisToIfThen/localVarLhs.kt.after +++ b/idea/testData/intentions/branched/elvisToIfThen/localVarLhs.kt.after @@ -1,5 +1,4 @@ fun main(args: Array) { var a: String? = "A" - val a1 = a - if (a1 != null) a1 else "bar" + if (a != null) a else "bar" } diff --git a/idea/testData/intentions/branched/ifThenToDoubleBang/applicableForLocalStableVar.kt b/idea/testData/intentions/branched/ifThenToDoubleBang/applicableForLocalStableVar.kt new file mode 100644 index 00000000000..bf3bc9d7f16 --- /dev/null +++ b/idea/testData/intentions/branched/ifThenToDoubleBang/applicableForLocalStableVar.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + var foo = maybeFoo() + val bar = if (foo == null) + throw NullPointerException() + else + foo + return foo +} diff --git a/idea/testData/intentions/branched/ifThenToDoubleBang/applicableForLocalStableVar.kt.after b/idea/testData/intentions/branched/ifThenToDoubleBang/applicableForLocalStableVar.kt.after new file mode 100644 index 00000000000..bd6df0d0229 --- /dev/null +++ b/idea/testData/intentions/branched/ifThenToDoubleBang/applicableForLocalStableVar.kt.after @@ -0,0 +1,10 @@ +// WITH_RUNTIME +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + var foo = maybeFoo() + val bar = foo!! + return foo +} diff --git a/idea/testData/intentions/branched/ifThenToDoubleBang/notApplicableForLocalVar.kt b/idea/testData/intentions/branched/ifThenToDoubleBang/notApplicableForLocalUnstableVar.kt similarity index 65% rename from idea/testData/intentions/branched/ifThenToDoubleBang/notApplicableForLocalVar.kt rename to idea/testData/intentions/branched/ifThenToDoubleBang/notApplicableForLocalUnstableVar.kt index c5bea081ab4..179bbf075c5 100644 --- a/idea/testData/intentions/branched/ifThenToDoubleBang/notApplicableForLocalVar.kt +++ b/idea/testData/intentions/branched/ifThenToDoubleBang/notApplicableForLocalUnstableVar.kt @@ -1,11 +1,18 @@ // WITH_RUNTIME -//IS_APPLICABLE: false +// IS_APPLICABLE: false fun maybeFoo(): String? { return "foo" } +fun capture(block: () -> Unit): Unit = Unit + fun main(args: Array) { var foo = maybeFoo() + + capture { + foo = null + } + if (foo == null) throw NullPointerException() else diff --git a/idea/testData/intentions/branched/ifThenToElvis/applicableForLocalStableVar.kt b/idea/testData/intentions/branched/ifThenToElvis/applicableForLocalStableVar.kt new file mode 100644 index 00000000000..9ba668a0ef2 --- /dev/null +++ b/idea/testData/intentions/branched/ifThenToElvis/applicableForLocalStableVar.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + var foo = maybeFoo() + val bar = if (foo == null) + "hello" + else + foo + return foo +} diff --git a/idea/testData/intentions/branched/ifThenToElvis/applicableForLocalStableVar.kt.after b/idea/testData/intentions/branched/ifThenToElvis/applicableForLocalStableVar.kt.after new file mode 100644 index 00000000000..3030ce63336 --- /dev/null +++ b/idea/testData/intentions/branched/ifThenToElvis/applicableForLocalStableVar.kt.after @@ -0,0 +1,10 @@ +// WITH_RUNTIME +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + var foo = maybeFoo() + val bar = foo ?: "hello" + return foo +} diff --git a/idea/testData/intentions/branched/ifThenToElvis/inspectionData/expected.xml b/idea/testData/intentions/branched/ifThenToElvis/inspectionData/expected.xml index 91889dde192..9233d0078a2 100644 --- a/idea/testData/intentions/branched/ifThenToElvis/inspectionData/expected.xml +++ b/idea/testData/intentions/branched/ifThenToElvis/inspectionData/expected.xml @@ -111,4 +111,12 @@ If-Then foldable to '?:' Replace 'if' expression with elvis expression + + applicableForLocalStableVar.kt + 8 + light_idea_test_case + + If-Then foldable to '?:' + Replace 'if' expression with elvis expression + diff --git a/idea/testData/intentions/branched/ifThenToElvis/notApplicableForLocalUnstableVar.kt b/idea/testData/intentions/branched/ifThenToElvis/notApplicableForLocalUnstableVar.kt new file mode 100644 index 00000000000..ebb2b11cba0 --- /dev/null +++ b/idea/testData/intentions/branched/ifThenToElvis/notApplicableForLocalUnstableVar.kt @@ -0,0 +1,22 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun maybeFoo(): String? { + return "foo" +} + +fun capture(block: () -> Unit): Unit = Unit + +fun test(): String? { + var foo = maybeFoo() + + capture { + foo = null + } + + val bar = if (foo == null) + 42 + else + foo + + return foo +} diff --git a/idea/testData/intentions/branched/ifThenToElvis/notApplicableForLocalVar.kt b/idea/testData/intentions/branched/ifThenToElvis/notApplicableForLocalVar.kt deleted file mode 100644 index a9d3a6f648f..00000000000 --- a/idea/testData/intentions/branched/ifThenToElvis/notApplicableForLocalVar.kt +++ /dev/null @@ -1,12 +0,0 @@ -//IS_APPLICABLE: false -fun maybeFoo(): String? { - return "foo" -} - -fun main(args: Array) { - var foo = maybeFoo() - if (foo == null) - "bar" - else - foo -} diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/localVarLhs.kt.after b/idea/testData/intentions/branched/safeAccessToIfThen/localVarLhs.kt.after index ef41055be6e..0d9203a8db2 100644 --- a/idea/testData/intentions/branched/safeAccessToIfThen/localVarLhs.kt.after +++ b/idea/testData/intentions/branched/safeAccessToIfThen/localVarLhs.kt.after @@ -2,7 +2,6 @@ fun doSomething(a: T) {} fun main(args: Array) { var a: String? = "A" - val a1 = a - doSomething(if (a1 != null) a1.length else null) + doSomething(if (a != null) a.length else null) } diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index e454f55e82e..b02d4f77647 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -2135,6 +2135,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/branched/ifThenToDoubleBang"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); } + @TestMetadata("applicableForLocalStableVar.kt") + public void testApplicableForLocalStableVar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToDoubleBang/applicableForLocalStableVar.kt"); + doTest(fileName); + } + @TestMetadata("blockHasMoreThanOneStatement.kt") public void testBlockHasMoreThanOneStatement() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToDoubleBang/blockHasMoreThanOneStatement.kt"); @@ -2255,9 +2261,9 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } - @TestMetadata("notApplicableForLocalVar.kt") - public void testNotApplicableForLocalVar() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToDoubleBang/notApplicableForLocalVar.kt"); + @TestMetadata("notApplicableForLocalUnstableVar.kt") + public void testNotApplicableForLocalUnstableVar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToDoubleBang/notApplicableForLocalUnstableVar.kt"); doTest(fileName); } @@ -2330,6 +2336,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/branched/ifThenToElvis"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); } + @TestMetadata("applicableForLocalStableVar.kt") + public void testApplicableForLocalStableVar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/applicableForLocalStableVar.kt"); + doTest(fileName); + } + @TestMetadata("blockHasMoreThanOneStatement.kt") public void testBlockHasMoreThanOneStatement() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/blockHasMoreThanOneStatement.kt"); @@ -2510,9 +2522,9 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } - @TestMetadata("notApplicableForLocalVar.kt") - public void testNotApplicableForLocalVar() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/notApplicableForLocalVar.kt"); + @TestMetadata("notApplicableForLocalUnstableVar.kt") + public void testNotApplicableForLocalUnstableVar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/notApplicableForLocalUnstableVar.kt"); doTest(fileName); }