blob: 0e0cd92288abd714edf4b29eb40d5725693dec7a [file] [log] [blame]
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;
}
}