Use kotlin.Metadata instead of old annotations in IDE, reflection and tests

This commit is contained in:
Alexander Udalov
2016-01-26 17:20:25 +03:00
parent 7da981e12c
commit ba80e8ba81
6 changed files with 59 additions and 48 deletions
@@ -30,8 +30,7 @@ import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.*
object InlineTestUtil {
private val KOTLIN_MULTIFILE_CLASS_DESC =
AsmUtil.asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_MULTIFILE_CLASS)
private val KOTLIN_METADATA_DESC = AsmUtil.asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.METADATA)
fun checkNoCallsToInline(files: Iterable<OutputFile>, sourceFiles: List<KtFile>) {
val inlineInfo = obtainInlineInfo(files)
@@ -87,14 +86,20 @@ object InlineTestUtil {
val notInlined = ArrayList<NotInlinedCall>()
files.forEach { file ->
val cr = ClassReader(file.asByteArray())
cr.accept(object : ClassVisitorWithName() {
ClassReader(file.asByteArray()).accept(object : ClassVisitorWithName() {
private var skipMethodsOfThisClass = false
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
if (desc == KOTLIN_MULTIFILE_CLASS_DESC) {
skipMethodsOfThisClass = true
if (desc == KOTLIN_METADATA_DESC) {
return object : AnnotationVisitor(Opcodes.ASM5) {
override fun visit(name: String?, value: Any) {
if (name == JvmAnnotationNames.KIND_FIELD_NAME && value == KotlinClassHeader.Kind.MULTIFILE_CLASS.id) {
skipMethodsOfThisClass = true
}
}
}
}
return null
}
@@ -29,7 +29,8 @@ import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.*;
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.METADATA;
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.METADATA_VERSION_FIELD_NAME;
public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
public static final FqName PACKAGE_NAME = new FqName("test");
@@ -112,14 +113,14 @@ public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
}
private void doTestKotlinSyntheticClass(@NotNull String code, @NotNull String classFilePart) {
doTest(code, classFilePart, KOTLIN_SYNTHETIC_CLASS);
doTest(code, classFilePart);
}
private void doTestKotlinClass(@NotNull String code, @NotNull String classFilePart) {
doTest(code, classFilePart, KOTLIN_CLASS);
doTest(code, classFilePart);
}
private void doTest(@NotNull String code, @NotNull final String classFilePart, @NotNull FqName annotationFqName) {
private void doTest(@NotNull String code, @NotNull final String classFilePart) {
loadText("package " + PACKAGE_NAME + "\n\n" + code);
List<OutputFile> output = generateClassesInFile().asList();
Collection<OutputFile> files = Collections2.filter(output, new Predicate<OutputFile>() {
@@ -134,16 +135,17 @@ public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
String path = files.iterator().next().getRelativePath();
String fqName = path.substring(0, path.length() - ".class".length()).replace('/', '.');
Class<?> aClass = generateClass(fqName);
assertAnnotatedWith(aClass, annotationFqName.asString());
assertAnnotatedWithMetadata(aClass);
}
private void assertAnnotatedWith(@NotNull Class<?> aClass, @NotNull String annotationFqName) {
private void assertAnnotatedWithMetadata(@NotNull Class<?> aClass) {
String annotationFqName = METADATA.asString();
Class<? extends Annotation> annotationClass = loadAnnotationClassQuietly(annotationFqName);
assertTrue("No annotation " + annotationFqName + " found in " + aClass, aClass.isAnnotationPresent(annotationClass));
Annotation annotation = aClass.getAnnotation(annotationClass);
int[] version = (int[]) CodegenTestUtil.getAnnotationAttribute(annotation, VERSION_FIELD_NAME);
int[] version = (int[]) CodegenTestUtil.getAnnotationAttribute(annotation, METADATA_VERSION_FIELD_NAME);
assertNotNull(version);
assertTrue("Annotation " + annotationFqName + " is written with an unsupported format",
new JvmMetadataVersion(version).isCompatible());
@@ -79,7 +79,7 @@ abstract class AbstractLocalClassProtoTest : TestCaseWithTmpdir() {
@Suppress("UNCHECKED_CAST")
private fun assertHasAnnotationData(clazz: Class<*>) {
checkNotNull(clazz.getAnnotation(
clazz.classLoader.loadClass(JvmAnnotationNames.KOTLIN_CLASS.asString()) as Class<Annotation>
)) { "KotlinClass annotation is not found for class $clazz" }
clazz.classLoader.loadClass(JvmAnnotationNames.METADATA.asString()) as Class<Annotation>
)) { "Metadata annotation is not found for class $clazz" }
}
}
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
import java.lang.reflect.*
import java.util.*
import kotlin.jvm.internal.KotlinFileFacade
import kotlin.jvm.internal.Reflection
import kotlin.reflect.*
import kotlin.reflect.jvm.internal.KTypeImpl
@@ -104,20 +103,18 @@ val Field.kotlinProperty: KProperty<*>?
private fun Member.getKPackage(): KDeclarationContainer? {
// TODO: support multifile classes
return if (declaringClass.getAnnotation(KotlinFileFacade::class.java) != null) {
val header = ReflectKotlinClass.create(declaringClass)?.classHeader
if (header != null && header.kind == KotlinClassHeader.Kind.FILE_FACADE && header.metadataVersion.isCompatible()) {
// TODO: avoid reading and parsing metadata twice (here and later in KPackageImpl#descriptor)
val (nameResolver, proto) = JvmProtoBufUtil.readPackageDataFrom(header.data!!, header.strings!!)
val moduleName =
if (proto.hasExtension(JvmProtoBuf.packageModuleName))
nameResolver.getString(proto.getExtension(JvmProtoBuf.packageModuleName))
else JvmAbi.DEFAULT_MODULE_NAME
Reflection.getOrCreateKotlinPackage(declaringClass, moduleName)
}
else null
val header = ReflectKotlinClass.create(declaringClass)?.classHeader
if (header != null && header.kind == KotlinClassHeader.Kind.FILE_FACADE && header.metadataVersion.isCompatible()) {
// TODO: avoid reading and parsing metadata twice (here and later in KPackageImpl#descriptor)
val (nameResolver, proto) = JvmProtoBufUtil.readPackageDataFrom(header.data!!, header.strings!!)
val moduleName =
if (proto.hasExtension(JvmProtoBuf.packageModuleName))
nameResolver.getString(proto.getExtension(JvmProtoBuf.packageModuleName))
else JvmAbi.DEFAULT_MODULE_NAME
return Reflection.getOrCreateKotlinPackage(declaringClass, moduleName)
}
else null
return null
}
/**
@@ -94,12 +94,7 @@ class AllClassesCompletion(private val parameters: CompletionParameters,
private fun PsiClass.isSyntheticKotlinClass(): Boolean {
if ('$' !in name!!) return false // optimization to not analyze annotations of all classes
val metadata = modifierList?.findAnnotation(JvmAnnotationNames.METADATA.asString())
if (metadata != null &&
(metadata.findAttributeValue(JvmAnnotationNames.KIND_FIELD_NAME) as? PsiLiteral)?.value ==
KotlinClassHeader.Kind.SYNTHETIC_CLASS.id) {
return true
}
return modifierList?.findAnnotation(JvmAnnotationNames.KOTLIN_SYNTHETIC_CLASS.asString()) != null
return (metadata?.findAttributeValue(JvmAnnotationNames.KIND_FIELD_NAME) as? PsiLiteral)?.value ==
KotlinClassHeader.Kind.SYNTHETIC_CLASS.id
}
}
@@ -23,6 +23,7 @@ import com.intellij.util.indexing.FileContent
import org.jetbrains.kotlin.codegen.AsmUtil.asmDescByFqNameWithoutInnerClasses
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.*
import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion
import org.jetbrains.org.objectweb.asm.AnnotationVisitor
import org.jetbrains.org.objectweb.asm.ClassReader
@@ -38,30 +39,37 @@ object KotlinMetadataVersionIndex : KotlinAbiVersionIndexBase<KotlinMetadataVers
override fun getVersion() = VERSION
private val VERSION = 3
private val VERSION = 4
private val kotlinAnnotationsDesc = setOf(
KOTLIN_CLASS,
KOTLIN_FILE_FACADE,
KOTLIN_MULTIFILE_CLASS
).map { asmDescByFqNameWithoutInnerClasses(it) }
private val METADATA_DESC = asmDescByFqNameWithoutInnerClasses(METADATA)
private val kindsToIndex = setOf(
KotlinClassHeader.Kind.CLASS,
KotlinClassHeader.Kind.FILE_FACADE,
KotlinClassHeader.Kind.MULTIFILE_CLASS
)
private val INDEXER = DataIndexer<BinaryVersion, Void, FileContent>() { inputData: FileContent ->
var version: BinaryVersion? = null
var annotationPresent = false
var kind: KotlinClassHeader.Kind? = null
tryBlock(inputData) {
val classReader = ClassReader(inputData.content)
classReader.accept(object : ClassVisitor(Opcodes.ASM5) {
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
if (!kotlinAnnotationsDesc.contains(desc)) {
return null
}
if (desc != METADATA_DESC) return null
annotationPresent = true
return object : AnnotationVisitor(Opcodes.ASM5) {
override fun visit(name: String, value: Any) {
if (name == VERSION_FIELD_NAME && value is IntArray) {
version = JvmMetadataVersion(*value)
when (name) {
METADATA_VERSION_FIELD_NAME -> if (value is IntArray) {
version = JvmMetadataVersion(*value)
}
KIND_FIELD_NAME -> if (value is Int) {
kind = KotlinClassHeader.Kind.getById(value)
}
}
}
}
@@ -69,7 +77,11 @@ object KotlinMetadataVersionIndex : KotlinAbiVersionIndexBase<KotlinMetadataVers
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
}
if (annotationPresent && version == null) {
if (kind !in kindsToIndex) {
// Do not index metadata version for synthetic classes
version = null
}
else if (annotationPresent && version == null) {
// No version at all because the class is too old, or version is set to something weird
version = JvmMetadataVersion.INVALID_VERSION
}