• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.internal.codegen.kotlin;
18 
19 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
20 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
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.base.Preconditions;
29 import com.google.common.collect.ImmutableList;
30 import com.google.common.collect.ImmutableMap;
31 import com.google.common.collect.ImmutableSet;
32 import dagger.internal.codegen.extension.DaggerCollectors;
33 import dagger.internal.codegen.javapoet.TypeNames;
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   /** Gets the synthetic method for annotations of a given field element. */
getSyntheticAnnotationMethod(XFieldElement fieldElement)79   Optional<XMethodElement> getSyntheticAnnotationMethod(XFieldElement fieldElement) {
80     return getAnnotationMethod(fieldElement)
81         .map(
82             methodForAnnotations -> {
83               if (methodForAnnotations == MethodForAnnotations.MISSING) {
84                 throw new IllegalStateException(
85                     "Method for annotations is missing for " + fieldElement);
86               }
87               return methodForAnnotations.method();
88             });
89   }
90 
91   /**
92    * Returns true if the synthetic method for annotations is missing. This can occur when inspecting
93    * the Kotlin metadata of a property from another compilation unit.
94    */
95   boolean isMissingSyntheticAnnotationMethod(XFieldElement fieldElement) {
96     return getAnnotationMethod(fieldElement)
97         .map(methodForAnnotations -> methodForAnnotations == MethodForAnnotations.MISSING)
98         // This can be missing if there was no property annotation at all (e.g. no annotations or
99         // the qualifier is already properly attached to the field). For these cases, it isn't
100         // considered missing since there was no method to look for in the first place.
101         .orElse(false);
102   }
103 
104   private Optional<MethodForAnnotations> getAnnotationMethod(XFieldElement fieldElement) {
105     return elementFieldAnnotationMethodMap.computeIfAbsent(
106         fieldElement, this::getAnnotationMethodUncached);
107   }
108 
109   private Optional<MethodForAnnotations> getAnnotationMethodUncached(XFieldElement fieldElement) {
110     return findProperty(fieldElement)
111         .methodForAnnotationsSignature()
112         .map(
113             signature ->
114                 Optional.ofNullable(methodDescriptors().get(signature))
115                     .map(MethodForAnnotations::create)
116                     // The method may be missing across different compilations.
117                     // See https://youtrack.jetbrains.com/issue/KT-34684
118                     .orElse(MethodForAnnotations.MISSING));
119   }
120 
121   /** Gets the getter method of a given field element corresponding to a property. */
122   Optional<XMethodElement> getPropertyGetter(XFieldElement fieldElement) {
123     return elementFieldGetterMethodMap.computeIfAbsent(
124         fieldElement, this::getPropertyGetterUncached);
125   }
126 
127   private Optional<XMethodElement> getPropertyGetterUncached(XFieldElement fieldElement) {
128     return findProperty(fieldElement)
129         .getterSignature()
130         .flatMap(signature -> Optional.ofNullable(methodDescriptors().get(signature)));
131   }
132 
133   private PropertyMetadata findProperty(XFieldElement field) {
134     String fieldDescriptor = field.getJvmDescriptor();
135     if (classMetadata().propertiesByFieldSignature().containsKey(fieldDescriptor)) {
136       return classMetadata().propertiesByFieldSignature().get(fieldDescriptor);
137     } else {
138       // Fallback to finding property by name, see: https://youtrack.jetbrains.com/issue/KT-35124
139       final String propertyName = getPropertyNameFromField(field);
140       return classMetadata().propertiesByFieldSignature().values().stream()
141           .filter(property -> propertyName.contentEquals(property.name()))
142           .collect(DaggerCollectors.onlyElement());
143     }
144   }
145 
146   private static String getPropertyNameFromField(XFieldElement field) {
147     String name = getSimpleName(field);
148     if (name.endsWith(DELEGATED_PROPERTY_NAME_SUFFIX)) {
149       return name.substring(0, name.length() - DELEGATED_PROPERTY_NAME_SUFFIX.length());
150     } else {
151       return name;
152     }
153   }
154 
155   /** Parse Kotlin class metadata from a given type element. */
156   static KotlinMetadata from(XTypeElement typeElement) {
157     return new AutoValue_KotlinMetadata(typeElement, ClassMetadata.create(metadataOf(typeElement)));
158   }
159 
160   private static KotlinClassMetadata.Class metadataOf(XTypeElement typeElement) {
161     XAnnotation annotationMirror = typeElement.getAnnotation(TypeNames.KOTLIN_METADATA);
162     Preconditions.checkNotNull(annotationMirror);
163     Metadata metadataAnnotation =
164         JvmMetadataUtil.Metadata(
165             annotationMirror.getAsInt("k"),
166             annotationMirror.getAsIntList("mv").stream().mapToInt(Integer::intValue).toArray(),
167             annotationMirror.getAsStringList("d1").toArray(new String[0]),
168             annotationMirror.getAsStringList("d2").toArray(new String[0]),
169             annotationMirror.getAsString("xs"),
170             annotationMirror.getAnnotationValue("pn").hasStringValue()
171                 ? annotationMirror.getAsString("pn")
172                 : null,
173             annotationMirror.getAnnotationValue("xi").hasIntValue()
174                 ? annotationMirror.getAsInt("xi")
175                 : null);
176     KotlinClassMetadata metadata = KotlinClassMetadata.read(metadataAnnotation);
177     if (metadata == null) {
178       // Can happen if Kotlin < 1.0 or if metadata version is not supported, i.e.
179       // kotlinx-metadata-jvm is outdated.
180       throw new IllegalStateException(
181           "Unable to read Kotlin metadata due to unsupported metadata version.");
182     }
183     if (metadata instanceof KotlinClassMetadata.Class) {
184       // TODO(danysantiago): If when we need other types of metadata then move to right method.
185       return (KotlinClassMetadata.Class) metadata;
186     } else {
187       throw new IllegalStateException("Unsupported metadata type: " + metadata);
188     }
189   }
190 
191   @AutoValue
192   abstract static class ClassMetadata extends BaseMetadata {
193     abstract Optional<String> companionObjectName();
194 
195     abstract ImmutableSet<FunctionMetadata> constructors();
196 
197     abstract ImmutableMap<String, FunctionMetadata> functionsBySignature();
198 
199     abstract ImmutableMap<String, PropertyMetadata> propertiesByFieldSignature();
200 
201     static ClassMetadata create(KotlinClassMetadata.Class metadata) {
202       KmClass kmClass = metadata.toKmClass();
203       ClassMetadata.Builder builder =
204           ClassMetadata.builder(
205               kmClass.getFlags(), kmClass.getName()); // // SUPPRESS_GET_NAME_CHECK
206       builder.companionObjectName(Optional.ofNullable(kmClass.getCompanionObject()));
207       kmClass.getConstructors().forEach(it -> builder.addConstructor(FunctionMetadata.create(it)));
208       kmClass.getFunctions().forEach(it -> builder.addFunction(FunctionMetadata.create(it)));
209       kmClass.getProperties().forEach(it -> builder.addProperty(PropertyMetadata.create(it)));
210       return builder.build();
211     }
212 
213     private static Builder builder(int flags, String name) {
214       return new AutoValue_KotlinMetadata_ClassMetadata.Builder().flags(flags).name(name);
215     }
216 
217     @AutoValue.Builder
218     abstract static class Builder implements BaseMetadata.Builder<Builder> {
219       abstract Builder companionObjectName(Optional<String> companionObjectName);
220 
221       abstract ImmutableSet.Builder<FunctionMetadata> constructorsBuilder();
222 
223       abstract ImmutableMap.Builder<String, FunctionMetadata> functionsBySignatureBuilder();
224 
225       abstract ImmutableMap.Builder<String, PropertyMetadata> propertiesByFieldSignatureBuilder();
226 
227       Builder addConstructor(FunctionMetadata constructor) {
228         constructorsBuilder().add(constructor);
229         functionsBySignatureBuilder().put(constructor.signature(), constructor);
230         return this;
231       }
232 
233       Builder addFunction(FunctionMetadata function) {
234         functionsBySignatureBuilder().put(function.signature(), function);
235         return this;
236       }
237 
238       Builder addProperty(PropertyMetadata property) {
239         if (property.fieldSignature().isPresent()) {
240           propertiesByFieldSignatureBuilder().put(property.fieldSignature().get(), property);
241         }
242         return this;
243       }
244 
245       abstract ClassMetadata build();
246     }
247   }
248 
249   @AutoValue
250   abstract static class FunctionMetadata extends BaseMetadata {
251     abstract String signature();
252 
253     abstract ImmutableList<ValueParameterMetadata> parameters();
254 
255     static FunctionMetadata create(KmConstructor metadata) {
256       FunctionMetadata.Builder builder = FunctionMetadata.builder(metadata.getFlags(), "<init>");
257       metadata
258           .getValueParameters()
259           .forEach(
260               it ->
261                   builder.addParameter(
262                       ValueParameterMetadata.create(
263                           it.getFlags(), it.getName()))); // SUPPRESS_GET_NAME_CHECK
264       builder.signature(Objects.requireNonNull(JvmExtensionsKt.getSignature(metadata)).asString());
265       return builder.build();
266     }
267 
268     static FunctionMetadata create(KmFunction metadata) {
269       FunctionMetadata.Builder builder =
270           FunctionMetadata.builder(
271               metadata.getFlags(), metadata.getName()); // SUPPRESS_GET_NAME_CHECK
272       metadata
273           .getValueParameters()
274           .forEach(
275               it ->
276                   builder.addParameter(
277                       ValueParameterMetadata.create(
278                           it.getFlags(), it.getName()))); // SUPPRESS_GET_NAME_CHECK
279       builder.signature(Objects.requireNonNull(JvmExtensionsKt.getSignature(metadata)).asString());
280       return builder.build();
281     }
282 
283     private static Builder builder(int flags, String name) {
284       return new AutoValue_KotlinMetadata_FunctionMetadata.Builder().flags(flags).name(name);
285     }
286 
287     @AutoValue.Builder
288     abstract static class Builder implements BaseMetadata.Builder<Builder> {
289       abstract Builder signature(String signature);
290 
291       abstract ImmutableList.Builder<ValueParameterMetadata> parametersBuilder();
292 
293       Builder addParameter(ValueParameterMetadata parameter) {
294         parametersBuilder().add(parameter);
295         return this;
296       }
297 
298       abstract FunctionMetadata build();
299     }
300   }
301 
302   @AutoValue
303   abstract static class PropertyMetadata extends BaseMetadata {
304     /** Returns the JVM field descriptor of the backing field of this property. */
305     abstract Optional<String> fieldSignature();
306 
307     abstract Optional<String> getterSignature();
308 
309     /** Returns JVM method descriptor of the synthetic method for property annotations. */
310     abstract Optional<String> methodForAnnotationsSignature();
311 
312     static PropertyMetadata create(KmProperty metadata) {
313       PropertyMetadata.Builder builder =
314           PropertyMetadata.builder(
315               metadata.getFlags(), metadata.getName()); // SUPPRESS_GET_NAME_CHECK
316       builder.fieldSignature(
317           Optional.ofNullable(JvmExtensionsKt.getFieldSignature(metadata))
318               .map(JvmFieldSignature::asString));
319       builder.getterSignature(
320           Optional.ofNullable(JvmExtensionsKt.getGetterSignature(metadata))
321               .map(JvmMethodSignature::asString));
322       builder.methodForAnnotationsSignature(
323           Optional.ofNullable(JvmExtensionsKt.getSyntheticMethodForAnnotations(metadata))
324               .map(JvmMethodSignature::asString));
325       return builder.build();
326     }
327 
328     private static Builder builder(int flags, String name) {
329       return new AutoValue_KotlinMetadata_PropertyMetadata.Builder().flags(flags).name(name);
330     }
331 
332     @AutoValue.Builder
333     interface Builder extends BaseMetadata.Builder<Builder> {
334       Builder fieldSignature(Optional<String> signature);
335 
336       Builder getterSignature(Optional<String> signature);
337 
338       Builder methodForAnnotationsSignature(Optional<String> signature);
339 
340       PropertyMetadata build();
341     }
342   }
343 
344   @AutoValue
345   abstract static class ValueParameterMetadata extends BaseMetadata {
346     private static ValueParameterMetadata create(int flags, String name) {
347       return new AutoValue_KotlinMetadata_ValueParameterMetadata(flags, name);
348     }
349   }
350 
351   abstract static class BaseMetadata {
352     /** Returns the Kotlin metadata flags for this property. */
353     abstract int flags();
354 
355     /** returns {@code true} if the given flag (e.g. {@link Flag.IS_PRIVATE}) applies. */
356     boolean flags(Flag flag) {
357       return flag.invoke(flags());
358     }
359 
360     /** Returns the simple name of this property. */
361     abstract String name();
362 
363     interface Builder<BuilderT> {
364       BuilderT flags(int flags);
365 
366       BuilderT name(String name);
367     }
368   }
369 
370   @AutoValue
371   abstract static class MethodForAnnotations {
372     static MethodForAnnotations create(XMethodElement method) {
373       return new AutoValue_KotlinMetadata_MethodForAnnotations(method);
374     }
375 
376     static final MethodForAnnotations MISSING = MethodForAnnotations.create(null);
377 
378     @Nullable
379     abstract XMethodElement method();
380   }
381 }
382