Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access the Kotlin metadata API through reflection. #1575

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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