blob: 1015fc35e927fdca46b919456e69c2bbbda31150 [file] [log] [blame]
package org.checkerframework.framework.type;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
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;
import org.checkerframework.framework.type.visitor.AnnotatedTypeVisitor;
import org.plumelib.util.CollectionsPlume;
/**
* AnnotatedTypeCopier is a visitor that deep copies an AnnotatedTypeMirror exactly, including any
* lazily initialized fields. That is, if a field has already been initialized, it will be
* initialized in the copied type.
*
* <p>When making copies, a map of encountered {@literal references => copied} types is maintained.
* This ensures that, if a reference appears in multiple locations in the original type, a
* corresponding copy of the original type appears in the same locations in the output copy. This
* ensures that the recursive loops in the input type are preserved in its output copy (see
* makeOrReturnCopy)
*
* <p>In general, AnnotatedTypeMirrors should be copied via AnnotatedTypeMirror#deepCopy and
* AnnotatedTypeMirror#shallowCopy. AnnotatedTypeMirror#deepCopy makes use of AnnotatedTypeCopier
* under the covers. However, this visitor and its subclasses can be invoked as follows:
*
* <pre>{@code new AnnotatedTypeCopier().visit(myTypeVar);}</pre>
*
* Note: There are methods that may require a copy of a type mirror with slight changes. It is
* intended that this class can be overridden for these cases.
*
* @see org.checkerframework.framework.type.TypeVariableSubstitutor
* @see org.checkerframework.framework.type.AnnotatedTypeCopierWithReplacement
*/
public class AnnotatedTypeCopier
implements AnnotatedTypeVisitor<
AnnotatedTypeMirror, IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror>> {
/**
* This is a hack to handle the curious behavior of substitution on an AnnotatedExecutableType.
*
* @see org.checkerframework.framework.type.TypeVariableSubstitutor It is poor form to include
* such a flag on the base class for exclusive use in a subclass but it is the least bad
* option in this case.
*/
protected boolean visitingExecutableTypeParam = false;
/**
* See {@link #AnnotatedTypeCopier(boolean)}.
*
* @see #AnnotatedTypeCopier(boolean)
*/
protected final boolean copyAnnotations;
/**
* Creates an AnnotatedTypeCopier that may or may not copyAnnotations By default
* AnnotatedTypeCopier provides two major properties in its copies:
*
* <ol>
* <li>Structure preservation -- the exact structure of the original AnnotatedTypeMirror is
* preserved in the copy including all component types.
* <li>Annotation preservation -- All of the annotations from the original AnnotatedTypeMirror
* and its components have been copied to the new type.
* </ol>
*
* If copyAnnotations is set to false, the second property, annotation preservation, is removed.
* This is useful for cases in which the user may want to copy the structure of a type exactly but
* NOT its annotations.
*/
public AnnotatedTypeCopier(final boolean copyAnnotations) {
this.copyAnnotations = copyAnnotations;
}
/**
* Creates an AnnotatedTypeCopier that copies both the structure and annotations of the source
* AnnotatedTypeMirror.
*
* @see #AnnotatedTypeCopier(boolean)
*/
public AnnotatedTypeCopier() {
this(true);
}
@Override
public AnnotatedTypeMirror visit(AnnotatedTypeMirror type) {
return type.accept(this, new IdentityHashMap<>());
}
@Override
public AnnotatedTypeMirror visit(
AnnotatedTypeMirror type,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
return type.accept(this, originalToCopy);
}
@Override
public AnnotatedTypeMirror visitDeclared(
AnnotatedDeclaredType original,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
if (originalToCopy.containsKey(original)) {
return originalToCopy.get(original);
}
final AnnotatedDeclaredType copy = makeOrReturnCopy(original, originalToCopy);
if (original.wasRaw()) {
copy.setWasRaw();
}
if (original.enclosingType != null) {
copy.enclosingType = (AnnotatedDeclaredType) visit(original.enclosingType, originalToCopy);
}
if (original.typeArgs != null) {
final List<AnnotatedTypeMirror> copyTypeArgs =
CollectionsPlume.mapList(
(AnnotatedTypeMirror typeArg) -> visit(typeArg, originalToCopy),
original.getTypeArguments());
copy.setTypeArguments(copyTypeArgs);
}
return copy;
}
@Override
public AnnotatedTypeMirror visitIntersection(
AnnotatedIntersectionType original,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
if (originalToCopy.containsKey(original)) {
return originalToCopy.get(original);
}
final AnnotatedIntersectionType copy = makeOrReturnCopy(original, originalToCopy);
if (original.bounds != null) {
List<AnnotatedTypeMirror> copySupertypes =
CollectionsPlume.mapList(
(AnnotatedTypeMirror bound) -> visit(bound, originalToCopy), original.bounds);
copy.bounds = Collections.unmodifiableList(copySupertypes);
}
return copy;
}
@Override
public AnnotatedTypeMirror visitUnion(
AnnotatedUnionType original,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
if (originalToCopy.containsKey(original)) {
return originalToCopy.get(original);
}
final AnnotatedUnionType copy = makeOrReturnCopy(original, originalToCopy);
if (original.alternatives != null) {
final List<AnnotatedDeclaredType> copyAlternatives =
CollectionsPlume.mapList(
(AnnotatedDeclaredType supertype) ->
(AnnotatedDeclaredType) visit(supertype, originalToCopy),
original.alternatives);
copy.alternatives = Collections.unmodifiableList(copyAlternatives);
}
return copy;
}
@Override
public AnnotatedTypeMirror visitExecutable(
AnnotatedExecutableType original,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
if (originalToCopy.containsKey(original)) {
return originalToCopy.get(original);
}
final AnnotatedExecutableType copy = makeOrReturnCopy(original, originalToCopy);
copy.setElement(original.getElement());
if (original.receiverType != null) {
copy.receiverType = (AnnotatedDeclaredType) visit(original.receiverType, originalToCopy);
}
for (final AnnotatedTypeMirror param : original.paramTypes) {
copy.paramTypes.add(visit(param, originalToCopy));
}
for (final AnnotatedTypeMirror thrown : original.throwsTypes) {
copy.throwsTypes.add(visit(thrown, originalToCopy));
}
copy.returnType = visit(original.returnType, originalToCopy);
for (final AnnotatedTypeVariable typeVariable : original.typeVarTypes) {
// This field is needed to identify exactly when the declaration of an executable's
// type parameter is visited. When subtypes of this class visit the type parameter's
// component types, they will likely set visitingExecutableTypeParam to false.
// Therefore, we set this variable on each iteration of the loop.
// See TypeVariableSubstitutor.Visitor.visitTypeVariable for an example of this.
visitingExecutableTypeParam = true;
copy.typeVarTypes.add((AnnotatedTypeVariable) visit(typeVariable, originalToCopy));
}
visitingExecutableTypeParam = false;
return copy;
}
@Override
public AnnotatedTypeMirror visitArray(
AnnotatedArrayType original,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
if (originalToCopy.containsKey(original)) {
return originalToCopy.get(original);
}
final AnnotatedArrayType copy = makeOrReturnCopy(original, originalToCopy);
copy.setComponentType(visit(original.getComponentType(), originalToCopy));
return copy;
}
@Override
public AnnotatedTypeMirror visitTypeVariable(
AnnotatedTypeVariable original,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
if (originalToCopy.containsKey(original)) {
return originalToCopy.get(original);
}
final AnnotatedTypeVariable copy = makeOrReturnCopy(original, originalToCopy);
if (original.getUpperBoundField() != null) {
// TODO: figure out why asUse is needed here and remove it.
copy.setUpperBound(visit(original.getUpperBoundField(), originalToCopy).asUse());
}
if (original.getLowerBoundField() != null) {
// TODO: figure out why asUse is needed here and remove it.
copy.setLowerBound(visit(original.getLowerBoundField(), originalToCopy).asUse());
}
return copy;
}
@Override
public AnnotatedTypeMirror visitPrimitive(
AnnotatedPrimitiveType original,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
return makeOrReturnCopy(original, originalToCopy);
}
@Override
public AnnotatedTypeMirror visitNoType(
AnnotatedNoType original,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
return makeCopy(original);
}
@Override
public AnnotatedTypeMirror visitNull(
AnnotatedNullType original,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
return makeOrReturnCopy(original, originalToCopy);
}
@Override
public AnnotatedTypeMirror visitWildcard(
AnnotatedWildcardType original,
IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
if (originalToCopy.containsKey(original)) {
return originalToCopy.get(original);
}
final AnnotatedWildcardType copy = makeOrReturnCopy(original, originalToCopy);
if (original.isUninferredTypeArgument()) {
copy.setUninferredTypeArgument();
}
if (original.getExtendsBoundField() != null) {
copy.setExtendsBound(visit(original.getExtendsBoundField(), originalToCopy).asUse());
}
if (original.getSuperBoundField() != null) {
copy.setSuperBound(visit(original.getSuperBoundField(), originalToCopy).asUse());
}
copy.setTypeVariable(original.getTypeVariable());
return copy;
}
/**
* For any given object in the type being copied, we only want to generate one copy of that
* object. When that object is encountered again, using the previously generated copy will
* preserve the structure of the original AnnotatedTypeMirror.
*
* <p>makeOrReturnCopy first checks to see if an object has been encountered before. If so, it
* returns the previously generated duplicate of that object if not, it creates a duplicate of the
* object and stores it in the history, originalToCopy
*
* @param original a reference to a type to copy
* @param originalToCopy a mapping of previously encountered references to the copies made for
* those references
* @param <T> the type of original copy, this is a shortcut to avoid having to insert casts all
* over the visitor
* @return a copy of original
*/
@SuppressWarnings("unchecked")
protected <T extends AnnotatedTypeMirror> T makeOrReturnCopy(
T original, IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) {
if (originalToCopy.containsKey(original)) {
return (T) originalToCopy.get(original);
}
final T copy = makeCopy(original);
originalToCopy.put(original, copy);
return copy;
}
@SuppressWarnings("unchecked")
protected <T extends AnnotatedTypeMirror> T makeCopy(T original) {
final T copy =
(T)
AnnotatedTypeMirror.createType(
original.getUnderlyingType(), original.atypeFactory, original.isDeclaration());
maybeCopyPrimaryAnnotations(original, copy);
return copy;
}
/**
* This method is called in any location in which a primary annotation would be copied from source
* to dest. Note, this method obeys the copyAnnotations field. Subclasses of AnnotatedTypeCopier
* can use this method to customize annotations before copying.
*
* @param source the type whose primary annotations are being copied
* @param dest a copy of source that should receive its primary annotations
*/
protected void maybeCopyPrimaryAnnotations(
final AnnotatedTypeMirror source, final AnnotatedTypeMirror dest) {
if (copyAnnotations) {
dest.addAnnotations(source.getAnnotationsField());
}
}
}