From 0cb26b056696df290328a11d08e05469b2198171 Mon Sep 17 00:00:00 2001 From: Alex Tkachman Date: Sat, 31 Mar 2012 16:14:32 +0300 Subject: [PATCH] more annotations --- .../jet/codegen/AnnotationCodegen.java | 76 +++++++++++-- .../jet/codegen/ClassBodyCodegen.java | 39 ++++--- .../codegen/ImplementationBodyCodegen.java | 22 +++- .../codegen/intrinsics/IntrinsicMethods.java | 24 +++- .../org/jetbrains/jet/lang/psi/JetClass.java | 4 + .../jet/codegen/AnnotationGenTest.java | 52 ++++++++- .../org/jetbrains/jet/codegen/StdlibTest.java | 105 ++++++++++++++++++ 7 files changed, 288 insertions(+), 34 deletions(-) diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/AnnotationCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/AnnotationCodegen.java index dba7f0cb1ba..7d101c06e95 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/AnnotationCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/AnnotationCodegen.java @@ -17,20 +17,17 @@ package org.jetbrains.jet.codegen; import com.intellij.psi.PsiElement; +import org.jetbrains.jet.codegen.intrinsics.IntrinsicMethod; +import org.jetbrains.jet.codegen.intrinsics.IntrinsicMethods; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.descriptors.annotations.Annotated; import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; -import org.jetbrains.jet.lang.resolve.calls.DefaultValueArgument; -import org.jetbrains.jet.lang.resolve.calls.ResolvedCall; -import org.jetbrains.jet.lang.resolve.calls.ResolvedValueArgument; +import org.jetbrains.jet.lang.resolve.calls.*; import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant; import org.jetbrains.jet.lang.types.JetType; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.*; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -80,12 +77,67 @@ public abstract class AnnotationCodegen { ResolvedValueArgument valueArgument = entry.getValue(); if (!(valueArgument instanceof DefaultValueArgument)) { List valueArguments = valueArgument.getArguments(); - assert valueArguments.size() == 1 : "Number of assertions on " + resolvedCall.getResultingDescriptor() + " = " + valueArguments.size(); // todo - CompileTimeConstant compileTimeConstant = bindingContext.get(BindingContext.COMPILE_TIME_VALUE, valueArguments.get(0).getArgumentExpression()); - assert compileTimeConstant != null; + assert valueArguments.size() == 1 : "Number of arguments on " + resolvedCall.getResultingDescriptor() + " = " + valueArguments.size(); // todo + CompileTimeConstant compileTimeConstant = + bindingContext.get(BindingContext.COMPILE_TIME_VALUE, valueArguments.get(0).getArgumentExpression()); - Object value = compileTimeConstant.getValue(); - annotationVisitor.visit(entry.getKey().getName(), value); + String keyName = entry.getKey().getName(); + if(compileTimeConstant != null) { + Object value = compileTimeConstant.getValue(); + annotationVisitor.visit(keyName, value); + continue; + } + + ExpressionValueArgument expressionValueArgument = (ExpressionValueArgument)valueArgument; + JetExpression expression = expressionValueArgument.getArguments().get(0).getArgumentExpression(); + if(expression instanceof JetDotQualifiedExpression) { + JetDotQualifiedExpression qualifiedExpression = (JetDotQualifiedExpression)expression; + ResolvedCall call = + bindingContext.get(BindingContext.RESOLVED_CALL, qualifiedExpression.getSelectorExpression()); + if(call != null) { + if(call.getResultingDescriptor() instanceof PropertyDescriptor) { + PropertyDescriptor descriptor = (PropertyDescriptor)call.getResultingDescriptor(); + annotationVisitor.visitEnum(keyName, typeMapper.mapType(descriptor.getReturnType()).getDescriptor(),descriptor.getName()); + continue; + } + } + } + else { + if(expression instanceof JetCallExpression) { + JetCallExpression callExpression = (JetCallExpression)expression; + ResolvedCall call = + bindingContext.get(BindingContext.RESOLVED_CALL, callExpression.getCalleeExpression()); + if(call != null) { + List annotations = call.getResultingDescriptor().getOriginal().getAnnotations(); + String value = null; + if (annotations != null) { + for (AnnotationDescriptor annotation : annotations) { + if("Intrinsic".equals(annotation.getType().getConstructor().getDeclarationDescriptor().getName())) { + value = (String) annotation.getValueArguments().get(0).getValue(); + break; + } + } + } + if(IntrinsicMethods.KOTLIN_JAVA_CLASS_FUNCTION.equals(value)) { + annotationVisitor.visit(keyName, typeMapper.mapType(call.getResultingDescriptor().getReturnType().getArguments().get(0).getType())); + continue; + } + if(IntrinsicMethods.KOTLIN_ARRAYS_ARRAY.equals(value)) { + AnnotationVisitor visitor = annotationVisitor.visitArray(keyName); + VarargValueArgument next = (VarargValueArgument)call.getValueArguments().values().iterator().next(); + for (ValueArgument argument : next.getArguments()) { + CompileTimeConstant constant = + bindingContext.get(BindingContext.COMPILE_TIME_VALUE, argument.getArgumentExpression()); + visitor.visit(null, constant.getValue()); + } + visitor.visitEnd(); + continue; + } + } + } + } + + throw new IllegalStateException("Don't know how to compile annotation value"); } } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ClassBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ClassBodyCodegen.java index 75acff86cf0..142a8907710 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ClassBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ClassBodyCodegen.java @@ -29,6 +29,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; + /** * @author max * @author yole @@ -99,27 +102,33 @@ public abstract class ClassBodyCodegen { functionCodegen.gen(declaration); } - private void generatePrimaryConstructorProperties(PropertyCodegen propertyCodegen, PsiElement origin) { + private void generatePrimaryConstructorProperties(PropertyCodegen propertyCodegen, JetClassOrObject origin) { + boolean isAnnotation = origin instanceof JetClass && ((JetClass)origin).isAnnotation(); OwnerKind kind = context.getContextKind(); for (JetParameter p : getPrimaryConstructorParameters()) { if (p.getValOrVarNode() != null) { PropertyDescriptor propertyDescriptor = state.getBindingContext().get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, p); if (propertyDescriptor != null) { - propertyCodegen.generateDefaultGetter(propertyDescriptor, Opcodes.ACC_PUBLIC, p); - if (propertyDescriptor.isVar()) { - propertyCodegen.generateDefaultSetter(propertyDescriptor, Opcodes.ACC_PUBLIC, origin); - } + if(!isAnnotation) { + propertyCodegen.generateDefaultGetter(propertyDescriptor, ACC_PUBLIC, p); + if (propertyDescriptor.isVar()) { + propertyCodegen.generateDefaultSetter(propertyDescriptor, ACC_PUBLIC, origin); + } - //noinspection ConstantConditions - if (!(kind instanceof OwnerKind.DelegateKind) && state.getBindingContext().get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor)) { - int modifiers = JetTypeMapper.getAccessModifiers(propertyDescriptor, 0); - if (!propertyDescriptor.isVar()) { - modifiers |= Opcodes.ACC_FINAL; + //noinspection ConstantConditions + if (!(kind instanceof OwnerKind.DelegateKind) && state.getBindingContext().get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor)) { + int modifiers = JetTypeMapper.getAccessModifiers(propertyDescriptor, 0); + if (!propertyDescriptor.isVar()) { + modifiers |= Opcodes.ACC_FINAL; + } + if(state.getInjector().getJetStandardLibrary().isVolatile(propertyDescriptor)) { + modifiers |= Opcodes.ACC_VOLATILE; + } + v.newField(p, modifiers, p.getName(), state.getInjector().getJetTypeMapper().mapType(propertyDescriptor.getType()).getDescriptor(), null, null); } - if(state.getInjector().getJetStandardLibrary().isVolatile(propertyDescriptor)) { - modifiers |= Opcodes.ACC_VOLATILE; - } - v.newField(p, modifiers, p.getName(), state.getInjector().getJetTypeMapper().mapType(propertyDescriptor.getType()).getDescriptor(), null, null); + } + else { + v.newMethod(p, ACC_PUBLIC|ACC_ABSTRACT, p.getName(), "()" + state.getInjector().getJetTypeMapper().mapType(propertyDescriptor.getType()).getDescriptor(), null, null); } } } @@ -135,7 +144,7 @@ public abstract class ClassBodyCodegen { private void generateStaticInitializer() { if (staticInitializerChunks.size() > 0) { - final MethodVisitor mv = v.newMethod(null, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,"", "()V", null, null); + final MethodVisitor mv = v.newMethod(null, ACC_PUBLIC | Opcodes.ACC_STATIC,"", "()V", null, null); if (v.generateCode() == ClassBuilder.Mode.FULL) { mv.visitCode(); diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java index 1b5517fd293..3dec9fbf4cd 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java @@ -72,7 +72,8 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { boolean isInterface = false; boolean isFinal = false; boolean isStatic = false; - + boolean isAnnotation = false; + if(myClass instanceof JetClass) { JetClass jetClass = (JetClass) myClass; if (jetClass.hasModifier(JetTokens.ABSTRACT_KEYWORD)) @@ -81,6 +82,12 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { isAbstract = true; isInterface = true; } + if (jetClass.isAnnotation()) { + isAbstract = true; + isInterface = true; + isAnnotation = true; + signature.getInterfaces().add("java/lang/annotation/Annotation"); + } if (!jetClass.hasModifier(JetTokens.OPEN_KEYWORD) && !isAbstract) { isFinal = true; } @@ -103,6 +110,9 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { if (isStatic) { access |= ACC_STATIC; } + if (isAnnotation) { + access |= ACC_ANNOTATION; + } v.defineClass(myClass, V1_6, access, signature.getName(), @@ -407,8 +417,14 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { } protected void generatePrimaryConstructor() { - if(myClass instanceof JetClass && ((JetClass) myClass).isTrait()) - return; + if(myClass instanceof JetClass) { + JetClass aClass = (JetClass)myClass; + if(aClass.isTrait()) + return; + if(aClass.isAnnotation()) { + return; + } + } ConstructorDescriptor constructorDescriptor = bindingContext.get(BindingContext.CONSTRUCTOR, myClass); diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/intrinsics/IntrinsicMethods.java b/compiler/backend/src/org/jetbrains/jet/codegen/intrinsics/IntrinsicMethods.java index 6f30e4fb87f..3eea7b0b88f 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/intrinsics/IntrinsicMethods.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/intrinsics/IntrinsicMethods.java @@ -63,6 +63,9 @@ public class IntrinsicMethods { public static final ArraySet ARRAY_SET = new ArraySet(); public static final ArrayGet ARRAY_GET = new ArrayGet(); public static final StringPlus STRING_PLUS = new StringPlus(); + public static final String KOTLIN_JAVA_CLASS_FUNCTION = "kotlin.javaClass.function"; + public static final String KOTLIN_ARRAYS_ARRAY = "kotlin.arrays.array"; + public static final String KOTLIN_JAVA_CLASS_PROPERTY = "kotlin.javaClass.property"; private Project myProject; private JetStandardLibrary myStdLib; @@ -83,9 +86,9 @@ public class IntrinsicMethods { @PostConstruct public void init() { - namedMethods.put("kotlin.javaClass.function", new JavaClassFunction()); - namedMethods.put("kotlin.javaClass.property", new JavaClassProperty()); - namedMethods.put("kotlin.arrays.array", new JavaClassArray()); + namedMethods.put(KOTLIN_JAVA_CLASS_FUNCTION, new JavaClassFunction()); + namedMethods.put(KOTLIN_JAVA_CLASS_PROPERTY, new JavaClassProperty()); + namedMethods.put(KOTLIN_ARRAYS_ARRAY, new JavaClassArray()); List primitiveCastMethods = OperatorConventions.NUMBER_CONVERSIONS.asList(); for (String method : primitiveCastMethods) { @@ -249,6 +252,21 @@ public class IntrinsicMethods { return descriptor.getMemberScope(typeParameters); } + public IntrinsicMethod isIntrinsicMethod(DeclarationDescriptor descriptor) { + List annotations = descriptor.getAnnotations(); + if (annotations != null) { + for (AnnotationDescriptor annotation : annotations) { + if("Intrinsic".equals(annotation.getType().getConstructor().getDeclarationDescriptor().getName())) { + String value = (String) annotation.getValueArguments().get(0).getValue(); + IntrinsicMethod intrinsicMethod = namedMethods.get(value); + if(intrinsicMethod != null) + return intrinsicMethod; + } + } + } + return null; + } + public IntrinsicMethod getIntrinsic(DeclarationDescriptor descriptor) { IntrinsicMethod intrinsicMethod = myMethods.get(descriptor.getOriginal()); if(intrinsicMethod == null) { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClass.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClass.java index a7d302ab4ef..e126c6036a0 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClass.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClass.java @@ -143,6 +143,10 @@ public class JetClass extends JetTypeParameterListOwner return findChildByType(JetTokens.TRAIT_KEYWORD) != null; } + public boolean isAnnotation() { + return hasModifier(JetTokens.ANNOTATION_KEYWORD); + } + @Override public IStubElementType getElementType() { // TODO (stubs) diff --git a/compiler/tests/org/jetbrains/jet/codegen/AnnotationGenTest.java b/compiler/tests/org/jetbrains/jet/codegen/AnnotationGenTest.java index cdc5f65b3cb..211d49d5418 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/AnnotationGenTest.java +++ b/compiler/tests/org/jetbrains/jet/codegen/AnnotationGenTest.java @@ -16,7 +16,13 @@ package org.jetbrains.jet.codegen; +import jet.JetObject; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class AnnotationGenTest extends CodegenTestCase { @@ -70,4 +76,48 @@ public class AnnotationGenTest extends CodegenTestCase { Deprecated annotation = (Deprecated) aClass.getAnnotation(Deprecated.class); assertNotNull(annotation); } -} + + public void testSimplestAnnotationClass() throws NoSuchFieldException, NoSuchMethodException { + loadText("annotation class A"); + Class aClass = generateClass("A"); + Class[] interfaces = aClass.getInterfaces(); + assertEquals(2, interfaces.length); + assertEquals(0, aClass.getDeclaredMethods().length); + assertTrue(Annotation.class == interfaces[0] || Annotation.class == interfaces[1]); + assertTrue(JetObject.class == interfaces[0] || JetObject.class == interfaces[1]); + assertTrue(aClass.isAnnotation()); + } + + public void testAnnotationClassWithStringProperty() + throws + NoSuchFieldException, + NoSuchMethodException, + ClassNotFoundException, + IllegalAccessException, + InstantiationException, + InvocationTargetException { + loadText("import java.lang.annotation.*\n" + + "" + + "Retention(RetentionPolicy.RUNTIME) annotation class A(val a: String)\n" + + "" + + "A(\"239\") class B()"); + Class aClass = generateClass("A"); + + Retention annotation = (Retention)aClass.getAnnotation(Retention.class); + RetentionPolicy value = annotation.value(); + assertEquals(RetentionPolicy.RUNTIME, value); + + Method[] methods = aClass.getDeclaredMethods(); + assertEquals(1, methods.length); + assertEquals("a", methods[0].getName()); + assertEquals(String.class, methods[0].getReturnType()); + assertEquals(0, methods[0].getParameterTypes().length); + assertTrue(aClass.isAnnotation()); + + Class bClass = aClass.getClassLoader().loadClass("B"); + Annotation bClassAnnotation = bClass.getAnnotation(aClass); + assertNotNull(bClassAnnotation); + + assertEquals("239", methods[0].invoke(bClassAnnotation)); + } +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/jet/codegen/StdlibTest.java b/compiler/tests/org/jetbrains/jet/codegen/StdlibTest.java index 51ce5366485..459ae563804 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/StdlibTest.java +++ b/compiler/tests/org/jetbrains/jet/codegen/StdlibTest.java @@ -21,6 +21,8 @@ import org.jetbrains.jet.lang.psi.JetPsiUtil; import java.io.File; import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; @@ -164,4 +166,107 @@ public class StdlibTest extends CodegenTestCase { loader.dispose(); } } + + public void testAnnotationClassWithClassProperty() + throws + NoSuchFieldException, + NoSuchMethodException, + ClassNotFoundException, + IllegalAccessException, + InstantiationException, + InvocationTargetException { + loadText("import java.lang.annotation.*\n" + + "" + + "Retention(RetentionPolicy.RUNTIME) annotation class A(val a: java.lang.Class<*>)\n" + + "" + + "A(javaClass()) class B()"); + Class aClass = generateClass("A"); + + Retention annotation = (Retention)aClass.getAnnotation(Retention.class); + RetentionPolicy value = annotation.value(); + assertEquals(RetentionPolicy.RUNTIME, value); + + Method[] methods = aClass.getDeclaredMethods(); + assertEquals(1, methods.length); + assertEquals("a", methods[0].getName()); + assertEquals(Class.class, methods[0].getReturnType()); + assertEquals(0, methods[0].getParameterTypes().length); + assertTrue(aClass.isAnnotation()); + + Class bClass = aClass.getClassLoader().loadClass("B"); + Annotation bClassAnnotation = bClass.getAnnotation(aClass); + assertNotNull(bClassAnnotation); + + assertEquals(String.class, methods[0].invoke(bClassAnnotation)); + } + + public void testAnnotationClassWithStringArrayProperty() + throws + NoSuchFieldException, + NoSuchMethodException, + ClassNotFoundException, + IllegalAccessException, + InstantiationException, + InvocationTargetException { + loadText("import java.lang.annotation.*\n" + + "" + + "Retention(RetentionPolicy.RUNTIME) annotation class A(val a: Array)\n" + + "" + + "A(array(\"239\",\"932\")) class B()"); + Class aClass = generateClass("A"); + + Retention annotation = (Retention)aClass.getAnnotation(Retention.class); + RetentionPolicy value = annotation.value(); + assertEquals(RetentionPolicy.RUNTIME, value); + + Method[] methods = aClass.getDeclaredMethods(); + assertEquals(1, methods.length); + assertEquals("a", methods[0].getName()); + assertEquals(String[].class, methods[0].getReturnType()); + assertEquals(0, methods[0].getParameterTypes().length); + assertTrue(aClass.isAnnotation()); + + Class bClass = aClass.getClassLoader().loadClass("B"); + Annotation bClassAnnotation = bClass.getAnnotation(aClass); + assertNotNull(bClassAnnotation); + + Object invoke = methods[0].invoke(bClassAnnotation); + assertEquals("239", ((String[])invoke)[0]); + assertEquals("932", ((String[])invoke)[1]); + } + + public void testAnnotationClassWithIntArrayProperty() + throws + NoSuchFieldException, + NoSuchMethodException, + ClassNotFoundException, + IllegalAccessException, + InstantiationException, + InvocationTargetException { + loadText("import java.lang.annotation.*\n" + + "" + + "Retention(RetentionPolicy.RUNTIME) annotation class A(val a: IntArray)\n" + + "" + + "A(intArray(239,932)) class B()"); + Class aClass = generateClass("A"); + + Retention annotation = (Retention)aClass.getAnnotation(Retention.class); + RetentionPolicy value = annotation.value(); + assertEquals(RetentionPolicy.RUNTIME, value); + + Method[] methods = aClass.getDeclaredMethods(); + assertEquals(1, methods.length); + assertEquals("a", methods[0].getName()); + assertEquals(int[].class, methods[0].getReturnType()); + assertEquals(0, methods[0].getParameterTypes().length); + assertTrue(aClass.isAnnotation()); + + Class bClass = aClass.getClassLoader().loadClass("B"); + Annotation bClassAnnotation = bClass.getAnnotation(aClass); + assertNotNull(bClassAnnotation); + + Object invoke = methods[0].invoke(bClassAnnotation); + assertEquals(239, ((int[])invoke)[0]); + assertEquals(932, ((int[])invoke)[1]); + } }