package org.checkerframework.framework.type; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import java.util.Map.Entry; import javax.lang.model.element.Element; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Types; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; /** TypeVariableSusbtitutor replaces type variables from a declaration with arguments to its use. */ public class TypeVariableSubstitutor { /** * Given a mapping between type variable's to typeArgument, replace each instance of type * variable with a copy of type argument. * * @see #substituteTypeVariable(AnnotatedTypeMirror, * org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable) * @return a copy of typeMirror with its type variables substituted */ public AnnotatedTypeMirror substitute( final Map<TypeVariable, AnnotatedTypeMirror> typeParamToArg, final AnnotatedTypeMirror typeMirror) { return new Visitor(typeParamToArg).visit(typeMirror); } /** * Given the types of a type parameter declaration, the argument to that type parameter * declaration, and a given use of that declaration, return a substitute for the use with the * correct annotations. * * <p>To determine what primary annotations are correct for the substitute the following rules * are used: If the type variable use has a primary annotation then apply that primary * annotation to the substitute. Otherwise, use the annotations of the argument. * * @param argument the argument to declaration (this will be a value in typeParamToArg) * @param use the use that is being replaced * @return a shallow copy of argument with the appropriate annotations applied */ protected AnnotatedTypeMirror substituteTypeVariable( final AnnotatedTypeMirror argument, final AnnotatedTypeVariable use) { final AnnotatedTypeMirror substitute = argument.shallowCopy(false); substitute.addAnnotations(argument.getAnnotationsField()); if (!use.getAnnotationsField().isEmpty()) { substitute.replaceAnnotations(use.getAnnotations()); } return substitute; } protected class Visitor extends AnnotatedTypeCopier { private final Map<TypeParameterElement, AnnotatedTypeMirror> elementToArgMap; public Visitor(final Map<TypeVariable, AnnotatedTypeMirror> typeParamToArg) { elementToArgMap = new HashMap<>(); for (Entry<TypeVariable, AnnotatedTypeMirror> paramToArg : typeParamToArg.entrySet()) { elementToArgMap.put( (TypeParameterElement) paramToArg.getKey().asElement(), paramToArg.getValue()); } } @Override public AnnotatedTypeMirror visitArray( AnnotatedArrayType original, IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) { if (originalToCopy.containsKey(original)) { return originalToCopy.get(original); } final AnnotatedArrayType copy = (AnnotatedArrayType) AnnotatedTypeMirror.createType( original.getUnderlyingType(), original.atypeFactory, original.isDeclaration()); maybeCopyPrimaryAnnotations(original, copy); originalToCopy.put(original, copy); // Substitution (along with any other operation that changes the component types of an AnnotatedTypeMirror) // may change the underlying Java type of components without updating the underlying Java // type of the parent type. We use the underlying type for various purposes (including equals/hashcode) // so this can lead to unpredictable behavior. Currently, we update the underlying type when // substituting on arrays in order to avoid an ErrorAbort in LubTypeVariableAnnotator. //TODO: Presumably there are more cases in which we want to do this final AnnotatedTypeMirror componentType = visit(original.getComponentType(), originalToCopy); final Types types = componentType.atypeFactory.types; final AnnotatedArrayType correctedCopy; if (!types.isSameType(componentType.getUnderlyingType(), copy.getUnderlyingType()) && componentType.getKind() != TypeKind.WILDCARD) { //TODO: THIS SHOULD BE CAPTURE CONVERTED final TypeMirror underlyingType = types.getArrayType(componentType.getUnderlyingType()); correctedCopy = (AnnotatedArrayType) AnnotatedTypeMirror.createType( underlyingType, copy.atypeFactory, false); correctedCopy.addAnnotations(copy.getAnnotations()); } else { correctedCopy = copy; } correctedCopy.setComponentType(componentType); return correctedCopy; } @Override public AnnotatedTypeMirror visitTypeVariable( AnnotatedTypeVariable original, IdentityHashMap<AnnotatedTypeMirror, AnnotatedTypeMirror> originalToCopy) { if (visitingExecutableTypeParam) { // AnnotatedExecutableType differs from AnnotatedDeclaredType in that its list of // type parameters cannot be adapted in place since the AnnotatedExecutable.typeVarTypes // field is of type AnnotatedTypeVariable and not AnnotatedTypeMirror. // When substituting, all component types that contain a use of the executable's type parameters // will be substituted. The executable's type parameters will have their bounds substituted // but the top-level AnnotatedTypeVariable's will remain visitingExecutableTypeParam = false; return super.visitTypeVariable(original, originalToCopy); } else { final Element typeVarElem = original.getUnderlyingType().asElement(); if (elementToArgMap.containsKey(typeVarElem)) { final AnnotatedTypeMirror argument = elementToArgMap.get(typeVarElem); return substituteTypeVariable(argument, original); } } return super.visitTypeVariable(original, originalToCopy); } } }