| // 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.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.utils.DescriptorUtils.descriptorToKotlinClassifier; |
| import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor; |
| import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType; |
| import static kotlinx.metadata.Flag.Property.IS_VAR; |
| 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.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexProto; |
| import com.android.tools.r8.graph.DexType; |
| 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.MethodTypeSignature; |
| import com.android.tools.r8.graph.GenericSignature.TypeSignature; |
| import com.android.tools.r8.naming.NamingLens; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| 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.KmTypeProjection; |
| import kotlinx.metadata.KmValueParameter; |
| import kotlinx.metadata.KmVariance; |
| import kotlinx.metadata.jvm.JvmExtensionsKt; |
| |
| class KotlinMetadataSynthesizer { |
| |
| static boolean isExtension(KmFunction kmFunction) { |
| return kmFunction.getReceiverParameterType() != null; |
| } |
| |
| static boolean isExtension(KmProperty kmProperty) { |
| return kmProperty.getReceiverParameterType() != null; |
| } |
| |
| static KmType toKmType(String descriptor) { |
| KmType kmType = new KmType(flagsOf()); |
| kmType.visitClass(descriptorToKotlinClassifier(descriptor)); |
| return kmType; |
| } |
| |
| static DexType toRenamedType( |
| DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) { |
| // 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(); |
| return renamedType; |
| } |
| |
| static String toRenamedBinaryName( |
| DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) { |
| DexType renamedType = toRenamedType(type, appView, lens); |
| 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) { |
| return null; |
| } |
| return descriptorToKotlinClassifier(renamedType.toDescriptorString()); |
| } |
| |
| // TODO(b/148654451): Canonicalization? |
| static 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); |
| } |
| |
| String classifier = toRenamedClassifier(type, appView, lens); |
| if (classifier == null) { |
| return null; |
| } |
| // 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(classifier); |
| // TODO(b/70169921): 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/70169921): 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); |
| } |
| return renamedKmType; |
| } |
| |
| static KmConstructor toRenamedKmConstructor( |
| DexEncodedMethod method, |
| 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; |
| } |
| KmConstructor kmConstructor = new KmConstructor(method.accessFlags.getAsKotlinFlags()); |
| JvmExtensionsKt.setSignature(kmConstructor, toJvmMethodSignature(method.method)); |
| MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView); |
| List<KmValueParameter> parameters = kmConstructor.getValueParameters(); |
| if (!populateKmValueParameters(parameters, method, signature, appView, lens)) { |
| return null; |
| } |
| return kmConstructor; |
| } |
| |
| static KmFunction toRenamedKmFunction( |
| DexEncodedMethod method, |
| AppView<AppInfoWithLiveness> appView, |
| NamingLens lens) { |
| // For library overrides, synthesize @Metadata always. |
| // For regular methods, make sure it is live or pinned. |
| if (!method.isLibraryMethodOverride().isTrue() |
| && !appView.appInfo().isPinned(method.method) |
| && !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.name == method.method.name |
| : method.toSourceString() + " -> " + renamedMethod.toSourceString(); |
| // TODO(b/70169921): Should we keep kotlin-specific flags only while synthesizing the base |
| // value from general JVM flags? |
| int flag = |
| appView.appInfo().isPinned(method.method) && method.getKotlinMemberInfo() != null |
| ? method.getKotlinMemberInfo().flags |
| : method.accessFlags.getAsKotlinFlags(); |
| KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString()); |
| JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod)); |
| |
| // TODO(b/129925954): Should this be integrated as part of DexDefinition instead of parsing it |
| // 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); |
| |
| DexProto proto = method.method.proto; |
| DexType returnType = proto.returnType; |
| TypeSignature returnSignature = signature.returnType().typeSignature(); |
| KmType kmReturnType = setRenamedKmType( |
| returnType, returnSignature, appView, lens, kmFunction::setReturnType); |
| if (kmReturnType == null) { |
| return null; |
| } |
| |
| if (method.isKotlinExtensionFunction()) { |
| 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); |
| if (kmReceiverType == null) { |
| return null; |
| } |
| } |
| |
| List<KmValueParameter> parameters = kmFunction.getValueParameters(); |
| if (!populateKmValueParameters(parameters, method, signature, appView, lens)) { |
| return null; |
| } |
| return kmFunction; |
| } |
| |
| private static boolean populateKmValueParameters( |
| List<KmValueParameter> parameters, |
| DexEncodedMethod method, |
| MethodTypeSignature signature, |
| AppView<AppInfoWithLiveness> appView, |
| NamingLens lens) { |
| boolean isExtension = method.isKotlinExtensionFunction(); |
| for (int i = isExtension ? 1 : 0; i < method.method.proto.parameters.values.length; i++) { |
| DexType parameterType = method.method.proto.parameters.values[i]; |
| DebugLocalInfo debugLocalInfo = method.getParameterInfo().get(i); |
| String parameterName = debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i); |
| KotlinValueParameterInfo valueParameterInfo = |
| method.getKotlinMemberInfo().getValueParameterInfo(isExtension ? i - 1 : i); |
| TypeSignature parameterTypeSignature = signature.getParameterTypeSignature(i); |
| KmValueParameter kmValueParameter = |
| toRewrittenKmValueParameter( |
| valueParameterInfo, |
| parameterType, |
| parameterTypeSignature, |
| parameterName, |
| appView, |
| lens); |
| if (kmValueParameter == null) { |
| return false; |
| } |
| parameters.add(kmValueParameter); |
| } |
| return true; |
| } |
| |
| private static KmValueParameter toRewrittenKmValueParameter( |
| KotlinValueParameterInfo valueParameterInfo, |
| DexType parameterType, |
| TypeSignature parameterTypeSignature, |
| String candidateParameterName, |
| AppView<AppInfoWithLiveness> appView, |
| NamingLens lens) { |
| 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); |
| if (kmParamType == null) { |
| return null; |
| } |
| if (valueParameterInfo != null) { |
| JvmExtensionsKt.getAnnotations(kmParamType).addAll(valueParameterInfo.annotations); |
| } |
| |
| if (valueParameterInfo != null && valueParameterInfo.isVararg) { |
| if (!parameterType.isArrayType()) { |
| return null; |
| } |
| DexType elementType = parameterType.toArrayElementType(appView.dexItemFactory()); |
| TypeSignature elementSignature = |
| parameterTypeSignature != null |
| ? parameterTypeSignature.toArrayElementTypeSignature(appView) : null; |
| KmType kmElementType = setRenamedKmType( |
| elementType, elementSignature, appView, lens, kmValueParameter::setVarargElementType); |
| if (kmElementType == null) { |
| return null; |
| } |
| } |
| |
| return kmValueParameter; |
| } |
| |
| /** |
| * A group of a field, and methods that correspond to a Kotlin property. |
| * |
| * <p> |
| * va(l|r) prop: T |
| * |
| * is converted to a backing field with signature `T prop` if necessary. If the property is a pure |
| * computation, e.g., return `this` or access to the status of other properties, a field is not |
| * needed for the property. |
| * <p> |
| * Depending on property visibility, getter is defined with signature `T getProp()` (or `isProp` |
| * for boolean property). |
| * <p> |
| * If it's editable, i.e., declared as `var`, setter is defined with signature `void setProp(T)`. |
| * <p> |
| * Yet another addition, `void prop$annotations()`, seems(?) to be used to store associated |
| * annotations. |
| * |
| * <p> |
| * Currently, it's unclear how users can selectively keep each. Assuming every piece is kept by |
| * more general rules, such as keeping non-private members, we expect to find those as a whole. |
| * When rewriting a corresponding property, each information is used to update corresponding part, |
| * e.g., field to update the return type of the property, getter to update jvmMethodSignature of |
| * 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; |
| final DexEncodedMethod annotations; |
| final boolean isExtension; |
| |
| private KmPropertyGroup( |
| int flags, |
| String name, |
| DexEncodedField field, |
| DexEncodedMethod getter, |
| int getterFlags, |
| DexEncodedMethod setter, |
| int setterFlags, |
| DexEncodedMethod annotations, |
| boolean isExtension) { |
| this.flags = flags; |
| this.name = name; |
| this.field = field; |
| this.getter = getter; |
| this.getterFlags = getterFlags; |
| this.setter = setter; |
| this.setterFlags = setterFlags; |
| this.annotations = annotations; |
| this.isExtension = isExtension; |
| } |
| |
| static Builder builder(int flags, String name) { |
| return new Builder(flags, name); |
| } |
| |
| static class Builder { |
| private final int flags; |
| private final String name; |
| private DexEncodedField field; |
| private DexEncodedMethod getter; |
| private int getterFlags; |
| private DexEncodedMethod setter; |
| private int setterFlags; |
| private DexEncodedMethod annotations; |
| |
| private boolean isExtensionGetter; |
| private boolean isExtensionSetter; |
| private boolean isExtensionAnnotations; |
| |
| private Builder(int flags, String name) { |
| this.flags = flags; |
| this.name = name; |
| } |
| |
| Builder foundBackingField(DexEncodedField field) { |
| this.field = field; |
| return this; |
| } |
| |
| Builder foundGetter(DexEncodedMethod getter, int flags) { |
| this.getter = getter; |
| this.getterFlags = flags; |
| return this; |
| } |
| |
| Builder foundSetter(DexEncodedMethod setter, int flags) { |
| this.setter = setter; |
| this.setterFlags = flags; |
| return this; |
| } |
| |
| Builder foundAnnotations(DexEncodedMethod annotations) { |
| this.annotations = annotations; |
| return this; |
| } |
| |
| Builder isExtensionGetter() { |
| this.isExtensionGetter = true; |
| return this; |
| } |
| |
| Builder isExtensionSetter() { |
| this.isExtensionSetter = true; |
| return this; |
| } |
| |
| Builder isExtensionAnnotations() { |
| this.isExtensionAnnotations = true; |
| return this; |
| } |
| |
| KmPropertyGroup build() { |
| boolean isExtension = isExtensionGetter || isExtensionSetter || isExtensionAnnotations; |
| // If this is an extension property, everything should be an extension. |
| if (isExtension) { |
| if (getter != null && !isExtensionGetter) { |
| return null; |
| } |
| if (setter != null && !isExtensionSetter) { |
| return null; |
| } |
| if (annotations != null && !isExtensionAnnotations) { |
| return null; |
| } |
| } |
| return new KmPropertyGroup( |
| flags, name, field, getter, getterFlags, setter, setterFlags, annotations, isExtension); |
| } |
| } |
| |
| 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; |
| } |
| 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)); |
| } |
| |
| GetterSetterCriteria criteria = checkGetterCriteria(); |
| if (criteria == GetterSetterCriteria.VIOLATE) { |
| return null; |
| } |
| |
| 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) { |
| // 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()); |
| } |
| |
| criteria = checkSetterCriteria(); |
| if (criteria == GetterSetterCriteria.VIOLATE) { |
| return null; |
| } |
| |
| 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; |
| } |
| } |
| } |
| |
| 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; |
| } |
| } |
| KotlinValueParameterInfo valueParameterInfo = |
| setter.getKotlinMemberInfo().getValueParameterInfo(valueIndex); |
| 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()); |
| } |
| |
| // If the property type remains null at the end, bail out to synthesize this property. |
| if (kmPropertyType == null) { |
| return null; |
| } |
| // For extension property, if the receiver type remains null at the end, bail out too. |
| if (isExtension && kmReceiverType == null) { |
| return null; |
| } |
| // 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)) { |
| kmProperty.setName(renamedPropertyName); |
| } |
| return kmProperty; |
| } |
| |
| enum GetterSetterCriteria { |
| NOT_AVAILABLE, |
| MET, |
| VIOLATE |
| } |
| |
| // Getter should look like: |
| // 1) T getProp(); for regular property, or |
| // 2) T getProp(R); for extension property, where |
| // T is a property type, and R is a receiver type. |
| private GetterSetterCriteria checkGetterCriteria() { |
| if (getter == null) { |
| return GetterSetterCriteria.NOT_AVAILABLE; |
| } |
| // Property type will be checked against a backing field type if any. |
| // And if they're different, we won't synthesize KmProperty for that. |
| // Checking parameter size. |
| if (isExtension) { |
| return getter.method.proto.parameters.size() == 1 |
| ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE; |
| } else { |
| return getter.method.proto.parameters.size() == 0 |
| ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE; |
| } |
| } |
| |
| // Setter should look like: |
| // 1) void setProp(T); for regular property, or |
| // 2) void setProp(R, T); for extension property, where |
| // T is a property type, and R is a receiver type. |
| private GetterSetterCriteria checkSetterCriteria() { |
| if (setter == null) { |
| return GetterSetterCriteria.NOT_AVAILABLE; |
| } |
| if (!setter.method.proto.returnType.isVoidType()) { |
| return GetterSetterCriteria.VIOLATE; |
| } |
| // Property type will be checked against a backing field type if any. |
| // And if they're different, we won't synthesize KmProperty for that. |
| // Plus, receiver type will be checked, too, against a getter. |
| if (isExtension) { |
| return setter.method.proto.parameters.size() == 2 |
| ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE; |
| } else { |
| return setter.method.proto.parameters.size() == 1 |
| ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("KmPropertyGroup {").append(System.lineSeparator()); |
| builder.append(" name: ").append(name).append(System.lineSeparator()); |
| builder.append(" isExtension: ").append(isExtension).append(System.lineSeparator()); |
| if (field != null) { |
| builder.append(" field: ") |
| .append(field.toSourceString()) |
| .append(System.lineSeparator()); |
| } |
| if (getter != null) { |
| builder |
| .append(" getter: ") |
| .append(getter.toSourceString()) |
| .append(System.lineSeparator()); |
| } |
| if (setter != null) { |
| builder |
| .append(" setter: ") |
| .append(setter.toSourceString()) |
| .append(System.lineSeparator()); |
| } |
| if (annotations != null) { |
| builder |
| .append(" annotations: ") |
| .append(annotations.toSourceString()) |
| .append(System.lineSeparator()); |
| } |
| builder.append("}"); |
| return builder.toString(); |
| } |
| } |
| } |