blob: ef9419cf99573de6e8a456414506af115091e386 [file] [log] [blame]
// Copyright (c) 2019, 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.addKotlinPrefix;
import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.parameterTypesFromJvmMethodSignature;
import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.returnTypeFromJvmMethodSignature;
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToInternalName;
import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
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.kotlin.KotlinMetadataJvmExtensionUtils.KmConstructorProcessor;
import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmFunctionProcessor;
import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.List;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmProperty;
import kotlinx.metadata.KmType;
import kotlinx.metadata.KmValueParameter;
import kotlinx.metadata.jvm.JvmMethodSignature;
public class KotlinMetadataSynthesizer {
static boolean isExtension(KmFunction kmFunction) {
return kmFunction.getReceiverParameterType() != null;
}
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;
}
// For library or classpath class, synthesize @Metadata always.
// For a program class, make sure it is live.
if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
return null;
}
DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
// For library or classpath class, we should not have renamed it.
DexClass clazz = appView.definitionFor(type);
assert clazz == null || clazz.isProgramClass() || renamedType == type
: type.toSourceString() + " -> " + renamedType.toSourceString();
// 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;
}
private static boolean isCompatible(String desc, DexType type) {
if (desc == null || type == null) {
return false;
}
return desc.equals(type.toDescriptorString());
}
private static boolean isCompatible(KmType kmType, DexType type, AppView<?> appView) {
if (kmType == null || type == null) {
return false;
}
String descriptor = null;
if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
descriptor = convertedType.toDescriptorString();
}
if (descriptor == null) {
descriptor = type.toDescriptorString();
}
assert descriptor != null;
return descriptor.equals(getDescriptorFromKmType(kmType));
}
private static boolean isCompatibleJvmMethodSignature(
JvmMethodSignature signature, DexEncodedMethod method) {
String methodName = method.method.name.toString();
if (!signature.getName().equals(methodName)) {
return false;
}
if (!isCompatible(
returnTypeFromJvmMethodSignature(signature), method.method.proto.returnType)) {
return false;
}
List<String> parameterTypes = parameterTypesFromJvmMethodSignature(signature);
if (parameterTypes == null || parameterTypes.size() != method.method.proto.parameters.size()) {
return false;
}
for (int i = 0; i < method.method.proto.parameters.size(); i++) {
if (!isCompatible(parameterTypes.get(i), method.method.proto.parameters.values[i])) {
return false;
}
}
return true;
}
public static boolean isCompatibleConstructor(
KmConstructor constructor, DexEncodedMethod method, AppView<?> appView) {
// Note that targets for @JvmName don't include constructor. So, it's not necessary to process
// JvmMethodSignature inside JvmConstructorExtension.
List<KmValueParameter> parameters = constructor.getValueParameters();
if (method.method.proto.parameters.size() != parameters.size()) {
return false;
}
for (int i = 0; i < method.method.proto.parameters.size(); i++) {
KmType kmType = parameters.get(i).getType();
if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
return false;
}
}
return true;
}
public static boolean isCompatibleFunction(
KmFunction function, DexEncodedMethod method, AppView<?> appView) {
// Check if a custom name is set to avoid name clash.
KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(function);
JvmMethodSignature jvmMethodSignature = kmFunctionProcessor.signature();
if (jvmMethodSignature != null && isCompatibleJvmMethodSignature(jvmMethodSignature, method)) {
return true;
}
if (!function.getName().equals(method.method.name.toString())) {
return false;
}
if (!isCompatible(function.getReturnType(), method.method.proto.returnType, appView)) {
return false;
}
List<KmValueParameter> parameters = function.getValueParameters();
if (method.method.proto.parameters.size() != parameters.size()) {
return false;
}
for (int i = 0; i < method.method.proto.parameters.size(); i++) {
KmType kmType = parameters.get(i).getType();
if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
return false;
}
}
return true;
}
public static boolean isCompatibleExtension(
KmFunction extension, DexEncodedMethod method, AppView<?> appView) {
// Check if a custom name is set to avoid name clash.
KmFunctionProcessor kmFunctionProcessor = new KmFunctionProcessor(extension);
JvmMethodSignature jvmMethodSignature = kmFunctionProcessor.signature();
if (jvmMethodSignature != null && isCompatibleJvmMethodSignature(jvmMethodSignature, method)) {
return true;
}
if (!extension.getName().equals(method.method.name.toString())) {
return false;
}
if (!isCompatible(extension.getReturnType(), method.method.proto.returnType, appView)) {
return false;
}
List<KmValueParameter> parameters = extension.getValueParameters();
if (method.method.proto.parameters.size() != parameters.size() + 1) {
return false;
}
assert method.method.proto.parameters.size() > 0;
assert extension.getReceiverParameterType() != null;
if (!isCompatible(
extension.getReceiverParameterType(), method.method.proto.parameters.values[0], appView)) {
return false;
}
for (int i = 1; i < method.method.proto.parameters.size(); i++) {
KmType kmType = parameters.get(i - 1).getType();
if (!isCompatible(kmType, method.method.proto.parameters.values[i], appView)) {
return false;
}
}
return true;
}
public static boolean isCompatibleProperty(
KmProperty kmProperty, DexEncodedMethod method, AppView<?> appView) {
KmPropertyProcessor kmPropertyProcessor = new KmPropertyProcessor(kmProperty);
// Check if a custom getter is defined via @get:JvmName("myGetter").
JvmMethodSignature getterSignature = kmPropertyProcessor.getterSignature();
if (getterSignature != null && isCompatibleJvmMethodSignature(getterSignature, method)) {
return true;
}
// Check if a custom setter is defined via @set:JvmName("mySetter").
JvmMethodSignature setterSignature = kmPropertyProcessor.setterSignature();
if (setterSignature != null && isCompatibleJvmMethodSignature(setterSignature, method)) {
return true;
}
// E.g., property `prop: T` is mapped to `getProp()T`, `setProp(T)V`, `prop$annotations()V`.
// For boolean property, though, getter is mapped to `isProp()Z`.
// TODO(b/70169921): Avoid decoding.
String methodName = method.method.name.toString();
if (!methodName.startsWith("is")
&& !methodName.startsWith("get")
&& !methodName.startsWith("set")
&& !methodName.endsWith("$annotations")) {
return false;
}
String propertyName = kmProperty.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 returnTypeDescriptor = getDescriptorFromKmType(kmProperty.returnType);
String getterPrefix =
returnTypeDescriptor != null && returnTypeDescriptor.endsWith("Boolean;") ? "is" : "get";
String getter = getterPrefix + capitalized;
if (methodName.equals(getter)
&& method.method.proto.parameters.size() == 0
&& isCompatible(kmProperty.returnType, method.method.proto.returnType, appView)) {
return true;
}
String setter = "set" + capitalized;
if (methodName.equals(setter)
&& method.method.proto.returnType.isVoidType()
&& method.method.proto.parameters.size() == 1
&& isCompatible(kmProperty.returnType, method.method.proto.parameters.values[0], appView)) {
return true;
}
return false;
}
static KmConstructor toRenamedKmConstructor(
DexEncodedMethod method,
KmConstructor original,
AppView<AppInfoWithLiveness> appView,
NamingLens lens) {
// Make sure it is an instance initializer and live.
if (!method.isInstanceInitializer()
|| !appView.appInfo().liveMethods.contains(method.method)) {
return null;
}
// TODO(b/70169921): {@link KmConstructor.extensions} is private, i.e., no way to alter!
// Thus, we rely on original metadata for now.
KmConstructorProcessor kmConstructorProcessor = new KmConstructorProcessor(original);
JvmMethodSignature jvmMethodSignature = kmConstructorProcessor.signature();
KmConstructor kmConstructor =
jvmMethodSignature != null
? original
// TODO(b/70169921): Consult kotlinx.metadata.Flag.Constructor to set IS_PRIMARY.
: new KmConstructor(method.accessFlags.getAsKotlinFlags());
List<KmValueParameter> parameters = kmConstructor.getValueParameters();
parameters.clear();
populateKmValueParameters(parameters, method, appView, lens, false);
return kmConstructor;
}
static KmFunction toRenamedKmFunction(
DexEncodedMethod method,
KmFunction original,
AppView<AppInfoWithLiveness> appView,
NamingLens lens) {
return toRenamedKmFunctionHelper(method, original, appView, lens, false);
}
static KmFunction toRenamedKmFunctionAsExtension(
DexEncodedMethod method,
KmFunction original,
AppView<AppInfoWithLiveness> appView,
NamingLens lens) {
return toRenamedKmFunctionHelper(method, original, appView, lens, true);
}
private static KmFunction toRenamedKmFunctionHelper(
DexEncodedMethod method,
KmFunction original,
AppView<AppInfoWithLiveness> appView,
NamingLens lens,
boolean isExtension) {
// For library overrides, synthesize @Metadata always.
// For regular methods, make sure it is live.
if (!method.isLibraryMethodOverride().isTrue()
&& !appView.appInfo().liveMethods.contains(method.method)) {
return null;
}
DexMethod renamedMethod = lens.lookupMethod(method.method, appView.dexItemFactory());
// For a library method override, we should not have renamed it.
assert !method.isLibraryMethodOverride().isTrue() || renamedMethod == method.method
: method.toSourceString() + " -> " + renamedMethod.toSourceString();
// TODO(b/70169921): {@link KmFunction.extensions} is private, i.e., no way to alter!
// Thus, we rely on original metadata for now.
assert !isExtension || original != null;
KmFunction kmFunction =
isExtension
? original
// TODO(b/70169921): Consult kotlinx.metadata.Flag.Function for kind (e.g., suspend).
: new KmFunction(method.accessFlags.getAsKotlinFlags(), renamedMethod.name.toString());
KmType kmReturnType = toRenamedKmType(method.method.proto.returnType, appView, lens);
assert kmReturnType != null;
kmFunction.setReturnType(kmReturnType);
if (isExtension) {
assert method.method.proto.parameters.values.length > 0;
KmType kmReceiverType =
toRenamedKmType(method.method.proto.parameters.values[0], appView, lens);
assert kmReceiverType != null;
kmFunction.setReceiverParameterType(kmReceiverType);
}
List<KmValueParameter> parameters = kmFunction.getValueParameters();
parameters.clear();
populateKmValueParameters(parameters, method, appView, lens, isExtension);
return kmFunction;
}
private static void populateKmValueParameters(
List<KmValueParameter> parameters,
DexEncodedMethod method,
AppView<AppInfoWithLiveness> appView,
NamingLens lens,
boolean isExtension) {
for (int i = isExtension ? 1 : 0; i < method.method.proto.parameters.values.length; i++) {
DexType paramType = method.method.proto.parameters.values[i];
DebugLocalInfo debugLocalInfo = method.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);
}
}
}