blob: 40640a72edb668961e4a441880cc1540b9e38046 [file] [log] [blame]
package org.checkerframework.framework.type;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeKind;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.visitor.DoubleAnnotatedTypeScanner;
import org.checkerframework.javacutil.BugInCF;
/**
* Replaces or adds all the annotations in the parameter with the annotations from the visited type.
* An annotation is replaced if the parameter type already has an annotation in the same hierarchy
* at the same location as the visited type.
*
* <p>Example use:
*
* <pre>{@code
* AnnotatedTypeMirror visitType = ...;
* AnnotatedTypeMirror parameter = ...;
* visitType.accept(new AnnotatedTypeReplacer(), parameter);
* }</pre>
*/
public class AnnotatedTypeReplacer extends DoubleAnnotatedTypeScanner<Void> {
/**
* Replaces or adds all annotations from {@code from} to {@code to}. Annotations from {@code from}
* will be used everywhere they exist, but annotations in {@code to} will be kept anywhere that
* {@code from} is unannotated.
*
* @param from the annotated type mirror from which to take new annotations
* @param to the annotated type mirror to which the annotations will be added
* @deprecated use {@link AnnotatedTypeFactory#replaceAnnotations(AnnotatedTypeMirror,
* AnnotatedTypeMirror)} instead.
*/
@Deprecated // 2021-03-25
@SuppressWarnings("interning:not.interned") // assertion
public static void replace(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) {
if (from == to) {
throw new BugInCF("From == to");
}
new AnnotatedTypeReplacer().visit(from, to);
}
/**
* Replaces or adds annotations in {@code top}'s hierarchy from {@code from} to {@code to}.
* Annotations from {@code from} will be used everywhere they exist, but annotations in {@code to}
* will be kept anywhere that {@code from} is unannotated.
*
* @param from the annotated type mirror from which to take new annotations
* @param to the annotated type mirror to which the annotations will be added
* @param top the top type of the hierarchy whose annotations will be added
* @deprecated use {@link AnnotatedTypeFactory#replaceAnnotations(AnnotatedTypeMirror,
* AnnotatedTypeMirror, AnnotationMirror)} instead.
*/
@Deprecated // 2021-03-25
@SuppressWarnings("interning:not.interned") // assertion
public static void replace(
final AnnotatedTypeMirror from, final AnnotatedTypeMirror to, final AnnotationMirror top) {
if (from == to) {
throw new BugInCF("from == to: %s", from);
}
new AnnotatedTypeReplacer(top).visit(from, to);
}
/** If top != null we replace only the annotations in the hierarchy of top. */
private AnnotationMirror top;
/** Construct an AnnotatedTypeReplacer that will replace all annotations. */
public AnnotatedTypeReplacer() {
this.top = null;
}
/**
* Construct an AnnotatedTypeReplacer that will only replace annotations in {@code top}'s
* hierarchy.
*
* @param top if top != null, then only annotation in the hierarchy of top are affected
*/
public AnnotatedTypeReplacer(final AnnotationMirror top) {
this.top = top;
}
/**
* If {@code top != null}, then only annotations in the hierarchy of {@code top} are affected;
* otherwise, all annotations are replaced.
*
* @param top if top != null, then only annotations in the hierarchy of top are replaced;
* otherwise, all annotations are replaced.
*/
public void setTop(@Nullable AnnotationMirror top) {
this.top = top;
}
@SuppressWarnings("interning:not.interned") // assertion
@Override
protected Void defaultAction(AnnotatedTypeMirror from, AnnotatedTypeMirror to) {
assert from != to;
if (from != null && to != null) {
replaceAnnotations(from, to);
}
return null;
}
/**
* Replace the annotations in to with the annotations in from, wherever from has an annotation.
*
* @param from the source of the annotations
* @param to the destination of the annotations, modified by this method
*/
protected void replaceAnnotations(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) {
if (top == null) {
to.replaceAnnotations(from.getAnnotations());
} else {
final AnnotationMirror replacement = from.getAnnotationInHierarchy(top);
if (replacement != null) {
to.replaceAnnotation(from.getAnnotationInHierarchy(top));
}
}
}
@Override
public Void visitTypeVariable(AnnotatedTypeVariable from, AnnotatedTypeMirror to) {
resolvePrimaries(from, to);
return super.visitTypeVariable(from, to);
}
@Override
public Void visitWildcard(AnnotatedWildcardType from, AnnotatedTypeMirror to) {
resolvePrimaries(from, to);
return super.visitWildcard(from, to);
}
/**
* For type variables and wildcards, the absence of a primary annotations has an implied meaning
* on substitution. Therefore, in these cases we remove the primary annotation and rely on the
* fact that the bounds are also merged into the type to.
*
* @param from a type variable or wildcard
* @param to the destination annotated type mirror
*/
public void resolvePrimaries(AnnotatedTypeMirror from, AnnotatedTypeMirror to) {
if (from.getKind() == TypeKind.WILDCARD || from.getKind() == TypeKind.TYPEVAR) {
if (top != null) {
if (from.getAnnotationInHierarchy(top) == null) {
to.removeAnnotationInHierarchy(top);
}
} else {
for (final AnnotationMirror toPrimaryAnno : to.getAnnotations()) {
if (from.getAnnotationInHierarchy(toPrimaryAnno) == null) {
to.removeAnnotation(toPrimaryAnno);
}
}
}
} else {
throw new BugInCF(
"ResolvePrimaries's from argument should be a type variable OR wildcard%n"
+ "from=%s%nto=%s",
from.toString(true), to.toString(true));
}
}
}