org.checkerframework.javacutil.TreePathUtil Maven / Gradle / Ivy
Show all versions of checker Show documentation
package org.checkerframework.javacutil;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.plumelib.util.IPair;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Set;
import java.util.StringJoiner;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
/**
* Utility methods for obtaining or analyzing a javac {@code TreePath}.
*
* @see TreeUtils
*/
public final class TreePathUtil {
/** Do not instantiate; this class is a collection of static methods. */
private TreePathUtil() {
throw new BugInCF("Class TreeUtils cannot be instantiated.");
}
///
/// Retrieving a path (from another path)
///
/**
* Gets path to the first (innermost) enclosing tree of the given kind. May return {@code path}
* itself.
*
* @param path the path defining the tree node
* @param kind the kind of the desired tree
* @return the path to the enclosing tree of the given type, {@code null} otherwise
*/
public static @Nullable TreePath pathTillOfKind(TreePath path, Tree.Kind kind) {
return pathTillOfKind(path, EnumSet.of(kind));
}
/**
* Gets path to the first (innermost) enclosing tree with any one of the given kinds. May return
* {@code path} itself.
*
* @param path the path defining the tree node
* @param kinds the set of kinds of the desired tree
* @return the path to the enclosing tree of the given type, {@code null} otherwise
*/
public static @Nullable TreePath pathTillOfKind(TreePath path, Set kinds) {
for (TreePath p = path; p != null; p = p.getParentPath()) {
if (kinds.contains(p.getLeaf().getKind())) {
return p;
}
}
return null;
}
/**
* Gets path to the first (innermost) enclosing class tree, where class is defined by the {@link
* TreeUtils#classTreeKinds()} method. May return {@code path} itself.
*
* @param path the path defining the tree node
* @return the path to the enclosing class tree, {@code null} otherwise
*/
public static @Nullable TreePath pathTillClass(TreePath path) {
return pathTillOfKind(path, TreeUtils.classTreeKinds());
}
/**
* Gets path to the first (innermost) enclosing method tree. May return {@code path} itself.
*
* @param path the path defining the tree node
* @return the path to the enclosing class tree, {@code null} otherwise
*/
public static @Nullable TreePath pathTillMethod(TreePath path) {
return pathTillOfKind(path, Tree.Kind.METHOD);
}
///
/// Retrieving a tree (from a path)
///
/**
* Gets the first (innermost) enclosing tree in path, of the given kind. May return the leaf of
* {@code path} itself.
*
* @param path the path defining the tree node
* @param kind the kind of the desired tree
* @return the enclosing tree of the given type as given by the path, {@code null} otherwise
*/
public static @Nullable Tree enclosingOfKind(TreePath path, Tree.Kind kind) {
return enclosingOfKind(path, EnumSet.of(kind));
}
/**
* Gets the first (innermost) enclosing tree in path, with any one of the given kinds. May
* return the leaf of {@code path} itself.
*
* @param path the path defining the tree node
* @param kinds the set of kinds of the desired tree
* @return the enclosing tree of the given type as given by the path, {@code null} otherwise
*/
public static @Nullable Tree enclosingOfKind(TreePath path, Set kinds) {
TreePath p = pathTillOfKind(path, kinds);
return (p == null) ? null : p.getLeaf();
}
/**
* Gets the first (innermost) enclosing tree in path, of the given class. May return the leaf of
* {@code path} itself.
*
* @param the type of {@code treeClass}
* @param path the path defining the tree node
* @param treeClass the class of the desired tree
* @return the enclosing tree of the given type as given by the path, {@code null} otherwise
*/
public static @Nullable T enclosingOfClass(TreePath path, Class treeClass) {
TreePath p = path;
while (p != null) {
Tree leaf = p.getLeaf();
if (treeClass.isInstance(leaf)) {
return treeClass.cast(leaf);
}
p = p.getParentPath();
}
return null;
}
/**
* Gets the path to nearest enclosing declaration (class, method, or variable) of the tree node
* defined by the given {@link TreePath}. May return the leaf of {@code path} itself.
*
* @param path the path defining the tree node
* @return path to the nearest enclosing class/method/variable in the path, or {@code null} if
* one does not exist
*/
public static @Nullable TreePath enclosingDeclarationPath(TreePath path) {
return pathTillOfKind(path, TreeUtils.declarationTreeKinds());
}
/**
* Gets the enclosing class of the tree node defined by the given {@link TreePath}. It returns a
* {@link Tree}, from which {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can be
* obtained. May return the leaf of {@code path} itself.
*
* @param path the path defining the tree node
* @return the enclosing class (or interface) as given by the path, or {@code null} if one does
* not exist
*/
public static @Nullable ClassTree enclosingClass(TreePath path) {
return (ClassTree) enclosingOfKind(path, TreeUtils.classTreeKinds());
}
/**
* Gets the enclosing variable of a tree node defined by the given {@link TreePath}. May return
* the leaf of {@code path} itself.
*
* @param path the path defining the tree node
* @return the enclosing variable as given by the path, or {@code null} if one does not exist
*/
public static @Nullable VariableTree enclosingVariable(TreePath path) {
return (VariableTree) enclosingOfKind(path, Tree.Kind.VARIABLE);
}
/**
* Gets the enclosing method of the tree node defined by the given {@link TreePath}. It returns
* a {@link Tree}, from which an {@code checkers.types.AnnotatedTypeMirror} or {@link Element}
* can be obtained. May return the leaf of {@code path} itself.
*
* Also see {@code AnnotatedTypeFactory#getEnclosingMethod} and {@code
* AnnotatedTypeFactory#getEnclosingClassOrMethod}, which do not require a TreePath.
*
* @param path the path defining the tree node
* @return the enclosing method as given by the path, or {@code null} if one does not exist
*/
public static @Nullable MethodTree enclosingMethod(TreePath path) {
return (MethodTree) enclosingOfKind(path, Tree.Kind.METHOD);
}
/**
* Gets the enclosing method or lambda expression of the tree node defined by the given {@link
* TreePath}. It returns a {@link Tree}, from which an {@code
* checkers.types.AnnotatedTypeMirror} or {@link Element} can be obtained. May return the leaf
* of {@code path} itself.
*
* @param path the path defining the tree node
* @return the enclosing method or lambda as given by the path, or {@code null} if one does not
* exist
*/
public static @Nullable Tree enclosingMethodOrLambda(TreePath path) {
return enclosingOfKind(path, EnumSet.of(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION));
}
/**
* Returns the top-level block that encloses the given path, or null if none does. Never returns
* the leaf of {@code path} itself.
*
* @param path a path
* @return the top-level block that encloses the given path, or null if none does
*/
public static @Nullable BlockTree enclosingTopLevelBlock(TreePath path) {
TreePath parentPath = path.getParentPath();
while (parentPath != null
&& !TreeUtils.classTreeKinds().contains(parentPath.getLeaf().getKind())) {
path = parentPath;
parentPath = parentPath.getParentPath();
}
if (path.getLeaf().getKind() == Tree.Kind.BLOCK) {
return (BlockTree) path.getLeaf();
}
return null;
}
/**
* Gets the first (innermost) enclosing tree in path, that is not a parenthesis. Never returns
* the leaf of {@code path} itself.
*
* @param path the path defining the tree node
* @return a pair of a non-parenthesis tree that contains the argument, and its child that is
* the argument or is a parenthesized version of it
*/
public static IPair enclosingNonParen(TreePath path) {
TreePath parentPath = path.getParentPath();
Tree enclosing = parentPath.getLeaf();
Tree enclosingChild = path.getLeaf();
while (enclosing.getKind() == Tree.Kind.PARENTHESIZED) {
parentPath = parentPath.getParentPath();
enclosingChild = enclosing;
enclosing = parentPath.getLeaf();
}
return IPair.of(enclosing, enclosingChild);
}
/**
* Returns the "assignment context" for the leaf of {@code treePath}, which is often the leaf of
* the parent of {@code treePath}. (Does not handle pseudo-assignment of an argument to a
* parameter or a receiver expression to a receiver.) This is not the same as {@code
* org.checkerframework.dataflow.cfg.node.AssignmentContext}, which represents the left-hand
* side rather than the assignment itself.
*
* The assignment context for {@code treePath} is the leaf of its parent, if that leaf is one
* of the following trees:
*
*
* - AssignmentTree
*
- CompoundAssignmentTree
*
- MethodInvocationTree
*
- NewArrayTree
*
- NewClassTree
*
- ReturnTree
*
- VariableTree
*
*
* If the parent is a ConditionalExpressionTree we need to distinguish two cases: If the leaf is
* either the then or else branch of the ConditionalExpressionTree, then recurse on the parent.
* If the leaf is the condition of the ConditionalExpressionTree, then return null to not
* consider this assignment context.
*
* If the leaf is a ParenthesizedTree, then recurse on the parent.
*
*
Otherwise, null is returned.
*
* @param treePath a path
* @return the assignment context as described, {@code null} otherwise
*/
public static @Nullable Tree getAssignmentContext(TreePath treePath) {
TreePath parentPath = treePath.getParentPath();
if (parentPath == null) {
return null;
}
Tree parent = parentPath.getLeaf();
switch (parent.getKind()) {
case ASSIGNMENT: // See below for CompoundAssignmentTree.
case METHOD_INVOCATION:
case NEW_ARRAY:
case NEW_CLASS:
case RETURN:
case VARIABLE:
return parent;
case CONDITIONAL_EXPRESSION:
ConditionalExpressionTree cet = (ConditionalExpressionTree) parent;
@SuppressWarnings("interning:not.interned") // AST node comparison
boolean conditionIsLeaf = (cet.getCondition() == treePath.getLeaf());
if (conditionIsLeaf) {
// The assignment context for the condition is simply boolean.
// No point in going on.
return null;
}
// Otherwise use the context of the ConditionalExpressionTree.
return getAssignmentContext(parentPath);
case PARENTHESIZED:
return getAssignmentContext(parentPath);
default:
// 11 Tree.Kinds are CompoundAssignmentTrees,
// so use instanceof rather than listing all 11.
if (parent instanceof CompoundAssignmentTree) {
return parent;
}
return null;
}
}
///
/// Predicates
///
/**
* Returns true if the tree is in a constructor or an initializer block.
*
* @param path the path to test
* @return true if the path is in a constructor or an initializer block
*/
public static boolean inConstructor(TreePath path) {
MethodTree method = enclosingMethod(path);
// If method is null, this is an initializer block.
return method == null || TreeUtils.isConstructor(method);
}
/**
* Returns true if the leaf of the tree path is in a static scope.
*
* @param path a TreePath whose leaf may or may not be in static scope
* @return true if the leaf of the tree path is in a static scope
*/
public static boolean isTreeInStaticScope(TreePath path) {
MethodTree enclosingMethod = enclosingMethod(path);
if (enclosingMethod != null) {
return enclosingMethod.getModifiers().getFlags().contains(Modifier.STATIC);
}
// no enclosing method, check for static or initializer block
BlockTree block = enclosingTopLevelBlock(path);
if (block != null) {
return block.isStatic();
}
// check if it's in a variable initializer
Tree t = enclosingVariable(path);
if (t != null) {
return ((VariableTree) t).getModifiers().getFlags().contains(Modifier.STATIC);
}
ClassTree classTree = enclosingClass(path);
if (classTree != null) {
return classTree.getModifiers().getFlags().contains(Modifier.STATIC);
}
return false;
}
/**
* Returns true if the path is to a top-level (not within a loop) assignment within an
* initializer block. The initializer block might be instance or static. Will return true for a
* re-assignment even if there is another initialization (within this initializer block, another
* initializer block, a constructor, or the variable declaration).
*
* @param path the path to test
* @return true if the path is to an initialization within an initializer block
*/
public static boolean isTopLevelAssignmentInInitializerBlock(TreePath path) {
TreePath origPath = path;
if (path.getLeaf().getKind() != Tree.Kind.ASSIGNMENT) {
return false;
}
path = path.getParentPath();
if (path.getLeaf().getKind() != Tree.Kind.EXPRESSION_STATEMENT) {
return false;
}
Tree prevLeaf = path.getLeaf();
path = path.getParentPath();
for (Iterator itor = path.iterator(); itor.hasNext(); ) {
Tree leaf = itor.next();
switch (leaf.getKind()) {
case CLASS:
case ENUM:
case PARAMETERIZED_TYPE:
return prevLeaf.getKind() == Tree.Kind.BLOCK;
case COMPILATION_UNIT:
throw new BugInCF("found COMPILATION_UNIT in " + toString(origPath));
case DO_WHILE_LOOP:
case ENHANCED_FOR_LOOP:
case FOR_LOOP:
case LAMBDA_EXPRESSION:
case METHOD:
return false;
default:
prevLeaf = leaf;
}
}
throw new BugInCF("path did not contain method or class: " + toString(origPath));
}
///
/// Formatting
///
/**
* Return a printed representation of a TreePath.
*
* @param path a TreePath
* @return a printed representation of the given TreePath
*/
public static String toString(TreePath path) {
StringJoiner result = new StringJoiner(System.lineSeparator() + " ");
result.add("TreePath:");
for (Tree t : path) {
result.add(TreeUtils.toStringTruncated(t, 65) + " " + t.getKind());
}
return result.toString();
}
/**
* Returns a string representation of the leaf of the given path, using {@link
* TreeUtils#toStringTruncated}.
*
* @param path a path
* @param length the maximum length for the result; must be at least 6
* @return a one-line string representation of the leaf of the given path that is no longer than
* {@code length} characters long
*/
public static String leafToStringTruncated(@Nullable TreePath path, int length) {
if (path == null) {
return "null";
}
return TreeUtils.toStringTruncated(path.getLeaf(), length);
}
}