8042981: Strip type annotations in Types' utility methods

Co-authored-by: Liam Miller-Cushon <cushon@openjdk.org>
Reviewed-by: cushon, jjg, jlahoda
This commit is contained in:
Joe Darcy 2024-01-26 20:55:46 +00:00
parent 6d1856234f
commit ed3272cc44
5 changed files with 451 additions and 13 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -35,6 +35,10 @@ import javax.lang.model.type.*;
/**
* Utility methods for operating on types.
*
* Where a method returns a type mirror or a collection of type
* mirrors, any type mirrors represent types with no type annotations,
* unless otherwise indicated.
*
* <p><b>Compatibility Note:</b> Methods may be added to this interface
* in future releases of the platform.
*
@ -153,6 +157,8 @@ public interface Types {
* the direct supertypes of a type mirror representing {@code
* java.lang.Object}.
*
* Annotations on the direct super types are preserved.
*
* @param t the type being examined
* @return the direct supertypes, or an empty list if none
* @throws IllegalArgumentException if given a type for an executable, package, or module
@ -235,6 +241,8 @@ public interface Types {
/**
* {@return an array type with the specified component type}
*
* Annotations on the component type are preserved.
*
* @param componentType the component type
* @throws IllegalArgumentException if the component type is not valid for
* an array
@ -245,6 +253,8 @@ public interface Types {
* {@return a new wildcard type} Either of the wildcard's
* bounds may be specified, or neither, but not both.
*
* Annotations on the bounds are preserved.
*
* @param extendsBound the extends (upper) bound, or {@code null} if none
* @param superBound the super (lower) bound, or {@code null} if none
* @throws IllegalArgumentException if bounds are not valid
@ -260,6 +270,8 @@ public interface Types {
* for example, this method may be used to get the
* parameterized type {@code Set<String>}.
*
* Annotations on the type arguments are preserved.
*
* <p> The number of type arguments must either equal the
* number of the type element's formal type parameters, or must be
* zero. If zero, and if the type element is generic,
@ -291,6 +303,8 @@ public interface Types {
* to get the type {@code Outer<String>}, and then invoking
* this method.
*
* Annotations on the type arguments are preserved.
*
* <p> If the containing type is a parameterized type,
* the number of type arguments must equal the
* number of {@code typeElem}'s formal type parameters.
@ -324,4 +338,29 @@ public interface Types {
* for the given type
*/
TypeMirror asMemberOf(DeclaredType containing, Element element);
/**
* {@return a type mirror equivalent to the argument, but with no annotations}
* If the type mirror is a composite type, such as an array type
* or a wildcard type, any constitute types, such as the
* component type of an array and the type of the bounds of a
* wildcard type, also have no annotations, recursively.
*
* <p>For most kinds of type mirrors, the result of
* {@snippet lang="java" :
* types.isSameType(typeMirror, types.stripAnnotations(typeMirror))
* }
* is {@code true}. The predicate is {@code false} on wildcard
* types for {@linkplain #isSameType(TypeMirror, TypeMirror)
* reasons discussed elsewhere}.
*
* @param t the type mirror
* @param <T> the specific type of type mirror
* @implSpec
* The default implementation throws {@code UnsupportedOperationException}.
* @since 23
*/
default <T extends TypeMirror> T stripAnnotations(T t) {
throw new UnsupportedOperationException();
}
}

View File

@ -340,7 +340,7 @@ public abstract class Type extends AnnoConstruct implements TypeMirror, PoolCons
* it should not be used outside this class.
*/
protected Type typeNoMetadata() {
return metadata.isEmpty() ? this : baseType();
return metadata.isEmpty() ? this : stripMetadata();
}
/**
@ -426,25 +426,42 @@ public abstract class Type extends AnnoConstruct implements TypeMirror, PoolCons
return accept(stripMetadata, null);
}
//where
/**
* Note: this visitor only needs to handle cases where
* 'contained' types can be annotated. These cases are
* described in JVMS 4.7.20.2 and are : classes (for type
* parameters and enclosing types), wildcards, and arrays.
*/
private static final TypeMapping<Void> stripMetadata = new StructuralTypeMapping<Void>() {
@Override
public Type visitClassType(ClassType t, Void aVoid) {
return super.visitClassType((ClassType)t.typeNoMetadata(), aVoid);
return super.visitClassType((ClassType) dropMetadata(t), aVoid);
}
@Override
public Type visitArrayType(ArrayType t, Void aVoid) {
return super.visitArrayType((ArrayType)t.typeNoMetadata(), aVoid);
}
@Override
public Type visitTypeVar(TypeVar t, Void aVoid) {
return super.visitTypeVar((TypeVar)t.typeNoMetadata(), aVoid);
return super.visitArrayType((ArrayType) dropMetadata(t), aVoid);
}
@Override
public Type visitWildcardType(WildcardType wt, Void aVoid) {
return super.visitWildcardType((WildcardType)wt.typeNoMetadata(), aVoid);
return super.visitWildcardType((WildcardType) dropMetadata(wt), aVoid);
}
@Override
public Type visitType(Type t, Void aVoid) {
return dropMetadata(t);
}
private static Type dropMetadata(Type t) {
if (t.getMetadata().isEmpty()) {
return t;
}
Type baseType = t.baseType();
if (baseType.getMetadata().isEmpty()) {
return baseType;
}
return baseType.cloneWithMetadata(List.nil());
}
};

View File

@ -134,7 +134,7 @@ public class JavacTypes implements javax.lang.model.util.Types {
TypeKind kind = t.getKind();
if (kind == TypeKind.PACKAGE || kind == TypeKind.MODULE)
throw new IllegalArgumentException(t.toString());
return types.erasure((Type)t).stripMetadataIfNeeded();
return types.erasure((Type)t).stripMetadata();
}
@DefinedBy(Api.LANGUAGE_MODEL)
@ -155,7 +155,7 @@ public class JavacTypes implements javax.lang.model.util.Types {
@DefinedBy(Api.LANGUAGE_MODEL)
public TypeMirror capture(TypeMirror t) {
validateTypeNotIn(t, EXEC_OR_PKG_OR_MOD);
return types.capture((Type)t).stripMetadataIfNeeded();
return types.capture((Type)t).stripMetadata();
}
@DefinedBy(Api.LANGUAGE_MODEL)
@ -304,6 +304,13 @@ public class JavacTypes implements javax.lang.model.util.Types {
}
@DefinedBy(Api.LANGUAGE_MODEL)
@SuppressWarnings("unchecked")
public <T extends TypeMirror> T stripAnnotations(T t) {
return (T)((Type) t).stripMetadata();
}
private static final Set<TypeKind> EXEC_OR_PKG_OR_MOD =
EnumSet.of(TypeKind.EXECUTABLE, TypeKind.PACKAGE, TypeKind.MODULE);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,6 +26,7 @@ import java.util.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import static javax.lang.model.SourceVersion.*;
@ -322,4 +323,73 @@ public abstract class JavacTestingAbstractProcessor extends AbstractProcessor {
@Override
public boolean isFunctionalInterface(TypeElement type) {return false;}
}
/**
* Vacuous implementation of javax.lang.model.util.Types to aid
* in test development. Methods with defaults in the interface are
* *not* overridden to allow them to be tested.
*/
public static class VacuousTypes implements Types {
public VacuousTypes() {}
@Override
public Element asElement(TypeMirror t) {return null;}
@Override
public boolean isSameType(TypeMirror t1, TypeMirror t2) {return false;}
@Override
public boolean isSubtype(TypeMirror t1, TypeMirror t2) {return false;};
@Override
public boolean isAssignable(TypeMirror t1, TypeMirror t2) {return false;};
@Override
public boolean contains(TypeMirror t1, TypeMirror t2) {return false;};
@Override
public boolean isSubsignature(ExecutableType m1, ExecutableType m2) {return false;}
@Override
public List<? extends TypeMirror> directSupertypes(TypeMirror t) {return null;}
@Override
public TypeMirror erasure(TypeMirror t) {return null;}
@Override
public TypeElement boxedClass(PrimitiveType p) {return null;}
@Override
public PrimitiveType unboxedType(TypeMirror t) {return null;}
@Override
public TypeMirror capture(TypeMirror t) {return null;}
@Override
public PrimitiveType getPrimitiveType(TypeKind kind) {return null;}
@Override
public NullType getNullType() {return null;}
@Override
public NoType getNoType(TypeKind kind) {return null;}
@Override
public ArrayType getArrayType(TypeMirror componentType) {return null;}
@Override
public WildcardType getWildcardType(TypeMirror extendsBound,
TypeMirror superBound) {return null;}
@Override
public DeclaredType getDeclaredType(TypeElement typeElem, TypeMirror... typeArgs) {return null;}
@Override
public DeclaredType getDeclaredType(DeclaredType containing,
TypeElement typeElem, TypeMirror... typeArgs) {return null;}
@Override
public TypeMirror asMemberOf(DeclaredType containing, Element element) {return null;}
}
}

View File

@ -0,0 +1,305 @@
/*
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8042981
* @summary Test if annotations are stripped from the results of Types' methods
* @library /tools/javac/lib
* @modules java.compiler
* jdk.compiler
* @build JavacTestingAbstractProcessor TestAnnotationStripping
* @compile -processor TestAnnotationStripping -proc:only TestAnnotationStripping.java
*/
import java.lang.annotation.*;
import java.util.*;
import static java.util.Objects.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import static javax.lang.model.SourceVersion.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import static javax.lang.model.util.ElementFilter.*;
import static javax.tools.Diagnostic.Kind.*;
import static javax.tools.StandardLocation.*;
/**
* Test if annotations are stripped from the results of Types' methods
*/
public class TestAnnotationStripping extends JavacTestingAbstractProcessor {
private Types vacuousTypes = new VacuousTypes();
/**
* Check expected behavior on classes and packages.
*/
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
TypeElement juSetElt = eltUtils.getTypeElement("java.util.Set");
TypeElement testElt = elements.getTypeElement("TestAnnotationStripping");
TypeElement boxElt = elements.getTypeElement("TestAnnotationStripping.Box");
TypeMirror expectedAnnotation = eltUtils.getTypeElement("TestTypeAnnotation").asType();
for (ExecutableElement m :
methodsIn(eltUtils.getTypeElement("HostClass").getEnclosedElements())) {
/*
* The kinds of types include:
*
* arrays
* declared types (classes, interfaces, etc.)
* error types
* executable types
* intersection types
* no-type
* null type
* primitive types
* type variable
* union type
* wildcards
*
* A subset of these can appear at the return type of
* a method. The general methodology is to verify that
* types that can appear as return types when
* annotated with type annotations appear as specified
* as the result of type operations or when new types
* are constructed.
*/
TypeMirror returnType = m.getReturnType();
System.err.println("Checking " + returnType);
testVacuous(returnType);
checkDeepEmptyAnnotations(typeUtils.stripAnnotations(returnType));
checkExpectedTypeAnnotations(returnType, expectedAnnotation);
// Note: the result of Types.asElement is *not*
// checked for its annotations since the return value
// is an Element and not a TypeMirror.
System.err.print("\tcapture()");
checkDeepEmptyAnnotations(typeUtils.capture(returnType));
System.err.print("\terasure()");
checkDeepEmptyAnnotations(typeUtils.erasure(returnType));
System.err.print("\tgetArrayType()");
ArrayType arrayType = typeUtils.getArrayType(returnType);
checkEmptyAnnotations(arrayType);
/*
* "Annotations on the component type are preserved."
*/
checkEqualTypeAndAnnotations(returnType, arrayType.getComponentType());
if (!returnType.getKind().isPrimitive()) {
/*
* For getWildcardType()
* "Annotations on the bounds are preserved."
*/
WildcardType wcType;
checkEmptyAnnotations(wcType = typeUtils.getWildcardType(returnType, null));
checkEqualTypeAndAnnotations(returnType, wcType.getExtendsBound());
checkEmptyAnnotations(wcType = typeUtils.getWildcardType(null, returnType));
checkEqualTypeAndAnnotations(returnType, wcType.getSuperBound());
/*
* For getDeclaredType()
* "Annotations on the type arguments are preserved."
*/
DeclaredType declaredType = typeUtils.getDeclaredType(juSetElt, returnType);
checkEqualTypeAndAnnotations(returnType, declaredType.getTypeArguments().get(0));
// Check both overloads
declaredType = typeUtils.getDeclaredType(typeUtils.getDeclaredType(testElt), // outer type
boxElt,
returnType);
checkEqualTypeAndAnnotations(returnType, declaredType.getTypeArguments().get(0));
}
System.out.println(returnType.getAnnotation(TestTypeAnnotation.class));
System.out.println(returnType.getAnnotationsByType(TestTypeAnnotation.class).length);
TestTypeAnnotation ta = requireNonNull(returnType.getAnnotation(TestTypeAnnotation.class),
returnType.toString());
System.err.println();
System.err.println();
}
if (failures > 0)
throw new RuntimeException(failures + " failures occured.");
}
return true;
}
void testVacuous(TypeMirror tm ) {
try {
var result = vacuousTypes.stripAnnotations(tm);
messager.printError("Unexpected non-exceptional result returned " + result);
} catch(UnsupportedOperationException uoe) {
; // Expected
}
}
private int failures = 0;
void checkExpectedTypeAnnotations(AnnotatedConstruct ac, TypeMirror expectedAnnotation) {
List<? extends AnnotationMirror> annotations = ac.getAnnotationMirrors();
if (annotations.size() != 1) {
failures++;
System.err.println("\t\t\tUnexpected annotations size: " + annotations.size());
} else if (!typeUtils.isSameType(annotations.get(0).getAnnotationType(), expectedAnnotation)) {
failures++;
System.err.println("\t\t\tUnexpected annotations type: " + annotations);
}
}
void checkEmptyAnnotations(AnnotatedConstruct ac) {
System.err.println("\t" + ac);
if (ac == null)
return;
else {
List<? extends AnnotationMirror> annotations = ac.getAnnotationMirrors();
int count = annotations.size();
if (count != 0) {
failures++;
System.err.println(ac.getClass());
System.err.println("\t\t\tUnexpected nonzero annotations size: " + annotations);
}
}
}
void checkDeepEmptyAnnotations(TypeMirror ac) {
System.err.println("\t" + ac);
if (ac == null) {
return;
}
new SimpleTypeVisitor14<Void, Void>() {
@Override
protected Void defaultAction(TypeMirror t, Void o) {
checkEmptyAnnotations(t);
return null;
}
@Override
public Void visitArray(ArrayType t, Void o) {
scan(t.getComponentType());
return super.visitArray(t, o);
}
@Override
public Void visitDeclared(DeclaredType t, Void o) {
scan(t.getEnclosingType());
t.getTypeArguments().stream().forEach(this::scan);
return super.visitDeclared(t, o);
}
@Override
public Void visitTypeVariable(TypeVariable t, Void o) {
// the bounds correspond to the type variable declaration, not its use
// scan(t.getUpperBound());
// scan(t.getLowerBound());
return super.visitTypeVariable(t, o);
}
@Override
public Void visitWildcard(WildcardType t, Void o) {
scan(t.getExtendsBound());
scan(t.getSuperBound());
return super.visitWildcard(t, o);
}
private void scan(TypeMirror t) {
if (t != null) {
visit(t);
}
}
}.visit(ac);
}
void checkEqualTypeAndAnnotations(TypeMirror tm1, TypeMirror tm2) {
if (!typeUtils.isSameType(tm1, tm2)) {
failures++;
System.err.printf("Unequal types %s and %s.%n", tm1, tm2);
}
if (!Objects.equals(tm1.getAnnotationMirrors(), tm1.getAnnotationMirrors())) {
failures++;
System.err.printf("Unequal annotations on and %s.%n", tm1, tm2);
}
}
// Nested class to test getDeclaredType overload.
class Box<T> {
private T contents;
public Box(T t){
contents = t;
}
T value() { return contents;};
}
}
/*
* Class to host annotations for testing
*/
class HostClass {
// Declared type Integer
public static @TestTypeAnnotation("foo") Integer foo() {return null;}
// Primitive type int
public static @TestTypeAnnotation("foo2") int foo2() {return 0;}
public static @TestTypeAnnotation("foo3") String foo3() {return null;}
// Declared raw type Set
public static java.util.@TestTypeAnnotation("foo4")Set foo4() {return null;}
// Array type
public static String @TestTypeAnnotation("foo5")[] foo5() {return null;}
// Declared type Set with instantiated type parameter
public static java.util. @TestTypeAnnotation("foo6") Set < @TestTypeAnnotation("foo7") String> foo6() {return null;}
// Type variable
public static <@TestTypeAnnotation("foo8") T extends @TestTypeAnnotation("foo9") String> @TestTypeAnnotation("foo10") T foo7() {return null;}
// Declared type including wildcard
public static java.util. @TestTypeAnnotation("foo11") Set < @TestTypeAnnotation("foo12") ? extends @TestTypeAnnotation("foo13") Number> foo8() {return null;}
// Type variable with intersection type
public static <@TestTypeAnnotation("foo14") S extends Number & Runnable> @TestTypeAnnotation("foo15") S foo9() {return null;}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@interface TestTypeAnnotation {
String value() default "";
}