From 011a9f23b9776584bce8102f3a6fe7d719b2c2fd Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Fri, 25 Dec 2015 14:25:38 +0300 Subject: [PATCH] Implicit exhaustive when check for definite variable initialization (KT-8700) --- .../jetbrains/kotlin/cfg/ControlFlowInfo.kt | 13 +++++++++++++ .../cfg/ControlFlowInformationProvider.java | 18 ++++++++++++++++++ .../tests/when/BranchFalseBypass.kt | 4 ++-- .../tests/when/ExhaustiveInitialization.kt | 4 ++-- .../tests/when/ExhaustiveNoInitialization.kt | 4 ++-- .../tests/when/ExhaustiveNullable.kt | 4 ++-- .../when/ExhaustiveValOverConditionalInit.kt | 4 ++-- .../when/ExhaustiveVarOverConditionalInit.kt | 4 ++-- .../tests/when/NonExhaustiveWarningFalse.kt | 4 ++-- .../tests/when/PropertyNotInitialized.kt | 4 ++-- 10 files changed, 47 insertions(+), 16 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInfo.kt index c62f8934ced..f6be19b4189 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInfo.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInfo.kt @@ -38,6 +38,19 @@ open class ControlFlowInfo internal constructor(protected val map: MutableMap class InitControlFlowInfo(map: MutableMap = hashMapOf()) : ControlFlowInfo(map) { override fun copy() = InitControlFlowInfo(HashMap(map)) + + // this = output of EXHAUSTIVE_WHEN_ELSE instruction + // merge = input of MergeInstruction + // returns true if definite initialization in when happens here + fun checkDefiniteInitializationInWhen(merge: InitControlFlowInfo): Boolean { + for (entry in entries) { + if (entry.value.initState == InitState.INITIALIZED_EXHAUSTIVELY && + merge[entry.key]?.initState == InitState.INITIALIZED) { + return true + } + } + return false + } } class UseControlFlowInfo(map: MutableMap = hashMapOf()) : diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java index 1c66769db9e..69b4fd48619 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java @@ -713,10 +713,28 @@ public class ControlFlowInformationProvider { } public void markWhenWithoutElse() { + final Map> initializers = pseudocodeVariablesData.getVariableInitializers(); PseudocodeTraverserKt.traverse( pseudocode, TraversalOrder.FORWARD, new ControlFlowInformationProvider.FunctionVoid1() { @Override public void execute(@NotNull Instruction instruction) { + if (instruction instanceof MagicInstruction) { + MagicInstruction magicInstruction = (MagicInstruction) instruction; + if (magicInstruction.getKind() == MagicKind.EXHAUSTIVE_WHEN_ELSE) { + Instruction next = magicInstruction.getNext(); + if (next instanceof MergeInstruction) { + MergeInstruction mergeInstruction = (MergeInstruction) next; + if (initializers.containsKey(mergeInstruction) && initializers.containsKey(magicInstruction)) { + InitControlFlowInfo mergeInfo = initializers.get(mergeInstruction).getIncoming(); + InitControlFlowInfo magicInfo = initializers.get(magicInstruction).getOutgoing(); + if (mergeInstruction.getElement() instanceof KtWhenExpression && + magicInfo.checkDefiniteInitializationInWhen(mergeInfo)) { + trace.record(IMPLICIT_EXHAUSTIVE_WHEN, (KtWhenExpression) mergeInstruction.getElement()); + } + } + } + } + } PseudoValue value = instruction instanceof InstructionWithValue ? ((InstructionWithValue) instruction).getOutputValue() : null; diff --git a/compiler/testData/diagnostics/tests/when/BranchFalseBypass.kt b/compiler/testData/diagnostics/tests/when/BranchFalseBypass.kt index 8416dc804d1..2b7718f0672 100644 --- a/compiler/testData/diagnostics/tests/when/BranchFalseBypass.kt +++ b/compiler/testData/diagnostics/tests/when/BranchFalseBypass.kt @@ -3,10 +3,10 @@ enum class My { A, B } fun test(a: My): String { val q: String? - when (a) { + when (a) { My.A -> q = "1" My.B -> q = "2" - } + } // When is exhaustive return q } diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveInitialization.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveInitialization.kt index 0e15e5efb3d..3364fa57dbc 100644 --- a/compiler/testData/diagnostics/tests/when/ExhaustiveInitialization.kt +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveInitialization.kt @@ -5,11 +5,11 @@ enum class Direction { fun foo(dir: Direction): Int { val res: Int // See KT-6046: res is always initialized - when (dir) { + when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 - } + } return res } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.kt index b7f68238504..f55818abc2e 100644 --- a/compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.kt +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.kt @@ -1,10 +1,10 @@ fun foo(b: Boolean): Int { val x: Int val y: Int - when (b) { + when (b) { true -> y = 1 false -> y = 0 - } + } // x is initialized here x = 3 return x + y diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveNullable.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveNullable.kt index 19edb89d8fd..43630bdaa59 100644 --- a/compiler/testData/diagnostics/tests/when/ExhaustiveNullable.kt +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveNullable.kt @@ -5,10 +5,10 @@ enum class MyEnum { fun foo(x: MyEnum?): Int { val y: Int // See KT-6046: y is always initialized - when (x) { + when (x) { MyEnum.A -> y = 1 MyEnum.B -> y = 2 null -> y = 0 - } + } return y } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.kt index 58fd61ffef3..c3e37e00e7c 100644 --- a/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.kt +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.kt @@ -3,10 +3,10 @@ fun foo(a: Boolean, b: Boolean): Int { if (a) { x = 1 } - when (b) { + when (b) { true -> x = 2 false -> x = 3 - } + } return x } diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt index 50d1442f1cf..c0d98cd7d74 100644 --- a/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt @@ -3,10 +3,10 @@ fun foo(a: Boolean, b: Boolean): Int { if (a) { x = 1 } - when (b) { + when (b) { true -> x = 2 false -> x = 3 - } + } return x } diff --git a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningFalse.kt b/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningFalse.kt index c78080fd833..1304db0191a 100644 --- a/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningFalse.kt +++ b/compiler/testData/diagnostics/tests/when/NonExhaustiveWarningFalse.kt @@ -3,11 +3,11 @@ enum class X { A, B, C, D } fun foo(arg: X): String { val res: String - when (arg) { + when (arg) { X.A -> res = "A" X.B -> res = "B" X.C -> res = "C" X.D -> res = "D" - } + } return res } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/PropertyNotInitialized.kt b/compiler/testData/diagnostics/tests/when/PropertyNotInitialized.kt index c286b1e93c3..09e7a8ca35f 100644 --- a/compiler/testData/diagnostics/tests/when/PropertyNotInitialized.kt +++ b/compiler/testData/diagnostics/tests/when/PropertyNotInitialized.kt @@ -7,10 +7,10 @@ enum class E { class Outer(e: E) { private val prop: Int init { - when(e ) { + when(e ) { // When is exhaustive, property is always initialized E.A -> prop = 1 E.B -> prop = 2 - } + } } } \ No newline at end of file