Merge "Add Kotlin tests for null-check simplification."
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 3e46ff8..93f4249 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -47,7 +47,7 @@
   // invoke the tested method.
   private static final String JASMIN_MAIN_CLASS = "TestMain";
 
-  @Parameters(name = "{0}_{1}")
+  @Parameters(name = "allowAccessModification: {0} target: {1}")
   public static Collection<Object[]> data() {
     ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
     for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
@@ -171,9 +171,16 @@
     return proguardRules.toString();
   }
 
+  protected String keepAllMembers(String className) {
+    return "-keep class " + className + " {" + System.lineSeparator()
+        + "  *;" + System.lineSeparator()
+        + "}";
+  }
+
   protected String keepClassMethod(String className, MethodSignature methodSignature) {
-    return "-keep class " + className + " {" + System.lineSeparator() +
-        methodSignature.toString() + ";" + System.lineSeparator() + "}";
+    return "-keep class " + className + " {" + System.lineSeparator()
+        + methodSignature.toString() + ";" + System.lineSeparator()
+        + "}";
   }
 
   protected void runTest(String folder, String mainClass, AndroidAppInspector inspector)
@@ -199,7 +206,7 @@
     // Build with R8
     AndroidApp.Builder builder = AndroidApp.builder();
     builder.addProgramFiles(classpath);
-    AndroidApp app = compileWithR8(builder.build(), proguardRules.toString());
+    AndroidApp app = compileWithR8(builder.build(), proguardRules);
 
     // Materialize file for execution.
     Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
new file mode 100644
index 0000000..e324dee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -0,0 +1,83 @@
+// 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.kotlin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.code.Format21t;
+import com.android.tools.r8.code.Format22t;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class SimplifyIfNotNullKotlinTest extends AbstractR8KotlinTestBase {
+  private static final String FOLDER = "non_null";
+  private static final String STRING = "java.lang.String";
+
+  private static boolean isIf(Instruction instruction) {
+    return instruction instanceof Format21t || instruction instanceof Format22t;
+  }
+
+  @Test
+  public void test_example1() throws Exception {
+    final TestKotlinClass ex1 = new TestKotlinClass("non_null.Example1Kt");
+    final MethodSignature testMethodSignature =
+        new MethodSignature("forMakeAndModel", "java.util.SortedMap",
+            ImmutableList.of("java.util.Collection", STRING, STRING, "java.lang.Integer"));
+
+    final String mainClassName = ex1.getClassName();
+    final String extraRules = keepAllMembers(mainClassName);
+    runTest(FOLDER, mainClassName, extraRules, app -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject clazz = checkClassExists(dexInspector, ex1.getClassName());
+
+      MethodSubject testMethod = checkMethodIsPresent(clazz, testMethodSignature);
+      DexCode dexCode = getDexCode(testMethod);
+      long count = Arrays.stream(dexCode.instructions)
+          .filter(SimplifyIfNotNullKotlinTest::isIf).count();
+      if (allowAccessModification) {
+        // TODO(b/76200247): 6 -> 5
+        // Three null-check's from inlined checkParameterIsNotNull for receiver and two arguments.
+        assertEquals(6, count);
+      } else {
+        // One after Iterator#hasNext, and another in the filter predicate: sinceYear != null.
+        assertEquals(2, count);
+      }
+    });
+  }
+
+  @Test
+  public void test_example2() throws Exception {
+    final TestKotlinClass ex2 = new TestKotlinClass("non_null.Example2Kt");
+    final MethodSignature testMethodSignature =
+        new MethodSignature("aOrDefault", STRING, ImmutableList.of(STRING, STRING));
+
+    final String mainClassName = ex2.getClassName();
+    final String extraRules = keepAllMembers(mainClassName);
+    runTest(FOLDER, mainClassName, extraRules, app -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject clazz = checkClassExists(dexInspector, ex2.getClassName());
+
+      MethodSubject testMethod = checkMethodIsPresent(clazz, testMethodSignature);
+      DexCode dexCode = getDexCode(testMethod);
+      long count = Arrays.stream(dexCode.instructions)
+          .filter(SimplifyIfNotNullKotlinTest::isIf).count();
+      if (allowAccessModification) {
+        // TODO(b/76202537): 3 -> 2,
+        //   Yet another null-check from checkParameterIsNotNull should subsume another from ?:
+        assertEquals(3, count);
+      } else {
+        // One null-check from force inlined coalesce and another from ?:
+        assertEquals(2, count);
+      }
+    });
+  }
+
+}
diff --git a/src/test/kotlinR8TestResources/non_null/example1.kt b/src/test/kotlinR8TestResources/non_null/example1.kt
new file mode 100644
index 0000000..02a3fcf
--- /dev/null
+++ b/src/test/kotlinR8TestResources/non_null/example1.kt
@@ -0,0 +1,29 @@
+// 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 non_null
+
+data class Car(
+        val make: String,
+        val model: String,
+        val year: Int,
+        val plateNumber: String)
+
+fun Collection<Car>.forMakeAndModel(
+        make: String, model: String, sinceYear: Int?
+) = this.asSequence()
+        .filter { it.make == make }
+        .filter { it.model == model }
+        .filter { sinceYear != null && it.year >= sinceYear }
+        .groupBy { it.year }
+        .toSortedMap()
+
+fun main(args: Array<String>) {
+    val leaf = Car("Nissan", "Leaf", 2015, "  LEAF  ")
+    val ms1 = Car("Tesla", "Model S", 2015, "  LGTM1 ")
+    val ms2 = Car("Tesla", "Model S", 2017, "  LGTM2 ")
+    val m3 = Car("Tesla", "Model 3", 2018, "  LGTM3 ")
+    val cars: List<Car> = mutableListOf(leaf, ms1, ms2, m3)
+    println(cars.forMakeAndModel("Tesla", "Model S", null))
+}
diff --git a/src/test/kotlinR8TestResources/non_null/example2.kt b/src/test/kotlinR8TestResources/non_null/example2.kt
new file mode 100644
index 0000000..e35b2e8
--- /dev/null
+++ b/src/test/kotlinR8TestResources/non_null/example2.kt
@@ -0,0 +1,14 @@
+// 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 non_null
+
+inline fun coalesce(a: String?, b: String?): String? = a ?: b
+fun aOrDefault(a: String?, default: String): String =
+        coalesce(a, default) ?: throw AssertionError()
+
+fun main(args: Array<String>) {
+    println(aOrDefault(null, "null"))
+    println(aOrDefault("null", "non-null"))
+}
\ No newline at end of file