blob: c16f69b7e7ac7847e8902ca0e197abce7f13bac6 [file] [log] [blame]
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.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.util.EnumSet;
import java.util.Set;
import java.util.StringJoiner;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* 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 Error("Class TreeUtils cannot be instantiated.");
}
///
/// Retrieving a path
///
/**
* Gets path to the first (innermost) enclosing tree of the specified kind.
*
* @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(final TreePath path, final Tree.Kind kind) {
return pathTillOfKind(path, EnumSet.of(kind));
}
/**
* Gets path to the first (innermost) enclosing tree with any one of the specified kinds.
*
* @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(final TreePath path, final Set<Tree.Kind> kinds) {
TreePath p = path;
while (p != null) {
Tree leaf = p.getLeaf();
assert leaf != null;
if (kinds.contains(leaf.getKind())) {
return p;
}
p = p.getParentPath();
}
return null;
}
/**
* Gets path to the first (innermost) enclosing class tree, where class is defined by the {@link
* TreeUtils#classTreeKinds()} method.
*
* @param path the path defining the tree node
* @return the path to the enclosing class tree, {@code null} otherwise
*/
public static @Nullable TreePath pathTillClass(final TreePath path) {
return pathTillOfKind(path, TreeUtils.classTreeKinds());
}
/**
* Gets path to the first (innermost) enclosing method tree.
*
* @param path the path defining the tree node
* @return the path to the enclosing class tree, {@code null} otherwise
*/
public static @Nullable TreePath pathTillMethod(final TreePath path) {
return pathTillOfKind(path, Tree.Kind.METHOD);
}
///
/// Retrieving a tree
///
/**
* Gets the first (innermost) enclosing tree in path, of the specified kind.
*
* @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(final TreePath path, final Tree.Kind kind) {
return enclosingOfKind(path, EnumSet.of(kind));
}
/**
* Gets the first (innermost) enclosing tree in path, with any one of the specified kinds.
*
* @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(final TreePath path, final Set<Tree.Kind> kinds) {
TreePath p = pathTillOfKind(path, kinds);
return (p == null) ? null : p.getLeaf();
}
/**
* Gets the first (innermost) enclosing tree in path, of the specified class.
*
* @param <T> 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 <T extends Tree> @Nullable T enclosingOfClass(
final TreePath path, final Class<T> 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 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.
*
* @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(final TreePath path) {
return (ClassTree) enclosingOfKind(path, TreeUtils.classTreeKinds());
}
/**
* Gets the enclosing variable of a tree node defined by the given {@link TreePath}.
*
* @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(final 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.
*
* <p>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(final 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.
*
* @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(final TreePath path) {
return enclosingOfKind(path, EnumSet.of(Tree.Kind.METHOD, Kind.LAMBDA_EXPRESSION));
}
/**
* Returns the top-level block that encloses the given path, or null if none does.
*
* @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.
*
* @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 Pair<Tree, Tree> enclosingNonParen(final TreePath path) {
TreePath parentPath = path.getParentPath();
Tree enclosing = parentPath.getLeaf();
Tree enclosingChild = path.getLeaf();
while (enclosing.getKind() == Kind.PARENTHESIZED) {
parentPath = parentPath.getParentPath();
enclosingChild = enclosing;
enclosing = parentPath.getLeaf();
}
return Pair.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.
*
* <p>The assignment context for {@code treePath} is the leaf of its parent, if that leaf is one
* of the following trees:
*
* <ul>
* <li>AssignmentTree
* <li>CompoundAssignmentTree
* <li>MethodInvocationTree
* <li>NewArrayTree
* <li>NewClassTree
* <li>ReturnTree
* <li>VariableTree
* </ul>
*
* 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.
*
* <p>If the leaf is a ParenthesizedTree, then recurse on the parent.
*
* <p>Otherwise, null is returned.
*
* @param treePath a path
* @return the assignment context as described, {@code null} otherwise
*/
public static @Nullable Tree getAssignmentContext(final 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 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 its 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;
}
///
/// 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);
}
}