aboutsummaryrefslogtreecommitdiff
path: root/value/src/main/java/com
diff options
context:
space:
mode:
authoremcmanus <emcmanus@google.com>2020-05-18 16:03:55 -0700
committerColin Decker <cgdecker@gmail.com>2020-05-20 15:51:33 -0400
commit2797d38007e3a07e6197986721c99a1bdcdd4614 (patch)
tree45065b3a333cff67dacd5083f6e89086e93ca08b /value/src/main/java/com
parent365d3f6de10fc5b34241cc9162d123206558d530 (diff)
downloadauto-2797d38007e3a07e6197986721c99a1bdcdd4614.tar.gz
Defer processing in `@AutoValue` classes if any abstract method has an undefined return type or parameter type. This avoids problems in certain cases where other annotation processors will generate the currently-undefined type.
Fixes https://github.com/google/auto/issues/847. RELNOTES=Fixed a problem when an `@AutoValue` class references types that are generated by other annotation processors. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=312172526
Diffstat (limited to 'value/src/main/java/com')
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java18
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java19
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderSpec.java14
-rw-r--r--value/src/main/java/com/google/auto/value/processor/MissingTypeException.java28
-rw-r--r--value/src/main/java/com/google/auto/value/processor/MissingTypes.java139
-rw-r--r--value/src/main/java/com/google/auto/value/processor/TypeEncoder.java3
-rw-r--r--value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java4
7 files changed, 181 insertions, 44 deletions
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
index 404b8a1e..a41127c7 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
@@ -24,6 +24,7 @@ import com.google.auto.common.AnnotationMirrors;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.service.AutoService;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
@@ -43,7 +44,6 @@ import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
@@ -135,12 +135,18 @@ public class AutoOneOfProcessor extends AutoValueOrOneOfProcessor {
AnnotationValue kindValue =
AnnotationMirrors.getAnnotationValue(oneOfAnnotation.get(), "value");
Object value = kindValue.getValue();
- if (value instanceof TypeMirror && ((TypeMirror) value).getKind().equals(TypeKind.DECLARED)) {
- return MoreTypes.asDeclared((TypeMirror) value);
- } else {
- // This is presumably because the referenced type doesn't exist.
- throw new MissingTypeException();
+ if (value instanceof TypeMirror) {
+ TypeMirror kindType = (TypeMirror) value;
+ switch (kindType.getKind()) {
+ case DECLARED:
+ return MoreTypes.asDeclared(kindType);
+ case ERROR:
+ throw new MissingTypeException(MoreTypes.asError(kindType));
+ default:
+ break;
+ }
}
+ throw new MissingTypeException(null);
}
private ImmutableMap<String, String> propertyToKindMap(
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java
index 2987fcf9..494577a5 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java
@@ -31,6 +31,7 @@ import static java.util.stream.Collectors.toSet;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.common.Visibility;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
@@ -125,6 +126,15 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor {
return processingEnv.getElementUtils();
}
+ /**
+ * Qualified names of {@code @AutoValue} or {@code AutoOneOf} classes that we attempted to process
+ * but had to abandon because we needed other types that they referenced and those other types
+ * were missing. This is used by tests.
+ */
+ final ImmutableList<String> deferredTypeNames() {
+ return ImmutableList.copyOf(deferredTypeNames);
+ }
+
@Override
public final SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
@@ -693,14 +703,17 @@ abstract class AutoValueOrOneOfProcessor extends AbstractProcessor {
/**
* Returns the subset of all abstract methods in the given set of methods. A given method
- * signature is only mentioned once, even if it is inherited on more than one path.
+ * signature is only mentioned once, even if it is inherited on more than one path. If any of the
+ * abstract methods has a return type or parameter type that is not currently defined then this
+ * method will throw an exception that will cause us to defer processing of the current class
+ * until a later annotation-processing round.
*/
- static ImmutableSet<ExecutableElement> abstractMethodsIn(
- ImmutableSet<ExecutableElement> methods) {
+ static ImmutableSet<ExecutableElement> abstractMethodsIn(Iterable<ExecutableElement> methods) {
Set<Name> noArgMethods = new HashSet<>();
ImmutableSet.Builder<ExecutableElement> abstracts = ImmutableSet.builder();
for (ExecutableElement method : methods) {
if (method.getModifiers().contains(Modifier.ABSTRACT)) {
+ MissingTypes.deferIfMissingTypesIn(method);
boolean hasArgs = !method.getParameters().isEmpty();
if (hasArgs || noArgMethods.add(method.getSimpleName())) {
// If an abstract method with the same signature is inherited on more than one path,
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
index 73d3d49f..32e9e6d4 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
@@ -51,7 +51,6 @@ import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Types;
/**
@@ -257,8 +256,7 @@ class BuilderSpec {
if (!optionalClassifier.isPresent()) {
return;
}
- for (ExecutableElement method :
- ElementFilter.methodsIn(builderTypeElement.getEnclosedElements())) {
+ for (ExecutableElement method : methodsIn(builderTypeElement.getEnclosedElements())) {
if (method.getSimpleName().contentEquals("builder")
&& method.getModifiers().contains(Modifier.STATIC)
&& method.getAnnotationMirrors().isEmpty()) {
@@ -480,14 +478,20 @@ class BuilderSpec {
return true;
}
- // Return a set of all abstract methods in the given TypeElement or inherited from ancestors.
- private Set<ExecutableElement> abstractMethods(TypeElement typeElement) {
+ /**
+ * Returns a set of all abstract methods in the given TypeElement or inherited from ancestors. If
+ * any of the abstract methods has a return type or parameter type that is not currently defined
+ * then this method will throw an exception that will cause us to defer processing of the current
+ * class until a later annotation-processing round.
+ */
+ private ImmutableSet<ExecutableElement> abstractMethods(TypeElement typeElement) {
Set<ExecutableElement> methods =
getLocalAndInheritedMethods(
typeElement, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
ImmutableSet.Builder<ExecutableElement> abstractMethods = ImmutableSet.builder();
for (ExecutableElement method : methods) {
if (method.getModifiers().contains(Modifier.ABSTRACT)) {
+ MissingTypes.deferIfMissingTypesIn(method);
abstractMethods.add(method);
}
}
diff --git a/value/src/main/java/com/google/auto/value/processor/MissingTypeException.java b/value/src/main/java/com/google/auto/value/processor/MissingTypeException.java
deleted file mode 100644
index b64d0390..00000000
--- a/value/src/main/java/com/google/auto/value/processor/MissingTypeException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2014 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.value.processor;
-
-/**
- * Exception thrown in the specific case where processing of a class was abandoned because it
- * required types that the class references to be present and they were not. This case is handled
- * specially because it is possible that those types might be generated later during annotation
- * processing, so we should reattempt the processing of the class in a later annotation processing
- * round.
- *
- * @author Éamonn McManus
- */
-@SuppressWarnings("serial")
-class MissingTypeException extends RuntimeException {}
diff --git a/value/src/main/java/com/google/auto/value/processor/MissingTypes.java b/value/src/main/java/com/google/auto/value/processor/MissingTypes.java
new file mode 100644
index 00000000..ea8126b5
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/MissingTypes.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2014 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.value.processor;
+
+import java.util.List;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.SimpleTypeVisitor8;
+
+/**
+ * Handling of undefined types. When we see an undefined type, it might genuinely be undefined, or
+ * it might be a type whose source code will be generated later on as part of the same compilation.
+ * If we encounter an undefined type in a place where we need to know the type, we throw {@link
+ * MissingTypeException}. We then catch that and defer processing for the current class until the
+ * next annotation-processing "round". If the missing class has been generated in the meanwhile, we
+ * may now be able to complete processing. After a round has completed without generating any new
+ * source code, if there are still missing types then we report an error.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+final class MissingTypes {
+ private MissingTypes() {}
+
+ /**
+ * Exception thrown in the specific case where processing of a class was abandoned because it
+ * required types that the class references to be present and they were not. This case is handled
+ * specially because it is possible that those types might be generated later during annotation
+ * processing, so we should reattempt the processing of the class in a later annotation processing
+ * round.
+ */
+ @SuppressWarnings("serial")
+ static class MissingTypeException extends RuntimeException {
+ MissingTypeException(ErrorType missingType) {
+ // Although it is not specified as such, in practice ErrorType.toString() is the type name
+ // that appeared in the source code. Showing it here can help in debugging issues with
+ // deferral.
+ super(missingType == null ? null : missingType.toString());
+ }
+ }
+
+ /**
+ * Check that the return type and parameter types of the given method are all defined, and arrange
+ * to defer processing until the next round if not.
+ *
+ * @throws MissingTypeException if the return type or a parameter type of the given method is
+ * undefined
+ */
+ static void deferIfMissingTypesIn(ExecutableElement method) {
+ MISSING_TYPE_VISITOR.check(method.getReturnType());
+ for (VariableElement param : method.getParameters()) {
+ MISSING_TYPE_VISITOR.check(param.asType());
+ }
+ }
+
+ private static final MissingTypeVisitor MISSING_TYPE_VISITOR = new MissingTypeVisitor();
+
+ private static class MissingTypeVisitor extends SimpleTypeVisitor8<Void, TypeMirrorSet> {
+ // Avoid infinite recursion for a type like `Enum<E extends Enum<E>>` by remembering types that
+ // we have already seen on this visit. Recursion has to go through a declared type, such as Enum
+ // in this example, so in principle it should be enough to check only in visitDeclared. However
+ // Eclipse has a quirk where the second E in `Enum<E extends Enum<E>>` is not the same as the
+ // first, and if you ask for its bounds you will get another `Enum<E>` with a third E. So we
+ // also check in visitTypeVariable. TypeMirrorSet does consider that all these E variables are
+ // the same so infinite recursion is avoided.
+ void check(TypeMirror type) {
+ type.accept(this, new TypeMirrorSet());
+ }
+
+ @Override
+ public Void visitError(ErrorType t, TypeMirrorSet visiting) {
+ throw new MissingTypeException(t);
+ }
+
+ @Override
+ public Void visitArray(ArrayType t, TypeMirrorSet visiting) {
+ return t.getComponentType().accept(this, visiting);
+ }
+
+ @Override
+ public Void visitDeclared(DeclaredType t, TypeMirrorSet visiting) {
+ if (visiting.add(t)) {
+ visitAll(t.getTypeArguments(), visiting);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitTypeVariable(TypeVariable t, TypeMirrorSet visiting) {
+ if (visiting.add(t)) {
+ t.getLowerBound().accept(this, visiting);
+ t.getUpperBound().accept(this, visiting);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitWildcard(WildcardType t, TypeMirrorSet visiting) {
+ if (t.getSuperBound() != null) {
+ t.getSuperBound().accept(this, visiting);
+ }
+ if (t.getExtendsBound() != null) {
+ t.getExtendsBound().accept(this, visiting);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitIntersection(IntersectionType t, TypeMirrorSet visiting) {
+ return visitAll(t.getBounds(), visiting);
+ }
+
+ private Void visitAll(List<? extends TypeMirror> types, TypeMirrorSet visiting) {
+ for (TypeMirror type : types) {
+ type.accept(this, visiting);
+ }
+ return null;
+ }
+ }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java b/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
index 912091f8..d1bbbab5 100644
--- a/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
+++ b/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
@@ -21,6 +21,7 @@ import static java.util.stream.Collectors.toList;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.OptionalInt;
@@ -290,7 +291,7 @@ final class TypeEncoder {
@Override
public StringBuilder visitError(ErrorType t, StringBuilder p) {
- throw new MissingTypeException();
+ throw new MissingTypeException(t);
}
}
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
index 6918ae5f..290e59e0 100644
--- a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
+++ b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
@@ -20,6 +20,8 @@ import static java.util.stream.Collectors.toCollection;
import static javax.lang.model.element.Modifier.PRIVATE;
import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
import com.google.common.collect.ImmutableSortedSet;
import java.util.HashMap;
import java.util.HashSet;
@@ -298,7 +300,7 @@ final class TypeSimplifier {
Map<String, Name> simpleNamesToQualifiedNames = new HashMap<>();
for (TypeMirror type : types) {
if (type.getKind() == TypeKind.ERROR) {
- throw new MissingTypeException();
+ throw new MissingTypeException(MoreTypes.asError(type));
}
String simpleName = typeUtils.asElement(type).getSimpleName().toString();
/*