CFG exhaustive when else instruction for KT-8700
This commit is contained in:
committed by
Mikhail Glukhikh
parent
697228eae0
commit
b805ce06c2
@@ -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.<PseudoValue>emptyList(), MagicKind.EXHAUSTIVE_WHEN_ELSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,11 @@ private fun <D> Pseudocode.collectDataFromSubgraph(
|
||||
|
||||
data class Edges<T>(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<Instruction>,
|
||||
order: TraversalOrder,
|
||||
// true to continue traversal
|
||||
handler: ((Instruction) -> Boolean)?
|
||||
handler: ((Instruction) -> TraverseInstructionResult)?
|
||||
): Boolean {
|
||||
val stack = ArrayDeque<Instruction>()
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<VariableControlFlowState>() {
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -169,7 +169,7 @@ public class PseudocodeVariablesData {
|
||||
|
||||
Map<VariableDescriptor, VariableControlFlowState> enterInstructionData = Maps.newHashMap();
|
||||
for (VariableDescriptor variable : variablesInScope) {
|
||||
TriInitState initState = null;
|
||||
InitState initState = null;
|
||||
boolean isDeclared = true;
|
||||
for (Map<VariableDescriptor, VariableControlFlowState> edgeData : incomingEdgesData) {
|
||||
VariableControlFlowState varControlFlowState = edgeData.get(variable);
|
||||
@@ -194,6 +194,19 @@ public class PseudocodeVariablesData {
|
||||
@NotNull Map<VariableDescriptor, VariableControlFlowState> enterInstructionData,
|
||||
@NotNull LexicalScopeVariableInfo lexicalScopeVariableInfo
|
||||
) {
|
||||
if (instruction instanceof MagicInstruction) {
|
||||
MagicInstruction magicInstruction = (MagicInstruction) instruction;
|
||||
if (magicInstruction.getKind() == MagicKind.EXHAUSTIVE_WHEN_ELSE) {
|
||||
Map<VariableDescriptor, VariableControlFlowState> exitInstructionData = Maps.newHashMap(enterInstructionData);
|
||||
for (Map.Entry<VariableDescriptor, VariableControlFlowState> 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<Instruction, Edges<Map<VariableDescriptor, VariableUseState>>> getVariableUseStatusData() {
|
||||
return pseudocodeVariableDataCollector.collectData(
|
||||
BACKWARD, /*mergeDataWithLocalDeclarations=*/ true,
|
||||
TraversalOrder.BACKWARD, /*mergeDataWithLocalDeclarations=*/ true,
|
||||
new InstructionDataMergeStrategy<VariableUseState>() {
|
||||
@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" : "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Boolean> implements Function1<Instruction, Boolean> {
|
||||
public class TailRecursionDetector extends InstructionVisitorWithResult<Boolean> implements Function1<Instruction, TraverseInstructionResult> {
|
||||
private final KtElement subroutine;
|
||||
private final Instruction start;
|
||||
|
||||
@@ -39,8 +40,8 @@ public class TailRecursionDetector extends InstructionVisitorWithResult<Boolean>
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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<Instruction> collectReachableInstructions() {
|
||||
Set<Instruction> visited = Sets.newHashSet();
|
||||
PseudocodeTraverserKt.traverseFollowingInstructions(getEnterInstruction(), visited, FORWARD, null);
|
||||
PseudocodeTraverserKt.traverseFollowingInstructions(getEnterInstruction(), visited, FORWARD,
|
||||
new Function1<Instruction, TraverseInstructionResult>() {
|
||||
@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());
|
||||
}
|
||||
|
||||
+2
-1
@@ -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)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
== Direction ==
|
||||
enum class Direction {
|
||||
NORTH, SOUTH, WEST, EAST
|
||||
}
|
||||
---------------------
|
||||
L0:
|
||||
1 <START> INIT: in: {} out: {}
|
||||
L1:
|
||||
<END>
|
||||
error:
|
||||
<ERROR>
|
||||
sink:
|
||||
<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 <START> INIT: in: {} out: {}
|
||||
v(dir: Direction) INIT: in: {} out: {dir=D}
|
||||
magic[FAKE_INITIALIZER](dir: Direction) -> <v0> INIT: in: {dir=D} out: {dir=D}
|
||||
w(dir|<v0>) 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) -> <v1> USE: in: {res=READ} out: {dir=READ, res=READ}
|
||||
mark(Direction.NORTH -> res = 1)
|
||||
mark(Direction.NORTH)
|
||||
mark(Direction.NORTH)
|
||||
r(NORTH) -> <v2>
|
||||
magic[EQUALS_IN_WHEN_CONDITION](Direction.NORTH|<v1>, <v2>) -> <v3>
|
||||
jmp?(L4|<v3>) USE: in: {res=READ} out: {res=READ}
|
||||
L3 ['when' entry body]:
|
||||
r(1) -> <v4> USE: in: {res=WRITTEN_AFTER_READ} out: {res=WRITTEN_AFTER_READ}
|
||||
w(res|<v4>) 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) -> <v5>
|
||||
magic[EQUALS_IN_WHEN_CONDITION](Direction.SOUTH|<v1>, <v5>) -> <v6>
|
||||
jmp?(L6|<v6>) USE: in: {res=READ} out: {res=READ}
|
||||
L5 ['when' entry body]:
|
||||
r(2) -> <v7> USE: in: {res=WRITTEN_AFTER_READ} out: {res=WRITTEN_AFTER_READ}
|
||||
w(res|<v7>) 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) -> <v8>
|
||||
magic[EQUALS_IN_WHEN_CONDITION](Direction.WEST|<v1>, <v8>) -> <v9>
|
||||
jmp?(L8|<v9>) USE: in: {res=READ} out: {res=READ}
|
||||
L7 ['when' entry body]:
|
||||
r(3) -> <v10> USE: in: {res=WRITTEN_AFTER_READ} out: {res=WRITTEN_AFTER_READ}
|
||||
w(res|<v10>) 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) -> <v11>
|
||||
magic[EQUALS_IN_WHEN_CONDITION](Direction.EAST|<v1>, <v11>) -> <v12>
|
||||
jmp?(L10|<v12>) USE: in: {res=READ} out: {res=READ}
|
||||
L9 ['when' entry body]:
|
||||
r(4) -> <v13> USE: in: {res=WRITTEN_AFTER_READ} out: {res=WRITTEN_AFTER_READ}
|
||||
w(res|<v13>) 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 }) -> <v14> 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 }|!<v15>, !<v16>, !<v17>, !<v18>) -> <v19> INIT: in: {dir=ID, res=ID} out: {dir=ID, res=ID} USE: in: {res=READ} out: {res=READ}
|
||||
r(res) -> <v20> USE: in: {} out: {res=READ}
|
||||
ret(*|<v20>) L1
|
||||
L1:
|
||||
1 <END> INIT: in: {dir=ID} out: {dir=ID}
|
||||
error:
|
||||
<ERROR> INIT: in: {} out: {}
|
||||
sink:
|
||||
<SINK> INIT: in: {dir=ID} out: {dir=ID} USE: in: {} out: {}
|
||||
=====================
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
---------------------
|
||||
<v0>: Direction NEW: magic[FAKE_INITIALIZER](dir: Direction) -> <v0>
|
||||
<v14>: * NEW: magic[EXHAUSTIVE_WHEN_ELSE](when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 }) -> <v14>
|
||||
dir <v1>: * NEW: r(dir) -> <v1>
|
||||
NORTH <v2>: * NEW: r(NORTH) -> <v2>
|
||||
Direction.NORTH <v2>: * COPY
|
||||
Direction.NORTH <v3>: * NEW: magic[EQUALS_IN_WHEN_CONDITION](Direction.NORTH|<v1>, <v2>) -> <v3>
|
||||
1 <v4>: Int NEW: r(1) -> <v4>
|
||||
res = 1 !<v15>: *
|
||||
SOUTH <v5>: * NEW: r(SOUTH) -> <v5>
|
||||
Direction.SOUTH <v5>: * COPY
|
||||
Direction.SOUTH <v6>: * NEW: magic[EQUALS_IN_WHEN_CONDITION](Direction.SOUTH|<v1>, <v5>) -> <v6>
|
||||
2 <v7>: Int NEW: r(2) -> <v7>
|
||||
res = 2 !<v16>: *
|
||||
WEST <v8>: * NEW: r(WEST) -> <v8>
|
||||
Direction.WEST <v8>: * COPY
|
||||
Direction.WEST <v9>: * NEW: magic[EQUALS_IN_WHEN_CONDITION](Direction.WEST|<v1>, <v8>) -> <v9>
|
||||
3 <v10>: Int NEW: r(3) -> <v10>
|
||||
res = 3 !<v17>: *
|
||||
EAST <v11>: * NEW: r(EAST) -> <v11>
|
||||
Direction.EAST <v11>: * COPY
|
||||
Direction.EAST <v12>: * NEW: magic[EQUALS_IN_WHEN_CONDITION](Direction.EAST|<v1>, <v11>) -> <v12>
|
||||
4 <v13>: Int NEW: r(4) -> <v13>
|
||||
res = 4 !<v18>: *
|
||||
when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 } <v19>: * NEW: merge(when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 }|!<v15>, !<v16>, !<v17>, !<v18>) -> <v19>
|
||||
res <v20>: Int NEW: r(res) -> <v20>
|
||||
return res !<v21>: *
|
||||
{ val res: Int when (dir) { Direction.NORTH -> res = 1 Direction.SOUTH -> res = 2 Direction.WEST -> res = 3 Direction.EAST -> res = 4 } return res } !<v21>: * COPY
|
||||
=====================
|
||||
@@ -18,29 +18,29 @@ L0:
|
||||
mark(true)
|
||||
r(true) -> <v2>
|
||||
magic[EQUALS_IN_WHEN_CONDITION](true|<v1>, <v2>) -> <v3>
|
||||
jmp?(L4|<v3>) NEXT:[mark(false -> return 0), r(1) -> <v4>]
|
||||
jmp?(L4|<v3>) NEXT:[mark(false -> return 0), r(1) -> <v4>]
|
||||
L3 ['when' entry body]:
|
||||
r(1) -> <v4>
|
||||
ret(*|<v4>) L1 NEXT:[<END>]
|
||||
- jmp(L2) NEXT:[merge(when (flag) { true -> return 1 false -> return 0 }|!<v8>, !<v9>) -> <v10>] PREV:[]
|
||||
ret(*|<v4>) L1 NEXT:[<END>]
|
||||
- jmp(L2) NEXT:[merge(when (flag) { true -> return 1 false -> return 0 }|!<v9>, !<v10>) -> <v11>] PREV:[]
|
||||
L4 [next 'when' entry]:
|
||||
mark(false -> return 0) PREV:[jmp?(L4|<v3>)]
|
||||
mark(false -> return 0) PREV:[jmp?(L4|<v3>)]
|
||||
mark(false)
|
||||
r(false) -> <v5>
|
||||
magic[EQUALS_IN_WHEN_CONDITION](false|<v1>, <v5>) -> <v6>
|
||||
jmp?(L6|<v6>) NEXT:[jmp(error), r(0) -> <v7>]
|
||||
jmp?(L6|<v6>) NEXT:[magic[EXHAUSTIVE_WHEN_ELSE](when (flag) { true -> return 1 false -> return 0 }) -> <v8>, r(0) -> <v7>]
|
||||
L5 ['when' entry body]:
|
||||
r(0) -> <v7>
|
||||
ret(*|<v7>) L1 NEXT:[<END>]
|
||||
- jmp(L2) NEXT:[merge(when (flag) { true -> return 1 false -> return 0 }|!<v8>, !<v9>) -> <v10>] PREV:[]
|
||||
ret(*|<v7>) L1 NEXT:[<END>]
|
||||
- jmp(L2) NEXT:[merge(when (flag) { true -> return 1 false -> return 0 }|!<v9>, !<v10>) -> <v11>] PREV:[]
|
||||
L6 [next 'when' entry]:
|
||||
jmp(error) NEXT:[<ERROR>] PREV:[jmp?(L6|<v6>)]
|
||||
magic[EXHAUSTIVE_WHEN_ELSE](when (flag) { true -> return 1 false -> return 0 }) -> <v8> PREV:[jmp?(L6|<v6>)]
|
||||
L2 [after 'when' expression]:
|
||||
- merge(when (flag) { true -> return 1 false -> return 0 }|!<v8>, !<v9>) -> <v10> PREV:[]
|
||||
- merge(when (flag) { true -> return 1 false -> return 0 }|!<v9>, !<v10>) -> <v11>
|
||||
L1:
|
||||
1 <END> NEXT:[<SINK>] PREV:[ret(*|<v4>) L1, ret(*|<v7>) L1]
|
||||
1 <END> NEXT:[<SINK>] PREV:[ret(*|<v4>) L1, ret(*|<v7>) L1]
|
||||
error:
|
||||
<ERROR> PREV:[jmp(error)]
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
|
||||
@@ -7,15 +7,16 @@ fun foo(flag: Boolean): Int {
|
||||
}
|
||||
---------------------
|
||||
<v0>: Boolean NEW: magic[FAKE_INITIALIZER](flag: Boolean) -> <v0>
|
||||
<v8>: * NEW: magic[EXHAUSTIVE_WHEN_ELSE](when (flag) { true -> return 1 false -> return 0 }) -> <v8>
|
||||
flag <v1>: * NEW: r(flag) -> <v1>
|
||||
true <v2>: * NEW: r(true) -> <v2>
|
||||
true <v3>: * NEW: magic[EQUALS_IN_WHEN_CONDITION](true|<v1>, <v2>) -> <v3>
|
||||
1 <v4>: Int NEW: r(1) -> <v4>
|
||||
return 1 !<v8>: *
|
||||
return 1 !<v9>: *
|
||||
false <v5>: * NEW: r(false) -> <v5>
|
||||
false <v6>: * NEW: magic[EQUALS_IN_WHEN_CONDITION](false|<v1>, <v5>) -> <v6>
|
||||
0 <v7>: Int NEW: r(0) -> <v7>
|
||||
return 0 !<v9>: *
|
||||
when (flag) { true -> return 1 false -> return 0 } <v10>: * NEW: merge(when (flag) { true -> return 1 false -> return 0 }|!<v8>, !<v9>) -> <v10>
|
||||
{ when (flag) { true -> return 1 false -> return 0 } } <v10>: * COPY
|
||||
return 0 !<v10>: *
|
||||
when (flag) { true -> return 1 false -> return 0 } <v11>: * NEW: merge(when (flag) { true -> return 1 false -> return 0 }|!<v9>, !<v10>) -> <v11>
|
||||
{ when (flag) { true -> return 1 false -> return 0 } } <v11>: * COPY
|
||||
=====================
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
enum class Color { RED, GREEN, BLUE }
|
||||
|
||||
fun foo(arr: Array<Color>): 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
|
||||
<!UNREACHABLE_CODE!>return Color.BLUE<!>
|
||||
}
|
||||
return Color.GREEN
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package
|
||||
|
||||
public fun foo(/*0*/ arr: kotlin.Array<Color>): Color
|
||||
|
||||
public final enum class Color : kotlin.Enum<Color> {
|
||||
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<Color>
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Color
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<Color>
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package
|
||||
|
||||
public fun foo(/*0*/ b: kotlin.Boolean): kotlin.Int
|
||||
@@ -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.
|
||||
<!UNREACHABLE_CODE!>if (dir == Direction.SOUTH) return 2<!>
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package
|
||||
|
||||
public fun foo(/*0*/ dir: Direction): kotlin.Int
|
||||
|
||||
public final enum class Direction : kotlin.Enum<Direction> {
|
||||
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<Direction>
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Direction
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<Direction>
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
fun foo(a: Boolean, b: Boolean): Int {
|
||||
val x: Int
|
||||
if (a) {
|
||||
x = 1
|
||||
}
|
||||
when (b) {
|
||||
true -> <!VAL_REASSIGNMENT!>x<!> = 2
|
||||
false -> x = 3
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
fun bar(a: Boolean, b: Boolean): Int {
|
||||
val x: Int
|
||||
if (a) {
|
||||
x = 1
|
||||
}
|
||||
when (b) {
|
||||
false -> <!VAL_REASSIGNMENT!>x<!> = 3
|
||||
}
|
||||
return <!UNINITIALIZED_VARIABLE!>x<!>
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 <!UNINITIALIZED_VARIABLE!>x<!>
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
+2
-1
@@ -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<Instruction>.getVarDescriptorsAccessedAfterwards(bindingContext
|
||||
doTraversal(it.body.enterInstruction)
|
||||
}
|
||||
|
||||
true
|
||||
TraverseInstructionResult.CONTINUE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user