package org.checkerframework.checker.nullness;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeKind;
import org.checkerframework.checker.nullness.KeyForPropagator.PropagationDirection;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
/**
* For the following initializations we wish to propagate the annotations from the left-hand side to
* the right-hand side or vice versa:
*
* <p>1. If a keySet is being saved to a newly declared set, we transfer the annotations from the
* keySet to the lhs. e.g.,
*
* <pre>{@code
* // Previously, the user would be required to annotate the LHS's type argument with @KeyFor("m")
* Set<String> keySet = m.keySet();
* }</pre>
*
* 2. If a variable declaration contains type arguments with an @KeyFor annotation and its
* initializer is a new class tree with corresponding type arguments that have an @UknownKeyFor
* primary annotation we transfer from the LHS to RHS. e.g.,
*
* <pre>{@code
* // normally a user would have to write the @KeyFor("m") on both sides
* List<@KeyFor("m") String> keys = new ArrayList<String>();
* }</pre>
*
* 3. IMPORTANT NOTE: The following case must be (and is) handled in KeyForAnnotatedTypeFactory. In
* BaseTypeVisitor we check to make sure that the constructor called in a NewClassTree is actually
* compatible with the annotations placed on the NewClassTree. This requires that, prior to this
* check we also propagate the annotations to this constructor in constructorFromUse so that the
* constructor call matches the type given to the NewClassTree.
*
* @see
* org.checkerframework.checker.nullness.KeyForAnnotatedTypeFactory#constructorFromUse(com.sun.source.tree.NewClassTree)
* <p>Note propagation only occurs between two AnnotatedDeclaredTypes. If either side is not an
* AnnotatedDeclaredType then this class does nothing.
*/
public class KeyForPropagationTreeAnnotator extends TreeAnnotator {
private final KeyForPropagator keyForPropagator;
private final ExecutableElement keySetMethod;
public KeyForPropagationTreeAnnotator(
AnnotatedTypeFactory atypeFactory, KeyForPropagator propagationTreeAnnotator) {
super(atypeFactory);
this.keyForPropagator = propagationTreeAnnotator;
keySetMethod =
TreeUtils.getMethod(
java.util.Map.class.getName(),
"keySet",
0,
atypeFactory.getProcessingEnv());
}
/** @return true iff expression is a call to java.util.Map.KeySet */
public boolean isCallToKeyset(ExpressionTree expression) {
if (expression instanceof MethodInvocationTree) {
return TreeUtils.isMethodInvocation(
expression, keySetMethod, atypeFactory.getProcessingEnv());
}
return false;
}
/**
* Transfers annotations to the variableTree if the right side is a call to
* java.util.Map.KeySet.
*/
@Override
public Void visitVariable(VariableTree variableTree, AnnotatedTypeMirror type) {
super.visitVariable(variableTree, type);
// This should only happen on map.keySet();
if (type.getKind() == TypeKind.DECLARED) {
final ExpressionTree initializer = variableTree.getInitializer();
if (isCallToKeyset(initializer)) {
final AnnotatedDeclaredType variableType = (AnnotatedDeclaredType) type;
final AnnotatedTypeMirror initializerType =
atypeFactory.getAnnotatedType(initializer);
// array types and boxed primitives etc don't require propagation
if (variableType.getKind() == TypeKind.DECLARED) {
keyForPropagator.propagate(
(AnnotatedDeclaredType) initializerType,
variableType,
PropagationDirection.TO_SUPERTYPE,
atypeFactory);
}
}
}
return null;
}
/** Transfers annotations to type if the left hand side is a variable declaration. */
@Override
public Void visitNewClass(NewClassTree node, AnnotatedTypeMirror type) {
Pair<Tree, AnnotatedTypeMirror> context =
atypeFactory.getVisitorState().getAssignmentContext();
if (type.getKind() == TypeKind.DECLARED && context != null && context.first != null) {
AnnotatedTypeMirror assignedTo =
TypeArgInferenceUtil.assignedTo(atypeFactory, atypeFactory.getPath(node));
if (assignedTo != null) {
// array types and boxed primitives etc don't require propagation
if (assignedTo.getKind() == TypeKind.DECLARED) {
final AnnotatedDeclaredType newClassType = (AnnotatedDeclaredType) type;
keyForPropagator.propagate(
newClassType,
(AnnotatedDeclaredType) assignedTo,
PropagationDirection.TO_SUBTYPE,
atypeFactory);
}
}
}
return super.visitNewClass(node, type);
}
}