| 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()); |
| } |
| } |
| } |