blob: 632d1703f842c2b9165d3327973753281359cd69 [file] [log] [blame]
/*
* Copyright 2016 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.auto.common;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleTypeVisitor8;
import javax.lang.model.util.Types;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Determines if one method overrides another. This class defines two ways of doing that:
* {@code NativeOverrides} uses the method
* {@link Elements#overrides(ExecutableElement, ExecutableElement, TypeElement)} while
* {@code ExplicitOverrides} reimplements that method in a way that is more consistent between
* compilers, in particular between javac and ecj (the Eclipse compiler).
*
* @author emcmanus@google.com (Éamonn McManus)
*/
abstract class Overrides {
abstract boolean overrides(
ExecutableElement overrider, ExecutableElement overridden, TypeElement in);
static class NativeOverrides extends Overrides {
private final Elements elementUtils;
NativeOverrides(Elements elementUtils) {
this.elementUtils = elementUtils;
}
@Override
boolean overrides(ExecutableElement overrider, ExecutableElement overridden, TypeElement in) {
return elementUtils.overrides(overrider, overridden, in);
}
}
static class ExplicitOverrides extends Overrides {
private final Types typeUtils;
ExplicitOverrides(Types typeUtils) {
this.typeUtils = typeUtils;
}
@Override
public boolean overrides(
ExecutableElement overrider, ExecutableElement overridden, TypeElement in) {
if (!overrider.getSimpleName().equals(overridden.getSimpleName())) {
// They must have the same name.
return false;
}
// We should just be able to write overrider.equals(overridden) here, but that runs afoul
// of a problem with Eclipse. If for example you look at the method Stream<E> stream() in
// Collection<E>, as obtained by collectionTypeElement.getEnclosedElements(), it will not
// compare equal to the method Stream<E> stream() as obtained by
// elementUtils.getAllMembers(listTypeElement), even though List<E> inherits the method
// from Collection<E>. The reason is that, in ecj, getAllMembers does type substitution,
// so the return type of stream() is Stream<E'>, where E' is the E from List<E> rather than
// the one from Collection<E>. Instead we compare the enclosing element, which will be
// Collection<E> no matter how we got the method. If two methods are in the same type
// then it's impossible for one to override the other, regardless of whether they are the
// same method.
if (overrider.getEnclosingElement().equals(overridden.getEnclosingElement())) {
return false;
}
if (overridden.getModifiers().contains(Modifier.STATIC)) {
// Static methods can't be overridden (though they can be hidden by other static methods).
return false;
}
Visibility overriddenVisibility = Visibility.ofElement(overridden);
Visibility overriderVisibility = Visibility.ofElement(overrider);
if (overriddenVisibility.equals(Visibility.PRIVATE)
|| overriderVisibility.compareTo(overriddenVisibility) < 0) {
// Private methods can't be overridden, and methods can't be overridden by less-visible
// methods. The latter condition is enforced by the compiler so in theory we might report
// an "incorrect" result here for code that javac would not have allowed.
return false;
}
if (!isSubsignature(overrider, overridden, in)) {
return false;
}
if (!MoreElements.methodVisibleFromPackage(overridden, MoreElements.getPackage(overrider))) {
// If the overridden method is a package-private method in a different package then it
// can't be overridden.
return false;
}
if (!MoreElements.isType(overridden.getEnclosingElement())) {
return false;
// We don't know how this could happen but we avoid blowing up if it does.
}
TypeElement overriddenType = MoreElements.asType(overridden.getEnclosingElement());
// We erase the types before checking subtypes, because the TypeMirror we get for List<E> is
// not a subtype of the one we get for Collection<E> since the two E instances are not the
// same. For the purposes of overriding, type parameters in the containing type should not
// matter because if the code compiles at all then they must be consistent.
if (!typeUtils.isSubtype(
typeUtils.erasure(in.asType()), typeUtils.erasure(overriddenType.asType()))) {
return false;
}
if (in.getKind().isClass()) {
// Method mC in or inherited by class C (JLS 8.4.8.1)...
if (overriddenType.getKind().isClass()) {
// ...overrides from C another method mA declared in class A. The only condition we
// haven't checked is that C does not inherit mA. Ideally we could just write this:
// return !elementUtils.getAllMembers(in).contains(overridden);
// But that doesn't work in Eclipse. For example, getAllMembers(AbstractList)
// contains List.isEmpty() where you might reasonably expect it to contain
// AbstractCollection.isEmpty(). So we need to visit superclasses until we reach
// one that declares the same method, and check that we haven't reached mA. We compare
// the enclosing elements rather than the methods themselves for the reason described
// at the start of the method.
ExecutableElement inherited = methodFromSuperclasses(in, overridden);
return inherited != null
&& !overridden.getEnclosingElement().equals(inherited.getEnclosingElement());
} else if (overriddenType.getKind().isInterface()) {
// ...overrides from C another method mI declared in interface I. We've already checked
// the conditions (assuming that the only alternative to mI being abstract or default is
// mI being static, which we eliminated above). However, it appears that the logic here
// is necessary in order to be compatible with javac's `overrides` method. An inherited
// abstract method does not override another method. (But, if it is not inherited,
// it does, including if `in` inherits a concrete method of the same name from its
// superclass.) Here again we can use getAllMembers with javac but not with ecj. javac
// says that getAllMembers(AbstractList) contains both AbstractCollection.size() and
// List.size(), but ecj doesn't have the latter. The spec is not particularly clear so
// either seems justifiable. So we need to look up the interface path that goes from `in`
// to `overriddenType` (or the several paths if there are several) and apply similar logic
// to methodFromSuperclasses above.
if (overrider.getModifiers().contains(Modifier.ABSTRACT)) {
ExecutableElement inherited = methodFromSuperinterfaces(in, overridden);
return inherited != null
&& !overridden.getEnclosingElement().equals(inherited.getEnclosingElement());
} else {
return true;
}
} else {
// We don't know what this is so say no.
return false;
}
} else {
return in.getKind().isInterface();
// Method mI in or inherited by interface I (JLS 9.4.1.1). We've already checked everything.
// If this is not an interface then we don't know what it is so we say no.
}
}
private boolean isSubsignature(
ExecutableElement overrider, ExecutableElement overridden, TypeElement in) {
DeclaredType inType = MoreTypes.asDeclared(in.asType());
try {
ExecutableType overriderExecutable =
MoreTypes.asExecutable(typeUtils.asMemberOf(inType, overrider));
ExecutableType overriddenExecutable =
MoreTypes.asExecutable(typeUtils.asMemberOf(inType, overridden));
return typeUtils.isSubsignature(overriderExecutable, overriddenExecutable);
} catch (IllegalArgumentException e) {
// This might mean that at least one of the methods is not in fact declared in or inherited
// by `in` (in which case we should indeed return false); or it might mean that we are
// tickling an Eclipse bug such as https://bugs.eclipse.org/bugs/show_bug.cgi?id=499026
// (in which case we fall back on explicit code to find the parameters).
int nParams = overrider.getParameters().size();
if (overridden.getParameters().size() != nParams) {
return false;
}
List<TypeMirror> overriderParams = erasedParameterTypes(overrider, in);
List<TypeMirror> overriddenParams = erasedParameterTypes(overridden, in);
if (overriderParams == null || overriddenParams == null) {
// This probably means that one or other of the methods is not in `in`.
return false;
}
for (int i = 0; i < nParams; i++) {
if (!typeUtils.isSameType(overriderParams.get(i), overriddenParams.get(i))) {
// If the erasures of the parameters don't correspond, return false. We erase so we
// don't get any confusion about different type variables not comparing equal.
return false;
}
}
return true;
}
}
/**
* Returns the list of erased parameter types of the given method as they appear in the given
* type. For example, if the method is {@code add(E)} from {@code List<E>} and we ask how it
* appears in {@code class NumberList implements List<Number>}, the answer will be
* {@code Number}. That will also be the answer for {@code class NumberList<E extends Number>
* implements List<E>}. The parameter types are erased since the purpose of this method is to
* determine whether two methods are candidates for one to override the other.
*/
@Nullable
ImmutableList<TypeMirror> erasedParameterTypes(ExecutableElement method, TypeElement in) {
if (method.getParameters().isEmpty()) {
return ImmutableList.of();
}
return new TypeSubstVisitor().erasedParameterTypes(method, in);
}
/**
* Visitor that replaces type variables with their values in the types it sees. If we know
* that {@code E} is {@code String}, then we can return {@code String} for {@code E},
* {@code List<String>} for {@code List<E>}, {@code String[]} for {@code E[]}, etc. We don't
* have to cover all types here because (1) the type is going to end up being erased, and
* (2) wildcards can't appear in direct supertypes. So for example it is illegal to write
* {@code class MyList implements List<? extends Number>}. It's legal to write
* {@code class MyList implements List<Set<? extends Number>>} but that doesn't matter
* because the {@code E} of the {@code List} is going to be erased to raw {@code Set}.
*/
private class TypeSubstVisitor extends SimpleTypeVisitor8<TypeMirror, Void> {
/**
* The bindings of type variables. We can put them all in one map because E in {@code List<E>}
* is not the same as E in {@code Collection<E>}. As we ascend the type hierarchy we'll add
* mappings for all the variables we see. We could equivalently create a new map for each type
* we visit, but this is slightly simpler and probably about as performant.
*/
private final Map<TypeParameterElement, TypeMirror> typeBindings = Maps.newLinkedHashMap();
@Nullable
ImmutableList<TypeMirror> erasedParameterTypes(ExecutableElement method, TypeElement in) {
if (method.getEnclosingElement().equals(in)) {
ImmutableList.Builder<TypeMirror> params = ImmutableList.builder();
for (VariableElement param : method.getParameters()) {
params.add(typeUtils.erasure(visit(param.asType())));
}
return params.build();
}
// Make a list of supertypes we are going to visit recursively: the superclass, if there
// is one, plus the superinterfaces.
List<TypeMirror> supers = Lists.newArrayList();
if (in.getSuperclass().getKind() == TypeKind.DECLARED) {
supers.add(in.getSuperclass());
}
supers.addAll(in.getInterfaces());
for (TypeMirror supertype : supers) {
DeclaredType declared = MoreTypes.asDeclared(supertype);
TypeElement element = MoreElements.asType(declared.asElement());
List<? extends TypeMirror> actuals = declared.getTypeArguments();
List<? extends TypeParameterElement> formals = element.getTypeParameters();
if (actuals.isEmpty()) {
// Either the formal type arguments are also empty or `declared` is raw.
actuals = formals.stream().map(t -> t.getBounds().get(0)).collect(toList());
}
Verify.verify(actuals.size() == formals.size());
for (int i = 0; i < actuals.size(); i++) {
typeBindings.put(formals.get(i), actuals.get(i));
}
ImmutableList<TypeMirror> params = erasedParameterTypes(method, element);
if (params != null) {
return params;
}
}
return null;
}
@Override
protected TypeMirror defaultAction(TypeMirror e, Void p) {
return e;
}
@Override
public TypeMirror visitTypeVariable(TypeVariable t, Void p) {
Element element = typeUtils.asElement(t);
if (element instanceof TypeParameterElement) {
TypeParameterElement e = (TypeParameterElement) element;
if (typeBindings.containsKey(e)) {
return visit(typeBindings.get(e));
}
}
// We erase the upper bound to avoid infinite recursion. We can get away with erasure for
// the reasons described above.
return visit(typeUtils.erasure(t.getUpperBound()));
}
@Override
public TypeMirror visitDeclared(DeclaredType t, Void p) {
if (t.getTypeArguments().isEmpty()) {
return t;
}
List<TypeMirror> newArgs = Lists.newArrayList();
for (TypeMirror arg : t.getTypeArguments()) {
newArgs.add(visit(arg));
}
return typeUtils.getDeclaredType(asTypeElement(t), newArgs.toArray(new TypeMirror[0]));
}
@Override
public TypeMirror visitArray(ArrayType t, Void p) {
return typeUtils.getArrayType(visit(t.getComponentType()));
}
}
/**
* Returns the given method as it appears in the given type. This is the method itself,
* or the nearest override in a superclass of the given type, or null if the method is not
* found in the given type or any of its superclasses.
*/
@Nullable ExecutableElement methodFromSuperclasses(TypeElement in, ExecutableElement method) {
for (TypeElement t = in; t != null; t = superclass(t)) {
ExecutableElement tMethod = methodInType(t, method);
if (tMethod != null) {
return tMethod;
}
}
return null;
}
/**
* Returns the given interface method as it appears in the given type. This is the method
* itself, or the nearest override in a superinterface of the given type, or null if the method
* is not found in the given type or any of its transitive superinterfaces.
*/
@Nullable
ExecutableElement methodFromSuperinterfaces(TypeElement in, ExecutableElement method) {
TypeElement methodContainer = MoreElements.asType(method.getEnclosingElement());
Preconditions.checkArgument(methodContainer.getKind().isInterface());
TypeMirror methodContainerType = typeUtils.erasure(methodContainer.asType());
ImmutableList<TypeElement> types = ImmutableList.of(in);
// On the first pass through this loop, `types` is the type we're starting from,
// which might be a class or an interface. On later passes it is a list of direct
// superinterfaces we saw in the previous pass, but only the ones that were assignable
// to the interface that `method` appears in.
while (!types.isEmpty()) {
ImmutableList.Builder<TypeElement> newTypes = ImmutableList.builder();
for (TypeElement t : types) {
TypeMirror candidateType = typeUtils.erasure(t.asType());
if (typeUtils.isAssignable(candidateType, methodContainerType)) {
ExecutableElement tMethod = methodInType(t, method);
if (tMethod != null) {
return tMethod;
}
newTypes.addAll(superinterfaces(t));
}
if (t.getKind().isClass()) {
TypeElement sup = superclass(t);
if (sup != null) {
newTypes.add(sup);
}
}
}
types = newTypes.build();
}
return null;
}
/**
* Returns the method from within the given type that has the same erased signature as the given
* method, or null if there is no such method.
*/
private @Nullable ExecutableElement methodInType(TypeElement type, ExecutableElement method) {
int nParams = method.getParameters().size();
List<TypeMirror> params = erasedParameterTypes(method, type);
if (params == null) {
return null;
}
methods:
for (ExecutableElement tMethod : ElementFilter.methodsIn(type.getEnclosedElements())) {
if (tMethod.getSimpleName().equals(method.getSimpleName())
&& tMethod.getParameters().size() == nParams) {
for (int i = 0; i < nParams; i++) {
TypeMirror tParamType = typeUtils.erasure(tMethod.getParameters().get(i).asType());
if (!typeUtils.isSameType(params.get(i), tParamType)) {
continue methods;
}
}
return tMethod;
}
}
return null;
}
private @Nullable TypeElement superclass(TypeElement type) {
TypeMirror sup = type.getSuperclass();
if (sup.getKind() == TypeKind.DECLARED) {
return MoreElements.asType(typeUtils.asElement(sup));
} else {
return null;
}
}
private ImmutableList<TypeElement> superinterfaces(TypeElement type) {
ImmutableList.Builder<TypeElement> types = ImmutableList.builder();
for (TypeMirror sup : type.getInterfaces()) {
types.add(MoreElements.asType(typeUtils.asElement(sup)));
}
return types.build();
}
private TypeElement asTypeElement(TypeMirror typeMirror) {
DeclaredType declaredType = MoreTypes.asDeclared(typeMirror);
Element element = declaredType.asElement();
return MoreElements.asType(element);
}
}
}