Introduce binary representation for annotations

Will be used where annotations can't be stored elsewhere: for example,
built-ins, JS, type annotations on JDK<8
This commit is contained in:
Alexander Udalov
2014-11-28 23:24:54 +03:00
parent 9a86318908
commit 63bfa004fd
30 changed files with 6958 additions and 164 deletions
+12
View File
@@ -25,3 +25,15 @@ extend Package {
// id in StringTable
repeated int32 class_name = 150 [packed = true];
}
extend Class {
repeated Annotation class_annotation = 150;
}
extend Callable {
repeated Annotation callable_annotation = 150;
}
extend Callable.ValueParameter {
repeated Annotation parameter_annotation = 150;
}
+59 -1
View File
@@ -41,6 +41,62 @@ message QualifiedNameTable {
repeated QualifiedName qualified_name = 1;
}
message Annotation {
message Argument {
message Value {
enum Type {
BYTE = 0;
CHAR = 1;
SHORT = 2;
INT = 3;
LONG = 4;
FLOAT = 5;
DOUBLE = 6;
BOOLEAN = 7;
STRING = 8;
CLASS = 9;
ENUM = 10;
ANNOTATION = 11;
ARRAY = 12;
}
// Note: a *Value* has a Type, not an Argument! This is done for future language features which may involve using arrays
// of elements of different types. Such entries are allowed in the constant pool of JVM class files.
// However, to save space, this field is optional: in case of homogeneous arrays, only the type of the first element is required
optional Type type = 1;
// Only one of the following values should be present. Consider using `oneof` instead when we upgrade to protobuf 2.6.0+
optional sint64 int_value = 2;
optional float float_value = 3;
optional double double_value = 4;
// id in StringTable
optional int32 string_value = 5;
// If type = CLASS, FQ name id of the referenced class; if type = ENUM, FQ name id of the enum class
optional int32 class_id = 6;
// id in StringTable
optional int32 enum_value_id = 7;
optional Annotation annotation = 8;
repeated Value array_element = 9;
}
// id in StringTable
required int32 name_id = 1;
required Value value = 2;
}
// Class FQ name id
required int32 id = 1;
repeated Argument argument = 2;
}
message Type {
message Constructor {
enum Kind {
@@ -151,6 +207,8 @@ message Class {
// This field is present if and only if the class has a primary constructor
optional PrimaryConstructor primary_constructor = 13;
// todo: other constructors?
extensions 100 to 199;
}
message Package {
@@ -189,7 +247,7 @@ message Callable {
optional int32 flags = 1;
optional string extra_visibility = 2; // for things like java-specific visibilities
/*
/*
isNotDefault
Visibility
Modality
@@ -0,0 +1,164 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.descriptors.serialization
import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.jet.lang.descriptors.ModuleDescriptor
import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptorImpl
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor
import org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation
import org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation.Argument
import org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation.Argument.Value
import org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation.Argument.Value.Type
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import org.jetbrains.jet.lang.resolve.name.Name
import org.jetbrains.jet.lang.resolve.constants.*
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.descriptors.ClassDescriptor
import org.jetbrains.jet.lang.resolve.name.ClassId
import org.jetbrains.jet.lang.types.ErrorUtils
import org.jetbrains.jet.lang.descriptors.ClassKind
public class AnnotationDeserializer(private val module: ModuleDescriptor) {
private val builtIns: KotlinBuiltIns
get() = module.builtIns
public fun deserializeAnnotation(proto: Annotation, nameResolver: NameResolver): AnnotationDescriptor {
val annotationClass = resolveClass(nameResolver.getClassId(proto.getId()))
val arguments = if (proto.getArgumentCount() == 0 || ErrorUtils.isError(annotationClass)) {
mapOf()
}
else {
val parameterByName = annotationClass.getConstructors().single().getValueParameters().toMap { it.getName() }
val arguments = proto.getArgumentList().map { resolveArgument(it, parameterByName, nameResolver) }.filterNotNull()
arguments.toMap()
}
return AnnotationDescriptorImpl(annotationClass.getDefaultType(), arguments)
}
private fun resolveArgument(
proto: Argument,
parameterByName: Map<Name, ValueParameterDescriptor>,
nameResolver: NameResolver
): Pair<ValueParameterDescriptor, CompileTimeConstant<*>>? {
val parameter = parameterByName[nameResolver.getName(proto.getNameId())] ?: return null
return Pair(parameter, resolveValue(parameter.getType(), proto.getValue(), nameResolver))
}
private fun resolveValue(
expectedType: JetType,
value: ProtoBuf.Annotation.Argument.Value,
nameResolver: NameResolver
): CompileTimeConstant<*> {
val result = when (value.getType()) {
Type.BYTE -> ByteValue(value.getIntValue().toByte(), true, true, true)
Type.CHAR -> CharValue(value.getIntValue().toChar(), true, true, true)
Type.SHORT -> ShortValue(value.getIntValue().toShort(), true, true, true)
Type.INT -> IntValue(value.getIntValue().toInt(), true, true, true)
Type.LONG -> LongValue(value.getIntValue(), true, true, true)
Type.FLOAT -> FloatValue(value.getFloatValue(), true, true)
Type.DOUBLE -> DoubleValue(value.getDoubleValue(), true, true)
Type.BOOLEAN -> BooleanValue(value.getIntValue() != 0L, true, true)
Type.STRING -> {
StringValue(nameResolver.getString(value.getStringValue()), true, true)
}
Type.CLASS -> {
// TODO: support class literals
error("Class literal annotation arguments are not supported yet (${nameResolver.getClassId(value.getClassId())})")
}
Type.ENUM -> {
resolveEnumValue(nameResolver.getClassId(value.getClassId()), nameResolver.getName(value.getEnumValueId()))
}
Type.ANNOTATION -> {
AnnotationValue(deserializeAnnotation(value.getAnnotation(), nameResolver))
}
Type.ARRAY -> {
if (!KotlinBuiltIns.isArray(expectedType) &&
!KotlinBuiltIns.isPrimitiveArray(expectedType)) return ErrorValue.create("Unexpected argument value")
val arrayElements = value.getArrayElementList()
val actualArrayType =
if (arrayElements.isNotEmpty()) {
val actualElementType = resolveArrayElementType(arrayElements.first(), nameResolver)
builtIns.getPrimitiveArrayJetTypeByPrimitiveJetType(actualElementType) ?: builtIns.getArrayType(actualElementType)
}
else {
// In the case of empty array, no element has the element type, so we fall back to the expected type.
// This is not very accurate when annotation class has been changed without recompiling clients,
// but should not in fact matter because the value is empty anyway
expectedType
}
val expectedElementType = builtIns.getArrayElementType(expectedType)
ArrayValue(
arrayElements.map { resolveValue(expectedElementType, it, nameResolver) },
actualArrayType,
true, true
)
}
else -> error("Unsupported annotation argument type: ${value.getType()} (expected $expectedType)")
}
if (result.getType(builtIns) != expectedType) {
// This means that an annotation class has been changed incompatibly without recompiling clients
return ErrorValue.create("Unexpected argument value")
}
return result
}
// NOTE: see analogous code in BinaryClassAnnotationAndConstantLoaderImpl
private fun resolveEnumValue(enumClassId: ClassId, enumEntryName: Name): CompileTimeConstant<*> {
val enumClass = resolveClass(enumClassId)
if (enumClass.getKind() == ClassKind.ENUM_CLASS) {
val enumEntry = enumClass.getUnsubstitutedInnerClassesScope().getClassifier(enumEntryName)
if (enumEntry is ClassDescriptor) {
return EnumValue(enumEntry, true)
}
}
return ErrorValue.create("Unresolved enum entry: $enumClassId.$enumEntryName")
}
private fun resolveArrayElementType(value: Value, nameResolver: NameResolver): JetType =
with(builtIns) {
when (value.getType()) {
Type.BYTE -> getByteType()
Type.CHAR -> getCharType()
Type.SHORT -> getShortType()
Type.INT -> getIntType()
Type.LONG -> getLongType()
Type.FLOAT -> getFloatType()
Type.DOUBLE -> getDoubleType()
Type.BOOLEAN -> getBooleanType()
Type.STRING -> getStringType()
Type.CLASS -> error("Arrays of class literals are not supported yet") // TODO: support arrays of class literals
Type.ENUM -> resolveClass(nameResolver.getClassId(value.getClassId())).getDefaultType()
Type.ANNOTATION -> resolveClass(nameResolver.getClassId(value.getAnnotation().getId())).getDefaultType()
Type.ARRAY -> error("Array of arrays is impossible")
else -> error("Unknown type: ${value.getType()}")
}
}
private fun resolveClass(classId: ClassId): ClassDescriptor {
return module.findClassAcrossModuleDependencies(classId)
?: ErrorUtils.createErrorClass(classId.asSingleFqName().asString())
}
}
@@ -0,0 +1,145 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.descriptors.serialization
import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.jet.lang.descriptors.ClassDescriptor
import org.jetbrains.jet.lang.descriptors.annotations.AnnotationArgumentVisitor
import org.jetbrains.jet.lang.resolve.constants.*
import org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation.Argument.Value
import org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation.Argument.Value.Type
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
public object AnnotationSerializer {
public fun serializeAnnotation(annotation: AnnotationDescriptor, stringTable: StringTable): ProtoBuf.Annotation {
return with(ProtoBuf.Annotation.newBuilder()) {
val annotationClass = annotation.getType().getConstructor().getDeclarationDescriptor() as? ClassDescriptor
?: error("Annotation type is not a class: ${annotation.getType()}")
setId(stringTable.getFqNameIndex(annotationClass))
for ((parameter, value) in annotation.getAllValueArguments()) {
val argument = ProtoBuf.Annotation.Argument.newBuilder()
argument.setNameId(stringTable.getSimpleNameIndex(parameter.getName()))
argument.setValue(valueProto(value, parameter.getType(), stringTable))
addArgument(argument)
}
build()
}
}
fun valueProto(constant: CompileTimeConstant<*>, type: JetType, nameTable: StringTable): Value.Builder = with(Value.newBuilder()) {
constant.accept(object : AnnotationArgumentVisitor<Unit, Unit> {
override fun visitAnnotationValue(value: AnnotationValue, data: Unit) {
setType(Type.ANNOTATION)
setAnnotation(serializeAnnotation(value.getValue(), nameTable))
}
override fun visitArrayValue(value: ArrayValue, data: Unit) {
setType(Type.ARRAY)
for (element in value.getValue()) {
addArrayElement(valueProto(element, KotlinBuiltIns.getInstance().getArrayElementType(type), nameTable).build())
}
}
override fun visitBooleanValue(value: BooleanValue, data: Unit) {
setType(Type.BOOLEAN)
setIntValue(if (value.getValue()) 1 else 0)
}
override fun visitByteValue(value: ByteValue, data: Unit) {
setType(Type.BYTE)
setIntValue(value.getValue().toLong())
}
override fun visitCharValue(value: CharValue, data: Unit) {
setType(Type.CHAR)
setIntValue(value.getValue().toLong())
}
override fun visitDoubleValue(value: DoubleValue, data: Unit) {
setType(Type.DOUBLE)
setDoubleValue(value.getValue())
}
override fun visitEnumValue(value: EnumValue, data: Unit) {
setType(Type.ENUM)
val enumEntry = value.getValue()
setClassId(nameTable.getFqNameIndex(enumEntry.getContainingDeclaration() as ClassDescriptor))
setEnumValueId(nameTable.getSimpleNameIndex(enumEntry.getName()))
}
override fun visitErrorValue(value: ErrorValue, data: Unit) {
throw UnsupportedOperationException("Error value: $value")
}
override fun visitFloatValue(value: FloatValue, data: Unit) {
setType(Type.FLOAT)
setFloatValue(value.getValue())
}
override fun visitIntValue(value: IntValue, data: Unit) {
setType(Type.INT)
setIntValue(value.getValue().toLong())
}
override fun visitJavaClassValue(value: JavaClassValue, data: Unit) {
// TODO: support class literals
throw UnsupportedOperationException("Class literal annotation arguments are not yet supported: $value")
}
override fun visitLongValue(value: LongValue, data: Unit) {
setType(Type.LONG)
setIntValue(value.getValue())
}
override fun visitNullValue(value: NullValue, data: Unit) {
throw UnsupportedOperationException("Null should not appear in annotation arguments")
}
override fun visitNumberTypeValue(constant: IntegerValueTypeConstant, data: Unit) {
// TODO: IntegerValueTypeConstant should not occur in annotation arguments
val number = constant.getValue(type)
val specificConstant = with(KotlinBuiltIns.getInstance()) {
when (type) {
getLongType() -> LongValue(number.toLong(), true, true, true)
getIntType() -> IntValue(number.toInt(), true, true, true)
getShortType() -> ShortValue(number.toShort(), true, true, true)
getByteType() -> ByteValue(number.toByte(), true, true, true)
else -> throw IllegalStateException("Integer constant $constant has non-integer type $type")
}
}
specificConstant.accept(this, data)
}
override fun visitShortValue(value: ShortValue, data: Unit) {
setType(Type.SHORT)
setIntValue(value.getValue().toLong())
}
override fun visitStringValue(value: StringValue, data: Unit) {
setType(Type.STRING)
setStringValue(nameTable.getStringIndex(value.getValue()))
}
}, Unit)
this
}
}
@@ -8,6 +8,9 @@ public final class BuiltInsProtoBuf {
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
registry.add(org.jetbrains.jet.descriptors.serialization.BuiltInsProtoBuf.className);
registry.add(org.jetbrains.jet.descriptors.serialization.BuiltInsProtoBuf.classAnnotation);
registry.add(org.jetbrains.jet.descriptors.serialization.BuiltInsProtoBuf.callableAnnotation);
registry.add(org.jetbrains.jet.descriptors.serialization.BuiltInsProtoBuf.parameterAnnotation);
}
public static final int CLASS_NAME_FIELD_NUMBER = 150;
/**
@@ -24,6 +27,51 @@ public final class BuiltInsProtoBuf {
150,
com.google.protobuf.WireFormat.FieldType.INT32,
true);
public static final int CLASS_ANNOTATION_FIELD_NUMBER = 150;
/**
* <code>extend .org.jetbrains.jet.descriptors.serialization.Class { ... }</code>
*/
public static final
com.google.protobuf.GeneratedMessageLite.GeneratedExtension<
org.jetbrains.jet.descriptors.serialization.ProtoBuf.Class,
java.util.List<org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation>> classAnnotation = com.google.protobuf.GeneratedMessageLite
.newRepeatedGeneratedExtension(
org.jetbrains.jet.descriptors.serialization.ProtoBuf.Class.getDefaultInstance(),
org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation.getDefaultInstance(),
null,
150,
com.google.protobuf.WireFormat.FieldType.MESSAGE,
false);
public static final int CALLABLE_ANNOTATION_FIELD_NUMBER = 150;
/**
* <code>extend .org.jetbrains.jet.descriptors.serialization.Callable { ... }</code>
*/
public static final
com.google.protobuf.GeneratedMessageLite.GeneratedExtension<
org.jetbrains.jet.descriptors.serialization.ProtoBuf.Callable,
java.util.List<org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation>> callableAnnotation = com.google.protobuf.GeneratedMessageLite
.newRepeatedGeneratedExtension(
org.jetbrains.jet.descriptors.serialization.ProtoBuf.Callable.getDefaultInstance(),
org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation.getDefaultInstance(),
null,
150,
com.google.protobuf.WireFormat.FieldType.MESSAGE,
false);
public static final int PARAMETER_ANNOTATION_FIELD_NUMBER = 150;
/**
* <code>extend .org.jetbrains.jet.descriptors.serialization.Callable.ValueParameter { ... }</code>
*/
public static final
com.google.protobuf.GeneratedMessageLite.GeneratedExtension<
org.jetbrains.jet.descriptors.serialization.ProtoBuf.Callable.ValueParameter,
java.util.List<org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation>> parameterAnnotation = com.google.protobuf.GeneratedMessageLite
.newRepeatedGeneratedExtension(
org.jetbrains.jet.descriptors.serialization.ProtoBuf.Callable.ValueParameter.getDefaultInstance(),
org.jetbrains.jet.descriptors.serialization.ProtoBuf.Annotation.getDefaultInstance(),
null,
150,
com.google.protobuf.WireFormat.FieldType.MESSAGE,
false);
static {
}
@@ -135,6 +135,8 @@ public class DescriptorSerializer {
builder.setClassObject(classObjectProto(classObject));
}
extension.serializeClass(classDescriptor, builder, stringTable);
return builder;
}
File diff suppressed because it is too large Load Diff
@@ -18,12 +18,20 @@ package org.jetbrains.jet.descriptors.serialization;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor;
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
import org.jetbrains.jet.lang.descriptors.PackageFragmentDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import java.util.Collection;
public abstract class SerializerExtension {
public void serializeClass(
@NotNull ClassDescriptor descriptor,
@NotNull ProtoBuf.Class.Builder proto,
@NotNull StringTable stringTable
) {
}
public void serializePackage(
@NotNull Collection<PackageFragmentDescriptor> packageFragments,
@NotNull ProtoBuf.Package.Builder proto,
@@ -20,63 +20,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.descriptors.serialization.NameResolver;
import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
import java.util.List;
public interface AnnotationAndConstantLoader<A, C> {
AnnotationAndConstantLoader<AnnotationDescriptor, CompileTimeConstant<?>> UNSUPPORTED =
new AnnotationAndConstantLoader<AnnotationDescriptor, CompileTimeConstant<?>>() {
@NotNull
@Override
public List<AnnotationDescriptor> loadClassAnnotations(
@NotNull ProtoBuf.Class classProto,
@NotNull NameResolver nameResolver
) {
return annotationsNotSupported();
}
@NotNull
@Override
public List<AnnotationDescriptor> loadCallableAnnotations(
@NotNull ProtoContainer container,
@NotNull ProtoBuf.Callable proto,
@NotNull NameResolver nameResolver,
@NotNull AnnotatedCallableKind kind
) {
return annotationsNotSupported();
}
@NotNull
@Override
public List<AnnotationDescriptor> loadValueParameterAnnotations(
@NotNull ProtoContainer container,
@NotNull ProtoBuf.Callable callable,
@NotNull NameResolver nameResolver,
@NotNull AnnotatedCallableKind kind,
@NotNull ProtoBuf.Callable.ValueParameter proto
) {
return annotationsNotSupported();
}
@Nullable
@Override
public CompileTimeConstant<?> loadPropertyConstant(
@NotNull ProtoContainer container,
@NotNull ProtoBuf.Callable proto,
@NotNull NameResolver nameResolver,
@NotNull AnnotatedCallableKind kind
) {
throw new UnsupportedOperationException("Constants are not supported");
}
@NotNull
private List<AnnotationDescriptor> annotationsNotSupported() {
throw new UnsupportedOperationException("Annotations are not supported");
}
};
@NotNull
List<A> loadClassAnnotations(
@NotNull ProtoBuf.Class classProto,