From 9724d81a4995879cc1335d53dde168592aecbd18 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Mon, 8 Feb 2021 11:26:36 +0300 Subject: [PATCH] [FIR] Support boolean elvis bound smartcasts #KT-44511 Fixed --- ...TouchedTilContractsPhaseTestGenerated.java | 5 +++ .../booleanElvisBoundSmartcast.fir.txt | 35 +++++++++++++++++++ .../booleans/booleanElvisBoundSmartcast.kt | 22 ++++++++++++ .../runners/FirDiagnosticTestGenerated.java | 6 ++++ ...DiagnosticsWithLightTreeTestGenerated.java | 6 ++++ .../fir/resolve/dfa/FirDataFlowAnalyzer.kt | 22 ++++++++++-- ...ControlFlowStatementsResolveTransformer.kt | 2 +- .../tests/smartCasts/elvis/basicOn.fir.kt | 6 ++-- 8 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.fir.txt create mode 100644 compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.kt diff --git a/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java b/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java index bde3f0cf9ec..24546bf8cde 100644 --- a/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java +++ b/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java @@ -2818,6 +2818,11 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans"), Pattern.compile("^([^.]+)\\.kt$"), null, true); } + @TestMetadata("booleanElvisBoundSmartcast.kt") + public void testBooleanElvisBoundSmartcast() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.kt"); + } + @TestMetadata("booleanOperators.kt") public void testBooleanOperators() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanOperators.kt"); diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.fir.txt b/compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.fir.txt new file mode 100644 index 00000000000..2429c056d0f --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.fir.txt @@ -0,0 +1,35 @@ +FILE: booleanElvisBoundSmartcast.kt + public final class A : R|kotlin/Any| { + public constructor(b: R|kotlin/Boolean|): R|A| { + super() + } + + public final val b: R|kotlin/Boolean| = R|/b| + public get(): R|kotlin/Boolean| + + public final fun foo(): R|kotlin/Unit| { + } + + } + public final fun test_1(a: R|A?|): R|kotlin/Unit| { + when () { + R|/a|?.{ $subj$.R|/A.b| } ?: Boolean(false) -> { + R|/a|.R|/A.foo|() + } + else -> { + R|/a|.#() + } + } + + } + public final fun test_2(a: R|A?|): R|kotlin/Unit| { + when () { + R|/a|?.{ $subj$.R|/A.b| } ?: Boolean(true) -> { + R|/a|.#() + } + else -> { + R|/a|.R|/A.foo|() + } + } + + } diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.kt b/compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.kt new file mode 100644 index 00000000000..f7e4d049fc3 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.kt @@ -0,0 +1,22 @@ +// !LANGUAGE: +BooleanElvisBoundSmartCasts +// ISSUE: KT-44511, also relates to KT-8492 and KT-26357 + +class A(val b: Boolean) { + fun foo() {} +} + +fun test_1(a: A?) { + if (a?.b ?: false) { + a.foo() // OK + } else { + a.foo() // Error + } +} + +fun test_2(a: A?) { + if (a?.b ?: true) { + a.foo() // Error + } else { + a.foo() // OK + } +} diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java index 9b2a3b940cd..8b9c3797d72 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java @@ -3194,6 +3194,12 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans"), Pattern.compile("^([^.]+)\\.kt$"), null, true); } + @Test + @TestMetadata("booleanElvisBoundSmartcast.kt") + public void testBooleanElvisBoundSmartcast() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.kt"); + } + @Test @TestMetadata("booleanOperators.kt") public void testBooleanOperators() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java index f7ad4f2e209..04ec51b634d 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java @@ -3229,6 +3229,12 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans"), Pattern.compile("^([^.]+)\\.kt$"), null, true); } + @Test + @TestMetadata("booleanElvisBoundSmartcast.kt") + public void testBooleanElvisBoundSmartcast() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/booleanElvisBoundSmartcast.kt"); + } + @Test @TestMetadata("booleanOperators.kt") public void testBooleanOperators() throws Exception { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt index e9e965b1d18..84b0db2b66d 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.fir.resolve.dfa +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.PrivateForInline import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription @@ -14,6 +15,7 @@ import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference import org.jetbrains.kotlin.fir.contracts.description.ConeReturnsEffectDeclaration import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.languageVersionSettings import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.PersistentImplicitReceiverStack @@ -26,6 +28,7 @@ import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBod import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.CallableId +import org.jetbrains.kotlin.fir.symbols.StandardClassIds import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.visitors.transformSingle import org.jetbrains.kotlin.name.FqName @@ -1123,8 +1126,23 @@ abstract class FirDataFlowAnalyzer( } } - fun exitElvis() { - graphBuilder.exitElvis().mergeIncomingFlow() + fun exitElvis(elvisExpression: FirElvisExpression) { + val node = graphBuilder.exitElvis().mergeIncomingFlow() + if (!components.session.languageVersionSettings.supportsFeature(LanguageFeature.BooleanElvisBoundSmartCasts)) return + val lhs = elvisExpression.lhs + val rhs = elvisExpression.rhs + if (rhs is FirConstExpression<*> && rhs.kind == ConstantValueKind.Boolean) { + if (lhs.typeRef.coneType.classId != StandardClassIds.Boolean) return + + val flow = node.flow + // a ?: false == true -> a != null + // a ?: true == false -> a != null + val elvisVariable = variableStorage.getOrCreateVariable(flow, elvisExpression) + val lhsVariable = variableStorage.getOrCreateVariable(flow, lhs) + + val value = rhs.value as Boolean + flow.addImplication(elvisVariable.eq(!value) implies (lhsVariable.notEq(null))) + } } // Callable reference diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirControlFlowStatementsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirControlFlowStatementsResolveTransformer.kt index 00ab34356f2..57fb8188b3c 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirControlFlowStatementsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirControlFlowStatementsResolveTransformer.kt @@ -244,7 +244,7 @@ class FirControlFlowStatementsResolveTransformer(transformer: FirBodyResolveTran } } - dataFlowAnalyzer.exitElvis() + dataFlowAnalyzer.exitElvis(elvisExpression) return result.compose() } } diff --git a/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.fir.kt index 89029fb6fd7..dacb3b62b03 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.fir.kt @@ -11,13 +11,13 @@ interface Order { fun foo(o: Any) { val order = o as? Order if (order?.expired ?: false) { - order.doSomething() + order.doSomething() } else { } if (order?.notExpired() ?: false) { - order.doSomething() + order.doSomething() } } @@ -47,4 +47,4 @@ fun baz(o: Boolean?) { else { o.hashCode() } -} \ No newline at end of file +}