Skip to content

Commit

Permalink
Indicate _which_ type was missing when AutoValue etc give up because …
Browse files Browse the repository at this point in the history
…of missing types.

Usually there will be another error message pointing to the missing type, but sometimes not. That could be quite frustrating.

RELNOTES=Better error message when AutoValue, AutoBuilder, etc give up because of missing types. We now say what the first missing type was.
PiperOrigin-RevId: 515094473
  • Loading branch information
eamonnmcmanus authored and Google Java Core Libraries committed Mar 8, 2023
1 parent d6a087b commit 2e734f6
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Property> annotationBuilderPropertySet(TypeElement annotationType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
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;
import static com.google.common.collect.Iterables.getOnlyElement;
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;

Expand Down Expand Up @@ -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<String> deferredTypeNames = new ArrayList<>();
private final Map<String, String> deferredTypeNames = new LinkedHashMap<>();

AutoValueishProcessor(String annotationClassName, boolean appliesToInterfaces) {
this.annotationClassName = annotationClassName;
Expand Down Expand Up @@ -143,7 +144,7 @@ final Elements elementUtils() {
* This is used by tests.
*/
final ImmutableList<String> deferredTypeNames() {
return ImmutableList.copyOf(deferredTypeNames);
return ImmutableList.copyOf(deferredTypeNames.keySet());
}

@Override
Expand Down Expand Up @@ -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
Expand All @@ -402,30 +403,34 @@ public final boolean process(Set<? extends TypeElement> annotations, RoundEnviro
+ " because the annotation class was not found");
return false;
}
List<TypeElement> deferredTypes =
deferredTypeNames.stream()
.map(name -> elementUtils().getTypeElement(name))
.collect(toList());
ImmutableMap<TypeElement, String> 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<? extends Element> annotatedElements =
roundEnv.getElementsAnnotatedWith(annotationType);
List<TypeElement> types =
new ImmutableList.Builder<TypeElement>()
.addAll(deferredTypes)
.addAll(deferredTypes.keySet())
.addAll(ElementFilter.typesIn(annotatedElements))
.build();
deferredTypeNames.clear();
Expand All @@ -440,7 +445,7 @@ public final boolean process(Set<? extends TypeElement> 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,8 @@ public void missingPropertyType() {
.hadErrorContaining("MissingType")
.inFile(javaFileObject)
.onLineContaining("MissingType");
assertThat(compilation)
.hadErrorContaining("references undefined types including MissingType");
}

@Test
Expand Down

0 comments on commit 2e734f6

Please sign in to comment.