diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java index 0185313618..607e7367b9 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java @@ -754,7 +754,7 @@ private void buildAnnotation( text = TypeEncoder.decode(text, processingEnv, vars.pkg, /* baseType= */ javaLangVoid); text = Reformatter.fixup(text); writeSourceFile(autoAnnotationClassName, text, autoBuilderType); - addDeferredType(autoBuilderType); + addDeferredType(autoBuilderType, autoAnnotationClassName); } private ImmutableSet annotationBuilderPropertySet(TypeElement annotationType) { diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java index 2cb5a165cb..a7293f26d1 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java @@ -20,6 +20,7 @@ import static com.google.auto.common.MoreElements.getPackage; import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.auto.common.MoreStreams.toImmutableList; +import static com.google.auto.common.MoreStreams.toImmutableMap; import static com.google.auto.common.MoreStreams.toImmutableSet; import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_PACKAGE_NAME; import static com.google.auto.value.processor.ClassNames.COPY_ANNOTATIONS_NAME; @@ -27,7 +28,6 @@ import static com.google.common.collect.Sets.union; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static javax.lang.model.util.ElementFilter.constructorsIn; @@ -98,8 +98,9 @@ abstract class AutoValueishProcessor extends AbstractProcessor { /** * Qualified names of {@code @AutoValue} (etc) classes that we attempted to process but had to * abandon because we needed other types that they referenced and those other types were missing. + * The corresponding value tells the name of the missing type, if known, or is empty otherwise. */ - private final List deferredTypeNames = new ArrayList<>(); + private final Map deferredTypeNames = new LinkedHashMap<>(); AutoValueishProcessor(String annotationClassName, boolean appliesToInterfaces) { this.annotationClassName = annotationClassName; @@ -143,7 +144,7 @@ final Elements elementUtils() { * This is used by tests. */ final ImmutableList deferredTypeNames() { - return ImmutableList.copyOf(deferredTypeNames); + return ImmutableList.copyOf(deferredTypeNames.keySet()); } @Override @@ -381,11 +382,11 @@ public int hashCode() { } } - void addDeferredType(TypeElement type) { - // We save the name of the type rather - // than its TypeElement because it is not guaranteed that it will be represented by - // the same TypeElement on the next round. - deferredTypeNames.add(type.getQualifiedName().toString()); + void addDeferredType(TypeElement type, String missingType) { + // We save the name of the type containing the problem, rather than its TypeElement, because it + // is not guaranteed that it will be represented by the same TypeElement on the next round. We + // save the name of the missing type for better diagnostics. (It may be empty.) + deferredTypeNames.put(type.getQualifiedName().toString(), missingType); } @Override @@ -402,30 +403,34 @@ public final boolean process(Set annotations, RoundEnviro + " because the annotation class was not found"); return false; } - List deferredTypes = - deferredTypeNames.stream() - .map(name -> elementUtils().getTypeElement(name)) - .collect(toList()); + ImmutableMap deferredTypes = + deferredTypeNames.entrySet().stream() + .collect( + toImmutableMap( + entry -> elementUtils().getTypeElement(entry.getKey()), Map.Entry::getValue)); if (roundEnv.processingOver()) { // This means that the previous round didn't generate any new sources, so we can't have found // any new instances of @AutoValue; and we can't have any new types that are the reason a type // was in deferredTypes. - for (TypeElement type : deferredTypes) { - errorReporter.reportError( - type, - "[%sUndefined] Did not generate @%s class for %s because it references" - + " undefined types", - simpleAnnotationName, - simpleAnnotationName, - type.getQualifiedName()); - } + deferredTypes.forEach( + (type, missing) -> { + String including = missing.isEmpty() ? "" : ("including " + missing); + errorReporter.reportError( + type, + "[%sUndefined] Did not generate @%s class for %s because it references" + + " undefined types %s", + simpleAnnotationName, + simpleAnnotationName, + type.getQualifiedName(), + including); + }); return false; } Collection annotatedElements = roundEnv.getElementsAnnotatedWith(annotationType); List types = new ImmutableList.Builder() - .addAll(deferredTypes) + .addAll(deferredTypes.keySet()) .addAll(ElementFilter.typesIn(annotatedElements)) .build(); deferredTypeNames.clear(); @@ -440,7 +445,7 @@ public final boolean process(Set annotations, RoundEnviro // that other type was missing. It is possible that the missing type will be generated by // further annotation processing, so we will try again on the next round (perhaps failing // again and adding it back to the list). - addDeferredType(type); + addDeferredType(type, e.getMessage()); } catch (RuntimeException e) { String trace = Throwables.getStackTraceAsString(e); errorReporter.reportError( 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 index ea8126b5c1..8bc0119efc 100644 --- a/value/src/main/java/com/google/auto/value/processor/MissingTypes.java +++ b/value/src/main/java/com/google/auto/value/processor/MissingTypes.java @@ -54,7 +54,7 @@ static class MissingTypeException extends RuntimeException { // 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()); + super(missingType == null ? "" : missingType.toString()); } } diff --git a/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java index ce8a0b3b46..01ec81fefb 100644 --- a/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java @@ -972,6 +972,8 @@ public void missingPropertyType() { .hadErrorContaining("MissingType") .inFile(javaFileObject) .onLineContaining("MissingType"); + assertThat(compilation) + .hadErrorContaining("references undefined types including MissingType"); } @Test