blob: 707feed32351d5026adf9e0de6f546c8d42af8e9 [file] [log] [blame]
package org.checkerframework.framework.type;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IntersectionTypeTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.PrimitiveTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnionTypeTree;
import com.sun.source.tree.WildcardTree;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.TypeVariableSymbol;
import com.sun.tools.javac.code.Type.TypeVar;
import com.sun.tools.javac.code.Type.WildcardType;
import com.sun.tools.javac.tree.JCTree.JCWildcard;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeVariable;
import org.checkerframework.checker.interning.qual.FindDistinct;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.CollectionsPlume;
/**
* Converts type trees into AnnotatedTypeMirrors.
*
* @see org.checkerframework.framework.type.TypeFromTree
*/
class TypeFromTypeTreeVisitor extends TypeFromTreeVisitor {
private final Map<Tree, AnnotatedTypeMirror> visitedBounds = new HashMap<>();
@Override
public AnnotatedTypeMirror visitAnnotatedType(AnnotatedTypeTree node, AnnotatedTypeFactory f) {
AnnotatedTypeMirror type = visit(node.getUnderlyingType(), f);
if (type == null) { // e.g., for receiver type
type = f.toAnnotatedType(f.types.getNoType(TypeKind.NONE), false);
}
assert AnnotatedTypeFactory.validAnnotatedType(type);
List<? extends AnnotationMirror> annos = TreeUtils.annotationsFromTree(node);
if (type.getKind() == TypeKind.WILDCARD) {
// Work-around for https://github.com/eisop/checker-framework/issues/17
// For an annotated wildcard tree node, the type attached to the
// node is a WildcardType with a correct bound (set to the type
// variable which the wildcard instantiates). The underlying type is
// also a WildcardType but with a bound of null. Here we update the
// bound of the underlying WildcardType to be consistent.
WildcardType wildcardAttachedToNode = (WildcardType) TreeUtils.typeOf(node);
WildcardType underlyingWildcard = (WildcardType) type.getUnderlyingType();
underlyingWildcard.withTypeVar(wildcardAttachedToNode.bound);
// End of work-around
final AnnotatedWildcardType wctype = ((AnnotatedWildcardType) type);
final ExpressionTree underlyingTree = node.getUnderlyingType();
if (underlyingTree.getKind() == Kind.UNBOUNDED_WILDCARD) {
// primary annotations on unbounded wildcard types apply to both bounds
wctype.getExtendsBound().addAnnotations(annos);
wctype.getSuperBound().addAnnotations(annos);
} else if (underlyingTree.getKind() == Kind.EXTENDS_WILDCARD) {
wctype.getSuperBound().addAnnotations(annos);
} else if (underlyingTree.getKind() == Kind.SUPER_WILDCARD) {
wctype.getExtendsBound().addAnnotations(annos);
} else {
throw new BugInCF(
"Unexpected kind for type. node="
+ node
+ " type="
+ type
+ " kind="
+ underlyingTree.getKind());
}
} else {
type.addAnnotations(annos);
}
return type;
}
@Override
public AnnotatedTypeMirror visitArrayType(ArrayTypeTree node, AnnotatedTypeFactory f) {
AnnotatedTypeMirror component = visit(node.getType(), f);
AnnotatedTypeMirror result = f.type(node);
assert result instanceof AnnotatedArrayType;
((AnnotatedArrayType) result).setComponentType(component);
return result;
}
@Override
public AnnotatedTypeMirror visitParameterizedType(
ParameterizedTypeTree node, AnnotatedTypeFactory f) {
ClassSymbol baseType = (ClassSymbol) TreeUtils.elementFromTree(node.getType());
updateWildcardBounds(node.getTypeArguments(), baseType.getTypeParameters());
List<AnnotatedTypeMirror> args =
CollectionsPlume.mapList((Tree t) -> visit(t, f), node.getTypeArguments());
AnnotatedTypeMirror result = f.type(node); // use creator?
AnnotatedTypeMirror atype = visit(node.getType(), f);
result.addAnnotations(atype.getAnnotations());
// new ArrayList<>() type is AnnotatedExecutableType for some reason
// Don't initialize the type arguments if they are empty. The type arguments might be a
// diamond which should be inferred.
if (result instanceof AnnotatedDeclaredType && !args.isEmpty()) {
assert result instanceof AnnotatedDeclaredType : node + " --> " + result;
((AnnotatedDeclaredType) result).setTypeArguments(args);
}
return result;
}
/**
* Work around a bug in javac 9 where sometimes the bound field is set to the transitive
* supertype's type parameter instead of the type parameter which the wildcard directly
* instantiates. See https://github.com/eisop/checker-framework/issues/18
*
* <p>Sets each wildcard type argument's bound from typeArgs to the corresponding type parameter
* from typeParams.
*
* <p>If typeArgs.size() == 0 the method does nothing and returns. Otherwise, typeArgs.size() has
* to be equal to typeParams.size().
*
* <p>For each wildcard type argument and corresponding type parameter, sets the
* WildcardType.bound field to the corresponding type parameter, if and only if the owners of the
* existing bound and the type parameter are different.
*
* <p>In scenarios where the bound's owner is the same, we don't want to replace a
* capture-converted bound in the wildcard type with a non-capture-converted bound given by the
* type parameter declaration.
*
* @param typeArgs the type of the arguments at (e.g., at the call side)
* @param typeParams the type of the formal parameters (e.g., at the method declaration)
*/
@SuppressWarnings("interning:not.interned") // workaround for javac bug
private void updateWildcardBounds(
List<? extends Tree> typeArgs, List<TypeVariableSymbol> typeParams) {
if (typeArgs.isEmpty()) {
// Nothing to do for empty type arguments.
return;
}
assert typeArgs.size() == typeParams.size();
Iterator<? extends Tree> typeArgsItr = typeArgs.iterator();
Iterator<TypeVariableSymbol> typeParamsItr = typeParams.iterator();
while (typeArgsItr.hasNext()) {
Tree typeArg = typeArgsItr.next();
TypeVariableSymbol typeParam = typeParamsItr.next();
if (typeArg instanceof WildcardTree) {
TypeVar typeVar = (TypeVar) typeParam.asType();
WildcardType wcType = (WildcardType) ((JCWildcard) typeArg).type;
if (wcType.bound != null
&& wcType.bound.tsym != null
&& typeVar.tsym != null
&& wcType.bound.tsym.owner != typeVar.tsym.owner) {
wcType.withTypeVar(typeVar);
}
}
}
}
@Override
public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree node, AnnotatedTypeFactory f) {
return f.type(node);
}
@Override
public AnnotatedTypeVariable visitTypeParameter(
TypeParameterTree node, @FindDistinct AnnotatedTypeFactory f) {
List<AnnotatedTypeMirror> bounds = new ArrayList<>(node.getBounds().size());
for (Tree t : node.getBounds()) {
AnnotatedTypeMirror bound;
if (visitedBounds.containsKey(t) && f == visitedBounds.get(t).atypeFactory) {
bound = visitedBounds.get(t);
} else {
visitedBounds.put(t, f.type(t));
bound = visit(t, f);
visitedBounds.remove(t);
}
bounds.add(bound);
}
AnnotatedTypeVariable result = (AnnotatedTypeVariable) f.type(node);
List<? extends AnnotationMirror> annotations = TreeUtils.annotationsFromTree(node);
result.getLowerBound().addAnnotations(annotations);
switch (bounds.size()) {
case 0:
break;
case 1:
result.setUpperBound(bounds.get(0));
break;
default:
AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) result.getUpperBound();
intersection.setBounds(bounds);
intersection.copyIntersectionBoundAnnotations();
}
return result;
}
@Override
public AnnotatedTypeMirror visitWildcard(WildcardTree node, AnnotatedTypeFactory f) {
AnnotatedTypeMirror bound = visit(node.getBound(), f);
AnnotatedTypeMirror result = f.type(node);
assert result instanceof AnnotatedWildcardType;
// for wildcards unlike type variables there are bounds that differ in type from
// result. These occur for RAW types. In this case, use the newly created bound
// rather than merging into result
if (node.getKind() == Tree.Kind.SUPER_WILDCARD) {
((AnnotatedWildcardType) result).setSuperBound(bound);
} else if (node.getKind() == Tree.Kind.EXTENDS_WILDCARD) {
((AnnotatedWildcardType) result).setExtendsBound(bound);
}
return result;
}
/**
* If a tree is can be found for the declaration of the type variable {@code type}, then a {@link
* AnnotatedTypeVariable} is returned with explicit annotations from the type variables declared
* bounds. If a tree cannot be found, then {@code type}, converted to a use, is returned.
*
* @param type type variable used to find declaration tree
* @param f annotated type factory
* @return the AnnotatedTypeVariable from the declaration of {@code type} or {@code type} if no
* tree is found.
*/
private AnnotatedTypeVariable getTypeVariableFromDeclaration(
AnnotatedTypeVariable type, AnnotatedTypeFactory f) {
TypeVariable typeVar = type.getUnderlyingType();
TypeParameterElement tpe = (TypeParameterElement) typeVar.asElement();
Element elt = tpe.getGenericElement();
if (elt instanceof TypeElement) {
TypeElement typeElt = (TypeElement) elt;
int idx = typeElt.getTypeParameters().indexOf(tpe);
ClassTree cls = (ClassTree) f.declarationFromElement(typeElt);
if (cls == null || cls.getTypeParameters().isEmpty()) {
// The type parameters in the source tree were already erased. The element already
// contains all necessary information and we can return that.
return type.asUse();
}
// `forTypeVariable` is called for Identifier, MemberSelect and UnionType trees,
// none of which are declarations. But `cls.getTypeParameters()` returns a list
// of type parameter declarations (`TypeParameterTree`), so this call
// will return a declaration ATV. So change it to a use.
return visitTypeParameter(cls.getTypeParameters().get(idx), f).asUse();
} else if (elt instanceof ExecutableElement) {
ExecutableElement exElt = (ExecutableElement) elt;
int idx = exElt.getTypeParameters().indexOf(tpe);
MethodTree meth = (MethodTree) f.declarationFromElement(exElt);
if (meth == null) {
// throw new BugInCF("TypeFromTree.forTypeVariable: did not find source for: "
// + elt);
return type.asUse();
}
// This works the same as the case above. Even though `meth` itself is not a
// type declaration tree, the elements of `meth.getTypeParameters()` still are.
AnnotatedTypeVariable result =
visitTypeParameter(meth.getTypeParameters().get(idx), f).shallowCopy();
result.setDeclaration(false);
return result;
} else if (TypesUtils.isCaptured(typeVar)) {
// Captured types can have a generic element (owner) that is
// not an element at all, namely Symtab.noSymbol.
return type.asUse();
} else {
throw new BugInCF("TypeFromTree.forTypeVariable: not a supported element: " + elt);
}
}
@Override
public AnnotatedTypeMirror visitIdentifier(IdentifierTree node, AnnotatedTypeFactory f) {
AnnotatedTypeMirror type = f.type(node);
if (type.getKind() == TypeKind.TYPEVAR) {
return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f);
}
return type;
}
@Override
public AnnotatedTypeMirror visitMemberSelect(MemberSelectTree node, AnnotatedTypeFactory f) {
AnnotatedTypeMirror type = f.type(node);
if (type.getKind() == TypeKind.TYPEVAR) {
return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f);
}
return type;
}
@Override
public AnnotatedTypeMirror visitUnionType(UnionTypeTree node, AnnotatedTypeFactory f) {
AnnotatedTypeMirror type = f.type(node);
if (type.getKind() == TypeKind.TYPEVAR) {
return getTypeVariableFromDeclaration((AnnotatedTypeVariable) type, f);
}
return type;
}
@Override
public AnnotatedTypeMirror visitIntersectionType(
IntersectionTypeTree node, AnnotatedTypeFactory f) {
// This method is only called for IntersectionTypes in casts. There is no IntersectionTypeTree
// for a type variable bound that is an intersection. See #visitTypeParameter.
AnnotatedIntersectionType type = (AnnotatedIntersectionType) f.type(node);
List<AnnotatedTypeMirror> bounds =
CollectionsPlume.mapList((Tree boundTree) -> visit(boundTree, f), node.getBounds());
type.setBounds(bounds);
type.copyIntersectionBoundAnnotations();
return type;
}
}