| package org.checkerframework.framework.util.element; |
| |
| import com.sun.tools.javac.code.Attribute; |
| import com.sun.tools.javac.code.Symbol.MethodSymbol; |
| import com.sun.tools.javac.code.Symbol.VarSymbol; |
| import com.sun.tools.javac.code.TargetType; |
| import com.sun.tools.javac.code.TypeAnnotationPosition; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.TypeParameterElement; |
| import javax.lang.model.type.TypeKind; |
| import org.checkerframework.framework.type.AnnotatedTypeFactory; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; |
| import org.checkerframework.framework.type.ElementAnnotationApplier; |
| import org.checkerframework.framework.util.element.ElementAnnotationUtil.UnexpectedAnnotationLocationException; |
| import org.checkerframework.javacutil.BugInCF; |
| |
| /** Apply annotations to the use of a type parameter declaration. */ |
| public class TypeVarUseApplier { |
| |
| public static void apply( |
| final AnnotatedTypeMirror type, final Element element, final AnnotatedTypeFactory typeFactory) |
| throws UnexpectedAnnotationLocationException { |
| new TypeVarUseApplier(type, element, typeFactory).extractAndApply(); |
| } |
| |
| private static ElementKind[] acceptedKinds = { |
| ElementKind.PARAMETER, |
| ElementKind.FIELD, |
| ElementKind.LOCAL_VARIABLE, |
| ElementKind.RESOURCE_VARIABLE, |
| ElementKind.METHOD |
| }; |
| |
| /** |
| * Returns true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type variable |
| * component, and the element is not a TYPE_PARAMETER. |
| * |
| * @return true if type is an AnnotatedTypeVariable, or an AnnotatedArrayType with a type variable |
| * component, and the element is not a TYPE_PARAMETER |
| */ |
| public static boolean accepts(AnnotatedTypeMirror type, Element element) { |
| return (type instanceof AnnotatedTypeVariable || isGenericArrayType(type)) |
| && ElementAnnotationUtil.contains(element.getKind(), acceptedKinds); |
| } |
| |
| private static boolean isGenericArrayType(AnnotatedTypeMirror type) { |
| return type instanceof AnnotatedArrayType |
| && getNestedComponentType(type) instanceof AnnotatedTypeVariable; |
| } |
| |
| private static AnnotatedTypeMirror getNestedComponentType(AnnotatedTypeMirror type) { |
| |
| AnnotatedTypeMirror componentType = type; |
| while (componentType instanceof AnnotatedArrayType) { |
| componentType = ((AnnotatedArrayType) componentType).getComponentType(); |
| } |
| |
| return componentType; |
| } |
| |
| // In order to avoid sprinkling code for type parameter uses all over the various locations |
| // uses can show up we also handle generic array types. T [] myTArr; |
| private final AnnotatedArrayType arrayType; |
| |
| private final AnnotatedTypeVariable typeVariable; |
| private final TypeParameterElement declarationElem; |
| private final Element useElem; |
| |
| private AnnotatedTypeFactory typeFactory; |
| |
| TypeVarUseApplier( |
| final AnnotatedTypeMirror type, |
| final Element element, |
| final AnnotatedTypeFactory typeFactory) { |
| if (!accepts(type, element)) { |
| throw new BugInCF( |
| "TypeParamUseApplier does not accept type/element combination (" |
| + " type ( " |
| + type |
| + " ) element ( " |
| + element |
| + " ) "); |
| } |
| |
| if (isGenericArrayType(type)) { |
| this.arrayType = (AnnotatedArrayType) type; |
| this.typeVariable = (AnnotatedTypeVariable) getNestedComponentType(type); |
| this.declarationElem = (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); |
| this.useElem = element; |
| this.typeFactory = typeFactory; |
| |
| } else { |
| this.arrayType = null; |
| this.typeVariable = (AnnotatedTypeVariable) type; |
| this.declarationElem = (TypeParameterElement) typeVariable.getUnderlyingType().asElement(); |
| this.useElem = element; |
| this.typeFactory = typeFactory; |
| } |
| } |
| |
| /** |
| * Applies the bound annotations from the declaration of the type parameter and then applies the |
| * explicit annotations written on the type variable. |
| * |
| * @throws UnexpectedAnnotationLocationException if invalid location for an annotation was found |
| */ |
| public void extractAndApply() throws UnexpectedAnnotationLocationException { |
| ElementAnnotationUtil.addDeclarationAnnotationsFromElement( |
| typeVariable, useElem.getAnnotationMirrors()); |
| |
| // apply declaration annotations |
| ElementAnnotationApplier.apply(typeVariable, declarationElem, typeFactory); |
| |
| final List<Attribute.TypeCompound> annotations = getAnnotations(useElem, declarationElem); |
| |
| final List<Attribute.TypeCompound> typeVarAnnotations; |
| if (arrayType != null) { |
| // if the outer-most type is an array type then we want to ensure the outer annotations |
| // are not applied as the type variables primary annotation |
| typeVarAnnotations = removeComponentAnnotations(arrayType, annotations); |
| ElementAnnotationUtil.annotateViaTypeAnnoPosition(arrayType, annotations); |
| |
| } else { |
| typeVarAnnotations = annotations; |
| } |
| |
| for (final Attribute.TypeCompound annotation : typeVarAnnotations) { |
| typeVariable.replaceAnnotation(annotation); |
| } |
| } |
| |
| private List<Attribute.TypeCompound> removeComponentAnnotations( |
| final AnnotatedArrayType arrayType, final List<Attribute.TypeCompound> annotations) { |
| |
| final List<Attribute.TypeCompound> componentAnnotations = new ArrayList<>(); |
| |
| if (arrayType != null) { |
| for (int i = 0; i < annotations.size(); ) { |
| final Attribute.TypeCompound anno = annotations.get(i); |
| if (isBaseComponent(arrayType, anno)) { |
| componentAnnotations.add(anno); |
| annotations.remove(anno); |
| } else { |
| i++; |
| } |
| } |
| } |
| |
| return componentAnnotations; |
| } |
| |
| private boolean isBaseComponent( |
| final AnnotatedArrayType arrayType, final Attribute.TypeCompound anno) { |
| try { |
| return ElementAnnotationUtil.getTypeAtLocation(arrayType, anno.getPosition().location) |
| .getKind() |
| == TypeKind.TYPEVAR; |
| } catch (UnexpectedAnnotationLocationException ex) { |
| return false; |
| } |
| } |
| |
| /** |
| * Depending on what element type the annotations are stored on, the relevant annotations might be |
| * stored with different annotation positions. getAnnotations finds the correct annotations by |
| * annotation position and element kind and returns them |
| */ |
| private static List<Attribute.TypeCompound> getAnnotations( |
| final Element useElem, final Element declarationElem) { |
| final List<Attribute.TypeCompound> annotations; |
| switch (useElem.getKind()) { |
| case METHOD: |
| annotations = getReturnAnnos(useElem); |
| break; |
| |
| case PARAMETER: |
| annotations = getParameterAnnos(useElem); |
| break; |
| |
| case FIELD: |
| case LOCAL_VARIABLE: |
| case RESOURCE_VARIABLE: |
| annotations = getVariableAnnos(useElem); |
| break; |
| |
| default: |
| throw new BugInCF( |
| "TypeVarUseApplier::extractAndApply : " |
| + "Unhandled element kind " |
| + useElem.getKind() |
| + "useElem ( " |
| + useElem |
| + " ) " |
| + "declarationElem ( " |
| + declarationElem |
| + " ) "); |
| } |
| |
| return annotations; |
| } |
| |
| /** |
| * Returns annotations on an element that apply to variable declarations. |
| * |
| * @param variableElem the element whose annotations to check |
| * @return annotations on an element that apply to variable declarations |
| */ |
| private static List<Attribute.TypeCompound> getVariableAnnos(final Element variableElem) { |
| final VarSymbol varSymbol = (VarSymbol) variableElem; |
| final List<Attribute.TypeCompound> annotations = new ArrayList<>(); |
| |
| for (Attribute.TypeCompound anno : varSymbol.getRawTypeAttributes()) { |
| |
| TypeAnnotationPosition pos = anno.position; |
| switch (pos.type) { |
| case FIELD: |
| case LOCAL_VARIABLE: |
| case RESOURCE_VARIABLE: |
| case EXCEPTION_PARAMETER: |
| annotations.add(anno); |
| break; |
| |
| default: |
| } |
| } |
| |
| return annotations; |
| } |
| |
| /** |
| * Currently, the metadata for storing annotations (i.e. the Attribute.TypeCompounds) is null for |
| * binary-only parameters and type parameters. However, it is present on the method. So in order |
| * to ensure that we correctly retrieve the annotations we need to index from the method and |
| * retrieve the annotations from its metadata. |
| * |
| * @return a list of annotations that were found on METHOD_FORMAL_PARAMETERS that match the |
| * parameter index of the input element in the parent methods formal parameter list |
| */ |
| private static List<Attribute.TypeCompound> getParameterAnnos(final Element paramElem) { |
| final Element enclosingElement = paramElem.getEnclosingElement(); |
| if (!(enclosingElement instanceof ExecutableElement)) { |
| throw new BugInCF( |
| "Bad element passed to TypeFromElement.getTypeParameterAnnotationAttributes: " |
| + "element: " |
| + paramElem |
| + " not found in enclosing executable: " |
| + enclosingElement); |
| } |
| |
| final MethodSymbol enclosingMethod = (MethodSymbol) enclosingElement; |
| |
| if (enclosingMethod.getKind() != ElementKind.CONSTRUCTOR |
| && enclosingMethod.getKind() != ElementKind.METHOD) { |
| // Initializer blocks don't have parameters, so there is nothing to do. |
| return Collections.emptyList(); |
| } |
| |
| // TODO: for the parameter in a lambda expression, the enclosingMethod isn't |
| // the lambda expression. Does this read the correct annotations? |
| |
| final int paramIndex = enclosingMethod.getParameters().indexOf(paramElem); |
| final List<Attribute.TypeCompound> annotations = enclosingMethod.getRawTypeAttributes(); |
| |
| final List<Attribute.TypeCompound> result = new ArrayList<>(); |
| for (final Attribute.TypeCompound typeAnno : annotations) { |
| if (typeAnno.position.type == TargetType.METHOD_FORMAL_PARAMETER) { |
| if (typeAnno.position.parameter_index == paramIndex) { |
| result.add(typeAnno); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Returns the annotations on the return type of the input ExecutableElement. |
| * |
| * @param methodElem the method whose return type annotations to return |
| * @return the annotations on the return type of the input ExecutableElement |
| */ |
| private static List<Attribute.TypeCompound> getReturnAnnos(final Element methodElem) { |
| if (!(methodElem instanceof ExecutableElement)) { |
| throw new BugInCF("Bad element passed to TypeVarUseApplier.getReturnAnnos:" + methodElem); |
| } |
| |
| final MethodSymbol enclosingMethod = (MethodSymbol) methodElem; |
| |
| final List<Attribute.TypeCompound> annotations = enclosingMethod.getRawTypeAttributes(); |
| final List<Attribute.TypeCompound> result = new ArrayList<>(); |
| for (final Attribute.TypeCompound typeAnno : annotations) { |
| if (typeAnno.position.type == TargetType.METHOD_RETURN) { |
| result.add(typeAnno); |
| } |
| } |
| |
| return result; |
| } |
| } |