Towards synthesizing Kotlin @Metadata: functions.

Test: tools/test.py
Test: tools/run_on_as_app.py --app rover-android --shrinker r8
Test: tools/run_on_as_app.py --app tivi --shrinker r8
Test: tools/run_on_as_app.py --app Simple-Calendar --shrinker r8

Bug: 70169921
Change-Id: I51fd23357c288e77266cf57f037e6a68fc1fca3d
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 98760b6..61def68 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -3,14 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
 import com.android.tools.r8.dex.Constants;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.function.BooleanSupplier;
+import kotlinx.metadata.Flag;
 
 /** Access flags common to classes, methods and fields. */
 public abstract class AccessFlags<T extends AccessFlags<T>> {
 
+  protected Flag[] EMPTY_FLAG = {};
+
   protected static final int BASE_FLAGS
       = Constants.ACC_PUBLIC
       | Constants.ACC_PRIVATE
@@ -65,6 +71,26 @@
 
   public abstract int getAsDexAccessFlags();
 
+  public int getAsKotlinFlags() {
+    List<Flag> flags = new ArrayList<>();
+    if (isPrivate()) {
+      flags.add(Flag.IS_PRIVATE);
+    }
+    if (isProtected()) {
+      flags.add(Flag.IS_PROTECTED);
+    }
+    if (isPublic()) {
+      flags.add(Flag.IS_PUBLIC);
+    }
+    if (isFinal()) {
+      flags.add(Flag.IS_FINAL);
+    }
+    if (isOpen()) {
+      flags.add(Flag.IS_OPEN);
+    }
+    return flagsOf(flags.toArray(EMPTY_FLAG));
+  }
+
   public final int getOriginalAccessFlags() {
     return originalFlags;
   }
@@ -171,6 +197,10 @@
     set(Constants.ACC_STATIC);
   }
 
+  public boolean isOpen() {
+    return !isFinal();
+  }
+
   public boolean isFinal() {
     return isSet(Constants.ACC_FINAL);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 0c96120..030497d 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -3,10 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
 import com.android.tools.r8.dex.Constants;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.function.BooleanSupplier;
+import kotlinx.metadata.Flag;
 
 public class ClassAccessFlags extends AccessFlags<ClassAccessFlags> {
 
@@ -83,6 +87,11 @@
   }
 
   @Override
+  public int getAsCfAccessFlags() {
+    return materialize();
+  }
+
+  @Override
   public int getAsDexAccessFlags() {
     // We unset the super flag here, as it is meaningless in DEX. Furthermore, we add missing
     // abstract to interfaces to work around a javac bug when generating package-info classes.
@@ -94,8 +103,25 @@
   }
 
   @Override
-  public int getAsCfAccessFlags() {
-    return materialize();
+  public int getAsKotlinFlags() {
+    int flag = super.getAsKotlinFlags();
+    List<Flag> flags = new ArrayList<>();
+    if (isAbstract()) {
+      flags.add(Flag.IS_ABSTRACT);
+    }
+    if (isClass()) {
+      flags.add(Flag.Class.IS_CLASS);
+    }
+    if (isInterface()) {
+      flags.add(Flag.Class.IS_INTERFACE);
+    }
+    if (isAnnotation()) {
+      flags.add(Flag.Class.IS_ANNOTATION_CLASS);
+    }
+    if (isEnum()) {
+      flags.add(Flag.Class.IS_ENUM_CLASS);
+    }
+    return flag | flagsOf(flags.toArray(EMPTY_FLAG));
   }
 
   /**
@@ -121,6 +147,10 @@
     }
   }
 
+  private boolean isClass() {
+    return !isInterface() && !isAnnotation() && !isEnum();
+  }
+
   public boolean isInterface() {
     return isSet(Constants.ACC_INTERFACE);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 358af04..2a87607 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -16,6 +16,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -26,6 +27,7 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import kotlinx.metadata.KmProperty;
 
 public abstract class DexClass extends DexDefinition {
 
@@ -208,6 +210,16 @@
     return Arrays.asList(virtualMethods);
   }
 
+  public List<DexEncodedMethod> kotlinFunctions(List<KmProperty> kmProperties) {
+    List<DexEncodedMethod> functions = new ArrayList<>();
+    for (DexEncodedMethod method : virtualMethods) {
+      if (method.isKotlinFunction(kmProperties)) {
+        functions.add(method);
+      }
+    }
+    return functions;
+  }
+
   public void appendVirtualMethod(DexEncodedMethod method) {
     DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
     System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 7fe4555..b276b8b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -73,6 +73,7 @@
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.IntPredicate;
+import kotlinx.metadata.KmProperty;
 import org.objectweb.asm.Opcodes;
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> {
@@ -348,6 +349,41 @@
     return accessFlags.isSynthetic();
   }
 
+  boolean isKotlinFunction(List<KmProperty> properties) {
+    return !isStaticMember() && !isKotlinProperty(properties);
+  }
+
+  // E.g., property `prop: T` is mapped to `getProp()T`, `setProp(T)V`, `prop$annotations()V`.
+  // TODO(b/70169921): Handle different name patterns via @JvmName.
+  boolean isKotlinProperty(List<KmProperty> properties) {
+    // TODO(b/70169921): Avoid decoding.
+    String methodName = method.name.toString();
+    if (!methodName.startsWith("get")
+        && !methodName.startsWith("set")
+        && !methodName.endsWith("$annotations")) {
+      return false;
+    }
+    for (KmProperty property : properties) {
+      String propertyName = property.getName();
+      assert propertyName.length() > 0;
+      String annotations = propertyName + "$annotations";
+      if (methodName.equals(annotations)) {
+        return true;
+      }
+      String capitalized =
+          Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
+      String getter = "get" + capitalized;
+      if (methodName.equals(getter)) {
+        return true;
+      }
+      String setter = "set" + capitalized;
+      if (methodName.equals(setter)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public boolean isOnlyInlinedIntoNestMembers() {
     return compilationState == PROCESSED_INLINING_CANDIDATE_SAME_NEST;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index bb39765..ca16abe 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.BooleanSupplier;
@@ -77,6 +78,11 @@
     return materialize();
   }
 
+  @Override
+  public int getAsKotlinFlags() {
+    throw new Unreachable("Kotlin property is not directly mapped to JVM field.");
+  }
+
   public boolean isVolatile() {
     return isSet(Constants.ACC_VOLATILE);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 2173ed0..1b8e3a0 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -3,10 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
 import com.android.tools.r8.dex.Constants;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.function.BooleanSupplier;
+import kotlinx.metadata.Flag;
 
 public class MethodAccessFlags extends AccessFlags<MethodAccessFlags> {
 
@@ -92,6 +96,11 @@
   }
 
   @Override
+  public int getAsCfAccessFlags() {
+    return materialize() & ~Constants.ACC_CONSTRUCTOR;
+  }
+
+  @Override
   public int getAsDexAccessFlags() {
     MethodAccessFlags copy = copy();
     if (copy.isSynchronized() && !copy.isNative()) {
@@ -102,8 +111,13 @@
   }
 
   @Override
-  public int getAsCfAccessFlags() {
-    return materialize() & ~Constants.ACC_CONSTRUCTOR;
+  public int getAsKotlinFlags() {
+    int flag = super.getAsKotlinFlags();
+    List<Flag> flags = new ArrayList<>();
+    if (isAbstract()) {
+      flags.add(Flag.IS_ABSTRACT);
+    }
+    return flag | flagsOf(flags.toArray(EMPTY_FLAG));
   }
 
   public boolean isSynchronized() {
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 73ab96a..1b34adf 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -11,8 +11,10 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -37,12 +39,30 @@
   public final Intrinsics intrinsics;
   public final Metadata metadata;
 
+  final Map<DexType, DexType> knownTypeConversion;
+
   public Kotlin(DexItemFactory factory) {
     this.factory = factory;
 
     this.functional = new Functional();
     this.intrinsics = new Intrinsics();
     this.metadata = new Metadata();
+
+    this.knownTypeConversion =
+        ImmutableMap.<DexType, DexType>builder()
+            // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/index.html
+            .put(factory.booleanType, factory.createType(addKotlinPrefix("Boolean;")))
+            .put(factory.byteType, factory.createType(addKotlinPrefix("Byte;")))
+            .put(factory.charType, factory.createType(addKotlinPrefix("Character;")))
+            .put(factory.shortType, factory.createType(addKotlinPrefix("Short;")))
+            .put(factory.intType, factory.createType(addKotlinPrefix("Int;")))
+            .put(factory.longType, factory.createType(addKotlinPrefix("Long;")))
+            .put(factory.floatType, factory.createType(addKotlinPrefix("Float;")))
+            .put(factory.doubleType, factory.createType(addKotlinPrefix("Double;")))
+            .put(factory.voidType, factory.createType(addKotlinPrefix("Unit;")))
+            .put(factory.stringType, factory.createType(addKotlinPrefix("String;")))
+            // TODO(b/70169921): Collections?
+            .build();
   }
 
   public final class Functional {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index 9f77f7a..e76bbcf 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,15 +4,21 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toKmType;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
 import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -50,9 +56,23 @@
       }
     }
     assert clazz.superType != null;
-    KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, appView, lens);
-    if (kmTypeForSupertype != null) {
-      superTypes.add(kmTypeForSupertype);
+    if (clazz.superType != appView.dexItemFactory().objectType) {
+      KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, appView, lens);
+      if (kmTypeForSupertype != null) {
+        superTypes.add(kmTypeForSupertype);
+      }
+    } else if (clazz.isInterface()) {
+      superTypes.add(toKmType(addKotlinPrefix("Any;")));
+    }
+
+    List<KmFunction> functions = kmClass.getFunctions();
+    functions.clear();
+    List<KmProperty> properties = kmClass.getProperties();
+    for (DexEncodedMethod method : clazz.kotlinFunctions(properties)) {
+      KmFunction kmFunction = toRenamedKmFunction(method.method, appView, lens);
+      if (kmFunction != null) {
+        functions.add(kmFunction);
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 4400273..0e195e7 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,17 +3,44 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToInternalName;
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.List;
+import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmValueParameter;
 
 class KotlinMetadataSynthesizer {
+  static KmType toKmType(String descriptor) {
+    KmType kmType = new KmType(flagsOf());
+    kmType.visitClass(descriptorToInternalName(descriptor));
+    return kmType;
+  }
+
   static KmType toRenamedKmType(
       DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    // E.g., [Ljava/lang/String; -> Lkotlin/Array;
+    if (type.isArrayType()) {
+      return toKmType(addKotlinPrefix("Array;"));
+    }
+    // E.g., void -> Lkotlin/Unit;
+    if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
+      KmType kmType = new KmType(flagsOf());
+      DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
+      assert convertedType != null;
+      kmType.visitClass(descriptorToInternalName(convertedType.toDescriptorString()));
+      return kmType;
+    }
     DexClass clazz = appView.definitionFor(type);
     if (clazz == null) {
       return null;
@@ -27,9 +54,48 @@
     // For library or classpath class, we should not have renamed it.
     assert clazz.isProgramClass() || renamedType == type
         : type.toSourceString() + " -> " + renamedType.toSourceString();
-    // TODO(b/70169921): Consult kotlinx.metadata.Flag for kotlin-specific flags (e.g., sealed).
-    KmType kmType = new KmType(clazz.accessFlags.getAsCfAccessFlags());
-    kmType.visitClass(DescriptorUtils.descriptorToInternalName(renamedType.toDescriptorString()));
+    // TODO(b/70169921): Mysterious, why attempts to properly set flags bothers kotlinc?
+    //   and/or why wiping out flags works for KmType but not KmFunction?!
+    KmType kmType = new KmType(flagsOf());
+    kmType.visitClass(descriptorToInternalName(renamedType.toDescriptorString()));
     return kmType;
   }
+
+  static KmFunction toRenamedKmFunction(
+      DexMethod method, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    DexEncodedMethod encodedMethod = appView.definitionFor(method);
+    if (encodedMethod == null) {
+      return null;
+    }
+    // For library overrides, synthesize @Metadata always.
+    // For regular methods, make sure it is live.
+    if (!encodedMethod.isLibraryMethodOverride().isTrue()
+        && !appView.appInfo().liveMethods.contains(method)) {
+      return null;
+    }
+    DexMethod renamedMethod = lens.lookupMethod(method, appView.dexItemFactory());
+    // For a library method override, we should not have renamed it.
+    assert !encodedMethod.isLibraryMethodOverride().isTrue() || renamedMethod == method
+        : method.toSourceString() + " -> " + renamedMethod.toSourceString();
+    // TODO(b/70169921): Consult kotlinx.metadata.Flag.Function for kind (e.g., suspend).
+    KmFunction kmFunction =
+        new KmFunction(encodedMethod.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString());
+    KmType kmReturnType = toRenamedKmType(method.proto.returnType, appView, lens);
+    assert kmReturnType != null;
+    kmFunction.setReturnType(kmReturnType);
+    List<KmValueParameter> parameters = kmFunction.getValueParameters();
+    for (int i = 0; i < method.proto.parameters.values.length; i++) {
+      DexType paramType = method.proto.parameters.values[i];
+      DebugLocalInfo debugLocalInfo = encodedMethod.getParameterInfo().get(i);
+      String parameterName =
+          debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i);
+      // TODO(b/70169921): Consult kotlinx.metadata.Flag.ValueParameter.
+      KmValueParameter kmValueParameter = new KmValueParameter(flagsOf(), parameterName);
+      KmType kmParamType = toRenamedKmType(paramType, appView, lens);
+      assert kmParamType != null;
+      kmValueParameter.setType(kmParamType);
+      parameters.add(kmValueParameter);
+    }
+    return kmFunction;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
index 3fedef1..b470bb6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInParameterTypeTest.java
@@ -6,16 +6,14 @@
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -85,26 +83,25 @@
           supertype -> supertype.getFinalDescriptor().contains("Itf")));
       assertTrue(superTypes.stream().anyMatch(
           supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
-      // TODO(b/70169921): should not refer to Itf
       List<ClassSubject> parameterTypes = kmClass.getParameterTypesInFunctions();
       assertTrue(parameterTypes.stream().anyMatch(
-          parameterType -> parameterType.getOriginalDescriptor().contains("Itf")));
+          parameterType -> parameterType.getFinalDescriptor().equals(itf.getFinalDescriptor())));
     });
 
     Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/parametertype_app";
-    ProcessResult processResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/70169921): update to just .compile() once fixed.
-            .compileRaw();
-    // TODO(b/70169921): should be able to compile!
-    assertNotEquals(0, processResult.exitCode);
-    assertThat(
-        processResult.stderr,
-        containsString("cannot access class '" + pkg + ".parametertype_lib.Itf'"));
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".parametertype_app.MainKt")
+        .assertSuccessWithOutputLines("Impl::bar", "Program::bar");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
index e7efcae..bcf2181 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameInReturnTypeTest.java
@@ -6,16 +6,14 @@
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
@@ -84,27 +82,26 @@
           supertype -> supertype.getFinalDescriptor().contains("Itf")));
       assertTrue(superTypes.stream().anyMatch(
           supertype -> supertype.getFinalDescriptor().equals(itf.getFinalDescriptor())));
-      // TODO(b/70169921): should not refer to Itf
       List<ClassSubject> functionReturnTypes = kmClass.getReturnTypesInFunctions();
       assertTrue(functionReturnTypes.stream().anyMatch(
-          returnType -> returnType.getOriginalDescriptor().contains("Itf")));
+          returnType -> returnType.getFinalDescriptor().equals(itf.getFinalDescriptor())));
     });
 
     Path libJar = compileResult.writeToZip();
 
     String appFolder = PKG_PREFIX + "/returntype_app";
-    ProcessResult processResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, KotlinTargetVersion.JAVA_8)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/70169921): update to just .compile() once fixed.
-            .compileRaw();
-    // TODO(b/70169921): should be able to compile!
-    assertNotEquals(0, processResult.exitCode);
-    assertThat(
-        processResult.stderr,
-        containsString("cannot access class '" + pkg + ".returntype_lib.Itf'"));
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".returntype_app.MainKt")
+        .assertSuccessWithOutputLines("Impl::foo", "Program::foo", "true");
   }
 }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
index 3c1585a..83caed2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_app/main.kt
@@ -4,8 +4,8 @@
 package com.android.tools.r8.kotlin.metadata.classpath_app
 
 import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.Extra
-import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.extension
+import com.android.tools.r8.kotlin.metadata.classpath_lib_ext.fooExt
 
 fun main() {
-  Extra().extension()
-}
\ No newline at end of file
+  Extra().fooExt()
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
index 975865b..501c583 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/classpath_lib_ext/impl.kt
@@ -13,6 +13,6 @@
 
 class Extra : Impl()
 
-fun Extra.extension() {
+fun Extra.fooExt() {
   foo()
-}
\ No newline at end of file
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/returntype_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/returntype_app/main.kt
index 37d446e..fd4ce90 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/returntype_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/returntype_app/main.kt
@@ -4,10 +4,9 @@
 package com.android.tools.r8.kotlin.metadata.returntype_app
 
 import com.android.tools.r8.kotlin.metadata.returntype_lib.Impl
-import com.android.tools.r8.kotlin.metadata.returntype_lib.Itf
 
 class ProgramClass : Impl() {
-  override fun foo(): Itf {
+  override fun foo(): Impl {
     super.foo()
     println("Program::foo")
     return this