diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.java index 54ab5813f8b..eda6ec3c6c4 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.java @@ -1297,7 +1297,7 @@ public class ControlFlowProcessor { // For the last entry of exhaustive when, // attempt to jump further should lead to error, not to "done" if (!iterator.hasNext() && WhenChecker.isWhenExhaustive(expression, trace)) { - builder.jumpToError(expression); + builder.magic(expression, null, Collections.emptyList(), MagicKind.EXHAUSTIVE_WHEN_ELSE); } } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt index 3a81d1d67e1..db2dd99e2c8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt @@ -167,6 +167,11 @@ private fun Pseudocode.collectDataFromSubgraph( data class Edges(val incoming: T, val outgoing: T) +enum class TraverseInstructionResult { + CONTINUE, + SKIP, + HALT +} // returns false when interrupted by handler public fun traverseFollowingInstructions( @@ -174,7 +179,7 @@ public fun traverseFollowingInstructions( visited: MutableSet, order: TraversalOrder, // true to continue traversal - handler: ((Instruction) -> Boolean)? + handler: ((Instruction) -> TraverseInstructionResult)? ): Boolean { val stack = ArrayDeque() stack.push(rootInstruction) @@ -182,9 +187,11 @@ public fun traverseFollowingInstructions( while (!stack.isEmpty()) { val instruction = stack.pop() if (!visited.add(instruction)) continue - if (handler != null && !handler(instruction)) return false - - instruction.getNextInstructions(order).forEach { stack.push(it) } + when (handler?.let { it(instruction) } ?: TraverseInstructionResult.CONTINUE) { + TraverseInstructionResult.CONTINUE -> instruction.getNextInstructions(order).forEach { stack.push(it) } + TraverseInstructionResult.SKIP -> {} + TraverseInstructionResult.HALT -> return false + } } return true } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeVariablesData.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeVariablesData.java index 76d167fc53e..03bbd1f42a6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeVariablesData.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeVariablesData.java @@ -24,11 +24,14 @@ import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode; import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeUtil; import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction; import org.jetbrains.kotlin.cfg.pseudocode.instructions.LexicalScope; +import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicInstruction; +import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind; import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.ReadValueInstruction; import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.WriteValueInstruction; import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction; import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.VariableDeclarationInstruction; import org.jetbrains.kotlin.cfg.pseudocodeTraverser.Edges; +import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.descriptors.VariableDescriptor; import org.jetbrains.kotlin.psi.KtDeclaration; @@ -40,9 +43,6 @@ import java.util.Collections; import java.util.Map; import java.util.Set; -import static org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.BACKWARD; -import static org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.FORWARD; - public class PseudocodeVariablesData { private final Pseudocode pseudocode; private final BindingContext bindingContext; @@ -126,7 +126,7 @@ public class PseudocodeVariablesData { final LexicalScopeVariableInfo lexicalScopeVariableInfo = pseudocodeVariableDataCollector.getLexicalScopeVariableInfo(); return pseudocodeVariableDataCollector.collectData( - FORWARD, /*mergeDataWithLocalDeclarations=*/ true, + TraversalOrder.FORWARD, /*mergeDataWithLocalDeclarations=*/ true, new InstructionDataMergeStrategy() { @NotNull @Override @@ -169,7 +169,7 @@ public class PseudocodeVariablesData { Map enterInstructionData = Maps.newHashMap(); for (VariableDescriptor variable : variablesInScope) { - TriInitState initState = null; + InitState initState = null; boolean isDeclared = true; for (Map edgeData : incomingEdgesData) { VariableControlFlowState varControlFlowState = edgeData.get(variable); @@ -194,6 +194,19 @@ public class PseudocodeVariablesData { @NotNull Map enterInstructionData, @NotNull LexicalScopeVariableInfo lexicalScopeVariableInfo ) { + if (instruction instanceof MagicInstruction) { + MagicInstruction magicInstruction = (MagicInstruction) instruction; + if (magicInstruction.getKind() == MagicKind.EXHAUSTIVE_WHEN_ELSE) { + Map exitInstructionData = Maps.newHashMap(enterInstructionData); + for (Map.Entry entry: enterInstructionData.entrySet()) { + if (!entry.getValue().definitelyInitialized()) { + exitInstructionData.put(entry.getKey(), + VariableControlFlowState.createInitializedExhaustively(entry.getValue().isDeclared)); + } + } + return exitInstructionData; + } + } if (!(instruction instanceof WriteValueInstruction) && !(instruction instanceof VariableDeclarationInstruction)) { return enterInstructionData; } @@ -233,7 +246,7 @@ public class PseudocodeVariablesData { @NotNull public Map>> getVariableUseStatusData() { return pseudocodeVariableDataCollector.collectData( - BACKWARD, /*mergeDataWithLocalDeclarations=*/ true, + TraversalOrder.BACKWARD, /*mergeDataWithLocalDeclarations=*/ true, new InstructionDataMergeStrategy() { @NotNull @Override @@ -281,17 +294,28 @@ public class PseudocodeVariablesData { ); } - private enum TriInitState { - INITIALIZED("I"), UNKNOWN("I?"), NOT_INITIALIZED(""); + private enum InitState { + // Definitely initialized + INITIALIZED("I"), + // Fake initializer in else branch of "exhaustive when without else", see MagicKind.EXHAUSTIVE_WHEN_ELSE + INITIALIZED_EXHAUSTIVELY("IE"), + // Initialized in some branches, not initialized in other branches + UNKNOWN("I?"), + // Definitely not initialized + NOT_INITIALIZED(""); private final String s; - TriInitState(String s) { + InitState(String s) { this.s = s; } - private TriInitState merge(@NotNull TriInitState other) { - if (this == other) return this; + private InitState merge(@NotNull InitState other) { + // X merge X = X + // X merge IE = IE merge X = X + // else X merge Y = I? + if (this == other || other == INITIALIZED_EXHAUSTIVELY) return this; + if (this == INITIALIZED_EXHAUSTIVELY) return other; return UNKNOWN; } @@ -303,32 +327,39 @@ public class PseudocodeVariablesData { public static class VariableControlFlowState { - public final TriInitState initState; + public final InitState initState; public final boolean isDeclared; - private VariableControlFlowState(TriInitState initState, boolean isDeclared) { + private VariableControlFlowState(InitState initState, boolean isDeclared) { this.initState = initState; this.isDeclared = isDeclared; } - private static final VariableControlFlowState VS_IT = new VariableControlFlowState(TriInitState.INITIALIZED, true); - private static final VariableControlFlowState VS_IF = new VariableControlFlowState(TriInitState.INITIALIZED, false); - private static final VariableControlFlowState VS_UT = new VariableControlFlowState(TriInitState.UNKNOWN, true); - private static final VariableControlFlowState VS_UF = new VariableControlFlowState(TriInitState.UNKNOWN, false); - private static final VariableControlFlowState VS_NT = new VariableControlFlowState(TriInitState.NOT_INITIALIZED, true); - private static final VariableControlFlowState VS_NF = new VariableControlFlowState(TriInitState.NOT_INITIALIZED, false); + private static final VariableControlFlowState VS_IT = new VariableControlFlowState(InitState.INITIALIZED, true); + private static final VariableControlFlowState VS_IF = new VariableControlFlowState(InitState.INITIALIZED, false); + private static final VariableControlFlowState VS_ET = new VariableControlFlowState(InitState.INITIALIZED_EXHAUSTIVELY, true); + private static final VariableControlFlowState VS_EF = new VariableControlFlowState(InitState.INITIALIZED_EXHAUSTIVELY, false); + private static final VariableControlFlowState VS_UT = new VariableControlFlowState(InitState.UNKNOWN, true); + private static final VariableControlFlowState VS_UF = new VariableControlFlowState(InitState.UNKNOWN, false); + private static final VariableControlFlowState VS_NT = new VariableControlFlowState(InitState.NOT_INITIALIZED, true); + private static final VariableControlFlowState VS_NF = new VariableControlFlowState(InitState.NOT_INITIALIZED, false); - private static VariableControlFlowState create(TriInitState initState, boolean isDeclared) { + private static VariableControlFlowState create(InitState initState, boolean isDeclared) { switch (initState) { case INITIALIZED: return isDeclared ? VS_IT : VS_IF; + case INITIALIZED_EXHAUSTIVELY: return isDeclared ? VS_ET : VS_EF; case UNKNOWN: return isDeclared ? VS_UT : VS_UF; default: return isDeclared ? VS_NT : VS_NF; } } + private static VariableControlFlowState createInitializedExhaustively(boolean isDeclared) { + return create(InitState.INITIALIZED_EXHAUSTIVELY, isDeclared); + } + private static VariableControlFlowState create(boolean isInitialized, boolean isDeclared) { - return create(isInitialized ? TriInitState.INITIALIZED : TriInitState.NOT_INITIALIZED, isDeclared); + return create(isInitialized ? InitState.INITIALIZED : InitState.NOT_INITIALIZED, isDeclared); } private static VariableControlFlowState create(boolean isInitialized) { @@ -340,16 +371,16 @@ public class PseudocodeVariablesData { } public boolean definitelyInitialized() { - return initState == TriInitState.INITIALIZED; + return initState == InitState.INITIALIZED; } public boolean mayBeInitialized() { - return initState != TriInitState.NOT_INITIALIZED; + return initState != InitState.NOT_INITIALIZED; } @Override public String toString() { - if (initState == TriInitState.NOT_INITIALIZED && !isDeclared) return "-"; + if (initState == InitState.NOT_INITIALIZED && !isDeclared) return "-"; return initState + (isDeclared ? "D" : ""); } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/TailRecursionDetector.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/TailRecursionDetector.java index 96bced33dd5..e7fd4fd0cae 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/TailRecursionDetector.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/TailRecursionDetector.java @@ -27,9 +27,10 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ThrowExceptionInst import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.MarkInstruction; import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineExitInstruction; import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction; +import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraverseInstructionResult; import org.jetbrains.kotlin.psi.KtElement; -public class TailRecursionDetector extends InstructionVisitorWithResult implements Function1 { +public class TailRecursionDetector extends InstructionVisitorWithResult implements Function1 { private final KtElement subroutine; private final Instruction start; @@ -39,8 +40,8 @@ public class TailRecursionDetector extends InstructionVisitorWithResult } @Override - public Boolean invoke(@NotNull Instruction instruction) { - return instruction == start || instruction.accept(this); + public TraverseInstructionResult invoke(@NotNull Instruction instruction) { + return instruction == start || instruction.accept(this) ? TraverseInstructionResult.CONTINUE : TraverseInstructionResult.HALT; } @Override diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.java index c22e349f450..fd224ec52c6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.java @@ -20,10 +20,13 @@ import com.google.common.collect.*; import com.intellij.util.containers.BidirectionalMap; import kotlin.MapsKt; import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.cfg.Label; import org.jetbrains.kotlin.cfg.pseudocode.instructions.*; +import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicInstruction; +import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind; import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MergeInstruction; import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.AbstractJumpInstruction; import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ConditionalJumpInstruction; @@ -33,6 +36,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineEnterI import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineExitInstruction; import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction; import org.jetbrains.kotlin.cfg.pseudocodeTraverser.PseudocodeTraverserKt; +import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraverseInstructionResult; import org.jetbrains.kotlin.psi.KtElement; import java.util.*; @@ -421,7 +425,17 @@ public class PseudocodeImpl implements Pseudocode { private Set collectReachableInstructions() { Set visited = Sets.newHashSet(); - PseudocodeTraverserKt.traverseFollowingInstructions(getEnterInstruction(), visited, FORWARD, null); + PseudocodeTraverserKt.traverseFollowingInstructions(getEnterInstruction(), visited, FORWARD, + new Function1() { + @Override + public TraverseInstructionResult invoke(Instruction instruction) { + if (instruction instanceof MagicInstruction && + ((MagicInstruction) instruction).getKind() == MagicKind.EXHAUSTIVE_WHEN_ELSE) { + return TraverseInstructionResult.SKIP; + } + return TraverseInstructionResult.CONTINUE; + } + }); if (!visited.contains(getExitInstruction())) { visited.add(getExitInstruction()); } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/eval/operationInstructions.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/eval/operationInstructions.kt index 38e4c9620ed..4f05546a035 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/eval/operationInstructions.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/eval/operationInstructions.kt @@ -143,7 +143,8 @@ public enum class MagicKind(val sideEffectFree: Boolean = false) { UNRESOLVED_CALL(), UNSUPPORTED_ELEMENT(), UNRECOGNIZED_WRITE_RHS(), - FAKE_INITIALIZER() + FAKE_INITIALIZER(), + EXHAUSTIVE_WHEN_ELSE() } // Merges values produced by alternative control-flow paths (such as 'if' branches) diff --git a/compiler/testData/cfg-variables/basic/ExhaustiveInitialization.instructions b/compiler/testData/cfg-variables/basic/ExhaustiveInitialization.instructions new file mode 100644 index 00000000000..6e532d8d37e --- /dev/null +++ b/compiler/testData/cfg-variables/basic/ExhaustiveInitialization.instructions @@ -0,0 +1,91 @@ +== Direction == +enum class Direction { + NORTH, SOUTH, WEST, EAST +} +--------------------- +L0: + 1 INIT: in: {} out: {} +L1: + +error: + +sink: + USE: in: {} out: {} +===================== +== foo == +fun foo(dir: Direction): Int { + val res: Int + when (dir) { + Direction.NORTH -> res = 1 + Direction.SOUTH -> res = 2 + Direction.WEST -> res = 3 + Direction.EAST -> res = 4 + } + return res +} +--------------------- +L0: + 1 INIT: in: {} out: {} + v(dir: Direction) INIT: in: {} out: {dir=D} + magic[FAKE_INITIALIZER](dir: Direction) -> INIT: in: {dir=D} out: {dir=D} + w(dir|) INIT: in: {dir=D} out: {dir=ID} USE: in: {dir=READ} out: {dir=READ} + 2 mark({ val res: Int when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 } return res }) INIT: in: {dir=ID} out: {dir=ID} + v(val res: Int) INIT: in: {dir=ID} out: {dir=ID, res=D} + mark(when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 }) INIT: in: {dir=ID, res=D} out: {dir=ID, res=D} USE: in: {dir=READ, res=READ} out: {dir=READ, res=READ} + r(dir) -> USE: in: {res=READ} out: {dir=READ, res=READ} + mark(Direction.NORTH -> res = 1) + mark(Direction.NORTH) + mark(Direction.NORTH) + r(NORTH) -> + magic[EQUALS_IN_WHEN_CONDITION](Direction.NORTH|, ) -> + jmp?(L4|) USE: in: {res=READ} out: {res=READ} +L3 ['when' entry body]: + r(1) -> USE: in: {res=WRITTEN_AFTER_READ} out: {res=WRITTEN_AFTER_READ} + w(res|) INIT: in: {dir=ID, res=D} out: {dir=ID, res=ID} USE: in: {res=READ} out: {res=WRITTEN_AFTER_READ} + jmp(L2) INIT: in: {dir=ID, res=ID} out: {dir=ID, res=ID} +L4 [next 'when' entry]: + mark(Direction.SOUTH -> res = 2) INIT: in: {dir=ID, res=D} out: {dir=ID, res=D} + mark(Direction.SOUTH) + mark(Direction.SOUTH) + r(SOUTH) -> + magic[EQUALS_IN_WHEN_CONDITION](Direction.SOUTH|, ) -> + jmp?(L6|) USE: in: {res=READ} out: {res=READ} +L5 ['when' entry body]: + r(2) -> USE: in: {res=WRITTEN_AFTER_READ} out: {res=WRITTEN_AFTER_READ} + w(res|) INIT: in: {dir=ID, res=D} out: {dir=ID, res=ID} USE: in: {res=READ} out: {res=WRITTEN_AFTER_READ} + jmp(L2) INIT: in: {dir=ID, res=ID} out: {dir=ID, res=ID} +L6 [next 'when' entry]: + mark(Direction.WEST -> res = 3) INIT: in: {dir=ID, res=D} out: {dir=ID, res=D} + mark(Direction.WEST) + mark(Direction.WEST) + r(WEST) -> + magic[EQUALS_IN_WHEN_CONDITION](Direction.WEST|, ) -> + jmp?(L8|) USE: in: {res=READ} out: {res=READ} +L7 ['when' entry body]: + r(3) -> USE: in: {res=WRITTEN_AFTER_READ} out: {res=WRITTEN_AFTER_READ} + w(res|) INIT: in: {dir=ID, res=D} out: {dir=ID, res=ID} USE: in: {res=READ} out: {res=WRITTEN_AFTER_READ} + jmp(L2) INIT: in: {dir=ID, res=ID} out: {dir=ID, res=ID} +L8 [next 'when' entry]: + mark(Direction.EAST -> res = 4) INIT: in: {dir=ID, res=D} out: {dir=ID, res=D} + mark(Direction.EAST) + mark(Direction.EAST) + r(EAST) -> + magic[EQUALS_IN_WHEN_CONDITION](Direction.EAST|, ) -> + jmp?(L10|) USE: in: {res=READ} out: {res=READ} +L9 ['when' entry body]: + r(4) -> USE: in: {res=WRITTEN_AFTER_READ} out: {res=WRITTEN_AFTER_READ} + w(res|) INIT: in: {dir=ID, res=D} out: {dir=ID, res=ID} USE: in: {res=READ} out: {res=WRITTEN_AFTER_READ} + jmp(L2) INIT: in: {dir=ID, res=ID} out: {dir=ID, res=ID} +L10 [next 'when' entry]: + magic[EXHAUSTIVE_WHEN_ELSE](when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 }) -> INIT: in: {dir=ID, res=D} out: {dir=ID, res=IED} +L2 [after 'when' expression]: + merge(when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 }|!, !, !, !) -> INIT: in: {dir=ID, res=ID} out: {dir=ID, res=ID} USE: in: {res=READ} out: {res=READ} + r(res) -> USE: in: {} out: {res=READ} + ret(*|) L1 +L1: + 1 INIT: in: {dir=ID} out: {dir=ID} +error: + INIT: in: {} out: {} +sink: + INIT: in: {dir=ID} out: {dir=ID} USE: in: {} out: {} +===================== diff --git a/compiler/testData/cfg-variables/basic/ExhaustiveInitialization.kt b/compiler/testData/cfg-variables/basic/ExhaustiveInitialization.kt new file mode 100644 index 00000000000..020be31fb2e --- /dev/null +++ b/compiler/testData/cfg-variables/basic/ExhaustiveInitialization.kt @@ -0,0 +1,14 @@ +enum class Direction { + NORTH, SOUTH, WEST, EAST +} + +fun foo(dir: Direction): Int { + val res: Int + 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/cfg-variables/basic/ExhaustiveInitialization.values b/compiler/testData/cfg-variables/basic/ExhaustiveInitialization.values new file mode 100644 index 00000000000..b6d61eddcab --- /dev/null +++ b/compiler/testData/cfg-variables/basic/ExhaustiveInitialization.values @@ -0,0 +1,46 @@ +== Direction == +enum class Direction { + NORTH, SOUTH, WEST, EAST +} +--------------------- +===================== +== foo == +fun foo(dir: Direction): Int { + val res: Int + when (dir) { + Direction.NORTH -> res = 1 + Direction.SOUTH -> res = 2 + Direction.WEST -> res = 3 + Direction.EAST -> res = 4 + } + return res +} +--------------------- + : Direction NEW: magic[FAKE_INITIALIZER](dir: Direction) -> + : * NEW: magic[EXHAUSTIVE_WHEN_ELSE](when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 }) -> +dir : * NEW: r(dir) -> +NORTH : * NEW: r(NORTH) -> +Direction.NORTH : * COPY +Direction.NORTH : * NEW: magic[EQUALS_IN_WHEN_CONDITION](Direction.NORTH|, ) -> +1 : Int NEW: r(1) -> +res = 1 !: * +SOUTH : * NEW: r(SOUTH) -> +Direction.SOUTH : * COPY +Direction.SOUTH : * NEW: magic[EQUALS_IN_WHEN_CONDITION](Direction.SOUTH|, ) -> +2 : Int NEW: r(2) -> +res = 2 !: * +WEST : * NEW: r(WEST) -> +Direction.WEST : * COPY +Direction.WEST : * NEW: magic[EQUALS_IN_WHEN_CONDITION](Direction.WEST|, ) -> +3 : Int NEW: r(3) -> +res = 3 !: * +EAST : * NEW: r(EAST) -> +Direction.EAST : * COPY +Direction.EAST : * NEW: magic[EQUALS_IN_WHEN_CONDITION](Direction.EAST|, ) -> +4 : Int NEW: r(4) -> +res = 4 !: * +when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 } : * NEW: merge(when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 }|!, !, !, !) -> +res : Int NEW: r(res) -> +return res !: * +{ val res: Int when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 } return res } !: * COPY +===================== diff --git a/compiler/testData/cfg/controlStructures/whenExhaustive.instructions b/compiler/testData/cfg/controlStructures/whenExhaustive.instructions index b2a62fcd3e8..c7a778e7b65 100644 --- a/compiler/testData/cfg/controlStructures/whenExhaustive.instructions +++ b/compiler/testData/cfg/controlStructures/whenExhaustive.instructions @@ -18,29 +18,29 @@ L0: mark(true) r(true) -> magic[EQUALS_IN_WHEN_CONDITION](true|, ) -> - jmp?(L4|) NEXT:[mark(false -> return 0), r(1) -> ] + jmp?(L4|) NEXT:[mark(false -> return 0), r(1) -> ] L3 ['when' entry body]: r(1) -> - ret(*|) L1 NEXT:[] -- jmp(L2) NEXT:[merge(when (flag) { true -> return 1 false -> return 0 }|!, !) -> ] PREV:[] + ret(*|) L1 NEXT:[] +- jmp(L2) NEXT:[merge(when (flag) { true -> return 1 false -> return 0 }|!, !) -> ] PREV:[] L4 [next 'when' entry]: - mark(false -> return 0) PREV:[jmp?(L4|)] + mark(false -> return 0) PREV:[jmp?(L4|)] mark(false) r(false) -> magic[EQUALS_IN_WHEN_CONDITION](false|, ) -> - jmp?(L6|) NEXT:[jmp(error), r(0) -> ] + jmp?(L6|) NEXT:[magic[EXHAUSTIVE_WHEN_ELSE](when (flag) { true -> return 1 false -> return 0 }) -> , r(0) -> ] L5 ['when' entry body]: r(0) -> - ret(*|) L1 NEXT:[] -- jmp(L2) NEXT:[merge(when (flag) { true -> return 1 false -> return 0 }|!, !) -> ] PREV:[] + ret(*|) L1 NEXT:[] +- jmp(L2) NEXT:[merge(when (flag) { true -> return 1 false -> return 0 }|!, !) -> ] PREV:[] L6 [next 'when' entry]: - jmp(error) NEXT:[] PREV:[jmp?(L6|)] + magic[EXHAUSTIVE_WHEN_ELSE](when (flag) { true -> return 1 false -> return 0 }) -> PREV:[jmp?(L6|)] L2 [after 'when' expression]: -- merge(when (flag) { true -> return 1 false -> return 0 }|!, !) -> PREV:[] +- merge(when (flag) { true -> return 1 false -> return 0 }|!, !) -> L1: - 1 NEXT:[] PREV:[ret(*|) L1, ret(*|) L1] + 1 NEXT:[] PREV:[ret(*|) L1, ret(*|) L1] error: - PREV:[jmp(error)] + PREV:[] sink: - PREV:[, ] + PREV:[, ] ===================== diff --git a/compiler/testData/cfg/controlStructures/whenExhaustive.values b/compiler/testData/cfg/controlStructures/whenExhaustive.values index c4dbd978b6b..7e227d701d9 100644 --- a/compiler/testData/cfg/controlStructures/whenExhaustive.values +++ b/compiler/testData/cfg/controlStructures/whenExhaustive.values @@ -7,15 +7,16 @@ fun foo(flag: Boolean): Int { } --------------------- : Boolean NEW: magic[FAKE_INITIALIZER](flag: Boolean) -> + : * NEW: magic[EXHAUSTIVE_WHEN_ELSE](when (flag) { true -> return 1 false -> return 0 }) -> flag : * NEW: r(flag) -> true : * NEW: r(true) -> true : * NEW: magic[EQUALS_IN_WHEN_CONDITION](true|, ) -> 1 : Int NEW: r(1) -> -return 1 !: * +return 1 !: * false : * NEW: r(false) -> false : * NEW: magic[EQUALS_IN_WHEN_CONDITION](false|, ) -> 0 : Int NEW: r(0) -> -return 0 !: * -when (flag) { true -> return 1 false -> return 0 } : * NEW: merge(when (flag) { true -> return 1 false -> return 0 }|!, !) -> -{ when (flag) { true -> return 1 false -> return 0 } } : * COPY +return 0 !: * +when (flag) { true -> return 1 false -> return 0 } : * NEW: merge(when (flag) { true -> return 1 false -> return 0 }|!, !) -> +{ when (flag) { true -> return 1 false -> return 0 } } : * COPY ===================== diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveBreakContinue.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveBreakContinue.kt new file mode 100644 index 00000000000..348d97080be --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveBreakContinue.kt @@ -0,0 +1,14 @@ +enum class Color { RED, GREEN, BLUE } + +fun foo(arr: Array): Color { + loop@ for (color in arr) { + when (color) { + Color.RED -> return color + Color.GREEN -> break@loop + Color.BLUE -> if (arr.size == 1) return color else continue@loop + } + // Unreachable + return Color.BLUE + } + return Color.GREEN +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveBreakContinue.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveBreakContinue.txt new file mode 100644 index 00000000000..eda3bf5b5cf --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveBreakContinue.txt @@ -0,0 +1,25 @@ +package + +public fun foo(/*0*/ arr: kotlin.Array): Color + +public final enum class Color : kotlin.Enum { + enum entry RED + + enum entry GREEN + + enum entry BLUE + + private constructor Color() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Color): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + @kotlin.Deprecated(level = DeprecationLevel.ERROR, message = "Use 'values()' function instead", replaceWith = kotlin.ReplaceWith(expression = "this.values()", imports = {})) public final /*synthesized*/ val values: kotlin.Array + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Color + public final /*synthesized*/ fun values(): kotlin.Array +} diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.kt new file mode 100644 index 00000000000..b7f68238504 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.kt @@ -0,0 +1,11 @@ +fun foo(b: Boolean): Int { + val x: Int + val y: Int + when (b) { + true -> y = 1 + false -> y = 0 + } + // x is initialized here + x = 3 + return x + y +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.txt new file mode 100644 index 00000000000..ae08fa1f1e8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.txt @@ -0,0 +1,3 @@ +package + +public fun foo(/*0*/ b: kotlin.Boolean): kotlin.Int diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveReturnThrow.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveReturnThrow.kt new file mode 100644 index 00000000000..8dc2ce05aed --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveReturnThrow.kt @@ -0,0 +1,14 @@ +enum class Direction { + NORTH, SOUTH, WEST, EAST +} + +fun foo(dir: Direction): Int { + when (dir) { + Direction.NORTH -> return 1 + Direction.SOUTH -> throw AssertionError("!!!") + Direction.WEST -> return 3 + Direction.EAST -> return 4 + } + // Error: Unreachable code. Return is not required. + if (dir == Direction.SOUTH) return 2 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveReturnThrow.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveReturnThrow.txt new file mode 100644 index 00000000000..61bb03d1e25 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveReturnThrow.txt @@ -0,0 +1,27 @@ +package + +public fun foo(/*0*/ dir: Direction): kotlin.Int + +public final enum class Direction : kotlin.Enum { + enum entry NORTH + + enum entry SOUTH + + enum entry WEST + + enum entry EAST + + private constructor Direction() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Direction): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + @kotlin.Deprecated(level = DeprecationLevel.ERROR, message = "Use 'values()' function instead", replaceWith = kotlin.ReplaceWith(expression = "this.values()", imports = {})) public final /*synthesized*/ val values: kotlin.Array + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Direction + public final /*synthesized*/ fun values(): kotlin.Array +} diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.kt new file mode 100644 index 00000000000..58fd61ffef3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.kt @@ -0,0 +1,22 @@ +fun foo(a: Boolean, b: Boolean): Int { + val x: Int + if (a) { + x = 1 + } + when (b) { + true -> x = 2 + false -> x = 3 + } + return x +} + +fun bar(a: Boolean, b: Boolean): Int { + val x: Int + if (a) { + x = 1 + } + when (b) { + false -> x = 3 + } + return x +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.txt new file mode 100644 index 00000000000..f702abace3c --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.txt @@ -0,0 +1,4 @@ +package + +public fun bar(/*0*/ a: kotlin.Boolean, /*1*/ b: kotlin.Boolean): kotlin.Int +public fun foo(/*0*/ a: kotlin.Boolean, /*1*/ b: kotlin.Boolean): kotlin.Int diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt b/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt new file mode 100644 index 00000000000..50d1442f1cf --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt @@ -0,0 +1,22 @@ +fun foo(a: Boolean, b: Boolean): Int { + var x: Int + if (a) { + x = 1 + } + when (b) { + true -> x = 2 + false -> x = 3 + } + return x +} + +fun bar(a: Boolean, b: Boolean): Int { + var x: Int + if (a) { + x = 1 + } + when (b) { + false -> x = 3 + } + return x +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.txt b/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.txt new file mode 100644 index 00000000000..f702abace3c --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.txt @@ -0,0 +1,4 @@ +package + +public fun bar(/*0*/ a: kotlin.Boolean, /*1*/ b: kotlin.Boolean): kotlin.Int +public fun foo(/*0*/ a: kotlin.Boolean, /*1*/ b: kotlin.Boolean): kotlin.Int diff --git a/compiler/tests/org/jetbrains/kotlin/cfg/DataFlowTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cfg/DataFlowTestGenerated.java index 61591e740c5..92e4ec0c805 100644 --- a/compiler/tests/org/jetbrains/kotlin/cfg/DataFlowTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cfg/DataFlowTestGenerated.java @@ -43,6 +43,12 @@ public class DataFlowTestGenerated extends AbstractDataFlowTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/cfg-variables/basic"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("ExhaustiveInitialization.kt") + public void testExhaustiveInitialization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg-variables/basic/ExhaustiveInitialization.kt"); + doTest(fileName); + } + @TestMetadata("IfWithUninitialized.kt") public void testIfWithUninitialized() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg-variables/basic/IfWithUninitialized.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java index c0a1969335d..a884f1735c7 100644 --- a/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java @@ -771,6 +771,12 @@ public class PseudoValueTestGenerated extends AbstractPseudoValueTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/cfg-variables/basic"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("ExhaustiveInitialization.kt") + public void testExhaustiveInitialization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg-variables/basic/ExhaustiveInitialization.kt"); + doTest(fileName); + } + @TestMetadata("IfWithUninitialized.kt") public void testIfWithUninitialized() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg-variables/basic/IfWithUninitialized.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 7534e751c4d..cd93ce42f0b 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -18048,6 +18048,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("ExhaustiveBreakContinue.kt") + public void testExhaustiveBreakContinue() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveBreakContinue.kt"); + doTest(fileName); + } + @TestMetadata("ExhaustiveEnumIs.kt") public void testExhaustiveEnumIs() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveEnumIs.kt"); @@ -18066,6 +18072,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("ExhaustiveNoInitialization.kt") + public void testExhaustiveNoInitialization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveNoInitialization.kt"); + doTest(fileName); + } + @TestMetadata("ExhaustiveNullable.kt") public void testExhaustiveNullable() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveNullable.kt"); @@ -18108,6 +18120,24 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("ExhaustiveReturnThrow.kt") + public void testExhaustiveReturnThrow() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveReturnThrow.kt"); + doTest(fileName); + } + + @TestMetadata("ExhaustiveValOverConditionalInit.kt") + public void testExhaustiveValOverConditionalInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveValOverConditionalInit.kt"); + doTest(fileName); + } + + @TestMetadata("ExhaustiveVarOverConditionalInit.kt") + public void testExhaustiveVarOverConditionalInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveVarOverConditionalInit.kt"); + doTest(fileName); + } + @TestMetadata("ExhaustiveWithNullabilityCheck.kt") public void testExhaustiveWithNullabilityCheck() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveWithNullabilityCheck.kt"); diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index ff5c2656c5a..cb3ed673320 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.* import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.MarkInstruction import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder +import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraverseInstructionResult import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverseFollowingInstructions import org.jetbrains.kotlin.descriptors.* @@ -114,7 +115,7 @@ private fun List.getVarDescriptorsAccessedAfterwards(bindingContext doTraversal(it.body.enterInstruction) } - true + TraverseInstructionResult.CONTINUE } }