Skip to content

Commit

Permalink
Access the Kotlin metadata API through reflection.
Browse files Browse the repository at this point in the history
This means we don't need a dependency on that API when building AutoValue. Therefore users can freely supply the version of the API that corresponds to the actual Kotlin version they are using. The downside is that they may need to add that dependency explicitly when previously they were getting it through AutoValue. (But possibly getting the wrong version.)

Fixes #1574.

RELNOTES=AutoValue no longer has an explicit dependency on the Kotlin Metadata API. **You may need to add an explicit dependency on `org.jetbrains.kotlinx:kotlinx-metadata-jvm.** The reason for this change is to avoid problems that occurred when the version of the API that AutoValue bundles was different from the version that the user code expected. See #1574.
PiperOrigin-RevId: 556951184
  • Loading branch information
eamonnmcmanus authored and Google Java Core Libraries committed Aug 14, 2023
1 parent b431b02 commit f8f0dec
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 101 deletions.
5 changes: 0 additions & 5 deletions value/processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,6 @@
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-metadata-jvm</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions value/src/it/functional/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-metadata-jvm</artifactId>
<version>0.7.0</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@
import static com.google.auto.common.MoreStreams.toImmutableList;
import static com.google.auto.common.MoreStreams.toImmutableMap;
import static com.google.auto.common.MoreStreams.toImmutableSet;
import static com.google.auto.common.MoreTypes.asTypeElement;
import static com.google.auto.value.processor.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION;
import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME;
import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME;
import static com.google.auto.value.processor.ClassNames.KOTLIN_METADATA_NAME;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;
import static javax.lang.model.util.ElementFilter.constructorsIn;
Expand All @@ -48,7 +46,6 @@
import java.lang.reflect.Field;
import java.util.AbstractMap.SimpleEntry;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
Expand All @@ -72,12 +69,6 @@
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.JavaFileObject;
import kotlinx.metadata.Flag;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmValueParameter;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;

Expand Down Expand Up @@ -457,12 +448,13 @@ private Executable findExecutable(

private ImmutableList<Executable> findRelevantExecutables(
TypeElement ofClass, String callMethod, TypeElement autoBuilderType) {
Optional<AnnotationMirror> kotlinMetadata = kotlinMetadataAnnotation(ofClass);
Optional<KotlinMetadata.KotlinMetadataAnnotation> kotlinMetadata =
KotlinMetadata.kotlinMetadataAnnotation(ofClass);
List<? extends Element> elements = ofClass.getEnclosedElements();
Stream<Executable> relevantExecutables =
callMethod.isEmpty()
? kotlinMetadata
.map(a -> kotlinConstructorsIn(a, ofClass).stream())
.map(a -> KotlinMetadata.kotlinConstructorsIn(errorReporter(), a, ofClass).stream())
.orElseGet(() -> constructorsIn(elements).stream().map(Executable::of))
: methodsIn(elements).stream()
.filter(m -> m.getSimpleName().contentEquals(callMethod))
Expand Down Expand Up @@ -575,91 +567,6 @@ private boolean visibleFrom(Element element, PackageElement fromPackage) {
}
}

private Optional<AnnotationMirror> kotlinMetadataAnnotation(Element element) {
// It would be MUCH simpler if we could just use ofClass.getAnnotation(Metadata.class).
// However that would be unsound. We want to shade the Kotlin runtime, including
// kotlin.Metadata, so as not to interfere with other things on the annotation classpath that
// might have a different version of the runtime. That means that if we referenced
// kotlin.Metadata.class here we would actually be referencing
// autovalue.shaded.kotlin.Metadata.class. Obviously the Kotlin class doesn't have that
// annotation.
return element.getAnnotationMirrors().stream()
.filter(
a ->
asTypeElement(a.getAnnotationType())
.getQualifiedName()
.contentEquals(KOTLIN_METADATA_NAME))
.<AnnotationMirror>map(a -> a) // get rid of that stupid wildcard
.findFirst();
}

/**
* Use Kotlin reflection to build {@link Executable} instances for the constructors in {@code
* ofClass} that include information about which parameters have default values.
*/
private ImmutableList<Executable> kotlinConstructorsIn(
AnnotationMirror metadata, TypeElement ofClass) {
ImmutableMap<String, AnnotationValue> annotationValues =
AnnotationMirrors.getAnnotationValuesWithDefaults(metadata).entrySet().stream()
.collect(toImmutableMap(e -> e.getKey().getSimpleName().toString(), e -> e.getValue()));
// We match the KmConstructor instances with the ExecutableElement instances based on the
// parameter names. We could possibly just assume that the constructors are in the same order.
Map<ImmutableSet<String>, ExecutableElement> map =
constructorsIn(ofClass.getEnclosedElements()).stream()
.collect(toMap(c -> parameterNames(c), c -> c, (a, b) -> a, LinkedHashMap::new));
ImmutableMap<ImmutableSet<String>, ExecutableElement> paramNamesToConstructor =
ImmutableMap.copyOf(map);
KotlinClassHeader header =
new KotlinClassHeader(
(Integer) annotationValues.get("k").getValue(),
intArrayValue(annotationValues.get("mv")),
stringArrayValue(annotationValues.get("d1")),
stringArrayValue(annotationValues.get("d2")),
(String) annotationValues.get("xs").getValue(),
(String) annotationValues.get("pn").getValue(),
(Integer) annotationValues.get("xi").getValue());
KotlinClassMetadata.Class classMetadata =
(KotlinClassMetadata.Class) KotlinClassMetadata.read(header);
KmClass kmClass = classMetadata.toKmClass();
ImmutableList.Builder<Executable> kotlinConstructorsBuilder = ImmutableList.builder();
for (KmConstructor constructor : kmClass.getConstructors()) {
ImmutableSet.Builder<String> allBuilder = ImmutableSet.builder();
ImmutableSet.Builder<String> optionalBuilder = ImmutableSet.builder();
for (KmValueParameter param : constructor.getValueParameters()) {
String name = param.getName();
allBuilder.add(name);
if (Flag.ValueParameter.DECLARES_DEFAULT_VALUE.invoke(param.getFlags())) {
optionalBuilder.add(name);
}
}
ImmutableSet<String> optional = optionalBuilder.build();
ImmutableSet<String> all = allBuilder.build();
ExecutableElement javaConstructor = paramNamesToConstructor.get(all);
if (javaConstructor != null) {
kotlinConstructorsBuilder.add(Executable.of(javaConstructor, optional));
}
}
return kotlinConstructorsBuilder.build();
}

private static int[] intArrayValue(AnnotationValue value) {
@SuppressWarnings("unchecked")
List<AnnotationValue> list = (List<AnnotationValue>) value.getValue();
return list.stream().mapToInt(v -> (int) v.getValue()).toArray();
}

private static String[] stringArrayValue(AnnotationValue value) {
@SuppressWarnings("unchecked")
List<AnnotationValue> list = (List<AnnotationValue>) value.getValue();
return list.stream().map(AnnotationValue::getValue).toArray(String[]::new);
}

private static ImmutableSet<String> parameterNames(ExecutableElement executableElement) {
return executableElement.getParameters().stream()
.map(v -> v.getSimpleName().toString())
.collect(toImmutableSet());
}

private static final ElementKind ELEMENT_KIND_RECORD = elementKindRecord();

private static ElementKind elementKindRecord() {
Expand Down
Loading

0 comments on commit f8f0dec

Please sign in to comment.