package org.checkerframework.framework.type.treeannotator; import com.sun.source.tree.BinaryTree; import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.UnaryTree; import java.util.Collection; import java.util.Set; import javax.lang.model.element.AnnotationMirror; 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.QualifierHierarchy; import org.checkerframework.javacutil.Pair; /** * {@link PropagationTreeAnnotator} adds qualifiers to types where the resulting type is a function * of an input type, e.g. the result of a binary operation is a LUB of the type of expressions in * the binary operation. * * <p>{@link PropagationTreeAnnotator} is generally ran first by {@link ListTreeAnnotator} since the * trees it handles are not usually targets of @implicit for. * * <p>{@link PropagationTreeAnnotator} does not traverse trees deeply by default. * * @see TreeAnnotator */ public class PropagationTreeAnnotator extends TreeAnnotator { private final QualifierHierarchy qualHierarchy; /** * Creates a {@link org.checkerframework.framework.type.typeannotator.ImplicitsTypeAnnotator} * from the given checker, using that checker's type hierarchy. */ public PropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { super(atypeFactory); this.qualHierarchy = atypeFactory.getQualifierHierarchy(); } @Override public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { assert type.getKind() == TypeKind.ARRAY : "PropagationTreeAnnotator.visitNewArray: should be an array type"; AnnotatedTypeMirror componentType = ((AnnotatedArrayType) type).getComponentType(); Collection<? extends AnnotationMirror> prev = null; if (tree.getInitializers() != null && tree.getInitializers().size() != 0) { // We have initializers, either with or without an array type. for (ExpressionTree init : tree.getInitializers()) { AnnotatedTypeMirror initType = atypeFactory.getAnnotatedType(init); // initType might be a typeVariable, so use effectiveAnnotations. Collection<AnnotationMirror> annos = initType.getEffectiveAnnotations(); prev = (prev == null) ? annos : qualHierarchy.leastUpperBounds(prev, annos); } } else { prev = componentType.getAnnotations(); } assert prev != null : "PropagationTreeAnnotator.visitNewArray: violated assumption about qualifiers"; Pair<Tree, AnnotatedTypeMirror> context = atypeFactory.getVisitorState().getAssignmentContext(); Collection<? extends AnnotationMirror> post; if (context != null && context.second != null && context.second instanceof AnnotatedArrayType) { AnnotatedTypeMirror contextComponentType = ((AnnotatedArrayType) context.second).getComponentType(); // Only compare the qualifiers that existed in the array type // Defaulting wasn't performed yet, so prev might have fewer qualifiers than // contextComponentType, which would cause a failure. // TODO: better solution? boolean prevIsSubtype = true; for (AnnotationMirror am : prev) { if (contextComponentType.isAnnotatedInHierarchy(am) && !this.qualHierarchy.isSubtype( am, contextComponentType.getAnnotationInHierarchy(am))) { prevIsSubtype = false; } } // TODO: checking conformance of component kinds is a basic sanity check // It fails for array initializer expressions. Those should be handled nicer. if (contextComponentType.getKind() == componentType.getKind() && (prev.isEmpty() || (!contextComponentType.getAnnotations().isEmpty() && prevIsSubtype))) { post = contextComponentType.getAnnotations(); } else { // The type of the array initializers is incompatible with the // context type! // Somebody else will complain. post = prev; } } else { // No context is available - simply use what we have. post = prev; } componentType.addMissingAnnotations(post); return null; } @Override public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) { if (hasPrimaryAnnotationInAllHierarchies(type)) { // If the type already has a primary annotation in all hierarchies, then the // propagated annotations won't be applied. So don't compute them. return null; } AnnotatedTypeMirror rhs = atypeFactory.getAnnotatedType(node.getExpression()); AnnotatedTypeMirror lhs = atypeFactory.getAnnotatedType(node.getVariable()); Set<? extends AnnotationMirror> lubs = qualHierarchy.leastUpperBounds(rhs.getAnnotations(), lhs.getAnnotations()); type.addMissingAnnotations(lubs); return null; } @Override public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { if (hasPrimaryAnnotationInAllHierarchies(type)) { // If the type already has a primary annotation in all hierarchies, then the // propagated annotations won't be applied. So don't compute them. // Also, calling getAnnotatedType on the left and right operands is potentially // expensive. return null; } AnnotatedTypeMirror a = atypeFactory.getAnnotatedType(node.getLeftOperand()); AnnotatedTypeMirror b = atypeFactory.getAnnotatedType(node.getRightOperand()); Set<? extends AnnotationMirror> lubs = qualHierarchy.leastUpperBounds( a.getEffectiveAnnotations(), b.getEffectiveAnnotations()); type.addMissingAnnotations(lubs); return null; } @Override public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) { if (hasPrimaryAnnotationInAllHierarchies(type)) { // If the type already has a primary annotation in all hierarchies, then the // propagated annotations won't be applied. So don't compute them. return null; } AnnotatedTypeMirror exp = atypeFactory.getAnnotatedType(node.getExpression()); type.addMissingAnnotations(exp.getAnnotations()); return null; } /* * TODO: would this make sense in general? @Override public Void visitConditionalExpression(ConditionalExpressionTree node, AnnotatedTypeMirror type) { if (!type.isAnnotated()) { AnnotatedTypeMirror a = typeFactory.getAnnotatedType(node.getTrueExpression()); AnnotatedTypeMirror b = typeFactory.getAnnotatedType(node.getFalseExpression()); Set<AnnotationMirror> lubs = qualHierarchy.leastUpperBounds(a.getEffectiveAnnotations(), b.getEffectiveAnnotations()); type.replaceAnnotations(lubs); } return super.visitConditionalExpression(node, type); }*/ @Override public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { if (hasPrimaryAnnotationInAllHierarchies(type)) { // If the type is already has a primary annotation in all hierarchies, then the // propagated annotations won't be applied. So don't compute them. return null; } AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(node.getExpression()); if (type.getKind() == TypeKind.TYPEVAR) { if (exprType.getKind() == TypeKind.TYPEVAR) { // If both types are type variables, take the direct annotations. type.addMissingAnnotations(exprType.getAnnotations()); } // else do nothing // TODO: What should we do if the type is a type variable, but the expression // is not? } else { // Use effective annotations from the expression, to get upper bound // of type variables. type.addMissingAnnotations(exprType.getEffectiveAnnotations()); } return null; } private boolean hasPrimaryAnnotationInAllHierarchies(AnnotatedTypeMirror type) { boolean annotated = true; for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { if (type.getEffectiveAnnotationInHierarchy(top) == null) { annotated = false; } } return annotated; } }