| // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.android.tools.r8.kotlin; |
| |
| import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction; |
| import static kotlinx.metadata.Flag.Class.IS_COMPANION_OBJECT; |
| |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.KmPropertyGroup; |
| import com.android.tools.r8.naming.NamingLens; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.StringUtils; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.function.BiFunction; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| import kotlinx.metadata.KmDeclarationContainer; |
| import kotlinx.metadata.KmFunction; |
| import kotlinx.metadata.KmProperty; |
| import kotlinx.metadata.KmType; |
| import kotlinx.metadata.KmTypeAlias; |
| import kotlinx.metadata.KmTypeParameter; |
| import kotlinx.metadata.jvm.KotlinClassHeader; |
| import kotlinx.metadata.jvm.KotlinClassMetadata; |
| |
| // Provides access to package/class-level Kotlin information. |
| public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> { |
| final DexClass clazz; |
| |
| KotlinInfo(MetadataKind metadata, DexClass clazz) { |
| assert clazz != null; |
| this.clazz = clazz; |
| processMetadata(metadata); |
| } |
| |
| // Subtypes will define how to process the given metadata. |
| abstract void processMetadata(MetadataKind metadata); |
| |
| // Subtypes will define how to rewrite metadata after shrinking and minification. |
| // Subtypes that represent subtypes of {@link KmDeclarationContainer} can use |
| // {@link #rewriteDeclarationContainer} below. |
| abstract void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens); |
| |
| abstract KotlinClassHeader createHeader(); |
| |
| public enum Kind { |
| Class, File, Synthetic, Part, Facade |
| } |
| |
| public abstract Kind getKind(); |
| |
| public boolean isClass() { |
| return false; |
| } |
| |
| public KotlinClass asClass() { |
| return null; |
| } |
| |
| public boolean isFile() { |
| return false; |
| } |
| |
| public KotlinFile asFile() { |
| return null; |
| } |
| |
| public boolean isSyntheticClass() { |
| return false; |
| } |
| |
| public KotlinSyntheticClass asSyntheticClass() { |
| return null; |
| } |
| |
| public boolean isClassPart() { |
| return false; |
| } |
| |
| public KotlinClassPart asClassPart() { |
| return null; |
| } |
| |
| public boolean isClassFacade() { |
| return false; |
| } |
| |
| public KotlinClassFacade asClassFacade() { |
| return null; |
| } |
| |
| boolean hasDeclarations() { |
| return isClass() || isFile() || isClassPart(); |
| } |
| |
| KmDeclarationContainer getDeclarations() { |
| if (isClass()) { |
| return asClass().kmClass; |
| } else if (isFile()) { |
| return asFile().kmPackage; |
| } else if (isClassPart()) { |
| return asClassPart().kmPackage; |
| } else { |
| throw new Unreachable("Unexpected KotlinInfo: " + this); |
| } |
| } |
| |
| // {@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) { |
| assert clazz != null; |
| |
| KmDeclarationContainer kmDeclarationContainer = getDeclarations(); |
| 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; |
| if (isClass()) { |
| KotlinClass ktClass = asClass(); |
| if (IS_COMPANION_OBJECT.invoke(ktClass.kmClass.getFlags()) && ktClass.hostClass != null) { |
| fields = ktClass.hostClass.fields(); |
| backingFieldTester = DexEncodedField::isKotlinBackingFieldForCompanionObject; |
| } |
| } |
| |
| for (DexEncodedField field : fields) { |
| if (backingFieldTester.test(field)) { |
| String name = field.getKotlinMemberInfo().propertyName; |
| assert name != null; |
| KmPropertyGroup.Builder builder = |
| propertyGroupBuilderMap.computeIfAbsent( |
| name, |
| k -> KmPropertyGroup.builder(field.getKotlinMemberInfo().propertyFlags, name)); |
| builder.foundBackingField(field); |
| } |
| } |
| |
| List<KmFunction> functions = kmDeclarationContainer.getFunctions(); |
| functions.clear(); |
| for (DexEncodedMethod method : clazz.methods()) { |
| if (method.isInitializer()) { |
| continue; |
| } |
| |
| if (method.isKotlinFunction() || method.isKotlinExtensionFunction()) { |
| KmFunction function = toRenamedKmFunction(method, appView, lens); |
| if (function != null) { |
| functions.add(function); |
| } |
| continue; |
| } |
| if (method.isKotlinProperty() || method.isKotlinExtensionProperty()) { |
| String name = method.getKotlinMemberInfo().propertyName; |
| assert name != null; |
| KmPropertyGroup.Builder builder = |
| propertyGroupBuilderMap.computeIfAbsent( |
| 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(method.getKotlinMemberInfo().propertyFlags, name)); |
| switch (method.getKotlinMemberInfo().memberKind) { |
| case EXTENSION_PROPERTY_GETTER: |
| builder.isExtensionGetter(); |
| // fallthrough; |
| case PROPERTY_GETTER: |
| builder.foundGetter(method, method.getKotlinMemberInfo().flags); |
| break; |
| case EXTENSION_PROPERTY_SETTER: |
| builder.isExtensionSetter(); |
| // fallthrough; |
| case PROPERTY_SETTER: |
| builder.foundSetter(method, method.getKotlinMemberInfo().flags); |
| break; |
| case EXTENSION_PROPERTY_ANNOTATIONS: |
| builder.isExtensionAnnotations(); |
| // fallthrough; |
| case PROPERTY_ANNOTATIONS: |
| builder.foundAnnotations(method); |
| break; |
| default: |
| throw new Unreachable("Not a Kotlin property: " + method.getKotlinMemberInfo()); |
| } |
| continue; |
| } |
| |
| // TODO(b/151194869): What should we do for methods that fall into this category---no mark? |
| } |
| |
| List<KmProperty> properties = kmDeclarationContainer.getProperties(); |
| properties.clear(); |
| for (KmPropertyGroup.Builder builder : propertyGroupBuilderMap.values()) { |
| KmPropertyGroup group = builder.build(); |
| if (group == null) { |
| continue; |
| } |
| KmProperty property = group.toRenamedKmProperty(appView, lens); |
| if (property != null) { |
| properties.add(property); |
| } |
| } |
| } |
| |
| public abstract String toString(String indent); |
| |
| String kmDeclarationContainerToString(String indent) { |
| StringBuilder sb = new StringBuilder(); |
| KmDeclarationContainer declarations = getDeclarations(); |
| appendKmSection(indent, "functions", declarations.getFunctions(), this::kmFunctionToString, sb); |
| appendKmSection( |
| indent, "properties", declarations.getProperties(), this::kmPropertyToString, sb); |
| appendKmSection( |
| indent, "typeAliases", declarations.getTypeAliases(), this::kmTypeAliasToString, sb); |
| return sb.toString(); |
| } |
| |
| final String INDENT = " "; |
| |
| private <T> void appendKmSection( |
| String indent, |
| String header, |
| List<T> items, |
| BiFunction<String, T, String> stringify, |
| StringBuilder sb) { |
| if (items.size() > 0) { |
| sb.append(indent); |
| sb.append(header); |
| sb.append(": ["); |
| sb.append(StringUtils.LINE_SEPARATOR); |
| } |
| for (T item : items) { |
| sb.append(stringify.apply(indent + INDENT, item)); |
| sb.append(","); |
| sb.append(StringUtils.LINE_SEPARATOR); |
| } |
| if (items.size() > 0) { |
| sb.append(indent); |
| sb.append("]"); |
| sb.append(StringUtils.LINE_SEPARATOR); |
| } |
| } |
| |
| private String kmFunctionToString(String indent, KmFunction function) { |
| assert function != null; |
| StringBuilder sb = new StringBuilder(); |
| sb.append(indent); |
| sb.append("KmFunction {"); |
| sb.append(StringUtils.LINE_SEPARATOR); |
| String newIndent = indent + INDENT; |
| KmType receiverParameterType = function.getReceiverParameterType(); |
| appendKeyValue( |
| newIndent, |
| "receiverParameterType", |
| receiverParameterType == null ? "null" : kmTypeToString(receiverParameterType), |
| sb); |
| appendKeyValue(newIndent, "returnType", kmTypeToString(function.returnType), sb); |
| appendKeyValue(newIndent, "name", function.getName(), sb); |
| // TODO(b/148581822): Print flags, generic signature etc. |
| sb.append(indent); |
| sb.append("}"); |
| return sb.toString(); |
| } |
| |
| private String kmPropertyToString(String indent, KmProperty property) { |
| // TODO(b/148581822): Add information. |
| return indent + "KmProperty { " + property + "}"; |
| } |
| |
| private String kmTypeAliasToString(String indent, KmTypeAlias alias) { |
| assert alias != null; |
| StringBuilder sb = new StringBuilder(indent); |
| sb.append("KmTypeAlias {"); |
| sb.append(StringUtils.LINE_SEPARATOR); |
| String newIndent = indent + INDENT; |
| appendKeyValue(newIndent, "name", alias.getName(), sb); |
| if (!alias.getTypeParameters().isEmpty()) { |
| appendKeyValue( |
| newIndent, |
| "typeParameters", |
| alias.getTypeParameters().stream() |
| .map(KmTypeParameter::getName) |
| .collect(Collectors.joining(",")), |
| sb); |
| } |
| appendType(newIndent, "underlyingType", alias.underlyingType, sb); |
| appendType(newIndent, "expandedType", alias.expandedType, sb); |
| // TODO(b/151783973): Extend with annotations. |
| sb.append(indent); |
| sb.append("}"); |
| return sb.toString(); |
| } |
| |
| void appendType(String indent, String key, KmType kmType, StringBuilder sb) { |
| sb.append(indent); |
| sb.append(key); |
| sb.append(" {"); |
| sb.append(StringUtils.LINE_SEPARATOR); |
| String newIndent = indent + INDENT; |
| appendKeyValue(newIndent, "classifier", kmType.classifier.toString(), sb); |
| if (!kmType.getArguments().isEmpty()) { |
| appendKeyValue( |
| newIndent, |
| "arguments", |
| kmType.getArguments().stream() |
| .map(arg -> arg.getType().classifier.toString()) |
| .collect(Collectors.joining(",")), |
| sb); |
| } |
| sb.append(indent); |
| sb.append("}"); |
| sb.append(StringUtils.LINE_SEPARATOR); |
| } |
| |
| void appendKeyValue(String indent, String key, String value, StringBuilder sb) { |
| sb.append(indent); |
| sb.append(key); |
| sb.append(": "); |
| sb.append(value); |
| sb.append(StringUtils.LINE_SEPARATOR); |
| } |
| |
| private String kmTypeToString(KmType type) { |
| return DescriptorUtils.getDescriptorFromKmType(type); |
| } |
| |
| @Override |
| public String toString() { |
| return toString(""); |
| } |
| } |