Extend KotlinMetadataSynthesizer with proper type-argument handling

Bug: 151925520
Bug: 152306391
Bug: 152078816
Bug: 152284661
Change-Id: I740453910192cbcaf4f14a557cea92a14a854e39
diff --git a/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java b/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java
new file mode 100644
index 0000000..ea1ddc3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2020, 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 kotlinx.metadata.FlagsKt.flagsOf;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+import java.util.function.Function;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmVariance;
+
+class ClassTypeSignatureToRenamedKmTypeConverter implements ClassTypeSignature.Converter<KmType> {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final List<KmTypeParameter> typeParameters;
+  private final Function<DexType, String> toRenamedClassClassifier;
+
+  ClassTypeSignatureToRenamedKmTypeConverter(
+      AppView<AppInfoWithLiveness> appView,
+      List<KmTypeParameter> typeParameters,
+      Function<DexType, String> toRenamedClassClassifier) {
+    this.appView = appView;
+    this.typeParameters = typeParameters;
+    this.toRenamedClassClassifier = toRenamedClassClassifier;
+  }
+
+  @Override
+  public KmType init() {
+    return new KmType(flagsOf());
+  }
+
+  @Override
+  public KmType visitType(DexType type, KmType result) {
+    String classifier = toRenamedClassClassifier.apply(type);
+    if (classifier == null) {
+      return null;
+    }
+    result.visitClass(classifier);
+    return result;
+  }
+
+  @Override
+  public KmType visitTypeArgument(FieldTypeSignature typeArgument, KmType result) {
+    if (result == null) {
+      return null;
+    }
+    List<KmTypeProjection> arguments = result.getArguments();
+    KotlinMetadataSynthesizerUtils.populateKmTypeFromSignature(
+        typeArgument,
+        () -> {
+          KmType kmTypeArgument = new KmType(flagsOf());
+          arguments.add(new KmTypeProjection(KmVariance.INVARIANT, kmTypeArgument));
+          return kmTypeArgument;
+        },
+        typeParameters,
+        appView.dexItemFactory());
+    return result;
+  }
+
+  @Override
+  public KmType visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, KmType result) {
+    // Do nothing
+    return result;
+  }
+
+  public List<KmTypeParameter> getTypeParameters() {
+    return typeParameters;
+  }
+}
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 00533e9..c891897 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -6,9 +6,6 @@
 
 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.toRenamedClassifier;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmConstructor;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
 import static kotlinx.metadata.Flag.IS_SEALED;
 
 import com.android.tools.r8.graph.AppView;
@@ -76,21 +73,26 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
     if (appView.options().enableKotlinMetadataRewritingForRenamedClasses
         && lens.lookupType(clazz.type, appView.dexItemFactory()) != clazz.type) {
-      String renamedClassifier = toRenamedClassifier(clazz.type, appView, lens);
+      String renamedClassifier = synthesizer.toRenamedClassifier(clazz.type);
       if (renamedClassifier != null) {
         assert !kmClass.getName().equals(renamedClassifier);
         kmClass.setName(renamedClassifier);
       }
     }
 
+    ClassTypeSignatureToRenamedKmTypeConverter converter =
+        new ClassTypeSignatureToRenamedKmTypeConverter(
+            appView, getTypeParameters(), synthesizer::toRenamedClassifier);
+
     // Rewriting upward hierarchy.
     List<KmType> superTypes = kmClass.getSupertypes();
     superTypes.clear();
     for (DexType itfType : clazz.interfaces.values) {
       // TODO(b/129925954): Use GenericSignature.ClassSignature#superInterfaceSignatures
-      KmType kmType = toRenamedKmType(itfType, null, appView, lens);
+      KmType kmType = synthesizer.toRenamedKmType(itfType, null, null, converter);
       if (kmType != null) {
         superTypes.add(kmType);
       }
@@ -98,7 +100,8 @@
     assert clazz.superType != null;
     if (clazz.superType != appView.dexItemFactory().objectType) {
       // TODO(b/129925954): Use GenericSignature.ClassSignature#superClassSignature
-      KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, null, appView, lens);
+      KmType kmTypeForSupertype =
+          synthesizer.toRenamedKmType(clazz.superType, null, null, converter);
       if (kmTypeForSupertype != null) {
         superTypes.add(kmTypeForSupertype);
       }
@@ -130,7 +133,7 @@
     sealedSubclasses.clear();
     if (IS_SEALED.invoke(kmClass.getFlags())) {
       for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
-        String classifier = toRenamedClassifier(subtype, appView, lens);
+        String classifier = synthesizer.toRenamedClassifier(subtype);
         if (classifier != null) {
           sealedSubclasses.add(classifier);
         }
@@ -148,7 +151,7 @@
       if (!method.isInstanceInitializer()) {
         continue;
       }
-      KmConstructor constructor = toRenamedKmConstructor(method, appView, lens);
+      KmConstructor constructor = synthesizer.toRenamedKmConstructor(method);
       if (constructor != null) {
         constructors.add(constructor);
       }
@@ -161,7 +164,7 @@
 
     // TODO(b/151193864): enum entries
 
-    rewriteDeclarationContainer(appView, lens);
+    rewriteDeclarationContainer(synthesizer);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 627ac9d..fc3fbbe 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
@@ -46,12 +44,13 @@
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     ListIterator<String> partClassIterator = partClassNames.listIterator();
+    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
     while (partClassIterator.hasNext()) {
       String partClassName = partClassIterator.next();
       partClassIterator.remove();
       DexType partClassType = appView.dexItemFactory().createType(
           DescriptorUtils.getDescriptorFromClassBinaryName(partClassName));
-      String renamedPartClassName = toRenamedBinaryName(partClassType, appView, lens);
+      String renamedPartClassName = synthesizer.toRenamedBinaryName(partClassType);
       if (renamedPartClassName != null) {
         partClassIterator.add(renamedPartClassName);
       }
@@ -82,6 +81,6 @@
 
   @Override
   public String toString(String indent) {
-    return indent + "MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
+    return indent + "MetaData.MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 69bdf1e..90f265c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
@@ -44,11 +42,12 @@
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     DexType facadeClassType = appView.dexItemFactory().createType(
         DescriptorUtils.getDescriptorFromClassBinaryName(facadeClassName));
-    facadeClassName = toRenamedBinaryName(facadeClassType, appView, lens);
+    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
+    facadeClassName = synthesizer.toRenamedBinaryName(facadeClassType);
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
-    rewriteDeclarationContainer(appView, lens);
+    rewriteDeclarationContainer(synthesizer);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 521181b..8ec966b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -38,7 +38,7 @@
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
-    rewriteDeclarationContainer(appView, lens);
+    rewriteDeclarationContainer(new KotlinMetadataSynthesizer(appView, lens, this));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 5275c4d..64ed559 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction;
 import static com.android.tools.r8.utils.StringUtils.LINE_SEPARATOR;
 import static kotlinx.metadata.Flag.Class.IS_COMPANION_OBJECT;
 
@@ -20,6 +19,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -49,7 +49,9 @@
 
 // Provides access to package/class-level Kotlin information.
 public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
+
   final DexClass clazz;
+  private static final List<KmTypeParameter> EMPTY_TYPE_PARAMS = ImmutableList.of();
 
   KotlinInfo(MetadataKind metadata, DexClass clazz) {
     assert clazz != null;
@@ -67,6 +69,13 @@
 
   abstract KotlinClassHeader createHeader();
 
+  public final List<KmTypeParameter> getTypeParameters() {
+    if (!this.isClass()) {
+      return EMPTY_TYPE_PARAMS;
+    }
+    return this.asClass().kmClass.getTypeParameters();
+  }
+
   public enum Kind {
     Class, File, Synthetic, Part, Facade
   }
@@ -131,23 +140,22 @@
 
   // {@link KmClass} and {@link KmPackage} are inherited from {@link KmDeclarationContainer} that
   // abstract functions and properties. Rewriting of those portions can be unified here.
-  void rewriteDeclarationContainer(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+  void rewriteDeclarationContainer(KotlinMetadataSynthesizer synthesizer) {
     assert clazz != null;
 
     KmDeclarationContainer kmDeclarationContainer = getDeclarations();
-    rewriteFunctions(appView, lens, kmDeclarationContainer.getFunctions());
-    rewriteProperties(appView, lens, kmDeclarationContainer.getProperties());
+    rewriteFunctions(synthesizer, kmDeclarationContainer.getFunctions());
+    rewriteProperties(synthesizer, kmDeclarationContainer.getProperties());
   }
 
-  private void rewriteFunctions(
-      AppView<AppInfoWithLiveness> appView, NamingLens lens, List<KmFunction> functions) {
+  private void rewriteFunctions(KotlinMetadataSynthesizer synthesizer, List<KmFunction> functions) {
     functions.clear();
     for (DexEncodedMethod method : clazz.methods()) {
       if (method.isInitializer()) {
         continue;
       }
       if (method.isKotlinFunction() || method.isKotlinExtensionFunction()) {
-        KmFunction function = toRenamedKmFunction(method, appView, lens);
+        KmFunction function = synthesizer.toRenamedKmFunction(method);
         if (function != null) {
           functions.add(function);
         }
@@ -157,11 +165,12 @@
   }
 
   private void rewriteProperties(
-      AppView<AppInfoWithLiveness> appView, NamingLens lens, List<KmProperty> properties) {
+      KotlinMetadataSynthesizer synthesizer, List<KmProperty> properties) {
     Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
     // Backing fields for a companion object are declared in its host class.
     Iterable<DexEncodedField> fields = clazz.fields();
     Predicate<DexEncodedField> backingFieldTester = DexEncodedField::isKotlinBackingField;
+    List<KmTypeParameter> classTypeParameters = getTypeParameters();
     if (isClass()) {
       KotlinClass ktClass = asClass();
       if (IS_COMPANION_OBJECT.invoke(ktClass.kmClass.getFlags()) && ktClass.hostClass != null) {
@@ -177,7 +186,8 @@
         assert name != null;
         KmPropertyGroup.Builder builder =
             propertyGroupBuilderMap.computeIfAbsent(
-                name, k -> KmPropertyGroup.builder(kotlinFieldInfo.flags, name));
+                name,
+                k -> KmPropertyGroup.builder(kotlinFieldInfo.flags, name, classTypeParameters));
         builder.foundBackingField(field);
       }
     }
@@ -195,19 +205,19 @@
                 name,
                 // Hitting here (creating a property builder) after visiting all fields means that
                 // this property doesn't have a backing field. Don't use members' flags.
-                k -> KmPropertyGroup.builder(kotlinPropertyInfo.flags, name));
+                k -> KmPropertyGroup.builder(kotlinPropertyInfo.flags, name, classTypeParameters));
         switch (kotlinPropertyInfo.memberKind) {
           case EXTENSION_PROPERTY_GETTER:
             builder.isExtensionGetter();
             // fallthrough;
           case PROPERTY_GETTER:
-            builder.foundGetter(method, kotlinPropertyInfo.getterFlags);
+            builder.foundGetter(method, kotlinPropertyInfo);
             break;
           case EXTENSION_PROPERTY_SETTER:
             builder.isExtensionSetter();
             // fallthrough;
           case PROPERTY_SETTER:
-            builder.foundSetter(method, kotlinPropertyInfo.setterFlags);
+            builder.foundSetter(method, kotlinPropertyInfo);
             break;
           case EXTENSION_PROPERTY_ANNOTATIONS:
             builder.isExtensionAnnotations();
@@ -227,7 +237,7 @@
       if (group == null) {
         continue;
       }
-      KmProperty property = group.toRenamedKmProperty(appView, lens);
+      KmProperty property = group.toRenamedKmProperty(synthesizer);
       if (property != null) {
         properties.add(property);
       }
@@ -563,6 +573,7 @@
         "KmType",
         sb,
         newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, kmType.getFlags() + "");
           appendKeyValue(newIndent, "classifier", sb, kmType.classifier.toString());
           appendKeyValue(
               newIndent,
@@ -686,6 +697,8 @@
         "KmTypeParameter",
         sb,
         newIndent -> {
+          appendKeyValue(newIndent, "id", sb, typeParameter.getId() + "");
+          appendKeyValue(newIndent, "flags", sb, typeParameter.getFlags() + "");
           appendKeyValue(newIndent, "name", sb, typeParameter.getName());
           appendKeyValue(newIndent, "variance", sb, typeParameter.getVariance().name());
           appendKeyValue(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
index 4238d3e..ed7b49f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -29,6 +29,7 @@
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmValueParameter;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
@@ -86,12 +87,18 @@
   public static class KotlinFunctionInfo extends KotlinMemberInfo {
 
     // Information from original KmValueParameter(s) if available.
-    private final List<KotlinValueParameterInfo> valueParameterInfos;
+    final List<KotlinValueParameterInfo> valueParameterInfos;
+    // Information from original KmFunction.returnType. Null if this is from a KmConstructor.
+    public final KotlinTypeInfo returnType;
 
     private KotlinFunctionInfo(
-        MemberKind memberKind, int flags, List<KotlinValueParameterInfo> valueParameterInfos) {
+        MemberKind memberKind,
+        int flags,
+        KotlinTypeInfo returnType,
+        List<KotlinValueParameterInfo> valueParameterInfos) {
       super(memberKind, flags);
       assert memberKind.isFunction() || memberKind.isConstructor();
+      this.returnType = returnType;
       this.valueParameterInfos = valueParameterInfos;
     }
 
@@ -148,6 +155,9 @@
     // Original property name for (extension) property. Otherwise, null.
     final String propertyName;
 
+    // Original return type information. This should never be NULL (even for setters without field).
+    final KotlinTypeInfo returnType;
+
     // Information from original KmValueParameter if available.
     final KotlinValueParameterInfo valueParameterInfo;
 
@@ -157,11 +167,13 @@
         int getterFlags,
         int setterFlags,
         String propertyName,
+        KotlinTypeInfo returnType,
         KotlinValueParameterInfo valueParameterInfo) {
       super(memberKind, flags);
       this.getterFlags = getterFlags;
       this.setterFlags = setterFlags;
       this.propertyName = propertyName;
+      this.returnType = returnType;
       this.valueParameterInfo = valueParameterInfo;
     }
 
@@ -178,25 +190,31 @@
 
   private static KotlinFunctionInfo createFunctionInfoFromConstructor(KmConstructor kmConstructor) {
     return createFunctionInfo(
-        CONSTRUCTOR, kmConstructor.getFlags(), kmConstructor.getValueParameters());
+        CONSTRUCTOR, kmConstructor.getFlags(), null, kmConstructor.getValueParameters());
   }
 
   private static KotlinFunctionInfo createFunctionInfo(
       MemberKind memberKind, KmFunction kmFunction) {
-    return createFunctionInfo(memberKind, kmFunction.getFlags(), kmFunction.getValueParameters());
+    return createFunctionInfo(
+        memberKind,
+        kmFunction.getFlags(),
+        kmFunction.getReturnType(),
+        kmFunction.getValueParameters());
   }
 
   private static KotlinFunctionInfo createFunctionInfo(
-      MemberKind memberKind, int flags, List<KmValueParameter> valueParameters) {
+      MemberKind memberKind, int flags, KmType returnType, List<KmValueParameter> valueParameters) {
+    assert memberKind.isFunction() || memberKind.isConstructor();
+    KotlinTypeInfo returnTypeInfo = KotlinTypeInfo.create(returnType);
     assert memberKind.isFunction() || memberKind.isConstructor();
     if (valueParameters.isEmpty()) {
-      return new KotlinFunctionInfo(memberKind, flags, EMPTY_PARAM_INFO);
+      return new KotlinFunctionInfo(memberKind, flags, returnTypeInfo, EMPTY_PARAM_INFO);
     }
     List<KotlinValueParameterInfo> valueParameterInfos = new ArrayList<>(valueParameters.size());
     for (KmValueParameter kmValueParameter : valueParameters) {
       valueParameterInfos.add(KotlinValueParameterInfo.fromKmValueParameter(kmValueParameter));
     }
-    return new KotlinFunctionInfo(memberKind, flags, valueParameterInfos);
+    return new KotlinFunctionInfo(memberKind, flags, returnTypeInfo, valueParameterInfos);
   }
 
   private static KotlinFieldInfo createFieldInfo(MemberKind memberKind, KmProperty kmProperty) {
@@ -213,6 +231,7 @@
         kmProperty.getGetterFlags(),
         kmProperty.getSetterFlags(),
         kmProperty.getName(),
+        KotlinTypeInfo.create(kmProperty.getReturnType()),
         KotlinValueParameterInfo.fromKmValueParameter(kmProperty.getSetterParameter()));
   }
 
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 2be6e27..2e90094 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,9 +3,10 @@
 // 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.NAME;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.populateKmTypeFromSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.toClassifier;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
@@ -24,17 +25,21 @@
 import com.android.tools.r8.graph.GenericSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinFunctionInfo;
+import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinPropertyInfo;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Box;
 import java.util.List;
 import java.util.function.Consumer;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeParameter;
 import kotlinx.metadata.KmTypeProjection;
 import kotlinx.metadata.KmValueParameter;
 import kotlinx.metadata.KmVariance;
@@ -42,6 +47,17 @@
 
 class KotlinMetadataSynthesizer {
 
+  private final AppView<AppInfoWithLiveness> appView;
+  private final NamingLens lens;
+  private final List<KmTypeParameter> classTypeParameters;
+
+  public KotlinMetadataSynthesizer(
+      AppView<AppInfoWithLiveness> appView, NamingLens lens, KotlinInfo<?> kotlinInfo) {
+    this.appView = appView;
+    this.lens = lens;
+    this.classTypeParameters = kotlinInfo.getTypeParameters();
+  }
+
   static boolean isExtension(KmFunction kmFunction) {
     return kmFunction.getReceiverParameterType() != null;
   }
@@ -56,8 +72,7 @@
     return kmType;
   }
 
-  static DexType toRenamedType(
-      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+  private DexType toRenamedType(DexType type) {
     // For library or classpath class, synthesize @Metadata always.
     // For a program class, make sure it is live.
     if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
@@ -71,141 +86,92 @@
     return renamedType;
   }
 
-  static String toRenamedBinaryName(
-      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    DexType renamedType = toRenamedType(type, appView, lens);
+  String toRenamedBinaryName(DexType type) {
+    DexType renamedType = toRenamedType(type);
     if (renamedType == null) {
       return null;
     }
     return getBinaryNameFromDescriptor(renamedType.toDescriptorString());
   }
 
-  static String toRenamedClassifier(
-      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
-    if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
-      DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
-      assert convertedType != null;
-      return descriptorToKotlinClassifier(convertedType.toDescriptorString());
-    }
-    // E.g., [Ljava/lang/String; -> kotlin/Array
-    if (type.isArrayType()) {
-      return NAME + "/Array";
-    }
-    DexType renamedType = toRenamedType(type, appView, lens);
-    if (renamedType == null) {
+  String toRenamedClassifier(DexType type) {
+    type = type.isClassType() ? toRenamedType(type) : type;
+    if (type == null) {
       return null;
     }
-    return descriptorToKotlinClassifier(renamedType.toDescriptorString());
+    return toClassifier(type, appView.dexItemFactory());
   }
 
   // TODO(b/148654451): Canonicalization?
-  static KmType toRenamedKmType(
+  KmType toRenamedKmType(
       DexType type,
       TypeSignature typeSignature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
-    if (typeSignature != null
-        && typeSignature.isFieldTypeSignature()
-        && typeSignature.asFieldTypeSignature().isClassTypeSignature()
-        && typeSignature.asFieldTypeSignature().asClassTypeSignature().type() == type) {
-      return toRenamedKmType(
-          typeSignature.asFieldTypeSignature().asClassTypeSignature(), appView, lens);
+      KotlinTypeInfo originalKotlinTypeInfo,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    if (originalKotlinTypeInfo != null && originalKotlinTypeInfo.isTypeAlias()) {
+      KmType kmType = new KmType(flagsOf());
+      kmType.visitTypeAlias(originalKotlinTypeInfo.asTypeAlias().getName());
+      return kmType;
     }
+    return toRenamedKmTypeWithClassifier(type, typeSignature, converter);
+  }
 
-    String classifier = toRenamedClassifier(type, appView, lens);
+  private KmType toRenamedKmTypeWithClassifierForFieldSignature(
+      DexType type,
+      FieldTypeSignature fieldSignature,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    if (fieldSignature.isClassTypeSignature()) {
+      ClassTypeSignature classTypeSignature = fieldSignature.asClassTypeSignature();
+      if (classTypeSignature.type() != type) {
+        return null;
+      }
+      return classTypeSignature.convert(converter);
+    }
+    // If we fail to set kmType.classifier we will get a UninitializedPropertyAccessException:
+    // lateinit property classifier has not been initialized.
+    Box<KmType> kmTypeBox = new Box<>();
+    populateKmTypeFromSignature(
+        fieldSignature,
+        () -> {
+          KmType value = new KmType(flagsOf());
+          kmTypeBox.set(value);
+          return value;
+        },
+        converter.getTypeParameters(),
+        appView.dexItemFactory());
+    return kmTypeBox.get();
+  }
+
+  private KmType toRenamedKmTypeWithClassifier(
+      DexType type,
+      TypeSignature typeSignature,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    if (typeSignature != null && typeSignature.isFieldTypeSignature()) {
+      KmType renamedKmType =
+          toRenamedKmTypeWithClassifierForFieldSignature(
+              type, typeSignature.asFieldTypeSignature(), converter);
+      if (renamedKmType != null) {
+        return renamedKmType;
+      }
+    }
+    String classifier = toRenamedClassifier(type);
     if (classifier == null) {
       return null;
     }
-    // TODO(b/151194869): 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(classifier);
+    // Seems like no flags for KmType are ever set, thus passing in flagsOf() seems ok.
+    KmType renamedKmType = new KmType(flagsOf());
+    renamedKmType.visitClass(classifier);
     // TODO(b/151194164): Can be generalized too, like ArrayTypeSignature.Converter ?
     // E.g., java.lang.String[] -> KmType(kotlin/Array, KmTypeProjection(OUT, kotlin/String))
     if (type.isArrayType() && !type.isPrimitiveArrayType()) {
       DexType elementType = type.toArrayElementType(appView.dexItemFactory());
-      KmType argumentType = toRenamedKmType(elementType, null, appView, lens);
-      kmType.getArguments().add(new KmTypeProjection(KmVariance.OUT, argumentType));
-    }
-    return kmType;
-  }
-
-  static KmType toRenamedKmType(
-      ClassTypeSignature classTypeSignature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
-    return classTypeSignature.convert(
-        new ClassTypeSignatureToRenamedKmTypeConverter(appView, lens));
-  }
-
-  static class ClassTypeSignatureToRenamedKmTypeConverter
-      implements ClassTypeSignature.Converter<KmType> {
-
-    private final AppView<AppInfoWithLiveness> appView;
-    private final NamingLens lens;
-
-    ClassTypeSignatureToRenamedKmTypeConverter(
-        AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-      this.appView = appView;
-      this.lens = lens;
-    }
-
-    @Override
-    public KmType init() {
-      return new KmType(flagsOf());
-    }
-
-    @Override
-    public KmType visitType(DexType type, KmType result) {
-      if (result == null) {
-        return result;
-      }
-      String classifier = toRenamedClassifier(type, appView, lens);
-      if (classifier == null) {
-        return null;
-      }
-      result.visitClass(classifier);
-      return result;
-    }
-
-    @Override
-    public KmType visitTypeArgument(FieldTypeSignature typeArgument, KmType result) {
-      if (result == null) {
-        return result;
-      }
-      if (typeArgument.isClassTypeSignature()) {
-        KmType argumentType = typeArgument.asClassTypeSignature().convert(this);
-        result.getArguments().add(new KmTypeProjection(KmVariance.INVARIANT, argumentType));
-      }
-      // TODO(b/151194164): for TypeVariableSignature, there is KmType::visitTypeParameter.
-      return result;
-    }
-
-    @Override
-    public KmType visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, KmType result) {
-      // Do nothing
-      return result;
-    }
-  }
-
-  private static KmType setRenamedKmType(
-      DexType type,
-      TypeSignature signature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens,
-      Consumer<KmType> consumer) {
-    KmType renamedKmType = toRenamedKmType(type, signature, appView, lens);
-    if (renamedKmType != null) {
-      consumer.accept(renamedKmType);
+      KmType argumentType = toRenamedKmTypeWithClassifier(elementType, null, converter);
+      renamedKmType.getArguments().add(new KmTypeProjection(KmVariance.OUT, argumentType));
     }
     return renamedKmType;
   }
 
-  static KmConstructor toRenamedKmConstructor(
-      DexEncodedMethod method,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+  KmConstructor toRenamedKmConstructor(DexEncodedMethod method) {
     // Make sure it is an instance initializer and live.
     if (!method.isInstanceInitializer()
         || !appView.appInfo().liveMethods.contains(method.method)) {
@@ -214,17 +180,23 @@
     KmConstructor kmConstructor = new KmConstructor(method.accessFlags.getAsKotlinFlags());
     JvmExtensionsKt.setSignature(kmConstructor, toJvmMethodSignature(method.method));
     MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
+    List<KmTypeParameter> typeParameters =
+        convertFormalTypeParameters(
+            signature.getFormalTypeParameters(),
+            kmTypeParameter -> {
+              assert false : "KmConstructor cannot have additional type parameters";
+            });
+    ClassTypeSignatureToRenamedKmTypeConverter converter =
+        new ClassTypeSignatureToRenamedKmTypeConverter(
+            appView, typeParameters, this::toRenamedClassifier);
     List<KmValueParameter> parameters = kmConstructor.getValueParameters();
-    if (!populateKmValueParameters(parameters, method, signature, appView, lens)) {
+    if (!populateKmValueParameters(method, signature, parameters, converter)) {
       return null;
     }
     return kmConstructor;
   }
 
-  static KmFunction toRenamedKmFunction(
-      DexEncodedMethod method,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+  KmFunction toRenamedKmFunction(DexEncodedMethod method) {
     // For library overrides, synthesize @Metadata always.
     // For regular methods, make sure it is live or pinned.
     if (!method.isLibraryMethodOverride().isTrue()
@@ -238,9 +210,11 @@
         : method.toSourceString() + " -> " + renamedMethod.toSourceString();
     // TODO(b/151194869): Should we keep kotlin-specific flags only while synthesizing the base
     //  value from general JVM flags?
+    KotlinFunctionInfo kotlinMemberInfo = method.getKotlinMemberInfo().asFunctionInfo();
+    assert kotlinMemberInfo != null;
     int flag =
-        appView.appInfo().isPinned(method.method) && method.getKotlinMemberInfo() != null
-            ? method.getKotlinMemberInfo().flags
+        appView.appInfo().isPinned(method.method)
+            ? kotlinMemberInfo.flags
             : method.accessFlags.getAsKotlinFlags();
     KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
     JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod));
@@ -249,41 +223,50 @@
     //  on demand? That may require utils to map internal encoding of signature back to
     //  corresponding backend definitions, like DexAnnotation (DEX) or Signature attribute (CF).
     MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
+    List<KmTypeParameter> typeParameters = kmFunction.getTypeParameters();
+    ClassTypeSignatureToRenamedKmTypeConverter converter =
+        new ClassTypeSignatureToRenamedKmTypeConverter(
+            appView,
+            convertFormalTypeParameters(signature.getFormalTypeParameters(), typeParameters::add),
+            this::toRenamedClassifier);
 
     DexProto proto = method.method.proto;
     DexType returnType = proto.returnType;
     TypeSignature returnSignature = signature.returnType().typeSignature();
-    KmType kmReturnType = setRenamedKmType(
-        returnType, returnSignature, appView, lens, kmFunction::setReturnType);
+    KmType kmReturnType =
+        toRenamedKmType(returnType, returnSignature, kotlinMemberInfo.returnType, converter);
     if (kmReturnType == null) {
       return null;
     }
-
+    kmFunction.setReturnType(kmReturnType);
     if (method.isKotlinExtensionFunction()) {
-      assert proto.parameters.values.length > 0
-          : method.method.toSourceString();
+      assert proto.parameters.values.length > 0 : method.method.toSourceString();
       DexType receiverType = proto.parameters.values[0];
       TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-      KmType kmReceiverType = setRenamedKmType(
-          receiverType, receiverSignature, appView, lens, kmFunction::setReceiverParameterType);
+      KmType kmReceiverType = toRenamedKmType(receiverType, receiverSignature, null, converter);
       if (kmReceiverType == null) {
         return null;
       }
+      kmFunction.setReceiverParameterType(kmReceiverType);
     }
 
-    List<KmValueParameter> parameters = kmFunction.getValueParameters();
-    if (!populateKmValueParameters(parameters, method, signature, appView, lens)) {
+    if (!populateKmValueParameters(method, signature, kmFunction.getValueParameters(), converter)) {
       return null;
     }
     return kmFunction;
   }
 
-  private static boolean populateKmValueParameters(
-      List<KmValueParameter> parameters,
+  private List<KmTypeParameter> convertFormalTypeParameters(
+      List<FormalTypeParameter> parameters, Consumer<KmTypeParameter> addedFromParameters) {
+    return KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+        classTypeParameters, parameters, appView.dexItemFactory(), addedFromParameters);
+  }
+
+  private boolean populateKmValueParameters(
       DexEncodedMethod method,
       MethodTypeSignature signature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+      List<KmValueParameter> parameters,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
     KotlinFunctionInfo kotlinFunctionInfo = method.getKotlinMemberInfo().asFunctionInfo();
     if (kotlinFunctionInfo == null) {
       return false;
@@ -298,12 +281,7 @@
       TypeSignature parameterTypeSignature = signature.getParameterTypeSignature(i);
       KmValueParameter kmValueParameter =
           toRewrittenKmValueParameter(
-              valueParameterInfo,
-              parameterType,
-              parameterTypeSignature,
-              parameterName,
-              appView,
-              lens);
+              valueParameterInfo, parameterType, parameterTypeSignature, parameterName, converter);
       if (kmValueParameter == null) {
         return false;
       }
@@ -312,26 +290,28 @@
     return true;
   }
 
-  private static KmValueParameter toRewrittenKmValueParameter(
+  private KmValueParameter toRewrittenKmValueParameter(
       KotlinValueParameterInfo valueParameterInfo,
       DexType parameterType,
       TypeSignature parameterTypeSignature,
       String candidateParameterName,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
     int flag = valueParameterInfo != null ? valueParameterInfo.flag : flagsOf();
     String name = valueParameterInfo != null ? valueParameterInfo.name : candidateParameterName;
     KmValueParameter kmValueParameter = new KmValueParameter(flag, name);
-    KmType kmParamType = setRenamedKmType(
-        parameterType, parameterTypeSignature, appView, lens, kmValueParameter::setType);
+    KotlinTypeInfo originalKmTypeInfo = valueParameterInfo != null ? valueParameterInfo.type : null;
+    KmType kmParamType =
+        toRenamedKmType(parameterType, parameterTypeSignature, originalKmTypeInfo, converter);
     if (kmParamType == null) {
       return null;
     }
+    kmValueParameter.setType(kmParamType);
     if (valueParameterInfo != null) {
       JvmExtensionsKt.getAnnotations(kmParamType).addAll(valueParameterInfo.annotations);
     }
 
     if (valueParameterInfo != null && valueParameterInfo.isVararg) {
+      // TODO(b/152389234): Test for arrays in varargs.
       if (!parameterType.isArrayType()) {
         return null;
       }
@@ -339,11 +319,11 @@
       TypeSignature elementSignature =
           parameterTypeSignature != null
               ? parameterTypeSignature.toArrayElementTypeSignature(appView) : null;
-      KmType kmElementType = setRenamedKmType(
-          elementType, elementSignature, appView, lens, kmValueParameter::setVarargElementType);
+      KmType kmElementType = toRenamedKmType(elementType, elementSignature, null, converter);
       if (kmElementType == null) {
         return null;
       }
+      kmValueParameter.setVarargElementType(kmElementType);
     }
 
     return kmValueParameter;
@@ -375,58 +355,65 @@
    * getter, and so on.
    */
   static class KmPropertyGroup {
+
     final int flags;
     final String name;
     final DexEncodedField field;
-    final DexEncodedMethod getter;
-    final int getterFlags;
-    final DexEncodedMethod setter;
-    final int setterFlags;
+    private final DexEncodedMethod getter;
+    private final KotlinPropertyInfo getterInfo;
+    private final DexEncodedMethod setter;
+    private final KotlinPropertyInfo setterInfo;
     final DexEncodedMethod annotations;
     final boolean isExtension;
+    private final List<KmTypeParameter> classTypeParameters;
 
     private KmPropertyGroup(
         int flags,
         String name,
         DexEncodedField field,
         DexEncodedMethod getter,
-        int getterFlags,
+        KotlinPropertyInfo getterInfo,
         DexEncodedMethod setter,
-        int setterFlags,
+        KotlinPropertyInfo setterInfo,
         DexEncodedMethod annotations,
-        boolean isExtension) {
+        boolean isExtension,
+        List<KmTypeParameter> classTypeParameters) {
       this.flags = flags;
       this.name = name;
       this.field = field;
       this.getter = getter;
-      this.getterFlags = getterFlags;
+      this.getterInfo = getterInfo;
       this.setter = setter;
-      this.setterFlags = setterFlags;
+      this.setterInfo = setterInfo;
       this.annotations = annotations;
       this.isExtension = isExtension;
+      this.classTypeParameters = classTypeParameters;
     }
 
-    static Builder builder(int flags, String name) {
-      return new Builder(flags, name);
+    static Builder builder(int flags, String name, List<KmTypeParameter> classTypeParameters) {
+      return new Builder(flags, name, classTypeParameters);
     }
 
     static class Builder {
+
       private final int flags;
       private final String name;
       private DexEncodedField field;
       private DexEncodedMethod getter;
-      private int getterFlags;
+      private KotlinPropertyInfo getterInfo;
       private DexEncodedMethod setter;
-      private int setterFlags;
+      private KotlinPropertyInfo setterInfo;
       private DexEncodedMethod annotations;
+      private List<KmTypeParameter> classTypeParameters;
 
       private boolean isExtensionGetter;
       private boolean isExtensionSetter;
       private boolean isExtensionAnnotations;
 
-      private Builder(int flags, String name) {
+      private Builder(int flags, String name, List<KmTypeParameter> classTypeParameters) {
         this.flags = flags;
         this.name = name;
+        this.classTypeParameters = classTypeParameters;
       }
 
       Builder foundBackingField(DexEncodedField field) {
@@ -434,15 +421,15 @@
         return this;
       }
 
-      Builder foundGetter(DexEncodedMethod getter, int flags) {
+      Builder foundGetter(DexEncodedMethod getter, KotlinPropertyInfo propertyInfo) {
         this.getter = getter;
-        this.getterFlags = flags;
+        this.getterInfo = propertyInfo;
         return this;
       }
 
-      Builder foundSetter(DexEncodedMethod setter, int flags) {
+      Builder foundSetter(DexEncodedMethod setter, KotlinPropertyInfo propertyInfo) {
         this.setter = setter;
-        this.setterFlags = flags;
+        this.setterInfo = propertyInfo;
         return this;
       }
 
@@ -481,178 +468,234 @@
           }
         }
         return new KmPropertyGroup(
-            flags, name, field, getter, getterFlags, setter, setterFlags, annotations, isExtension);
+            flags,
+            name,
+            field,
+            getter,
+            getterInfo,
+            setter,
+            setterInfo,
+            annotations,
+            isExtension,
+            classTypeParameters);
       }
     }
 
-    KmProperty toRenamedKmProperty(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-      KmProperty kmProperty = new KmProperty(flags, name, flagsOf(), flagsOf());
-      KmType kmPropertyType = null;
-      KmType kmReceiverType = null;
-
-      // A flag to indicate we can rename the property name. This will become false if any member
-      // is pinned. Then, we conservatively assume that users want the property to be pinned too.
-      // That is, we won't rename the property even though some other members could be renamed.
-      boolean canChangePropertyName = true;
-      // A candidate property name. Not overwritten by the following members, hence the order of
-      // preference: a backing field, getter, and setter.
-      String renamedPropertyName = name;
-      if (field != null) {
-        if (appView.appInfo().isPinned(field.field)) {
-          canChangePropertyName = false;
+    private String setFieldForProperty(
+        KmProperty kmProperty,
+        KmType defaultPropertyType,
+        AppView<AppInfoWithLiveness> appView,
+        NamingLens lens,
+        KotlinMetadataSynthesizer synthesizer) {
+      DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, this.classTypeParameters, synthesizer::toRenamedClassifier);
+      if (kmProperty.getReturnType() == defaultPropertyType) {
+        KmType kmPropertyType =
+            synthesizer.toRenamedKmType(field.field.type, null, null, converter);
+        if (kmPropertyType != null) {
+          kmProperty.setReturnType(kmPropertyType);
         }
-        DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
-        if (canChangePropertyName && renamedField.name != field.field.name) {
-          renamedPropertyName = renamedField.name.toString();
-        }
-        FieldTypeSignature signature =
-            GenericSignature.Parser.toFieldTypeSignature(field, appView);
-        kmPropertyType =
-            setRenamedKmType(field.field.type, signature, appView, lens, kmProperty::setReturnType);
-        JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(renamedField));
       }
+      JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(renamedField));
+      return appView.appInfo().isPinned(field.field) ? null : renamedField.name.toString();
+    }
 
-      GetterSetterCriteria criteria = checkGetterCriteria();
-      if (criteria == GetterSetterCriteria.VIOLATE) {
-        return null;
+    private boolean setReceiverParameterTypeForExtensionProperty(
+        KmProperty kmProperty,
+        DexEncodedMethod method,
+        AppView<AppInfoWithLiveness> appView,
+        KotlinMetadataSynthesizer synthesizer) {
+      if (!isExtension) {
+        return true;
       }
+      MethodTypeSignature signature =
+          GenericSignature.Parser.toMethodTypeSignature(method, appView);
+      List<KmTypeParameter> typeParameters =
+          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+              classTypeParameters,
+              signature.getFormalTypeParameters(),
+              appView.dexItemFactory(),
+              kmTypeParameter -> {});
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, typeParameters, synthesizer::toRenamedClassifier);
+      DexType receiverType = method.method.proto.parameters.values[0];
+      TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
+      KmType kmReceiverType =
+          synthesizer.toRenamedKmType(receiverType, receiverSignature, null, converter);
+      if (kmProperty.getReceiverParameterType() != null) {
+        // If the receiver type for the extension property is set already make sure it's consistent.
+        return getDescriptorFromKmType(kmReceiverType)
+            .equals(getDescriptorFromKmType(kmProperty.getReceiverParameterType()));
+      }
+      kmProperty.setReceiverParameterType(kmReceiverType);
+      return true;
+    }
 
-      if (criteria == GetterSetterCriteria.MET) {
-        assert getter != null && getter.method.proto.parameters.size() == (isExtension ? 1 : 0)
-            : "checkGetterCriteria: " + this.toString();
-        MethodTypeSignature signature =
-            GenericSignature.Parser.toMethodTypeSignature(getter, appView);
-        if (isExtension) {
-          TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-          kmReceiverType =
-              setRenamedKmType(
-                  getter.method.proto.parameters.values[0],
-                  receiverSignature,
-                  appView,
-                  lens,
-                  kmProperty::setReceiverParameterType);
-        }
-
-        DexType returnType = getter.method.proto.returnType;
-        TypeSignature returnSignature = signature.returnType().typeSignature();
-        if (kmPropertyType == null) {
-          // The property type is not set yet.
-          kmPropertyType = setRenamedKmType(
-              returnType, returnSignature, appView, lens, kmProperty::setReturnType);
-        } else {
-          // If property type is set already (via backing field), make sure it's consistent.
-          KmType kmPropertyTypeFromGetter =
-              toRenamedKmType(returnType, returnSignature, appView, lens);
-          if (!getDescriptorFromKmType(kmPropertyType)
-              .equals(getDescriptorFromKmType(kmPropertyTypeFromGetter))) {
-            return null;
-          }
-        }
-        if (appView.appInfo().isPinned(getter.method)) {
-          canChangePropertyName = false;
-        }
-        DexMethod renamedGetter = lens.lookupMethod(getter.method, appView.dexItemFactory());
-        if (canChangePropertyName
-            && renamedGetter.name != getter.method.name
-            && renamedPropertyName.equals(name)) {
-          renamedPropertyName = renamedGetter.name.toString();
-        }
-        kmProperty.setGetterFlags(getterFlags);
-        JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(renamedGetter));
-      } else if (field != null) {
+    private String setGetterForProperty(
+        KmProperty kmProperty,
+        KmType defaultPropertyType,
+        AppView<AppInfoWithLiveness> appView,
+        NamingLens lens,
+        KotlinMetadataSynthesizer synthesizer) {
+      if (checkGetterCriteria() == GetterSetterCriteria.NOT_AVAILABLE) {
         // Property without getter.
         // Even though a getter does not exist, `kotlinc` still set getter flags and use them to
         // determine when to direct field access v.s. getter calls for property resolution.
-        kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
+        if (field != null) {
+          kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
+        }
+        return kmProperty.getName();
       }
+      assert checkGetterCriteria() == GetterSetterCriteria.MET;
+      assert getter != null;
+      DexProto proto = getter.method.proto;
+      assert proto.parameters.size() == (isExtension ? 1 : 0)
+          : "checkGetterCriteria: " + this.toString();
+      MethodTypeSignature signature =
+          GenericSignature.Parser.toMethodTypeSignature(getter, appView);
+      List<KmTypeParameter> typeParameters =
+          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+              this.classTypeParameters,
+              signature.getFormalTypeParameters(),
+              appView.dexItemFactory(),
+              kmTypeParameter -> {});
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, typeParameters, synthesizer::toRenamedClassifier);
+      DexType returnType = proto.returnType;
+      TypeSignature returnSignature = signature.returnType().typeSignature();
+      KmType kmPropertyType =
+          synthesizer.toRenamedKmType(
+              returnType, returnSignature, getterInfo.returnType, converter);
+      if (kmProperty.getReturnType() == defaultPropertyType) {
+        // The property type is not set yet.
+        kmProperty.setReturnType(kmPropertyType);
+      } else if (!getDescriptorFromKmType(kmPropertyType)
+          .equals(getDescriptorFromKmType(kmProperty.getReturnType()))) {
+        // If property type is set already (via backing field), make sure it's consistent.
+        return null;
+      }
+      DexMethod renamedGetter = lens.lookupMethod(getter.method, appView.dexItemFactory());
+      kmProperty.setGetterFlags(getter.accessFlags.getAsKotlinFlags());
+      JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(renamedGetter));
+      return appView.appInfo().isPinned(getter.method) ? null : renamedGetter.name.toString();
+    }
 
-      criteria = checkSetterCriteria();
+    private String setSetterForProperty(
+        KmProperty kmProperty,
+        KmType defaultPropertyType,
+        AppView<AppInfoWithLiveness> appView,
+        NamingLens lens,
+        KotlinMetadataSynthesizer synthesizer) {
+      GetterSetterCriteria criteria = checkSetterCriteria();
       if (criteria == GetterSetterCriteria.VIOLATE) {
-        return null;
+        return kmProperty.getName();
       }
-
-      if (criteria == GetterSetterCriteria.MET) {
-        assert setter != null && setter.method.proto.parameters.size() == (isExtension ? 2 : 1)
-            : "checkSetterCriteria: " + this.toString();
-        MethodTypeSignature signature =
-            GenericSignature.Parser.toMethodTypeSignature(setter, appView);
-        if (isExtension) {
-          DexType receiverType = setter.method.proto.parameters.values[0];
-          TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-          if (kmReceiverType == null) {
-            kmReceiverType =
-                setRenamedKmType(
-                    receiverType,
-                    receiverSignature,
-                    appView,
-                    lens,
-                    kmProperty::setReceiverParameterType);
-          } else {
-            // If the receiver type for the extension property is set already (via getter),
-            // make sure it's consistent.
-            KmType kmReceiverTypeFromSetter =
-                toRenamedKmType(receiverType, receiverSignature, appView, lens);
-            if (!getDescriptorFromKmType(kmReceiverType)
-                .equals(getDescriptorFromKmType(kmReceiverTypeFromSetter))) {
-              return null;
-            }
-          }
+      if (criteria == GetterSetterCriteria.NOT_AVAILABLE) {
+        if (field != null && IS_VAR.invoke(flags)) {
+          // Editable property without setter.
+          // Even though a setter does not exist, `kotlinc` still set setter flags and use them to
+          // determine when to direct field access v.s. setter calls for property resolution.
+          kmProperty.setSetterFlags(field.accessFlags.getAsKotlinFlags());
         }
-
-        int valueIndex = isExtension ? 1 : 0;
-        DexType valueType = setter.method.proto.parameters.values[valueIndex];
-        TypeSignature valueSignature = signature.getParameterTypeSignature(valueIndex);
-        if (kmPropertyType == null) {
-          // The property type is not set yet.
-          kmPropertyType =
-              setRenamedKmType(valueType, valueSignature, appView, lens, kmProperty::setReturnType);
-        } else {
-          // If property type is set already (via either backing field or getter),
-          // make sure it's consistent.
-          KmType kmPropertyTypeFromSetter =
-              toRenamedKmType(valueType, valueSignature, appView, lens);
-          if (!getDescriptorFromKmType(kmPropertyType)
-              .equals(getDescriptorFromKmType(kmPropertyTypeFromSetter))) {
-            return null;
-          }
-        }
-        assert setter.getKotlinMemberInfo().isPropertyInfo();
-        KotlinValueParameterInfo valueParameterInfo =
-            setter.getKotlinMemberInfo().asPropertyInfo().valueParameterInfo;
-        KmValueParameter kmValueParameter = toRewrittenKmValueParameter(
-            valueParameterInfo, valueType, valueSignature, "value", appView, lens);
-        if (kmValueParameter != null) {
-          kmProperty.setSetterParameter(kmValueParameter);
-        }
-        if (appView.appInfo().isPinned(setter.method)) {
-          canChangePropertyName = false;
-        }
-        DexMethod renamedSetter = lens.lookupMethod(setter.method, appView.dexItemFactory());
-        if (canChangePropertyName
-            && renamedSetter.name != setter.method.name
-            && renamedPropertyName.equals(name)) {
-          renamedPropertyName = renamedSetter.name.toString();
-        }
-        kmProperty.setSetterFlags(setterFlags);
-        JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(renamedSetter));
-      } else if (field != null && IS_VAR.invoke(flags)) {
-        // Editable property without setter.
-        // Even though a setter does not exist, `kotlinc` still set setter flags and use them to
-        // determine when to direct field access v.s. setter calls for property resolution.
-        kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
+        return kmProperty.getName();
       }
-
-      // If the property type remains null at the end, bail out to synthesize this property.
-      if (kmPropertyType == null) {
-        return null;
+      assert criteria == GetterSetterCriteria.MET;
+      assert setter != null;
+      DexProto proto = setter.method.proto;
+      assert proto.parameters.size() == (isExtension ? 2 : 1)
+          : "checkSetterCriteria: " + this.toString();
+      MethodTypeSignature signature =
+          GenericSignature.Parser.toMethodTypeSignature(setter, appView);
+      List<KmTypeParameter> typeParameters =
+          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+              classTypeParameters,
+              signature.getFormalTypeParameters(),
+              appView.dexItemFactory(),
+              kmTypeParameter -> {});
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, typeParameters, synthesizer::toRenamedClassifier);
+      int valueIndex = isExtension ? 1 : 0;
+      DexType valueType = proto.parameters.values[valueIndex];
+      TypeSignature valueSignature = signature.getParameterTypeSignature(valueIndex);
+      KmType kmPropertyType =
+          synthesizer.toRenamedKmType(valueType, valueSignature, setterInfo.returnType, converter);
+      if (kmProperty.getReturnType() == defaultPropertyType) {
+        // The property type is not set yet.
+        kmProperty.setReturnType(kmPropertyType);
+      } else {
+        // If property type is set already make sure it's consistent.
+        if (!getDescriptorFromKmType(kmPropertyType)
+            .equals(getDescriptorFromKmType(kmProperty.getReturnType()))) {
+          return null;
+        }
       }
-      // For extension property, if the receiver type remains null at the end, bail out too.
-      if (isExtension && kmReceiverType == null) {
-        return null;
+      assert setter.getKotlinMemberInfo().isPropertyInfo();
+      KotlinValueParameterInfo valueParameterInfo =
+          setter.getKotlinMemberInfo().asPropertyInfo().valueParameterInfo;
+      KmValueParameter kmValueParameter =
+          synthesizer.toRewrittenKmValueParameter(
+              valueParameterInfo, valueType, valueSignature, "value", converter);
+      if (kmValueParameter != null) {
+        kmProperty.setSetterParameter(kmValueParameter);
+      }
+      DexMethod renamedSetter = lens.lookupMethod(setter.method, appView.dexItemFactory());
+      kmProperty.setSetterFlags(setterInfo.setterFlags);
+      JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(renamedSetter));
+      return appView.appInfo().isPinned(setter.method) ? null : renamedSetter.name.toString();
+    }
+
+    KmProperty toRenamedKmProperty(KotlinMetadataSynthesizer synthesizer) {
+      AppView<AppInfoWithLiveness> appView = synthesizer.appView;
+      NamingLens lens = synthesizer.lens;
+      KmProperty kmProperty = new KmProperty(flags, name, flagsOf(), flagsOf());
+
+      // Set default values
+      KmType defaultPropertyType = new KmType(flagsOf());
+      kmProperty.setReturnType(defaultPropertyType);
+
+      String renamedPropertyName = name;
+      if (getter != null) {
+        if (checkGetterCriteria() == GetterSetterCriteria.VIOLATE) {
+          return null;
+        }
+        if (!setReceiverParameterTypeForExtensionProperty(
+            kmProperty, getter, appView, synthesizer)) {
+          return null;
+        }
+        renamedPropertyName =
+            setGetterForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
+      }
+      if (setter != null) {
+        if (checkSetterCriteria() == GetterSetterCriteria.VIOLATE) {
+          return null;
+        }
+        if (!setReceiverParameterTypeForExtensionProperty(
+            kmProperty, setter, appView, synthesizer)) {
+          return null;
+        }
+        String renamedName =
+            setSetterForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
+        if (renamedPropertyName != null) {
+          renamedPropertyName = renamedName;
+        }
+      }
+      // Setting the property type from the field has to be done after the getter, otherwise we
+      // may potentially loose type-argument information.
+      if (field != null) {
+        String renamedName =
+            setFieldForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
+        if (renamedPropertyName != null) {
+          renamedPropertyName = renamedName;
+        }
       }
       // Rename the property name if and only if none of participating members is pinned, and
       // any of them is indeed renamed (to a new name).
-      if (canChangePropertyName && !renamedPropertyName.equals(name)) {
+      if (renamedPropertyName != null) {
         kmProperty.setName(renamedPropertyName);
       }
       return kmProperty;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
new file mode 100644
index 0000000..9382cd9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2020, 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 com.android.tools.r8.kotlin.Kotlin.NAME;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ArrayTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.TypeVariableSignature;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.KmVariance;
+
+class KotlinMetadataSynthesizerUtils {
+
+  static void populateKmTypeFromSignature(
+      FieldTypeSignature typeSignature,
+      Supplier<KmTypeVisitor> typeVisitor,
+      List<KmTypeParameter> allTypeParameters,
+      DexItemFactory factory) {
+    if (typeSignature.isClassTypeSignature()) {
+      populateKmTypeFromClassTypeSignature(
+          typeSignature.asClassTypeSignature(), typeVisitor, allTypeParameters, factory);
+    } else if (typeSignature.isArrayTypeSignature()) {
+      populateKmTypeFromArrayTypeSignature(
+          typeSignature.asArrayTypeSignature(), typeVisitor, allTypeParameters, factory);
+    } else {
+      assert typeSignature.isTypeVariableSignature();
+      populateKmTypeFromTypeVariableSignature(
+          typeSignature.asTypeVariableSignature(), typeVisitor, allTypeParameters);
+    }
+  }
+
+  private static void populateKmTypeFromTypeVariableSignature(
+      TypeVariableSignature typeSignature,
+      Supplier<KmTypeVisitor> visitor,
+      List<KmTypeParameter> allTypeParameters) {
+    for (KmTypeParameter typeParameter : allTypeParameters) {
+      if (typeParameter
+          .getName()
+          .equals(typeSignature.asTypeVariableSignature().getTypeVariable())) {
+        visitor.get().visitTypeParameter(typeParameter.getId());
+        return;
+      }
+    }
+  }
+
+  private static void populateKmTypeFromArrayTypeSignature(
+      ArrayTypeSignature typeSignature,
+      Supplier<KmTypeVisitor> visitor,
+      List<KmTypeParameter> allTypeParameters,
+      DexItemFactory factory) {
+    ArrayTypeSignature arrayTypeSignature = typeSignature.asArrayTypeSignature();
+    if (!arrayTypeSignature.elementSignature().isFieldTypeSignature()) {
+      return;
+    }
+    KmTypeVisitor kmType = visitor.get();
+    kmType.visitClass(NAME + "/Array");
+    populateKmTypeFromSignature(
+        arrayTypeSignature.elementSignature().asFieldTypeSignature(),
+        () -> kmType.visitArgument(flagsOf(), KmVariance.INVARIANT),
+        allTypeParameters,
+        factory);
+  }
+
+  private static void populateKmTypeFromClassTypeSignature(
+      ClassTypeSignature typeSignature,
+      Supplier<KmTypeVisitor> visitor,
+      List<KmTypeParameter> allTypeParameters,
+      DexItemFactory factory) {
+    // No need to record the trivial argument.
+    if (factory.objectType == typeSignature.type()) {
+      return;
+    }
+    KmTypeVisitor kmType = visitor.get();
+    kmType.visitClass(toClassifier(typeSignature.type(), factory));
+    for (FieldTypeSignature typeArgument : typeSignature.typeArguments()) {
+      populateKmTypeFromSignature(
+          typeArgument,
+          () -> kmType.visitArgument(flagsOf(), KmVariance.INVARIANT),
+          allTypeParameters,
+          factory);
+    }
+  }
+
+  static String toClassifier(DexType type, DexItemFactory factory) {
+    // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
+    if (factory.kotlin.knownTypeConversion.containsKey(type)) {
+      DexType convertedType = factory.kotlin.knownTypeConversion.get(type);
+      assert convertedType != null;
+      return descriptorToKotlinClassifier(convertedType.toDescriptorString());
+    }
+    // E.g., [Ljava/lang/String; -> kotlin/Array
+    if (type.isArrayType()) {
+      return NAME + "/Array";
+    }
+    return descriptorToKotlinClassifier(type.toDescriptorString());
+  }
+
+  /**
+   * Utility method building up all type-parameters from {@code classTypeParameters} combined with
+   * {@code parameters}, where the consumer {@code addedFromParameters} is called for every
+   * conversion of {@Code FormalTypeParameter} to {@Code KmTypeParameter}.
+   *
+   * <pre>
+   *  classTypeParameters: [KmTypeParameter(T), KmTypeParameter(S)]
+   *  parameters: [FormalTypeParameter(R)]
+   *  result: [KmTypeParameter(T), KmTypeParameter(S), KmTypeParameter(R)].
+   * </pre>
+   *
+   * @param classTypeParameters
+   * @param parameters
+   * @param factory
+   * @param addedFromParameters
+   * @return
+   */
+  static List<KmTypeParameter> convertFormalTypeParameters(
+      List<KmTypeParameter> classTypeParameters,
+      List<FormalTypeParameter> parameters,
+      DexItemFactory factory,
+      Consumer<KmTypeParameter> addedFromParameters) {
+    if (parameters.isEmpty()) {
+      return classTypeParameters;
+    }
+    ImmutableList.Builder<KmTypeParameter> builder = ImmutableList.builder();
+    builder.addAll(classTypeParameters);
+    int idCounter = classTypeParameters.size();
+    // Assign type-variables ids to names. All generic signatures has been minified at this point,
+    // but it may be that type-variables are used before we can see them (is that allowed?).
+    for (FormalTypeParameter parameter : parameters) {
+      KmTypeParameter element =
+          new KmTypeParameter(flagsOf(), parameter.getName(), idCounter++, KmVariance.INVARIANT);
+      builder.add(element);
+      addedFromParameters.accept(element);
+    }
+    idCounter = 0;
+    ImmutableList<KmTypeParameter> allTypeParameters = builder.build();
+    for (FormalTypeParameter parameter : parameters) {
+      KmTypeParameter kmTypeParameter =
+          allTypeParameters.get(classTypeParameters.size() + idCounter++);
+      visitUpperBound(parameter.getClassBound(), allTypeParameters, kmTypeParameter, factory);
+      if (parameter.getInterfaceBounds() != null) {
+        for (FieldTypeSignature interfaceBound : parameter.getInterfaceBounds()) {
+          visitUpperBound(interfaceBound, allTypeParameters, kmTypeParameter, factory);
+        }
+      }
+    }
+    return allTypeParameters;
+  }
+
+  private static void visitUpperBound(
+      FieldTypeSignature typeSignature,
+      List<KmTypeParameter> allTypeParameters,
+      KmTypeParameter parameter,
+      DexItemFactory factory) {
+    if (typeSignature.isUnknown()) {
+      return;
+    }
+    populateKmTypeFromSignature(
+        typeSignature, () -> parameter.visitUpperBound(flagsOf()), allTypeParameters, factory);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
new file mode 100644
index 0000000..130a660
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2020, 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 com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmClassifier;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeProjection;
+
+// Provides access to Kotlin information about a kotlin type.
+public class KotlinTypeInfo {
+
+  static final List<KotlinTypeProjectionInfo> EMPTY_ARGUMENTS = ImmutableList.of();
+
+  private final KmClassifier classifier;
+  private final List<KotlinTypeProjectionInfo> arguments;
+
+  private KotlinTypeInfo(KmClassifier classifier, List<KotlinTypeProjectionInfo> arguments) {
+    this.classifier = classifier;
+    this.arguments = arguments;
+  }
+
+  static KotlinTypeInfo create(KmType kmType) {
+    if (kmType == null) {
+      return null;
+    }
+    if (kmType.getArguments().isEmpty()) {
+      return new KotlinTypeInfo(kmType.classifier, EMPTY_ARGUMENTS);
+    }
+    ImmutableList.Builder<KotlinTypeProjectionInfo> arguments = new ImmutableList.Builder<>();
+    for (KmTypeProjection argument : kmType.getArguments()) {
+      arguments.add(KotlinTypeProjectionInfo.create(argument));
+    }
+    return new KotlinTypeInfo(kmType.classifier, arguments.build());
+  }
+
+  public boolean isTypeAlias() {
+    return classifier instanceof KmClassifier.TypeAlias;
+  }
+
+  public KmClassifier.TypeAlias asTypeAlias() {
+    return (KmClassifier.TypeAlias) classifier;
+  }
+
+  public List<KotlinTypeProjectionInfo> getArguments() {
+    return arguments;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
new file mode 100644
index 0000000..3782bd8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, 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 kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmVariance;
+
+// Provides access to Kotlin information about the type projection of a type (arguments).
+public class KotlinTypeProjectionInfo {
+
+  final KmVariance variance;
+  final KotlinTypeInfo typeInfo;
+
+  private KotlinTypeProjectionInfo(KmVariance variance, KotlinTypeInfo typeInfo) {
+    this.variance = variance;
+    this.typeInfo = typeInfo;
+  }
+
+  static KotlinTypeProjectionInfo create(KmTypeProjection kmTypeProjection) {
+    return new KotlinTypeProjectionInfo(
+        kmTypeProjection.getVariance(), KotlinTypeInfo.create(kmTypeProjection.getType()));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
index fcf3db6..e358c50 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -19,15 +19,23 @@
   final int flag;
   // Indicates whether the formal parameter is originally `vararg`.
   final boolean isVararg;
+  // Original information about the type.
+  final KotlinTypeInfo type;
+
   // TODO(b/151194869): Should we treat them as normal annotations? E.g., shrinking and renaming?
   // Annotations on the type of value parameter.
   final List<KmAnnotation> annotations;
 
   private KotlinValueParameterInfo(
-      String name, int flag, boolean isVararg, List<KmAnnotation> annotations) {
+      String name,
+      int flag,
+      boolean isVararg,
+      KotlinTypeInfo type,
+      List<KmAnnotation> annotations) {
     this.name = name;
     this.flag = flag;
     this.isVararg = isVararg;
+    this.type = type;
     this.annotations = annotations;
   }
 
@@ -40,6 +48,7 @@
         kmValueParameter.getName(),
         kmValueParameter.getFlags(),
         kmValueParameter.getVarargElementType() != null,
+        KotlinTypeInfo.create(kmType),
         kmType != null ? JvmExtensionsKt.getAnnotations(kmType) : ImmutableList.of());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
index 582a282..82b6c13 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -59,6 +59,7 @@
                 GenericSignatureTestClassCY.class,
                 GenericSignatureTestClassCYY.class)
             .compile()
+            .disassemble()
             .app;
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(app);
     DexItemFactory factory = appView.dexItemFactory();
@@ -306,7 +307,7 @@
     class ZZ<TT> extends YY {
       public YY yy;
 
-      <R extends Y & I> YY newYY(GenericSignatureTestClassB... bs) {
+      <R extends I> YY newYY(GenericSignatureTestClassB... bs) {
         return new YY();
       }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
index 07fbdd9..3d3d935 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
@@ -5,13 +5,11 @@
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
 
 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.StringUtils;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -25,7 +23,28 @@
 @RunWith(Parameterized.class)
 public class MetadataRewriteInTypeArgumentsTest extends KotlinMetadataTestBase {
   private static final String EXPECTED =
-      StringUtils.lines("42", "1", "42", "42", "1", "42", "42", "42", "1", "42");
+      StringUtils.lines(
+          "Hello World!",
+          "42",
+          "1",
+          "42",
+          "42",
+          "1",
+          "42",
+          "42",
+          "42",
+          "1",
+          "42",
+          "1",
+          "42",
+          "42",
+          "42",
+          "42",
+          "42",
+          "1",
+          "2",
+          "7",
+          "42");
 
   private final TestParameters parameters;
 
@@ -50,6 +69,7 @@
       Path typeAliasLibJar =
           kotlinc(KOTLINC, targetVersion)
               .addSourceFiles(getKotlinFileInTest(typeAliasLibFolder, "lib"))
+              .addSourceFiles(getKotlinFileInTest(typeAliasLibFolder, "lib_minified"))
               .compile();
       jarMap.put(targetVersion, typeAliasLibJar);
     }
@@ -74,26 +94,37 @@
   }
 
   @Test
-  public void testMetadataInTypeAlias_renamed() throws Exception {
+  public void testMetadataInTypeAlias_keepAll() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(jarMap.get(targetVersion))
             .addKeepAllClassesRule()
+            .addKeepAttributes(
+                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
             .compile()
-            // TODO(b/151925520): Add inspections when program compiles
+            // TODO(b/151925520): Add inspections when program compiles correctly.
+            // TODO(mkroghj): Also inspect the renaming of lib_minified
+            //  (not now, but when program compiles correctly).
             .writeToZip();
 
-    ProcessResult kotlinTestCompileResult =
+    Path mainJar =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typeargument_app", "main"))
-            .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/151925520): update to just .compile() once fixed.
-            .compileRaw();
-    // TODO(b/151925520): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(
-        kotlinTestCompileResult.stderr,
-        containsString("no type arguments expected for constructor Invariant()"));
+            .compile();
+
+    // TODO(b/152306391): Reified type-parameters are not flagged correctly.
+    testForJvm()
+        .addProgramFiles(mainJar)
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addRunClasspathFiles(libJar)
+        .run(parameters.getRuntime(), PKG + ".typeargument_app.MainKt")
+        .assertFailureWithErrorThatMatches(
+            containsString(
+                "This function has a reified type parameter and thus can only be inlined at"
+                    + " compilation time, not called directly"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
index 7fc2b69..c8f4a72 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
@@ -8,7 +8,10 @@
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.ContraVariant
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.Invariant
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.SomeClass
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.asList
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.asObfuscatedClass
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.unBoxAndBox
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.unboxAndPutInBox
 import com.android.tools.r8.kotlin.metadata.typeargument_lib.update
 
 class SomeSubClass(val x : Int) : SomeClass(), Comparable<SomeClass> {
@@ -24,7 +27,7 @@
 fun testInvariant() {
   val subtype1 = SomeSubClass(42)
   val subtype2 = SomeSubClass(1)
-  val inv = Invariant<SomeSubClass>()
+  val inv = Invariant<SomeSubClass, String>("Hello World!")
   println(inv.classGenerics(subtype1).x)
   println(inv.funGenerics(subtype2).x)
   println(inv.funGenericsWithUpperBound(subtype1).x)
@@ -43,6 +46,12 @@
 fun testExtension() {
   println(CoVariant(42).unBoxAndBox().t)
   println(CoVariant(1).update(42).t)
+  println(CoVariant(1).unboxAndPutInBox(CoVariant(42)).t)
+  println(CoVariant(42).asList().t.get(0))
+  val asList = CoVariant(42).asList(1, 2)
+  println(asList.t.get(0))
+  println(asList.t.get(1))
+  println(CoVariant(7).asObfuscatedClass().t.get(0).get(0).x)
 }
 
 fun main() {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
index 7c8b6cb..38234b4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
@@ -6,7 +6,11 @@
 
 open class SomeClass
 
-class Invariant<T> {
+class Invariant<T, C> {
+
+  constructor(someValue : C) {
+    println(someValue)
+  }
 
   fun classGenerics(t : T) : T {
     return t
@@ -52,7 +56,6 @@
   }
 }
 
-
 fun <T> CoVariant<T>.unBoxAndBox() : CoVariant<T> {
   return CoVariant(this.t)
 }
@@ -61,3 +64,24 @@
   println(this.t)
   return CoVariant(t)
 }
+
+fun <T> CoVariant<T>.unboxAndPutInBox(box : CoVariant<T>) : CoVariant<T> {
+  println(this.t)
+  println(box.t)
+  return CoVariant(box.t)
+}
+
+inline fun <reified T> CoVariant<T>.asList() : CoVariant<Array<T>> {
+  println(this.t)
+  return CoVariant(arrayOf(this.t))
+}
+
+inline fun <reified T> CoVariant<T>.asList(vararg ts : T) : CoVariant<Array<out T>> {
+  println(this.t)
+  return CoVariant(ts)
+}
+
+fun <T> CoVariant<T>.asObfuscatedClass() : CoVariant<Array<Array<ClassThatWillBeObfuscated>>> {
+  println(this.t)
+  return CoVariant(arrayOf(arrayOf(ClassThatWillBeObfuscated(42))))
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib_minified.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib_minified.kt
new file mode 100644
index 0000000..78c3742
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib_minified.kt
@@ -0,0 +1,3 @@
+package com.android.tools.r8.kotlin.metadata.typeargument_lib
+
+class ClassThatWillBeObfuscated(val x : Int)