• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.hilt.processor.internal.kotlin;
18 
19 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
20 import static kotlinx.metadata.Flag.ValueParameter.DECLARES_DEFAULT_VALUE;
21 
22 import androidx.room.compiler.processing.XAnnotation;
23 import androidx.room.compiler.processing.XFieldElement;
24 import androidx.room.compiler.processing.XMethodElement;
25 import androidx.room.compiler.processing.XTypeElement;
26 import com.google.auto.value.AutoValue;
27 import com.google.auto.value.extension.memoized.Memoized;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.ImmutableMap;
30 import com.google.common.collect.ImmutableSet;
31 import dagger.hilt.processor.internal.ClassNames;
32 import dagger.internal.codegen.extension.DaggerCollectors;
33 import dagger.internal.codegen.xprocessing.XElements;
34 import java.util.HashMap;
35 import java.util.Map;
36 import java.util.Objects;
37 import java.util.Optional;
38 import java.util.function.Function;
39 import javax.annotation.Nullable;
40 import kotlin.Metadata;
41 import kotlinx.metadata.Flag;
42 import kotlinx.metadata.KmClass;
43 import kotlinx.metadata.KmConstructor;
44 import kotlinx.metadata.KmFunction;
45 import kotlinx.metadata.KmProperty;
46 import kotlinx.metadata.jvm.JvmExtensionsKt;
47 import kotlinx.metadata.jvm.JvmFieldSignature;
48 import kotlinx.metadata.jvm.JvmMetadataUtil;
49 import kotlinx.metadata.jvm.JvmMethodSignature;
50 import kotlinx.metadata.jvm.KotlinClassMetadata;
51 
52 /** Data class of a TypeElement and its Kotlin metadata. */
53 @AutoValue
54 abstract class KotlinMetadata {
55   // Kotlin suffix for fields that are for a delegated property.
56   // See:
57   // https://github.com/JetBrains/kotlin/blob/master/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAbi.kt#L32
58   private static final String DELEGATED_PROPERTY_NAME_SUFFIX = "$delegate";
59 
60   // Map that associates field elements with its Kotlin synthetic method for annotations.
61   private final Map<XFieldElement, Optional<MethodForAnnotations>> elementFieldAnnotationMethodMap =
62       new HashMap<>();
63 
64   // Map that associates field elements with its Kotlin getter method.
65   private final Map<XFieldElement, Optional<XMethodElement>> elementFieldGetterMethodMap =
66       new HashMap<>();
67 
typeElement()68   abstract XTypeElement typeElement();
69 
classMetadata()70   abstract ClassMetadata classMetadata();
71 
72   @Memoized
methodDescriptors()73   ImmutableMap<String, XMethodElement> methodDescriptors() {
74     return typeElement().getDeclaredMethods().stream()
75         .collect(toImmutableMap(XMethodElement::getJvmDescriptor, Function.identity()));
76   }
77 
78   /** Returns true if any constructor of the defined a default parameter. */
79   @Memoized
containsConstructorWithDefaultParam()80   boolean containsConstructorWithDefaultParam() {
81     return classMetadata().constructors().stream()
82         .flatMap(constructor -> constructor.parameters().stream())
83         .anyMatch(parameter -> parameter.flags(DECLARES_DEFAULT_VALUE));
84   }
85 
86   /** Gets the synthetic method for annotations of a given field element. */
getSyntheticAnnotationMethod(XFieldElement fieldElement)87   Optional<XMethodElement> getSyntheticAnnotationMethod(XFieldElement fieldElement) {
88     return getAnnotationMethod(fieldElement)
89         .map(
90             methodForAnnotations -> {
91               if (methodForAnnotations == MethodForAnnotations.MISSING) {
92                 throw new IllegalStateException(
93                     "Method for annotations is missing for " + fieldElement);
94               }
95               return XElements.asMethod(methodForAnnotations.method());
96             });
97   }
98 
99   private Optional<MethodForAnnotations> getAnnotationMethod(XFieldElement fieldElement) {
100     return elementFieldAnnotationMethodMap.computeIfAbsent(
101         fieldElement, this::getAnnotationMethodUncached);
102   }
103 
104   private Optional<MethodForAnnotations> getAnnotationMethodUncached(XFieldElement fieldElement) {
105     return findProperty(fieldElement)
106         .methodForAnnotationsSignature()
107         .map(
108             signature ->
109                 Optional.ofNullable(methodDescriptors().get(signature))
110                     .map(MethodForAnnotations::create)
111                     // The method may be missing across different compilations.
112                     // See https://youtrack.jetbrains.com/issue/KT-34684
113                     .orElse(MethodForAnnotations.MISSING));
114   }
115 
116   /** Gets the getter method of a given field element corresponding to a property. */
117   Optional<XMethodElement> getPropertyGetter(XFieldElement fieldElement) {
118     return elementFieldGetterMethodMap.computeIfAbsent(
119         fieldElement, this::getPropertyGetterUncached);
120   }
121 
122   private Optional<XMethodElement> getPropertyGetterUncached(XFieldElement fieldElement) {
123     return findProperty(fieldElement)
124         .getterSignature()
125         .flatMap(signature -> Optional.ofNullable(methodDescriptors().get(signature)));
126   }
127 
128   private PropertyMetadata findProperty(XFieldElement field) {
129     String fieldDescriptor = field.getJvmDescriptor();
130     if (classMetadata().propertiesByFieldSignature().containsKey(fieldDescriptor)) {
131       return classMetadata().propertiesByFieldSignature().get(fieldDescriptor);
132     } else {
133       // Fallback to finding property by name, see: https://youtrack.jetbrains.com/issue/KT-35124
134       final String propertyName = getPropertyNameFromField(field);
135       return classMetadata().propertiesByFieldSignature().values().stream()
136           .filter(property -> propertyName.contentEquals(property.name()))
137           .collect(DaggerCollectors.onlyElement());
138     }
139   }
140 
141   private static String getPropertyNameFromField(XFieldElement field) {
142     String name = XElements.getSimpleName(field);
143     if (name.endsWith(DELEGATED_PROPERTY_NAME_SUFFIX)) {
144       return name.substring(0, name.length() - DELEGATED_PROPERTY_NAME_SUFFIX.length());
145     } else {
146       return name;
147     }
148   }
149 
150   /** Parse Kotlin class metadata from a given type element. */
151   static KotlinMetadata from(XTypeElement typeElement) {
152     return new AutoValue_KotlinMetadata(typeElement, ClassMetadata.create(metadataOf(typeElement)));
153   }
154 
155   private static KotlinClassMetadata.Class metadataOf(XTypeElement typeElement) {
156     XAnnotation annotation = typeElement.getAnnotation(ClassNames.KOTLIN_METADATA);
157     Metadata metadataAnnotation =
158         JvmMetadataUtil.Metadata(
159             annotation.getAsInt("k"),
160             annotation.getAsIntList("mv").stream().mapToInt(Integer::intValue).toArray(),
161             annotation.getAsStringList("d1").toArray(new String[0]),
162             annotation.getAsStringList("d2").toArray(new String[0]),
163             annotation.getAsString("xs"),
164             getOptionalStringValue(annotation, "pn").orElse(null),
165             getOptionalIntValue(annotation, "xi").orElse(null));
166     KotlinClassMetadata metadata = KotlinClassMetadata.read(metadataAnnotation);
167     if (metadata == null) {
168       // Can happen if Kotlin < 1.0 or if metadata version is not supported, i.e.
169       // kotlinx-metadata-jvm is outdated.
170       throw new IllegalStateException(
171           "Unable to read Kotlin metadata due to unsupported metadata version.");
172     }
173     if (metadata instanceof KotlinClassMetadata.Class) {
174       // TODO(danysantiago): If when we need other types of metadata then move to right method.
175       return (KotlinClassMetadata.Class) metadata;
176     } else {
177       throw new IllegalStateException("Unsupported metadata type: " + metadata);
178     }
179   }
180 
181   @AutoValue
182   abstract static class ClassMetadata extends BaseMetadata {
183     abstract Optional<String> companionObjectName();
184 
185     abstract ImmutableSet<FunctionMetadata> constructors();
186 
187     abstract ImmutableMap<String, FunctionMetadata> functionsBySignature();
188 
189     abstract ImmutableMap<String, PropertyMetadata> propertiesByFieldSignature();
190 
191     static ClassMetadata create(KotlinClassMetadata.Class metadata) {
192       KmClass kmClass = metadata.toKmClass();
193       ClassMetadata.Builder builder = ClassMetadata.builder(kmClass.getFlags(), kmClass.getName());
194       builder.companionObjectName(Optional.ofNullable(kmClass.getCompanionObject()));
195       kmClass.getConstructors().forEach(it -> builder.addConstructor(FunctionMetadata.create(it)));
196       kmClass.getFunctions().forEach(it -> builder.addFunction(FunctionMetadata.create(it)));
197       kmClass.getProperties().forEach(it -> builder.addProperty(PropertyMetadata.create(it)));
198       return builder.build();
199     }
200 
201     private static Builder builder(int flags, String name) {
202       return new AutoValue_KotlinMetadata_ClassMetadata.Builder().flags(flags).name(name);
203     }
204 
205     @AutoValue.Builder
206     abstract static class Builder implements BaseMetadata.Builder<Builder> {
207       abstract Builder companionObjectName(Optional<String> companionObjectName);
208 
209       abstract ImmutableSet.Builder<FunctionMetadata> constructorsBuilder();
210 
211       abstract ImmutableMap.Builder<String, FunctionMetadata> functionsBySignatureBuilder();
212 
213       abstract ImmutableMap.Builder<String, PropertyMetadata> propertiesByFieldSignatureBuilder();
214 
215       Builder addConstructor(FunctionMetadata constructor) {
216         constructorsBuilder().add(constructor);
217         functionsBySignatureBuilder().put(constructor.signature(), constructor);
218         return this;
219       }
220 
221       Builder addFunction(FunctionMetadata function) {
222         functionsBySignatureBuilder().put(function.signature(), function);
223         return this;
224       }
225 
226       Builder addProperty(PropertyMetadata property) {
227         if (property.fieldSignature().isPresent()) {
228           propertiesByFieldSignatureBuilder().put(property.fieldSignature().get(), property);
229         }
230         return this;
231       }
232 
233       abstract ClassMetadata build();
234     }
235   }
236 
237   @AutoValue
238   abstract static class FunctionMetadata extends BaseMetadata {
239     abstract String signature();
240 
241     abstract ImmutableList<ValueParameterMetadata> parameters();
242 
243     static FunctionMetadata create(KmConstructor metadata) {
244       FunctionMetadata.Builder builder = FunctionMetadata.builder(metadata.getFlags(), "<init>");
245       metadata
246           .getValueParameters()
247           .forEach(
248               it ->
249                   builder.addParameter(ValueParameterMetadata.create(it.getFlags(), it.getName())));
250       builder.signature(Objects.requireNonNull(JvmExtensionsKt.getSignature(metadata)).asString());
251       return builder.build();
252     }
253 
254     static FunctionMetadata create(KmFunction metadata) {
255       FunctionMetadata.Builder builder =
256           FunctionMetadata.builder(metadata.getFlags(), metadata.getName());
257       metadata
258           .getValueParameters()
259           .forEach(
260               it ->
261                   builder.addParameter(ValueParameterMetadata.create(it.getFlags(), it.getName())));
262       builder.signature(Objects.requireNonNull(JvmExtensionsKt.getSignature(metadata)).asString());
263       return builder.build();
264     }
265 
266     private static Builder builder(int flags, String name) {
267       return new AutoValue_KotlinMetadata_FunctionMetadata.Builder().flags(flags).name(name);
268     }
269 
270     @AutoValue.Builder
271     abstract static class Builder implements BaseMetadata.Builder<Builder> {
272       abstract Builder signature(String signature);
273 
274       abstract ImmutableList.Builder<ValueParameterMetadata> parametersBuilder();
275 
276       Builder addParameter(ValueParameterMetadata parameter) {
277         parametersBuilder().add(parameter);
278         return this;
279       }
280 
281       abstract FunctionMetadata build();
282     }
283   }
284 
285   @AutoValue
286   abstract static class PropertyMetadata extends BaseMetadata {
287     /** Returns the JVM field descriptor of the backing field of this property. */
288     abstract Optional<String> fieldSignature();
289 
290     abstract Optional<String> getterSignature();
291 
292     /** Returns JVM method descriptor of the synthetic method for property annotations. */
293     abstract Optional<String> methodForAnnotationsSignature();
294 
295     static PropertyMetadata create(KmProperty metadata) {
296       PropertyMetadata.Builder builder =
297           PropertyMetadata.builder(metadata.getFlags(), metadata.getName());
298       builder.fieldSignature(
299           Optional.ofNullable(JvmExtensionsKt.getFieldSignature(metadata))
300               .map(JvmFieldSignature::asString));
301       builder.getterSignature(
302           Optional.ofNullable(JvmExtensionsKt.getGetterSignature(metadata))
303               .map(JvmMethodSignature::asString));
304       builder.methodForAnnotationsSignature(
305           Optional.ofNullable(JvmExtensionsKt.getSyntheticMethodForAnnotations(metadata))
306               .map(JvmMethodSignature::asString));
307       return builder.build();
308     }
309 
310     private static Builder builder(int flags, String name) {
311       return new AutoValue_KotlinMetadata_PropertyMetadata.Builder().flags(flags).name(name);
312     }
313 
314     @AutoValue.Builder
315     interface Builder extends BaseMetadata.Builder<Builder> {
316       Builder fieldSignature(Optional<String> signature);
317 
318       Builder getterSignature(Optional<String> signature);
319 
320       Builder methodForAnnotationsSignature(Optional<String> signature);
321 
322       PropertyMetadata build();
323     }
324   }
325 
326   @AutoValue
327   abstract static class ValueParameterMetadata extends BaseMetadata {
328     private static ValueParameterMetadata create(int flags, String name) {
329       return new AutoValue_KotlinMetadata_ValueParameterMetadata(flags, name);
330     }
331   }
332 
333   abstract static class BaseMetadata {
334     /** Returns the Kotlin metadata flags for this property. */
335     abstract int flags();
336 
337     /** returns {@code true} if the given flag (e.g. {@link Flag.IS_PRIVATE}) applies. */
338     boolean flags(Flag flag) {
339       return flag.invoke(flags());
340     }
341 
342     /** Returns the simple name of this property. */
343     abstract String name();
344 
345     interface Builder<BuilderT> {
346       BuilderT flags(int flags);
347 
348       BuilderT name(String name);
349     }
350   }
351 
352   @AutoValue
353   abstract static class MethodForAnnotations {
354     static MethodForAnnotations create(XMethodElement method) {
355       return new AutoValue_KotlinMetadata_MethodForAnnotations(method);
356     }
357 
358     static final MethodForAnnotations MISSING = MethodForAnnotations.create(null);
359 
360     @Nullable
361     abstract XMethodElement method();
362   }
363 
364   private static Optional<Integer> getOptionalIntValue(XAnnotation annotation, String valueName) {
365     return isValuePresent(annotation, valueName)
366         ? Optional.of(annotation.getAsInt(valueName))
367         : Optional.empty();
368   }
369 
370   private static Optional<String> getOptionalStringValue(XAnnotation annotation, String valueName) {
371     return isValuePresent(annotation, valueName)
372         ? Optional.of(annotation.getAsString(valueName))
373         : Optional.empty();
374   }
375 
376   private static boolean isValuePresent(XAnnotation annotation, String valueName) {
377     return annotation.getAnnotationValues().stream()
378         .anyMatch(member -> member.getName().equals(valueName));
379   }
380 }
381