| package org.checkerframework.javacutil; |
| |
| import com.sun.tools.javac.code.Symbol; |
| import com.sun.tools.javac.code.Symtab; |
| import com.sun.tools.javac.code.Type; |
| import com.sun.tools.javac.code.Type.CapturedType; |
| import com.sun.tools.javac.code.Type.ClassType; |
| import com.sun.tools.javac.code.TypeTag; |
| import com.sun.tools.javac.model.JavacTypes; |
| import com.sun.tools.javac.processing.JavacProcessingEnvironment; |
| import com.sun.tools.javac.util.Context; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.StringJoiner; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.Name; |
| import javax.lang.model.element.NestingKind; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.TypeParameterElement; |
| import javax.lang.model.type.ArrayType; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.PrimitiveType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.type.TypeVariable; |
| import javax.lang.model.type.UnionType; |
| import javax.lang.model.type.WildcardType; |
| import javax.lang.model.util.Elements; |
| import javax.lang.model.util.Types; |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.checkerframework.checker.signature.qual.BinaryName; |
| import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; |
| import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; |
| import org.checkerframework.checker.signature.qual.FullyQualifiedName; |
| import org.plumelib.util.CollectionsPlume; |
| import org.plumelib.util.ImmutableTypes; |
| |
| /** |
| * A utility class that helps with {@link TypeMirror}s. It complements {@link Types}, providing |
| * methods that {@link Types} does not. |
| */ |
| public final class TypesUtils { |
| |
| /** Class cannot be instantiated. */ |
| private TypesUtils() { |
| throw new AssertionError("Class TypesUtils cannot be instantiated."); |
| } |
| |
| /// Creating types |
| |
| /** |
| * Returns the {@link TypeMirror} for a given {@link Class}. |
| * |
| * @param clazz a class |
| * @param types the type utilities |
| * @param elements the element utiliites |
| * @return the TypeMirror for {@code clazz} |
| */ |
| public static TypeMirror typeFromClass(Class<?> clazz, Types types, Elements elements) { |
| if (clazz == void.class) { |
| return types.getNoType(TypeKind.VOID); |
| } else if (clazz.isPrimitive()) { |
| String primitiveName = clazz.getName().toUpperCase(); |
| TypeKind primitiveKind = TypeKind.valueOf(primitiveName); |
| return types.getPrimitiveType(primitiveKind); |
| } else if (clazz.isArray()) { |
| TypeMirror componentType = typeFromClass(clazz.getComponentType(), types, elements); |
| return types.getArrayType(componentType); |
| } else { |
| String name = clazz.getCanonicalName(); |
| assert name != null : "@AssumeAssertion(nullness): assumption"; |
| TypeElement element = elements.getTypeElement(name); |
| if (element == null) { |
| throw new BugInCF("Unrecognized class: " + clazz); |
| } |
| return element.asType(); |
| } |
| } |
| |
| /** |
| * Returns an {@link ArrayType} with elements of type {@code componentType}. |
| * |
| * @param componentType the component type of the created array type |
| * @param types the type utilities |
| * @return an {@link ArrayType} whose elements have type {@code componentType} |
| */ |
| public static ArrayType createArrayType(TypeMirror componentType, Types types) { |
| JavacTypes t = (JavacTypes) types; |
| return t.getArrayType(componentType); |
| } |
| |
| /// Creating a Class<?> |
| |
| /** |
| * Returns the {@link Class} for a given {@link TypeMirror}. Returns {@code Object.class} if it |
| * cannot determine anything more specific. |
| * |
| * @param typeMirror a TypeMirror |
| * @return the class for {@code typeMirror} |
| */ |
| public static Class<?> getClassFromType(TypeMirror typeMirror) { |
| |
| switch (typeMirror.getKind()) { |
| case INT: |
| return int.class; |
| case LONG: |
| return long.class; |
| case SHORT: |
| return short.class; |
| case BYTE: |
| return byte.class; |
| case CHAR: |
| return char.class; |
| case DOUBLE: |
| return double.class; |
| case FLOAT: |
| return float.class; |
| case BOOLEAN: |
| return boolean.class; |
| |
| case ARRAY: |
| Class<?> componentClass = getClassFromType(((ArrayType) typeMirror).getComponentType()); |
| // In Java 12, use this instead: |
| // return fooClass.arrayType(); |
| return java.lang.reflect.Array.newInstance(componentClass, 0).getClass(); |
| |
| case DECLARED: |
| // BUG: need to compute a @ClassGetName, but this code computes a |
| // @CanonicalNameOrEmpty. They are different for inner classes. |
| @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for Names.toString |
| @DotSeparatedIdentifiers String typeString = TypesUtils.getQualifiedName((DeclaredType) typeMirror).toString(); |
| if (typeString.equals("<nulltype>")) { |
| return void.class; |
| } |
| |
| try { |
| return Class.forName(typeString); |
| } catch (ClassNotFoundException | UnsupportedClassVersionError e) { |
| return Object.class; |
| } |
| |
| default: |
| return Object.class; |
| } |
| } |
| |
| /// Getters |
| |
| /** |
| * Gets the fully qualified name for a provided type. It returns an empty name if type is an |
| * anonymous type. |
| * |
| * @param type the declared type |
| * @return the name corresponding to that type |
| */ |
| @SuppressWarnings("signature:return") // todo: add fake override of Name.toString. |
| public static @CanonicalNameOrEmpty String getQualifiedName(DeclaredType type) { |
| TypeElement element = (TypeElement) type.asElement(); |
| @CanonicalNameOrEmpty Name name = element.getQualifiedName(); |
| return name.toString(); |
| } |
| |
| /** |
| * Returns the simple type name, without annotations. |
| * |
| * @param type a type |
| * @return the simple type name, without annotations |
| */ |
| public static String simpleTypeName(TypeMirror type) { |
| switch (type.getKind()) { |
| case ARRAY: |
| return simpleTypeName(((ArrayType) type).getComponentType()) + "[]"; |
| case TYPEVAR: |
| return ((TypeVariable) type).asElement().getSimpleName().toString(); |
| case DECLARED: |
| return ((DeclaredType) type).asElement().getSimpleName().toString(); |
| case NULL: |
| return "<nulltype>"; |
| case VOID: |
| return "void"; |
| case WILDCARD: |
| WildcardType wildcard = (WildcardType) type; |
| TypeMirror extendsBound = wildcard.getExtendsBound(); |
| TypeMirror superBound = wildcard.getSuperBound(); |
| return "?" |
| + (extendsBound != null ? " extends " + simpleTypeName(extendsBound) : "") |
| + (superBound != null ? " super " + simpleTypeName(superBound) : ""); |
| case UNION: |
| StringJoiner sj = new StringJoiner(" | "); |
| for (TypeMirror alternative : ((UnionType) type).getAlternatives()) { |
| sj.add(simpleTypeName(alternative)); |
| } |
| return sj.toString(); |
| default: |
| if (type.getKind().isPrimitive()) { |
| return TypeAnnotationUtils.unannotatedType(type).toString(); |
| } else { |
| throw new BugInCF( |
| "simpleTypeName: unhandled type kind: %s, type: %s", type.getKind(), type); |
| } |
| } |
| } |
| |
| /** |
| * Returns the binary name. |
| * |
| * @param type a type |
| * @return the binary name |
| */ |
| public static @BinaryName String binaryName(TypeMirror type) { |
| if (type.getKind() != TypeKind.DECLARED) { |
| throw new BugInCF("Only declared types have a binary name"); |
| } |
| return ElementUtils.getBinaryName((TypeElement) ((DeclaredType) type).asElement()); |
| } |
| |
| /** |
| * Returns the type element for {@code type} if {@code type} is a class, interface, annotation |
| * type, or enum. Otherwise, returns null. |
| * |
| * @param type whose element is returned |
| * @return the type element for {@code type} if {@code type} is a class, interface, annotation |
| * type, or enum; otherwise, returns {@code null} |
| */ |
| public static @Nullable TypeElement getTypeElement(TypeMirror type) { |
| Element element = ((Type) type).asElement(); |
| if (element == null) { |
| return null; |
| } |
| if (ElementUtils.isTypeElement(element)) { |
| return (TypeElement) element; |
| } |
| return null; |
| } |
| |
| /** |
| * Given an array type, returns the type with all array levels stripped off. |
| * |
| * @param at an array type |
| * @return the type with all array levels stripped off |
| */ |
| public static TypeMirror getInnermostComponentType(ArrayType at) { |
| TypeMirror result = at; |
| while (result.getKind() == TypeKind.ARRAY) { |
| result = ((ArrayType) result).getComponentType(); |
| } |
| return result; |
| } |
| |
| /// Equality |
| |
| /** |
| * Returns true iff the arguments are both the same declared types. |
| * |
| * <p>This is needed because class {@code Type.ClassType} does not override equals. |
| * |
| * @param t1 the first type to test |
| * @param t2 the second type to test |
| * @return whether the arguments are the same declared types |
| */ |
| public static boolean areSameDeclaredTypes(Type.ClassType t1, Type.ClassType t2) { |
| // Do a cheaper test first |
| if (t1.tsym.name != t2.tsym.name) { |
| return false; |
| } |
| return t1.toString().equals(t1.toString()); |
| } |
| |
| /** |
| * Returns true iff the arguments are both the same primitive type. |
| * |
| * @param left a type |
| * @param right a type |
| * @return whether the arguments are the same primitive type |
| */ |
| public static boolean areSamePrimitiveTypes(TypeMirror left, TypeMirror right) { |
| if (!isPrimitive(left) || !isPrimitive(right)) { |
| return false; |
| } |
| |
| return (left.getKind() == right.getKind()); |
| } |
| |
| /// Predicates |
| |
| /** |
| * Checks if the type represents a java.lang.Object declared type. |
| * |
| * @param type the type |
| * @return true iff type represents java.lang.Object |
| */ |
| public static boolean isObject(TypeMirror type) { |
| return isDeclaredOfName(type, "java.lang.Object"); |
| } |
| |
| /** |
| * Checks if the type represents the java.lang.Class declared type. |
| * |
| * @param type the type |
| * @return true iff type represents java.lang.Class |
| */ |
| public static boolean isClass(TypeMirror type) { |
| return isDeclaredOfName(type, "java.lang.Class"); |
| } |
| |
| /** |
| * Checks if the type represents a java.lang.String declared type. |
| * |
| * @param type the type |
| * @return true iff type represents java.lang.String |
| */ |
| public static boolean isString(TypeMirror type) { |
| return isDeclaredOfName(type, "java.lang.String"); |
| } |
| |
| /** |
| * Checks if the type represents a boolean type, that is either boolean (primitive type) or |
| * java.lang.Boolean. |
| * |
| * @param type the type to test |
| * @return true iff type represents a boolean type |
| */ |
| public static boolean isBooleanType(TypeMirror type) { |
| return isDeclaredOfName(type, "java.lang.Boolean") || type.getKind() == TypeKind.BOOLEAN; |
| } |
| |
| /** |
| * Check if the type represents a declared type of the given qualified name. |
| * |
| * @param type the type |
| * @return type iff type represents a declared type of the qualified name |
| */ |
| public static boolean isDeclaredOfName(TypeMirror type, CharSequence qualifiedName) { |
| return type.getKind() == TypeKind.DECLARED |
| && getQualifiedName((DeclaredType) type).contentEquals(qualifiedName); |
| } |
| |
| public static boolean isBoxedPrimitive(TypeMirror type) { |
| if (type.getKind() != TypeKind.DECLARED) { |
| return false; |
| } |
| |
| String qualifiedName = getQualifiedName((DeclaredType) type).toString(); |
| |
| return (qualifiedName.equals("java.lang.Boolean") |
| || qualifiedName.equals("java.lang.Byte") |
| || qualifiedName.equals("java.lang.Character") |
| || qualifiedName.equals("java.lang.Short") |
| || qualifiedName.equals("java.lang.Integer") |
| || qualifiedName.equals("java.lang.Long") |
| || qualifiedName.equals("java.lang.Double") |
| || qualifiedName.equals("java.lang.Float")); |
| } |
| |
| /** |
| * Return true if this is an immutable type in the JDK. |
| * |
| * <p>This does not use immutability annotations and always returns false for user-defined |
| * classes. |
| */ |
| public static boolean isImmutableTypeInJdk(TypeMirror type) { |
| return isPrimitive(type) |
| || (type.getKind() == TypeKind.DECLARED |
| && ImmutableTypes.isImmutable(getQualifiedName((DeclaredType) type).toString())); |
| } |
| |
| /** |
| * Returns true if type represents a Throwable type (e.g. Exception, Error). |
| * |
| * @return true if type represents a Throwable type (e.g. Exception, Error) |
| */ |
| public static boolean isThrowable(TypeMirror type) { |
| while (type != null && type.getKind() == TypeKind.DECLARED) { |
| DeclaredType dt = (DeclaredType) type; |
| TypeElement elem = (TypeElement) dt.asElement(); |
| Name name = elem.getQualifiedName(); |
| if ("java.lang.Throwable".contentEquals(name)) { |
| return true; |
| } |
| type = elem.getSuperclass(); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true iff the argument is an anonymous type. |
| * |
| * @return whether the argument is an anonymous type |
| */ |
| public static boolean isAnonymous(TypeMirror type) { |
| return (type instanceof DeclaredType) |
| && ((TypeElement) ((DeclaredType) type).asElement()).getNestingKind() |
| == NestingKind.ANONYMOUS; |
| } |
| |
| /** |
| * Returns true iff the argument is a primitive type. |
| * |
| * @return whether the argument is a primitive type |
| */ |
| public static boolean isPrimitive(TypeMirror type) { |
| switch (type.getKind()) { |
| case BOOLEAN: |
| case BYTE: |
| case CHAR: |
| case DOUBLE: |
| case FLOAT: |
| case INT: |
| case LONG: |
| case SHORT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Returns true iff the argument is a primitive type or a boxed primitive type. |
| * |
| * @param type a type |
| * @return true if the argument is a primitive type or a boxed primitive type |
| */ |
| public static boolean isPrimitiveOrBoxed(TypeMirror type) { |
| switch (type.getKind()) { |
| case BOOLEAN: |
| case BYTE: |
| case CHAR: |
| case DOUBLE: |
| case FLOAT: |
| case INT: |
| case LONG: |
| case SHORT: |
| return true; |
| |
| case DECLARED: |
| String qualifiedName = getQualifiedName((DeclaredType) type).toString(); |
| return (qualifiedName.equals("java.lang.Boolean") |
| || qualifiedName.equals("java.lang.Byte") |
| || qualifiedName.equals("java.lang.Character") |
| || qualifiedName.equals("java.lang.Short") |
| || qualifiedName.equals("java.lang.Integer") |
| || qualifiedName.equals("java.lang.Long") |
| || qualifiedName.equals("java.lang.Double") |
| || qualifiedName.equals("java.lang.Float")); |
| |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Returns true iff the argument is a primitive numeric type. |
| * |
| * @param type a type |
| * @return true if the argument is a primitive numeric type |
| */ |
| public static boolean isNumeric(TypeMirror type) { |
| return TypeKindUtils.isNumeric(type.getKind()); |
| } |
| |
| /** The fully-qualified names of the numeric boxed types. */ |
| static final Set<@FullyQualifiedName String> numericBoxedTypes = |
| new HashSet<>( |
| Arrays.asList( |
| "java.lang.Byte", |
| "java.lang.Character", |
| "java.lang.Short", |
| "java.lang.Integer", |
| "java.lang.Long", |
| "java.lang.Double", |
| "java.lang.Float")); |
| |
| /** |
| * Returns true iff the argument is a boxed numeric type. |
| * |
| * @param type a type |
| * @return true if the argument is a boxed numeric type |
| */ |
| public static boolean isNumericBoxed(TypeMirror type) { |
| return type.getKind() == TypeKind.DECLARED |
| && numericBoxedTypes.contains(getQualifiedName((DeclaredType) type).toString()); |
| } |
| |
| /** |
| * Returns true iff the argument is an integral primitive type. |
| * |
| * @param type a type |
| * @return whether the argument is an integral primitive type |
| */ |
| public static boolean isIntegralPrimitive(TypeMirror type) { |
| switch (type.getKind()) { |
| case BYTE: |
| case CHAR: |
| case INT: |
| case LONG: |
| case SHORT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Return true if the argument TypeMirror is a (possibly boxed) integral type. |
| * |
| * @param type the type to inspect |
| * @return true if type is an integral type |
| */ |
| public static boolean isIntegralPrimitiveOrBoxed(TypeMirror type) { |
| TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); |
| return kind != null && TypeKindUtils.isIntegral(kind); |
| } |
| |
| /** |
| * Returns true if declaredType is a Class that is used to box primitive type (e.g. |
| * declaredType=java.lang.Double and primitiveType=22.5d ) |
| * |
| * @param declaredType a type that might be a boxed type |
| * @param primitiveType a type that might be a primitive type |
| * @return true if {@code declaredType} is a box of {@code primitiveType} |
| */ |
| public static boolean isBoxOf(TypeMirror declaredType, TypeMirror primitiveType) { |
| if (declaredType.getKind() != TypeKind.DECLARED) { |
| return false; |
| } |
| |
| final String qualifiedName = getQualifiedName((DeclaredType) declaredType).toString(); |
| switch (primitiveType.getKind()) { |
| case BOOLEAN: |
| return qualifiedName.equals("java.lang.Boolean"); |
| case BYTE: |
| return qualifiedName.equals("java.lang.Byte"); |
| case CHAR: |
| return qualifiedName.equals("java.lang.Character"); |
| case DOUBLE: |
| return qualifiedName.equals("java.lang.Double"); |
| case FLOAT: |
| return qualifiedName.equals("java.lang.Float"); |
| case INT: |
| return qualifiedName.equals("java.lang.Integer"); |
| case LONG: |
| return qualifiedName.equals("java.lang.Long"); |
| case SHORT: |
| return qualifiedName.equals("java.lang.Short"); |
| |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Returns true iff the argument is a boxed floating point type. |
| * |
| * @param type type to test |
| * @return whether the argument is a boxed floating point type |
| */ |
| public static boolean isBoxedFloating(TypeMirror type) { |
| if (type.getKind() != TypeKind.DECLARED) { |
| return false; |
| } |
| |
| String qualifiedName = getQualifiedName((DeclaredType) type).toString(); |
| return qualifiedName.equals("java.lang.Double") || qualifiedName.equals("java.lang.Float"); |
| } |
| |
| /** |
| * Returns true iff the argument is a primitive floating point type. |
| * |
| * @param type type mirror |
| * @return whether the argument is a primitive floating point type |
| */ |
| public static boolean isFloatingPrimitive(TypeMirror type) { |
| switch (type.getKind()) { |
| case DOUBLE: |
| case FLOAT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Return true if the argument TypeMirror is a (possibly boxed) floating point type. |
| * |
| * @param type the type to inspect |
| * @return true if type is a floating point type |
| */ |
| public static boolean isFloatingPoint(TypeMirror type) { |
| TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); |
| return kind != null && TypeKindUtils.isFloatingPoint(kind); |
| } |
| |
| /** |
| * Returns whether a TypeMirror represents a class type. |
| * |
| * @param type a type that might be a class type |
| * @return true if {@code} is a class type |
| */ |
| public static boolean isClassType(TypeMirror type) { |
| return (type instanceof Type.ClassType); |
| } |
| |
| /** |
| * Returns true if {@code type} has an enclosing type. |
| * |
| * @param type type to checker |
| * @return true if {@code type} has an enclosing type |
| */ |
| public static boolean hasEnclosingType(TypeMirror type) { |
| Type e = ((Type) type).getEnclosingType(); |
| return e.getKind() != TypeKind.NONE; |
| } |
| |
| /** |
| * Returns whether or not {@code type} is a functional interface type (as defined in JLS 9.8). |
| * |
| * @param type possible functional interface type |
| * @param env ProcessingEnvironment |
| * @return whether or not {@code type} is a functional interface type (as defined in JLS 9.8) |
| */ |
| public static boolean isFunctionalInterface(TypeMirror type, ProcessingEnvironment env) { |
| Context ctx = ((JavacProcessingEnvironment) env).getContext(); |
| com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); |
| return javacTypes.isFunctionalInterface((Type) type); |
| } |
| |
| /// Type variables and wildcards |
| |
| /** |
| * If the argument is a bounded TypeVariable or WildcardType, return its non-variable, |
| * non-wildcard upper bound. Otherwise, return the type itself. |
| * |
| * @param type a type |
| * @return the non-variable, non-wildcard upper bound of a type, if it has one, or itself if it |
| * has no bounds |
| */ |
| public static TypeMirror upperBound(TypeMirror type) { |
| do { |
| if (type instanceof TypeVariable) { |
| TypeVariable tvar = (TypeVariable) type; |
| if (tvar.getUpperBound() != null) { |
| type = tvar.getUpperBound(); |
| } else { |
| break; |
| } |
| } else if (type instanceof WildcardType) { |
| WildcardType wc = (WildcardType) type; |
| if (wc.getExtendsBound() != null) { |
| type = wc.getExtendsBound(); |
| } else { |
| break; |
| } |
| } else { |
| break; |
| } |
| } while (true); |
| return type; |
| } |
| |
| /** |
| * Get the type parameter for this wildcard from the underlying type's bound field This field is |
| * sometimes null, in that case this method will return null. |
| * |
| * @return the TypeParameterElement the wildcard is an argument to, {@code null} otherwise |
| */ |
| public static @Nullable TypeParameterElement wildcardToTypeParam( |
| final Type.WildcardType wildcard) { |
| |
| final Element typeParamElement; |
| if (wildcard.bound != null) { |
| typeParamElement = wildcard.bound.asElement(); |
| } else { |
| typeParamElement = null; |
| } |
| |
| return (TypeParameterElement) typeParamElement; |
| } |
| |
| /** |
| * Version of com.sun.tools.javac.code.Types.wildUpperBound(Type) that works with both jdk8 |
| * (called upperBound there) and jdk8u. |
| */ |
| // TODO: contrast to upperBound. |
| public static Type wildUpperBound(TypeMirror tm, ProcessingEnvironment env) { |
| Type t = (Type) tm; |
| if (t.hasTag(TypeTag.WILDCARD)) { |
| Context context = ((JavacProcessingEnvironment) env).getContext(); |
| Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); |
| if (w.isSuperBound()) { // returns true if w is unbound |
| Symtab syms = Symtab.instance(context); |
| // w.bound is null if the wildcard is from bytecode. |
| return w.bound == null ? syms.objectType : w.bound.getUpperBound(); |
| } else { |
| return wildUpperBound(w.type, env); |
| } |
| } else { |
| return TypeAnnotationUtils.unannotatedType(t); |
| } |
| } |
| |
| /** |
| * Version of com.sun.tools.javac.code.Types.wildLowerBound(Type) that works with both jdk8 |
| * (called upperBound there) and jdk8u. |
| */ |
| public static Type wildLowerBound(TypeMirror tm, ProcessingEnvironment env) { |
| Type t = (Type) tm; |
| if (t.hasTag(TypeTag.WILDCARD)) { |
| Context context = ((JavacProcessingEnvironment) env).getContext(); |
| Symtab syms = Symtab.instance(context); |
| Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); |
| return w.isExtendsBound() ? syms.botType : wildLowerBound(w.type, env); |
| } else { |
| return TypeAnnotationUtils.unannotatedType(t); |
| } |
| } |
| |
| /** |
| * Given a bounded type (wildcard or typevar) get the concrete type of its upper bound. If the |
| * bounded type extends other bounded types, this method will iterate through their bounds until a |
| * class, interface, or intersection is found. |
| * |
| * @return a type that is not a wildcard or typevar, or {@code null} if this type is an unbounded |
| * wildcard |
| */ |
| public static @Nullable TypeMirror findConcreteUpperBound(final TypeMirror boundedType) { |
| TypeMirror effectiveUpper = boundedType; |
| outerLoop: |
| while (true) { |
| switch (effectiveUpper.getKind()) { |
| case WILDCARD: |
| effectiveUpper = ((javax.lang.model.type.WildcardType) effectiveUpper).getExtendsBound(); |
| if (effectiveUpper == null) { |
| return null; |
| } |
| break; |
| |
| case TYPEVAR: |
| effectiveUpper = ((TypeVariable) effectiveUpper).getUpperBound(); |
| break; |
| |
| default: |
| break outerLoop; |
| } |
| } |
| return effectiveUpper; |
| } |
| |
| /** |
| * Returns true if the erased type of subtype is a subtype of the erased type of supertype. |
| * |
| * @param subtype possible subtype |
| * @param supertype possible supertype |
| * @param types a Types object |
| * @return true if the erased type of subtype is a subtype of the erased type of supertype |
| */ |
| public static boolean isErasedSubtype(TypeMirror subtype, TypeMirror supertype, Types types) { |
| return types.isSubtype(types.erasure(subtype), types.erasure(supertype)); |
| } |
| |
| /** Returns whether a TypeVariable represents a captured type. */ |
| public static boolean isCaptured(TypeMirror typeVar) { |
| if (typeVar.getKind() != TypeKind.TYPEVAR) { |
| return false; |
| } |
| return ((Type.TypeVar) TypeAnnotationUtils.unannotatedType(typeVar)).isCaptured(); |
| } |
| |
| /** If typeVar is a captured wildcard, returns that wildcard; otherwise returns {@code null}. */ |
| public static @Nullable WildcardType getCapturedWildcard(TypeVariable typeVar) { |
| if (isCaptured(typeVar)) { |
| return ((CapturedType) TypeAnnotationUtils.unannotatedType(typeVar)).wildcard; |
| } |
| return null; |
| } |
| |
| /// Least upper bound and greatest lower bound |
| |
| /** |
| * Returns the least upper bound of two {@link TypeMirror}s, ignoring any annotations on the |
| * types. |
| * |
| * <p>Wrapper around Types.lub to add special handling for null types, primitives, and wildcards. |
| * |
| * @param tm1 a {@link TypeMirror} |
| * @param tm2 a {@link TypeMirror} |
| * @param processingEnv the {@link ProcessingEnvironment} to use |
| * @return the least upper bound of {@code tm1} and {@code tm2} |
| */ |
| public static TypeMirror leastUpperBound( |
| TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) { |
| Type t1 = TypeAnnotationUtils.unannotatedType(tm1); |
| Type t2 = TypeAnnotationUtils.unannotatedType(tm2); |
| // Handle the 'null' type manually (not done by types.lub). |
| if (t1.getKind() == TypeKind.NULL) { |
| return t2; |
| } |
| if (t2.getKind() == TypeKind.NULL) { |
| return t1; |
| } |
| if (t1.getKind() == TypeKind.WILDCARD) { |
| WildcardType wc1 = (WildcardType) t1; |
| t1 = (Type) wc1.getExtendsBound(); |
| if (t1 == null) { |
| // Implicit upper bound of java.lang.Object |
| Elements elements = processingEnv.getElementUtils(); |
| return elements.getTypeElement("java.lang.Object").asType(); |
| } |
| } |
| if (t2.getKind() == TypeKind.WILDCARD) { |
| WildcardType wc2 = (WildcardType) t2; |
| t2 = (Type) wc2.getExtendsBound(); |
| if (t2 == null) { |
| // Implicit upper bound of java.lang.Object |
| Elements elements = processingEnv.getElementUtils(); |
| return elements.getTypeElement("java.lang.Object").asType(); |
| } |
| } |
| JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; |
| com.sun.tools.javac.code.Types types = |
| com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); |
| if (types.isSameType(t1, t2)) { |
| // Special case if the two types are equal. |
| return t1; |
| } |
| // Special case for primitives. |
| if (isPrimitive(t1) || isPrimitive(t2)) { |
| if (types.isAssignable(t1, t2)) { |
| return t2; |
| } else if (types.isAssignable(t2, t1)) { |
| return t1; |
| } else { |
| Elements elements = processingEnv.getElementUtils(); |
| return elements.getTypeElement("java.lang.Object").asType(); |
| } |
| } |
| return types.lub(t1, t2); |
| } |
| |
| /** |
| * Returns the greatest lower bound of two {@link TypeMirror}s, ignoring any annotations on the |
| * types. |
| * |
| * <p>Wrapper around Types.glb to add special handling for null types, primitives, and wildcards. |
| * |
| * @param tm1 a {@link TypeMirror} |
| * @param tm2 a {@link TypeMirror} |
| * @param processingEnv the {@link ProcessingEnvironment} to use |
| * @return the greatest lower bound of {@code tm1} and {@code tm2} |
| */ |
| public static TypeMirror greatestLowerBound( |
| TypeMirror tm1, TypeMirror tm2, ProcessingEnvironment processingEnv) { |
| Type t1 = TypeAnnotationUtils.unannotatedType(tm1); |
| Type t2 = TypeAnnotationUtils.unannotatedType(tm2); |
| JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; |
| com.sun.tools.javac.code.Types types = |
| com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); |
| if (types.isSameType(t1, t2)) { |
| // Special case if the two types are equal. |
| return t1; |
| } |
| // Handle the 'null' type manually. |
| if (t1.getKind() == TypeKind.NULL) { |
| return t1; |
| } |
| if (t2.getKind() == TypeKind.NULL) { |
| return t2; |
| } |
| // Special case for primitives. |
| if (isPrimitive(t1) || isPrimitive(t2)) { |
| if (types.isAssignable(t1, t2)) { |
| return t1; |
| } else if (types.isAssignable(t2, t1)) { |
| return t2; |
| } else { |
| // Javac types.glb returns TypeKind.Error when the GLB does |
| // not exist, but we can't create one. Use TypeKind.NONE |
| // instead. |
| return processingEnv.getTypeUtils().getNoType(TypeKind.NONE); |
| } |
| } |
| if (t1.getKind() == TypeKind.WILDCARD) { |
| return t2; |
| } |
| if (t2.getKind() == TypeKind.WILDCARD) { |
| return t1; |
| } |
| |
| // If neither type is a primitive type, null type, or wildcard |
| // and if the types are not the same, use javac types.glb |
| return types.glb(t1, t2); |
| } |
| |
| /** |
| * Returns the most specific type from the list, or null if none exists. |
| * |
| * @param typeMirrors a list of types |
| * @param processingEnv the {@link ProcessingEnvironment} to use |
| * @return the most specific of the types, or null if none exists |
| */ |
| public static @Nullable TypeMirror mostSpecific( |
| List<TypeMirror> typeMirrors, ProcessingEnvironment processingEnv) { |
| if (typeMirrors.size() == 1) { |
| return typeMirrors.get(0); |
| } else { |
| JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; |
| com.sun.tools.javac.code.Types types = |
| com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); |
| com.sun.tools.javac.util.List<Type> typeList = typeMirrorListToTypeList(typeMirrors); |
| Type glb = types.glb(typeList); |
| for (Type candidate : typeList) { |
| if (types.isSameType(glb, candidate)) { |
| return candidate; |
| } |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * Given a list of TypeMirror, return a list of Type. |
| * |
| * @param typeMirrors a list of TypeMirrors |
| * @return the argument, converted to a javac list |
| */ |
| private static com.sun.tools.javac.util.List<Type> typeMirrorListToTypeList( |
| List<TypeMirror> typeMirrors) { |
| List<Type> typeList = CollectionsPlume.mapList(Type.class::cast, typeMirrors); |
| return com.sun.tools.javac.util.List.from(typeList); |
| } |
| |
| /// Substitutions |
| |
| /** |
| * Returns the return type of a method, given the receiver of the method call. |
| * |
| * @param methodElement a method |
| * @param substitutedReceiverType the receiver type, after substitution |
| * @param env the environment |
| * @return the return type of the method |
| */ |
| public static TypeMirror substituteMethodReturnType( |
| Element methodElement, TypeMirror substitutedReceiverType, ProcessingEnvironment env) { |
| |
| com.sun.tools.javac.code.Types types = |
| com.sun.tools.javac.code.Types.instance(InternalUtils.getJavacContext(env)); |
| |
| Type substitutedMethodType = |
| types.memberType((Type) substitutedReceiverType, (Symbol) methodElement); |
| return substitutedMethodType.getReturnType(); |
| } |
| |
| /** |
| * Returns {@code type} as {@code superType} if {@code superType} is a super type of {@code type}; |
| * otherwise, null. |
| * |
| * @return {@code type} as {@code superType} if {@code superType} is a super type of {@code type}; |
| * otherwise, null |
| */ |
| public static TypeMirror asSuper( |
| TypeMirror type, TypeMirror superType, ProcessingEnvironment env) { |
| Context ctx = ((JavacProcessingEnvironment) env).getContext(); |
| com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); |
| return javacTypes.asSuper((Type) type, ((Type) superType).tsym); |
| } |
| |
| /** |
| * Returns the superclass of the given class. Returns null if there is not one. |
| * |
| * @param type a type |
| * @param types type utilities |
| * @return the superclass of the given class, or null |
| */ |
| public static @Nullable TypeMirror getSuperclass(TypeMirror type, Types types) { |
| List<? extends TypeMirror> superTypes = types.directSupertypes(type); |
| for (TypeMirror t : superTypes) { |
| // ignore interface types |
| if (!(t instanceof ClassType)) { |
| continue; |
| } |
| ClassType tt = (ClassType) t; |
| if (!tt.isInterface()) { |
| return t; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the type of primitive conversion from {@code from} to {@code to}. |
| * |
| * @param from a primitive type |
| * @param to a primitive type |
| * @return the type of primitive conversion from {@code from} to {@code to} |
| */ |
| public static TypeKindUtils.PrimitiveConversionKind getPrimitiveConversionKind( |
| PrimitiveType from, PrimitiveType to) { |
| return TypeKindUtils.getPrimitiveConversionKind(from.getKind(), to.getKind()); |
| } |
| |
| /** |
| * Returns a new type mirror with the same type as {@code type} where all the type variables in |
| * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs}. |
| * |
| * <p>This is a wrapper around {@link com.sun.tools.javac.code.Types#subst(Type, |
| * com.sun.tools.javac.util.List, com.sun.tools.javac.util.List)}. |
| * |
| * @param type type to do substitution in |
| * @param typeVariables type variables that should be replaced with the type mirror at the same |
| * index of {@code typeArgs} |
| * @param typeArgs type mirrors that should replace the type variable at the same index of {@code |
| * typeVariables} |
| * @param env processing environment |
| * @return a new type mirror with the same type as {@code type} where all the type variables in |
| * {@code typeVariables} have been substituted with the type arguments in {@code typeArgs} |
| */ |
| public static TypeMirror substitute( |
| TypeMirror type, |
| List<? extends TypeMirror> typeVariables, |
| List<? extends TypeMirror> typeArgs, |
| ProcessingEnvironment env) { |
| |
| List<Type> newP = CollectionsPlume.mapList(Type.class::cast, typeVariables); |
| |
| List<Type> newT = CollectionsPlume.mapList(Type.class::cast, typeArgs); |
| |
| JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) env; |
| com.sun.tools.javac.code.Types types = |
| com.sun.tools.javac.code.Types.instance(javacEnv.getContext()); |
| return types.subst( |
| (Type) type, |
| com.sun.tools.javac.util.List.from(newP), |
| com.sun.tools.javac.util.List.from(newT)); |
| } |
| |
| /** |
| * Returns the depth of an array type. |
| * |
| * @param arrayType an array type |
| * @return the depth of {@code arrayType} |
| */ |
| public static int getArrayDepth(TypeMirror arrayType) { |
| int counter = 0; |
| TypeMirror type = arrayType; |
| while (type.getKind() == TypeKind.ARRAY) { |
| counter++; |
| type = ((ArrayType) type).getComponentType(); |
| } |
| return counter; |
| } |
| } |