blob: fcbd03b0dbab9ccc61381658c7ca06fbf82c7b3e [file] [log] [blame]
package org.checkerframework.framework.type.visitor;
import java.util.IdentityHashMap;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
/**
* An {@code AnnotatedTypeScanner} visits an {@link AnnotatedTypeMirror} and all of its child {@link
* AnnotatedTypeMirror} and preforms some function depending on the kind of type. (By contrast, a
* {@link SimpleAnnotatedTypeScanner} scans an {@link AnnotatedTypeMirror} and performs the
* <em>same</em> function regardless of the kind of type.) The function returns some value with type
* {@code R} and takes an argument of type {@code P}. If the function does not return any value,
* then {@code R} should be {@link Void}. If the function takes no additional argument, then {@code
* P} should be {@link Void}.
*
* <p>The default implementation of the visitAnnotatedTypeMirror methods will determine a result as
* follows:
*
* <ul>
* <li>If the type being visited has no children, the {@link #defaultResult} is returned.
* <li>If the type being visited has one child, the result of visiting the child type is returned.
* <li>If the type being visited has more than one child, the result is determined by visiting
* each child in turn, and then combining the result of each with the cumulative result so
* far, as determined by the {@link #reduce} method.
* </ul>
*
* The {@link #reduce} method combines the results of visiting child types. It can be specified by
* passing a {@link Reduce} object to one of the constructors or by overriding the method directly.
* If it is not otherwise specified, then reduce returns the first result if it is not null;
* otherwise, the second result is returned. If the default result is nonnull and reduce never
* returns null, then both parameters passed to reduce will be nonnull.
*
* <p>When overriding a visitAnnotatedTypeMirror method, the returned expression should be {@code
* reduce(super.visitAnnotatedTypeMirror(type, parameter), result)} so that the whole type is
* scanned.
*
* <p>To begin scanning a type call {@link #visit(AnnotatedTypeMirror, Object)} or (to pass {@code
* null} as the last parameter) call {@link #visit(AnnotatedTypeMirror)}. Both methods call {@link
* #reset()}.
*
* <p>Here is an example of a scanner that counts the number of {@link AnnotatedTypeVariable} in an
* AnnotatedTypeMirror.
*
* <pre>
* {@code class CountTypeVariable extends AnnotatedTypeScanner<Integer, Void>} {
* public CountTypeVariable() {
* super(Integer::sum, 0);
* }
*
* {@literal @}Override
* public Integer visitTypeVariable(AnnotatedTypeVariable type, Void p) {
* return reduce(super.visitTypeVariable(type, p), 1);
* }
* }
* </pre>
*
* An {@code AnnotatedTypeScanner} keeps a map of visited types, in order to prevent infinite
* recursion on recursive types. Because of this map, you should not create a new {@code
* AnnotatedTypeScanner} for each use. Instead, store an {@code AnnotatedTypeScanner} as a field in
* the {@link org.checkerframework.framework.type.AnnotatedTypeFactory} or {@link
* org.checkerframework.common.basetype.BaseTypeVisitor} of the checker.
*
* <p>Below is an example of how to use {@code CountTypeVariable}.
*
* <pre>{@code
* private final CountTypeVariable countTypeVariable = new CountTypeVariable();
*
* void method(AnnotatedTypeMirror type) {
* int count = countTypeVariable.visit(type);
* }
* }</pre>
*
* @param <R> the return type of this visitor's methods. Use Void for visitors that do not need to
* return results.
* @param <P> the type of the additional parameter to this visitor's methods. Use Void for visitors
* that do not need an additional parameter.
*/
public abstract class AnnotatedTypeScanner<R, P> implements AnnotatedTypeVisitor<R, P> {
/**
* Reduces two results into a single result.
*
* @param <R> the result type
*/
@FunctionalInterface
public interface Reduce<R> {
/**
* Returns the combination of two results.
*
* @param r1 the first result
* @param r2 the second result
* @return the combination of the two results
*/
R reduce(R r1, R r2);
}
/** The reduce function to use. */
protected final Reduce<R> reduceFunction;
/** The result to return if no other result is provided. It should be immutable. */
protected final R defaultResult;
/**
* Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} is
* null, then the reduce function returns the first result if it is nonnull; otherwise the second
* result is returned.
*
* @param reduceFunction function used to combine two results
* @param defaultResult the result to return if a visit type method is not overridden; it should
* be immutable
*/
protected AnnotatedTypeScanner(@Nullable Reduce<R> reduceFunction, R defaultResult) {
if (reduceFunction == null) {
this.reduceFunction = (r1, r2) -> r1 == null ? r2 : r1;
} else {
this.reduceFunction = reduceFunction;
}
this.defaultResult = defaultResult;
}
/**
* Constructs an AnnotatedTypeScanner with the given reduce function. If {@code reduceFunction} is
* null, then the reduce function returns the first result if it is nonnull; otherwise the second
* result is returned. The default result is {@code null}
*
* @param reduceFunction function used to combine two results
*/
protected AnnotatedTypeScanner(@Nullable Reduce<R> reduceFunction) {
this(reduceFunction, null);
}
/**
* Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is
* nonnull; otherwise the second result is returned.
*
* @param defaultResult the result to return if a visit type method is not overridden; it should
* be immutable
*/
protected AnnotatedTypeScanner(R defaultResult) {
this(null, defaultResult);
}
/**
* Constructs an AnnotatedTypeScanner where the reduce function returns the first result if it is
* nonnull; otherwise the second result is returned. The default result is {@code null}.
*/
protected AnnotatedTypeScanner() {
this(null, null);
}
// To prevent infinite loops
protected final IdentityHashMap<AnnotatedTypeMirror, R> visitedNodes = new IdentityHashMap<>();
/**
* Reset the scanner to allow reuse of the same instance. Subclasses should override this method
* to clear their additional state; they must call the super implementation.
*/
public void reset() {
visitedNodes.clear();
}
/**
* Calls {@link #reset()} and then scans {@code type} using null as the parameter.
*
* @param type type to scan
* @return result of scanning {@code type}
*/
@Override
public final R visit(AnnotatedTypeMirror type) {
return visit(type, null);
}
/**
* Calls {@link #reset()} and then scans {@code type} using {@code p} as the parameter.
*
* @param type the type to visit
* @param p a visitor-specified parameter
* @return result of scanning {@code type}
*/
@Override
public final R visit(AnnotatedTypeMirror type, P p) {
reset();
return scan(type, p);
}
/**
* Scan {@code type} by calling {@code type.accept(this, p)}; this method may be overridden by
* subclasses.
*
* @param type type to scan
* @param p the parameter to use
* @return the result of visiting {@code type}
*/
protected R scan(AnnotatedTypeMirror type, P p) {
return type.accept(this, p);
}
/**
* Scan all the types and returns the reduced result.
*
* @param types types to scan
* @param p the parameter to use
* @return the reduced result of scanning all the types
*/
protected R scan(@Nullable Iterable<? extends AnnotatedTypeMirror> types, P p) {
if (types == null) {
return defaultResult;
}
R r = defaultResult;
boolean first = true;
for (AnnotatedTypeMirror type : types) {
r = (first ? scan(type, p) : scanAndReduce(type, p, r));
first = false;
}
return r;
}
protected R scanAndReduce(Iterable<? extends AnnotatedTypeMirror> types, P p, R r) {
return reduce(scan(types, p), r);
}
/**
* Scans {@code type} with the parameter {@code p} and reduces the result with {@code r}.
*
* @param type type to scan
* @param p parameter to use for when scanning {@code type}
* @param r result to combine with the result of scanning {@code type}
* @return the combination of {@code r} with the result of scanning {@code type}
*/
protected R scanAndReduce(AnnotatedTypeMirror type, P p, R r) {
return reduce(scan(type, p), r);
}
/**
* Combines {@code r1} and {@code r2} and returns the result. The default implementation returns
* {@code r1} if it is not null; otherwise, it returns {@code r2}.
*
* @param r1 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method never
* returns null
* @param r2 a result of scan, nonnull if {@link #defaultResult} is nonnull and this method never
* returns null
* @return the combination of {@code r1} and {@code r2}
*/
protected R reduce(R r1, R r2) {
return reduceFunction.reduce(r1, r2);
}
@Override
public R visitDeclared(AnnotatedDeclaredType type, P p) {
// Only declared types with type arguments might be recursive, so only store those.
boolean shouldStoreType = !type.getTypeArguments().isEmpty();
if (shouldStoreType && visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
if (shouldStoreType) {
visitedNodes.put(type, defaultResult);
}
R r = defaultResult;
if (type.getEnclosingType() != null) {
r = scan(type.getEnclosingType(), p);
if (shouldStoreType) {
visitedNodes.put(type, r);
}
}
r = scanAndReduce(type.getTypeArguments(), p, r);
if (shouldStoreType) {
visitedNodes.put(type, r);
}
return r;
}
@Override
public R visitIntersection(AnnotatedIntersectionType type, P p) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
visitedNodes.put(type, defaultResult);
R r = scan(type.getBounds(), p);
visitedNodes.put(type, r);
return r;
}
@Override
public R visitUnion(AnnotatedUnionType type, P p) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
visitedNodes.put(type, defaultResult);
R r = scan(type.getAlternatives(), p);
visitedNodes.put(type, r);
return r;
}
@Override
public R visitArray(AnnotatedArrayType type, P p) {
R r = scan(type.getComponentType(), p);
return r;
}
@Override
public R visitExecutable(AnnotatedExecutableType type, P p) {
R r = scan(type.getReturnType(), p);
if (type.getReceiverType() != null) {
r = scanAndReduce(type.getReceiverType(), p, r);
}
r = scanAndReduce(type.getParameterTypes(), p, r);
r = scanAndReduce(type.getThrownTypes(), p, r);
r = scanAndReduce(type.getTypeVariables(), p, r);
return r;
}
@Override
public R visitTypeVariable(AnnotatedTypeVariable type, P p) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
visitedNodes.put(type, defaultResult);
R r = scan(type.getLowerBound(), p);
visitedNodes.put(type, r);
r = scanAndReduce(type.getUpperBound(), p, r);
visitedNodes.put(type, r);
return r;
}
@Override
public R visitNoType(AnnotatedNoType type, P p) {
return defaultResult;
}
@Override
public R visitNull(AnnotatedNullType type, P p) {
return defaultResult;
}
@Override
public R visitPrimitive(AnnotatedPrimitiveType type, P p) {
return defaultResult;
}
@Override
public R visitWildcard(AnnotatedWildcardType type, P p) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
visitedNodes.put(type, defaultResult);
R r = scan(type.getExtendsBound(), p);
visitedNodes.put(type, r);
r = scanAndReduce(type.getSuperBound(), p, r);
visitedNodes.put(type, r);
return r;
}
}