Merge "Add test for "TreeShaking: Detect instance fields never written""
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 8fb77c1..ec38ea9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -32,7 +32,7 @@
   @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     Slot array = state.pop();
-    assert array.type.isObjectOrNull();
+    assert array.type.isObject();
     builder.addArrayLength(state.push(builder.getFactory().intType).register, array.register);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index c0b1a07..811a00c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -71,7 +71,7 @@
     Slot index = state.pop();
     Slot array = state.pop();
     Slot value;
-    assert array.type.isObjectOrNull();
+    assert array.type.isObject();
     ValueType memberType = ValueType.fromMemberType(type);
     if (array.preciseType != null) {
       value = state.push(array.preciseType.toArrayElementType(builder.getFactory()));
diff --git a/src/main/java/com/android/tools/r8/code/Format21t.java b/src/main/java/com/android/tools/r8/code/Format21t.java
index 40b7227..3b53e89 100644
--- a/src/main/java/com/android/tools/r8/code/Format21t.java
+++ b/src/main/java/com/android/tools/r8/code/Format21t.java
@@ -54,6 +54,8 @@
 
   public abstract Type getType();
 
+  protected abstract ValueType getOperandType();
+
   @Override
   public int[] getTargets() {
     return new int[]{BBBB, getSize()};
@@ -63,7 +65,7 @@
   public void buildIR(IRBuilder builder) {
     int offset = getOffset();
     int size = getSize();
-    builder.addIfZero(getType(), ValueType.INT_OR_FLOAT_OR_NULL, AA, offset + BBBB, offset + size);
+    builder.addIfZero(getType(), getOperandType(), AA, offset + BBBB, offset + size);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IfEqz.java b/src/main/java/com/android/tools/r8/code/IfEqz.java
index fc39c31..3e622e6 100644
--- a/src/main/java/com/android/tools/r8/code/IfEqz.java
+++ b/src/main/java/com/android/tools/r8/code/IfEqz.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.ValueType;
 
 public class IfEqz extends Format21t {
 
@@ -38,4 +39,9 @@
   public Type getType() {
     return Type.EQ;
   }
+
+  @Override
+  protected ValueType getOperandType() {
+    return ValueType.INT_OR_FLOAT_OR_NULL;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/IfGez.java b/src/main/java/com/android/tools/r8/code/IfGez.java
index 1fa59ef..bf3fc1c 100644
--- a/src/main/java/com/android/tools/r8/code/IfGez.java
+++ b/src/main/java/com/android/tools/r8/code/IfGez.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.ValueType;
 
 public class IfGez extends Format21t {
 
@@ -38,4 +39,9 @@
   public Type getType() {
     return Type.GE;
   }
+
+  @Override
+  protected ValueType getOperandType() {
+    return ValueType.INT;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/IfGtz.java b/src/main/java/com/android/tools/r8/code/IfGtz.java
index a2807b9..353738e 100644
--- a/src/main/java/com/android/tools/r8/code/IfGtz.java
+++ b/src/main/java/com/android/tools/r8/code/IfGtz.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.ValueType;
 
 public class IfGtz extends Format21t {
 
@@ -38,4 +39,9 @@
   public Type getType() {
     return Type.GT;
   }
+
+  @Override
+  protected ValueType getOperandType() {
+    return ValueType.INT;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/IfLez.java b/src/main/java/com/android/tools/r8/code/IfLez.java
index 405af0a..aa094fe 100644
--- a/src/main/java/com/android/tools/r8/code/IfLez.java
+++ b/src/main/java/com/android/tools/r8/code/IfLez.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.ValueType;
 
 public class IfLez extends Format21t {
 
@@ -38,4 +39,9 @@
   public Type getType() {
     return Type.LE;
   }
+
+  @Override
+  protected ValueType getOperandType() {
+    return ValueType.INT;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/IfLtz.java b/src/main/java/com/android/tools/r8/code/IfLtz.java
index d79b1d2..236b35d 100644
--- a/src/main/java/com/android/tools/r8/code/IfLtz.java
+++ b/src/main/java/com/android/tools/r8/code/IfLtz.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.ValueType;
 
 public class IfLtz extends Format21t {
 
@@ -38,4 +39,9 @@
   public Type getType() {
     return Type.LT;
   }
+
+  @Override
+  protected ValueType getOperandType() {
+    return ValueType.INT;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/IfNez.java b/src/main/java/com/android/tools/r8/code/IfNez.java
index ddf4551..f638f35 100644
--- a/src/main/java/com/android/tools/r8/code/IfNez.java
+++ b/src/main/java/com/android/tools/r8/code/IfNez.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.ValueType;
 
 public class IfNez extends Format21t {
 
@@ -38,4 +39,9 @@
   public Type getType() {
     return Type.NE;
   }
+
+  @Override
+  protected ValueType getOperandType() {
+    return ValueType.INT_OR_FLOAT_OR_NULL;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Move.java b/src/main/java/com/android/tools/r8/code/Move.java
index 90e44f4..0dc06f9 100644
--- a/src/main/java/com/android/tools/r8/code/Move.java
+++ b/src/main/java/com/android/tools/r8/code/Move.java
@@ -37,6 +37,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addMove(ValueType.INT_OR_FLOAT_OR_NULL, A, B);
+    builder.addMove(ValueType.INT_OR_FLOAT, A, B);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Move16.java b/src/main/java/com/android/tools/r8/code/Move16.java
index 6772538..966d473 100644
--- a/src/main/java/com/android/tools/r8/code/Move16.java
+++ b/src/main/java/com/android/tools/r8/code/Move16.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+
 public class Move16 extends Format32x {
 
   public static final int OPCODE = 0x3;
@@ -36,6 +37,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addMove(ValueType.INT_OR_FLOAT_OR_NULL, AAAA, BBBB);
+    builder.addMove(ValueType.INT_OR_FLOAT, AAAA, BBBB);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/MoveFrom16.java b/src/main/java/com/android/tools/r8/code/MoveFrom16.java
index a81a8a0..351f60a 100644
--- a/src/main/java/com/android/tools/r8/code/MoveFrom16.java
+++ b/src/main/java/com/android/tools/r8/code/MoveFrom16.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+
 public class MoveFrom16 extends Format22x {
 
   public static final int OPCODE = 0x2;
@@ -36,6 +37,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addMove(ValueType.INT_OR_FLOAT_OR_NULL, AA, BBBB);
+    builder.addMove(ValueType.INT_OR_FLOAT, AA, BBBB);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/ConstLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/ConstLatticeElement.java
index 18b2aca..65cbc0b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/ConstLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/ConstLatticeElement.java
@@ -26,10 +26,6 @@
     return Bottom.getInstance();
   }
 
-  public int getBranchCondition() {
-    return value.getIntValue();
-  }
-
   @Override
   public boolean isConst() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index 6c9f075..b4ff421 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -179,7 +179,7 @@
       if (theIf.isZeroTest()) {
         LatticeElement element = getLatticeElement(theIf.inValues().get(0));
         if (element.isConst()) {
-          BasicBlock target = theIf.targetFromCondition(element.asConst().getBranchCondition());
+          BasicBlock target = theIf.targetFromCondition(element.asConst().getConstNumber());
           if (!isExecutableEdge(jumpInstBlockNumber, target.getNumber())) {
             setExecutableEdge(jumpInstBlockNumber, target.getNumber());
             flowEdges.add(target);
@@ -190,9 +190,9 @@
         LatticeElement leftElement = getLatticeElement(theIf.inValues().get(0));
         LatticeElement rightElement = getLatticeElement(theIf.inValues().get(1));
         if (leftElement.isConst() && rightElement.isConst()) {
-          long leftValue = leftElement.asConst().getConstNumber().getIntValue();
-          long rightValue = rightElement.asConst().getConstNumber().getIntValue();
-          BasicBlock target = theIf.targetFromCondition(leftValue - rightValue);
+          ConstNumber leftNumber = leftElement.asConst().getConstNumber();
+          ConstNumber rightNumber = rightElement.asConst().getConstNumber();
+          BasicBlock target = theIf.targetFromCondition(leftNumber, rightNumber);
           if (!isExecutableEdge(jumpInstBlockNumber, target.getNumber())) {
             setExecutableEdge(jumpInstBlockNumber, target.getNumber());
             flowEdges.add(target);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 0b85683..a721955 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -35,8 +35,8 @@
   public ArrayPut(MemberType type, Value array, Value index, Value value) {
     super(null, Arrays.asList(array, index, value));
     assert type != null;
-    assert array.type.isObjectOrNull();
-    assert index.type.isSingleOrZero();
+    assert array.type.isObject();
+    assert index.type.isSingle();
     this.type = type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index f2d0974..71f1bec 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -281,7 +281,7 @@
     if (outType().isSingle() || outType().isWide()) {
       return PrimitiveTypeLatticeElement.getInstance();
     }
-    assert outType().isObjectOrNull();
+    assert outType().isObject();
     return Top.getInstance();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 1ec23d0..4529112 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -376,6 +376,7 @@
       for (Phi phi : block.getPhis()) {
         assert !phi.isTrivialPhi();
         assert phi.getOperands().size() == predecessorCount;
+        assert phi.outType().isPreciseType();
         values.add(phi);
         for (Value value : phi.getOperands()) {
           values.add(value);
@@ -392,6 +393,7 @@
         if (outValue != null) {
           values.add(outValue);
           assert outValue.definition == instruction;
+          assert outValue.outType().isPreciseType();
         }
         for (Value value : instruction.inValues()) {
           values.add(value);
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 2e1609d..266069b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -171,7 +171,29 @@
     return 0;
   }
 
-  public BasicBlock targetFromCondition(long cond) {
+  public BasicBlock targetFromCondition(ConstNumber value) {
+    assert isZeroTest();
+    assert value.outType() == ValueType.INT
+        || (value.outType().isObjectOrSingle() && (type == Type.EQ || type == Type.NE));
+    return targetFromCondition(Long.signum(value.getRawValue()));
+  }
+
+  public BasicBlock targetFromCondition(ConstNumber left, ConstNumber right) {
+    assert !isZeroTest();
+    assert left.outType() == right.outType();
+    assert left.outType() == ValueType.INT
+        || (left.outType().isObjectOrSingle() && (type == Type.EQ || type == Type.NE));
+    return targetFromCondition(Long.signum(left.getRawValue() - right.getRawValue()));
+  }
+
+  public BasicBlock targetFromNonNullObject() {
+    assert isZeroTest();
+    assert inValues.get(0).outType().isObject();
+    return targetFromCondition(1);
+  }
+
+  public BasicBlock targetFromCondition(int cond) {
+    assert Integer.signum(cond) == cond;
     switch (type) {
       case EQ:
         return cond == 0 ? getTrueTarget() : fallthroughBlock();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 8ed38ed..ededf98 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -28,8 +28,8 @@
 
   public InstancePut(MemberType type, DexField field, Value object, Value value) {
     super(type, field, null, Arrays.asList(object, value));
-    assert object().type.isObjectOrNull();
-    assert value().type.compatible(ValueType.fromDexType(field.type));
+    assert object().type.isObject();
+    assert value().type.verifyCompatible(ValueType.fromDexType(field.type));
   }
 
   public Value object() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index f5fab7d..186839b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -65,6 +65,7 @@
       EdgeType edgeType = pred.getEdgeType(block);
       // Since this read has been delayed we must provide the local info for the value.
       Value operand = builder.readRegister(register, type, pred, edgeType, getLocalInfo());
+      operand.constrainType(type);
       canBeNull |= operand.canBeNull();
       appendOperand(operand);
     }
@@ -244,11 +245,7 @@
     }
     builder.append(" <- phi");
     StringUtils.append(builder, ListUtils.map(operands, Value::toString));
-    ValueType computed = computeOutType(null);
     builder.append(" : ").append(type);
-    if (type != computed) {
-      builder.append(" / ").append(computed);
-    }
     return builder.toString();
   }
 
@@ -311,56 +308,6 @@
     return true;
   }
 
-  private ValueType computeOutType(Set<Phi> active) {
-    // Go through non-phi operands first to determine if we have an operand that dictates the type.
-    for (Value operand : operands) {
-      // Since a constant zero can be either an integer or an Object (null) we skip them
-      // when computing types and rely on other operands to specify the actual type.
-      if (!operand.isPhi()) {
-        if (operand.outType() != ValueType.INT_OR_FLOAT_OR_NULL) {
-          return operand.outType();
-        }
-        assert operand.isZero();
-      }
-    }
-    // We did not find a non-phi operand that dictates the type. Recurse on phi arguments.
-    if (active == null) {
-      active = new HashSet<>();
-    }
-    active.add(this);
-    for (Value operand : operands) {
-      if (operand.isPhi() && !active.contains(operand.asPhi())) {
-        ValueType phiType = operand.asPhi().computeOutType(active);
-        if (phiType != ValueType.INT_OR_FLOAT_OR_NULL) {
-          return phiType;
-        }
-      }
-    }
-    // All operands were the constant zero or phis with out type INT_OR_FLOAT_OR_NULL.
-    return ValueType.INT_OR_FLOAT_OR_NULL;
-  }
-
-  private static boolean verifyUnknownOrCompatible(ValueType known, ValueType computed) {
-    assert computed == ValueType.INT_OR_FLOAT_OR_NULL || known.compatible(computed);
-    return true;
-  }
-
-  @Override
-  public ValueType outType() {
-    if (type != ValueType.INT_OR_FLOAT_OR_NULL) {
-      assert verifyUnknownOrCompatible(type, computeOutType(null));
-      return type;
-    }
-    // If the phi has unknown type (ie, INT_OR_FLOAT_OR_NULL) then it must be computed. This is
-    // because of the type confusion around null and constant zero. The null object can be used in
-    // a single context (if tests) and the single 0 can be used as null. If the instruction
-    // triggering the creation of a phi does not determine the type (eg, a move can be of int,
-    // float or zero/null) we need to compute the actual type based on the operands.
-    ValueType computedType = computeOutType(null);
-    assert computedType.isObjectOrSingle();
-    return computedType;
-  }
-
   @Override
   public boolean isConstant() {
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 1dc0864..6d3a85b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -54,10 +54,10 @@
       int register = builder.allocatedRegister(returnValue(), getNumber());
       switch (MoveType.fromValueType(returnType)) {
         case OBJECT:
-          assert returnValue().outType().isObjectOrNull();
+          assert returnValue().outType().isObject();
           return new ReturnObject(register);
         case SINGLE:
-          assert returnValue().outType().isSingleOrZero();
+          assert returnValue().outType().isSingle();
           return new com.android.tools.r8.code.Return(register);
         case WIDE:
           assert returnValue().outType().isWide();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 272b565..908e0d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -24,6 +24,10 @@
 
 public class Value {
 
+  public void constrainType(ValueType constraint) {
+    type = type.meet(constraint);
+  }
+
   // Lazily allocated internal data for the debug information of locals.
   // This is wrapped in a class to avoid multiple pointers in the value structure.
   private static class DebugData {
@@ -92,7 +96,7 @@
   public static final Value UNDEFINED = new Value(UNDEFINED_NUMBER, ValueType.OBJECT, null);
 
   protected final int number;
-  protected final ValueType type;
+  protected ValueType type;
   public Instruction definition = null;
   private LinkedList<Instruction> users = new LinkedList<>();
   private Set<Instruction> uniqueUsers = null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
index bf67e23..c414342 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
@@ -34,22 +35,74 @@
     return !isWide();
   }
 
-  public boolean isObjectOrNull() {
-    return isObject() || this == INT_OR_FLOAT_OR_NULL;
-  }
-
-  public boolean isSingleOrZero() {
-    return isSingle() || this == INT_OR_FLOAT_OR_NULL;
-  }
-
   public boolean isPreciseType() {
     return this != ValueType.INT_OR_FLOAT
         && this != ValueType.LONG_OR_DOUBLE
         && this != ValueType.INT_OR_FLOAT_OR_NULL;
   }
 
-  public boolean compatible(ValueType other) {
-    return isWide() == other.isWide();
+  public ValueType meet(ValueType other) {
+    if (this == other) {
+      return this;
+    }
+    if (other == INT_OR_FLOAT_OR_NULL) {
+      return other.meet(this);
+    }
+    switch (this) {
+      case OBJECT:
+        {
+          if (other.isObject()) {
+            return this;
+          }
+          break;
+        }
+      case INT:
+      case FLOAT:
+        {
+          if (other.isSingle()) {
+            return this;
+          }
+          break;
+        }
+      case LONG:
+      case DOUBLE:
+        {
+          if (other.isWide()) {
+            return this;
+          }
+          break;
+        }
+      case INT_OR_FLOAT_OR_NULL:
+        {
+          if (other.isObjectOrSingle()) {
+            return other;
+          }
+          break;
+        }
+      case INT_OR_FLOAT:
+        {
+          if (other.isSingle()) {
+            return other;
+          }
+          break;
+        }
+      case LONG_OR_DOUBLE:
+        {
+          if (other.isWide()) {
+            return other;
+          }
+          break;
+        }
+      default:
+        throw new Unreachable("Unexpected value-type in meet: " + this);
+    }
+    throw new CompilationError("Cannot compute meet of types: " + this + " and " + other);
+  }
+
+  public boolean verifyCompatible(ValueType other) {
+    // Computing meet will throw on incompatible types.
+    assert meet(other) != null;
+    return true;
   }
 
   public int requiredRegisters() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
index 2ac8e60..8174797 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
@@ -127,7 +127,7 @@
     for (int i = 0; i < current.stack.length; i++) {
       ValueType currentType = current.stack[i].getImprecise();
       ValueType updateType = update.stack[i].getImprecise();
-      if (!currentType.compatible(updateType)) {
+      if (currentType != updateType) {
         throw new CompilationError(
             "Incompatible types in stack position "
                 + i
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 702218e..5be54f5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -313,6 +313,9 @@
 
   private int nextBlockNumber = 0;
 
+  // Flag indicating if the instructions define values with imprecise types.
+  private boolean hasImpreciseInstructionOutValueTypes = false;
+
   public IRBuilder(DexEncodedMethod method, AppInfo appInfo,
       SourceCode source, InternalOptions options) {
     this(method, appInfo, source, options, new ValueNumberGenerator());
@@ -435,20 +438,40 @@
     // necessary.
     ir.splitCriticalEdges();
 
-    // Clear the code so we don't build multiple times.
-    source.clear();
-    source = null;
-
     for (BasicBlock block : blocks) {
       block.deduplicatePhis();
     }
 
     ir.removeAllTrivialPhis();
 
+    if (hasImpreciseTypes()) {
+      TypeConstraintResolver resolver = new TypeConstraintResolver();
+      resolver.resolve(ir);
+    }
+
+    // Clear the code so we don't build multiple times.
+    source.clear();
+    source = null;
+
     assert ir.isConsistentSSA();
     return ir;
   }
 
+  private boolean hasImpreciseTypes() {
+    if (hasImpreciseInstructionOutValueTypes) {
+      return true;
+    }
+    // TODO(zerny): Consider keeping track of the imprecise phi types during phi construction.
+    for (BasicBlock block : blocks) {
+      for (Phi phi : block.getPhis()) {
+        if (!phi.outType().isPreciseType()) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   private boolean insertDebugPositions() {
     boolean hasDebugPositions = false;
     if (!options.debug) {
@@ -1344,8 +1367,10 @@
   }
 
   public void addReturn(ValueType type, int value) {
-    Value in = readRegister(value, type);
-    addReturn(new Return(in, type));
+    ValueType returnType = ValueType.fromDexType(method.method.proto.returnType);
+    assert returnType.verifyCompatible(type);
+    Value in = readRegister(value, returnType);
+    addReturn(new Return(in, returnType));
   }
 
   public void addReturn() {
@@ -1630,6 +1655,7 @@
     assert !value.hasLocalInfo()
         || value.getDebugLocalEnds() != null
         || source.verifyLocalInScope(value.getLocalInfo());
+    value.constrainType(type);
     return value;
   }
 
@@ -1806,6 +1832,7 @@
   }
 
   private void addInstruction(Instruction ir, Position position) {
+    hasImpreciseInstructionOutValueTypes |= ir.outValue() != null && !ir.outType().isPreciseType();
     ir.setPosition(position);
     attachLocalValues(ir);
     currentBlock.add(ir);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index b5f0a05..5c25fba 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -319,7 +319,6 @@
       }
       int localRegister = state.getLocalRegister(local.index, localType);
       ValueType existingLocalType = initializedLocals.get(localRegister);
-      assert existingLocalType == null || existingLocalType.compatible(localValueType);
       if (existingLocalType == null) {
         // For uninitialized entries write the local to ensure it exists in the local state.
         int writeRegister = state.writeLocal(local.index, localType);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
new file mode 100644
index 0000000..3b2be1f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Type constraint resolver that ensures that all SSA values have a "precise" type, ie, every value
+ * must be an element of exactly one of: object, int, float, long or double.
+ *
+ * <p>The resolution is a union-find over the SSA values, linking any type with an imprecise type to
+ * a parent value that has either the same imprecise type or a precise one. SSA values are linked if
+ * there type is constrained to be the same. This happens in two places:
+ *
+ * <ul>
+ *   <li>For phis, the out value and all operand values must have the same type
+ *   <li>For if-{eq,ne} instructions, the input values must have the same type.
+ * </ul>
+ *
+ * <p>All other constraints on types have been computed duing IR construction where every call to
+ * readRegister(ValueType) will constrain the type of the SSA value that the read resolves to.
+ */
+public class TypeConstraintResolver {
+
+  private final Map<Value, Value> unificationParents = new HashMap<>();
+
+  public void resolve(IRCode code) {
+    List<Value> impreciseValues = new ArrayList<>();
+    for (BasicBlock block : code.blocks) {
+      for (Phi phi : block.getPhis()) {
+        if (!phi.outType().isPreciseType()) {
+          impreciseValues.add(phi);
+        }
+        for (Value value : phi.getOperands()) {
+          merge(phi, value);
+        }
+      }
+      for (Instruction instruction : block.getInstructions()) {
+        if (instruction.outValue() != null && !instruction.outType().isPreciseType()) {
+          impreciseValues.add(instruction.outValue());
+        }
+
+        if (instruction.isIf() && instruction.inValues().size() == 2) {
+          If ifInstruction = instruction.asIf();
+          assert !ifInstruction.isZeroTest();
+          If.Type type = ifInstruction.getType();
+          if (type == If.Type.EQ || type == If.Type.NE) {
+            merge(ifInstruction.inValues().get(0), ifInstruction.inValues().get(1));
+          }
+        }
+      }
+    }
+    for (Value value : impreciseValues) {
+      value.constrainType(getPreciseType(value));
+    }
+  }
+
+  private void merge(Value value1, Value value2) {
+    link(canonical(value1), canonical(value2));
+  }
+
+  private ValueType getPreciseType(Value value) {
+    ValueType type = canonical(value).outType();
+    if (type.isPreciseType()) {
+      return type;
+    }
+    // If the type is still imprecise, then there are no constraints forcing its type and we
+    // arbitrarily choose long for wide values and int for single values.
+    if (type.isWide()) {
+      return ValueType.LONG;
+    }
+    return ValueType.INT;
+  }
+
+  private void link(Value canonical1, Value canonical2) {
+    if (canonical1 == canonical2) {
+      return;
+    }
+    ValueType type1 = canonical1.outType();
+    ValueType type2 = canonical2.outType();
+    if (type1.isPreciseType() && type2.isPreciseType()) {
+      if (type1 != type2) {
+        throw new CompilationError(
+            "Cannot unify types for values "
+                + canonical1
+                + ":"
+                + type1
+                + " and "
+                + canonical2
+                + ":"
+                + type2);
+      }
+      return;
+    }
+    if (type1.isPreciseType()) {
+      unificationParents.put(canonical2, canonical1);
+    } else {
+      unificationParents.put(canonical1, canonical2);
+    }
+  }
+
+  // Find root with path-compression.
+  private Value canonical(Value value) {
+    Value parent = value;
+    while (parent != null) {
+      Value grandparent = unificationParents.get(parent);
+      if (grandparent != null) {
+        unificationParents.put(value, grandparent);
+      }
+      value = parent;
+      parent = grandparent;
+    }
+    return value;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index e5c8962..9cff141 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1034,8 +1034,7 @@
                 if (argumentIndex != -1 && checkArgumentType(invoke, target.method,
                     argumentIndex)) {
                   Value argument = invoke.arguments().get(argumentIndex);
-                  assert invoke.outType().compatible(argument.outType())
-                      || (options.isGeneratingDex() && verifyCompatibleFromDex(invoke, argument));
+                  assert invoke.outType() == argument.outType();
                   invoke.outValue().replaceUsers(argument);
                   invoke.setOutValue(null);
                 }
@@ -1048,15 +1047,6 @@
     assert code.isConsistentGraph();
   }
 
-  private static boolean verifyCompatibleFromDex(Invoke invoke, Value argument) {
-    ValueType invokeType = invoke.outType();
-    ValueType argumentType = argument.outType();
-    assert argument.isZero();
-    assert (invokeType.isObject() && argumentType.isObjectOrNull())
-        || (invokeType.isSingle() && argumentType.isSingleOrZero());
-    return true;
-  }
-
   /**
    * For supporting assert javac adds the static field $assertionsDisabled to all classes which
    * have methods with assertions. This is used to support the Java VM -ea flag.
@@ -2161,12 +2151,14 @@
             && (theIf.isZeroTest() || inValues.get(1).isConstNumber())) {
           // Zero test with a constant of comparison between between two constants.
           if (theIf.isZeroTest()) {
-            int cond = inValues.get(0).getConstInstruction().asConstNumber().getIntValue();
-            simplifyIfWithKnownCondition(code, block, theIf, cond);
+            ConstNumber cond = inValues.get(0).getConstInstruction().asConstNumber();
+            BasicBlock target = theIf.targetFromCondition(cond);
+            simplifyIfWithKnownCondition(code, block, theIf, target);
           } else {
-            long left = (long) inValues.get(0).getConstInstruction().asConstNumber().getIntValue();
-            long right = (long) inValues.get(1).getConstInstruction().asConstNumber().getIntValue();
-            simplifyIfWithKnownCondition(code, block, theIf, Long.signum(left - right));
+            ConstNumber left = inValues.get(0).getConstInstruction().asConstNumber();
+            ConstNumber right = inValues.get(1).getConstInstruction().asConstNumber();
+            BasicBlock target = theIf.targetFromCondition(left, right);
+            simplifyIfWithKnownCondition(code, block, theIf, target);
           }
         } else if (inValues.get(0).hasValueRange()
             && (theIf.isZeroTest() || inValues.get(1).hasValueRange())) {
@@ -2193,7 +2185,6 @@
             // TODO(b/72693244): annotate type lattice to value
             TypeLatticeElement l = typeEnvironment.getLatticeElement(inValues.get(0));
             if (!l.isPrimitive() && !l.isNullable()) {
-              // Any non-zero value should work.
               simplifyIfWithKnownCondition(code, block, theIf, 1);
             }
           }
@@ -2204,13 +2195,17 @@
     assert code.isConsistentSSA();
   }
 
-  private void simplifyIfWithKnownCondition(IRCode code, BasicBlock block, If theIf, int cond) {
-    BasicBlock target = theIf.targetFromCondition(cond);
+  private void simplifyIfWithKnownCondition(
+      IRCode code, BasicBlock block, If theIf, BasicBlock target) {
     BasicBlock deadTarget =
         target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget();
     rewriteIfToGoto(code, block, theIf, target, deadTarget);
   }
 
+  private void simplifyIfWithKnownCondition(IRCode code, BasicBlock block, If theIf, int cond) {
+    simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(cond));
+  }
+
   // Find all method invocations that never returns normally, split the block
   // after each such invoke instruction and follow it with a block throwing a
   // null value (which should result in NPE). Note that this throw is not
@@ -2434,15 +2429,13 @@
     Value rightValue = inValues.get(1);
     if (leftValue.isConstNumber() || rightValue.isConstNumber()) {
       if (leftValue.isConstNumber()) {
-        int left = leftValue.getConstInstruction().asConstNumber().getIntValue();
-        if (left == 0) {
+        if (leftValue.getConstInstruction().asConstNumber().isZero()) {
           If ifz = new If(theIf.getType().forSwappedOperands(), rightValue);
           block.replaceLastInstruction(ifz);
           assert block.exit() == ifz;
         }
       } else {
-        int right = rightValue.getConstInstruction().asConstNumber().getIntValue();
-        if (right == 0) {
+        if (rightValue.getConstInstruction().asConstNumber().isZero()) {
           If ifz = new If(theIf.getType(), leftValue);
           block.replaceLastInstruction(ifz);
           assert block.exit() == ifz;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 1a4b97d..94d4290 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -183,9 +183,9 @@
         // ...
         If theIf = block.exit().asIf();
         Value knownToBeNonNullValue = theIf.inValues().get(0);
-        // Avoid adding redundant non-null instruction.
-        if (!knownToBeNonNullValue.isNeverNull()) {
-          BasicBlock target = theIf.targetFromCondition(1L);
+        // Avoid adding redundant non-null instruction (or non-null of non-object types).
+        if (knownToBeNonNullValue.outType().isObject() && !knownToBeNonNullValue.isNeverNull()) {
+          BasicBlock target = theIf.targetFromNonNullObject();
           // Ignore uncommon empty blocks.
           if (!target.isEmpty()) {
             DominatorTree dominatorTree = new DominatorTree(code);
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index dc21146..78ef9e9 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -77,10 +77,10 @@
     @Override
     public void print(ReasonFormatter formatter) {
       formatter.addReason("referenced in keep rule:");
-      formatter.addMessage("  " + keepRule + " {");
+      formatter.addMessage("  " + keepRule.toShortString() + " {");
       int ruleCount = 0;
       for (ProguardMemberRule memberRule : keepRule.getMemberRules()) {
-        formatter.addMessage("    " + memberRule);
+        formatter.addMessage("    " + memberRule + ";");
         if (++ruleCount > 10) {
           formatter.addMessage("      <...>");
           break;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 653f0cf..8de361e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -249,7 +249,7 @@
     return result;
   }
 
-  protected StringBuilder append(StringBuilder builder) {
+  protected StringBuilder append(StringBuilder builder, boolean includeMemberRules) {
     StringUtils.appendNonEmpty(builder, "@", classAnnotation, null);
     StringUtils.appendNonEmpty(builder, "", classAccessFlags, null);
     StringUtils.appendNonEmpty(builder, "!", negatedClassAccessFlags.toString().replace(" ", " !"),
@@ -269,18 +269,27 @@
       builder.append(' ');
       builder.append(inheritanceClassName);
     }
-    builder.append(" {\n");
-    memberRules.forEach(memberRule -> {
-      builder.append("  ");
-      builder.append(memberRule);
-      builder.append(";\n");
-    });
-    builder.append("}");
+    if (includeMemberRules) {
+      builder.append(" {\n");
+      memberRules.forEach(memberRule -> {
+        builder.append("  ");
+        builder.append(memberRule);
+        builder.append(";\n");
+      });
+      builder.append("}");
+    }
     return builder;
   }
 
+  /**
+   * Short String representation without member rules.
+   */
+  public String toShortString() {
+    return append(new StringBuilder(), false).toString();
+  }
+
   @Override
   public String toString() {
-    return append(new StringBuilder()).toString();
+    return append(new StringBuilder(), true).toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index d4d4bcb..54b90b2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -604,7 +604,7 @@
       builder.append('\n');
     }
     for (ProguardConfigurationRule rule : rules) {
-      rule.append(builder);
+      rule.append(builder, true);
       builder.append('\n');
     }
     return builder.toString();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index ddc55ab..ce1840f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -66,17 +66,12 @@
   }
 
   @Override
-  protected StringBuilder append(StringBuilder builder) {
+  protected StringBuilder append(StringBuilder builder, boolean includeMemberRules) {
     builder.append("-");
     builder.append(typeString());
     StringUtils.appendNonEmpty(builder, ",", modifierString(), null);
     builder.append(' ');
-    super.append(builder);
+    super.append(builder, includeMemberRules);
     return builder;
   }
-
-  @Override
-  public String toString() {
-    return append(new StringBuilder()).toString();
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index b12eb2d..ca6feed 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -595,11 +595,6 @@
           // Verifier says: can't modify final field LMain;.staticFinalField.
           .put("600-verifier-fails",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
-          // VFY: args to if-eq/if-ne must both be refs or cat1.
-          .put(
-              "134-reg-promotion",
-              TestCondition.match(
-                  TestCondition.R8DEX_COMPILER, TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // VFY: tried to get class from non-ref register.
           .put("506-verify-aput",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
@@ -863,8 +858,24 @@
           .put("121-modifiers", TestCondition.match(TestCondition.tools(DexTool.NONE)))
           // This test uses register r1 in method that is declared to only use 1 register (r0).
           .put("142-classloader2", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // Test with invalid register usage: invoke-static {v2,v2,v2} f(LIF)V
+          .put("457-regs", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // This test uses an uninitialized register.
           .put("471-uninitialized-locals", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // Test which mixes int and float registers.
+          .put("459-dead-phi", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // Test for verification error: contains an aput-object with an single-valued input.
+          .put("506-verify-aput", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // Test with invalid register usage: returns a register of either long or double.
+          .put("510-checker-try-catch", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // Test with invalid register usage: contains an int-to-byte on the result of aget-object.
+          .put("518-null-array-get", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // Test with invalid register usage: phi of int and float.
+          .put("535-regression-const-val", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // Test with invalid register usage: phi of int and float.
+          .put("552-checker-primitive-typeprop", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // Test with invalid register usage: invoke-static {v0,v0}, foo(IL)V
+          .put("557-checker-ref-equivalent", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // This test is starting from invalid dex code. It splits up a double value and uses
           // the first register of a double with the second register of another double.
           .put("800-smali", TestCondition.match(TestCondition.R8DEX_COMPILER))
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
index 3d921b6..2073ab9 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -152,7 +152,8 @@
       assertTrue(code.instructions[0] instanceof PackedSwitch);
     } else {
       if (key1 == 0) {
-        assertTrue(code.instructions[0] instanceof IfEqz);
+        // Const instruction may be before if.
+        assertTrue(code.instructions[0] instanceof IfEqz || code.instructions[1] instanceof IfEqz);
       } else {
         // Const instruction before if.
         assertTrue(code.instructions[1] instanceof IfEq);
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
new file mode 100644
index 0000000..1a74018
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.whyareyoukeeping;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import org.junit.Test;
+
+class A {
+
+}
+
+public class WhyAreYouKeepingTest extends TestBase {
+  @Test
+  public void test() throws Exception {
+    String proguardConfig = String.join("\n", ImmutableList.of(
+        "-keep class " + A.class.getCanonicalName() + " { *; }",
+        "-whyareyoukeeping class " + A.class.getCanonicalName()
+    ));
+    PrintStream stdout = System.out;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    System.setOut(new PrintStream(baos));
+    compileWithR8(ImmutableList.of(A.class), proguardConfig);
+    String output = new String(baos.toByteArray(), Charset.defaultCharset());
+    System.setOut(stdout);
+    String expected = String.join(System.lineSeparator(), ImmutableList.of(
+        "com.android.tools.r8.shaking.whyareyoukeeping.A",
+        "|- is live because referenced in keep rule:",
+        "|    -keep  class com.android.tools.r8.shaking.whyareyoukeeping.A {",
+        "|      *;",
+        "|    };",
+        ""));
+    assertEquals(expected, output);
+  }
+}