Support @Inherited annotation on annotations

Fix #883
This commit is contained in:
Alexey Andreev 2024-02-19 20:14:00 +01:00
parent 6788642ea9
commit c1b3deedff
4 changed files with 67 additions and 7 deletions

View File

@ -34,6 +34,7 @@ public final class Flags {
public static final int TRANSIENT = 4096; public static final int TRANSIENT = 4096;
public static final int VARARGS = 8192; public static final int VARARGS = 8192;
public static final int VOLATILE = 16384; public static final int VOLATILE = 16384;
public static final int INHERITED_ANNOTATION = 32768;
public static final int PACKAGE_PRIVATE = 0; public static final int PACKAGE_PRIVATE = 0;
public static final int PRIVATE = 1; public static final int PRIVATE = 1;

View File

@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -58,6 +59,7 @@ public class TClass<T> extends TObject implements TAnnotatedElement, TType {
String canonicalName; String canonicalName;
private PlatformClass platformClass; private PlatformClass platformClass;
private TAnnotation[] annotationsCache; private TAnnotation[] annotationsCache;
private TAnnotation[] declaredAnnotationsCache;
private Map<TClass<?>, TAnnotation> annotationsByType; private Map<TClass<?>, TAnnotation> annotationsByType;
private TField[] declaredFields; private TField[] declaredFields;
private TField[] fields; private TField[] fields;
@ -744,14 +746,33 @@ public class TClass<T> extends TObject implements TAnnotatedElement, TType {
@Override @Override
public TAnnotation[] getAnnotations() { public TAnnotation[] getAnnotations() {
if (annotationsCache == null) { if (annotationsCache == null) {
annotationsCache = (TAnnotation[]) Platform.getAnnotations(getPlatformClass()); TClass<?> cls = this;
var initial = true;
var map = new LinkedHashMap<Class<?>, TAnnotation>();
while (cls != null) {
for (var annot : cls.getDeclaredAnnotations()) {
var platformClass = ((TClass<?>) (Object) annot.annotationType()).platformClass;
if (initial || (platformClass.getMetadata().getFlags() & Flags.INHERITED_ANNOTATION) != 0) {
map.putIfAbsent(annot.annotationType(), annot);
}
}
cls = cls.getSuperclass();
initial = false;
}
annotationsCache = map.values().toArray(new TAnnotation[0]);
} }
return annotationsCache.clone(); return annotationsCache.clone();
} }
@Override @Override
public TAnnotation[] getDeclaredAnnotations() { public TAnnotation[] getDeclaredAnnotations() {
return getAnnotations(); if (declaredAnnotationsCache == null) {
declaredAnnotationsCache = (TAnnotation[]) Platform.getAnnotations(getPlatformClass());
if (declaredAnnotationsCache == null) {
declaredAnnotationsCache = new TAnnotation[0];
}
}
return declaredAnnotationsCache.clone();
} }
private void ensureAnnotationsByType() { private void ensureAnnotationsByType() {

View File

@ -17,6 +17,8 @@ package org.teavm.backend.javascript.rendering;
import com.carrotsearch.hppc.ObjectIntHashMap; import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap; import com.carrotsearch.hppc.ObjectIntMap;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -517,7 +519,16 @@ public class Renderer implements RenderingManager {
} }
writer.append("],").ws(); writer.append("],").ws();
writer.append(ElementModifier.pack(cls.readModifiers())).append(',').ws(); var flags = ElementModifier.pack(cls.readModifiers());
if (cls.hasModifier(ElementModifier.ANNOTATION)) {
var retention = cls.getAnnotations().get(Retention.class.getName());
if (retention != null && retention.getValue("value").getEnumValue().getFieldName().equals("RUNTIME")) {
if (cls.getAnnotations().get(Inherited.class.getName()) != null) {
flags |= 32768;
}
}
}
writer.append(flags).append(',').ws();
writer.append(cls.getLevel().ordinal()).append(',').ws(); writer.append(cls.getLevel().ordinal()).append(',').ws();
if (!requiredMetadata.enclosingClass() && !requiredMetadata.declaringClass() if (!requiredMetadata.enclosingClass() && !requiredMetadata.declaringClass()

View File

@ -20,11 +20,12 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.lang.annotation.Annotation; import java.lang.annotation.Inherited;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.teavm.junit.SkipPlatform; import org.teavm.junit.SkipPlatform;
@ -182,9 +183,8 @@ public class ClassTest {
@Test @Test
@SkipPlatform({TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI}) @SkipPlatform({TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI})
public void annotationsExposed() { public void annotationsExposed() {
Annotation[] annotations = A.class.getAnnotations(); var annotations = A.class.getAnnotations();
assertEquals(1, annotations.length); assertTrue(Stream.of(annotations).anyMatch(a -> a instanceof TestAnnot));
assertTrue(TestAnnot.class.isAssignableFrom(annotations[0].getClass()));
} }
@Test @Test
@ -225,6 +225,23 @@ public class ClassTest {
Set.of(ClassWithInterfaces.class.getInterfaces())); Set.of(ClassWithInterfaces.class.getInterfaces()));
} }
@Test
@SkipPlatform({TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI})
public void inheritedAnnotation() {
assertTrue(A.class.isAnnotationPresent(InheritedAnnot.class));
assertTrue(A.class.isAnnotationPresent(TestAnnot.class));
assertTrue(ASub.class.isAnnotationPresent(InheritedAnnot.class));
assertFalse(ASub.class.isAnnotationPresent(TestAnnot.class));
assertTrue(TestInterface1.class.isAnnotationPresent(InheritedAnnot.class));
assertFalse(ClassWithInterfaces.class.isAnnotationPresent(InheritedAnnot.class));
var annotationSet = Set.of(ASub.class.getAnnotations());
assertTrue(annotationSet.stream().anyMatch(a -> a instanceof InheritedAnnot));
assertFalse(annotationSet.stream().anyMatch(a -> a instanceof TestAnnot));
assertEquals(0, ASub.class.getDeclaredAnnotations().length);
}
private static class SuperclassWithoutInterfaces { private static class SuperclassWithoutInterfaces {
} }
@ -232,6 +249,7 @@ public class ClassTest {
implements TestInterface1, TestInterface2 { implements TestInterface1, TestInterface2 {
} }
@InheritedAnnot
private interface TestInterface1 { private interface TestInterface1 {
} }
@ -239,9 +257,13 @@ public class ClassTest {
} }
@TestAnnot @TestAnnot
@InheritedAnnot
private static class A { private static class A {
} }
private static class ASub extends A {
}
@AnnotWithDefaultField @AnnotWithDefaultField
private static class B { private static class B {
} }
@ -265,6 +287,11 @@ public class ClassTest {
int x() default 2; int x() default 2;
} }
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface InheritedAnnot {
}
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@interface AnnotWithVariousFields { @interface AnnotWithVariousFields {
boolean a(); boolean a();