Skip to content

Commit f26d2df

Browse files
eamonnmcmanusGoogle Java Core Libraries
authored andcommitted
Ensure that type annotations are placed correctly.
Treating them the same as other annotations leads to problems with nested types like `Map.Entry`. If a field or parameter is a `Map.Entry` that is `@Nullable`, and if `@Nullable` is a `TYPE_USE` annotation, then the declaration must use `Map.@nullable Entry`. JavaPoet will do this correctly if the annotations are applied to the `TypeName` for the declaration. Also ensure that type annotations are included in the `T` of `Provider<T>` when that appears in field or parameter declarations. Fixes #949. RELNOTES=AutoFactory type annotations are now placed correctly even for nested types. PiperOrigin-RevId: 350930373
1 parent a7cef3f commit f26d2df

File tree

4 files changed

+78
-20
lines changed

4 files changed

+78
-20
lines changed

factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import static javax.lang.model.element.Modifier.PUBLIC;
2727
import static javax.lang.model.element.Modifier.STATIC;
2828

29+
import com.google.auto.common.AnnotationMirrors;
30+
import com.google.auto.common.AnnotationValues;
2931
import com.google.common.collect.ImmutableList;
3032
import com.google.common.collect.ImmutableSet;
3133
import com.google.common.collect.ImmutableSetMultimap;
@@ -43,13 +45,19 @@
4345
import com.squareup.javapoet.TypeSpec;
4446
import com.squareup.javapoet.TypeVariableName;
4547
import java.io.IOException;
48+
import java.lang.annotation.Target;
4649
import java.util.Iterator;
50+
import java.util.List;
51+
import java.util.Optional;
4752
import java.util.stream.Stream;
4853
import javax.annotation.processing.Filer;
4954
import javax.annotation.processing.ProcessingEnvironment;
5055
import javax.inject.Inject;
5156
import javax.inject.Provider;
5257
import javax.lang.model.SourceVersion;
58+
import javax.lang.model.element.AnnotationMirror;
59+
import javax.lang.model.element.Element;
60+
import javax.lang.model.element.VariableElement;
5361
import javax.lang.model.type.TypeKind;
5462
import javax.lang.model.type.TypeMirror;
5563
import javax.lang.model.type.TypeVariable;
@@ -143,7 +151,7 @@ private void addFactoryMethods(
143151
ImmutableSet<TypeVariableName> factoryTypeVariables) {
144152
for (FactoryMethodDescriptor methodDescriptor : descriptor.methodDescriptors()) {
145153
MethodSpec.Builder method =
146-
MethodSpec.methodBuilder(methodDescriptor.name())
154+
methodBuilder(methodDescriptor.name())
147155
.addTypeVariables(getMethodTypeVariables(methodDescriptor, factoryTypeVariables))
148156
.returns(TypeName.get(methodDescriptor.returnType()))
149157
.varargs(methodDescriptor.isVarArgs());
@@ -219,17 +227,45 @@ private void addImplementationMethods(
219227
private ImmutableList<ParameterSpec> parameters(Iterable<Parameter> parameters) {
220228
ImmutableList.Builder<ParameterSpec> builder = ImmutableList.builder();
221229
for (Parameter parameter : parameters) {
222-
ParameterSpec.Builder parameterBuilder =
223-
ParameterSpec.builder(resolveTypeName(parameter.type().get()), parameter.name());
224-
Stream.of(parameter.nullable(), parameter.key().qualifier())
225-
.flatMap(Streams::stream)
226-
.map(AnnotationSpec::get)
227-
.forEach(parameterBuilder::addAnnotation);
228-
builder.add(parameterBuilder.build());
230+
TypeName type = resolveTypeName(parameter.type().get());
231+
// Remove TYPE_USE annotations, since resolveTypeName will already have included those in
232+
// the TypeName it returns.
233+
List<AnnotationSpec> annotations =
234+
Stream.of(parameter.nullable(), parameter.key().qualifier())
235+
.flatMap(Streams::stream)
236+
.filter(a -> !isTypeUseAnnotation(a))
237+
.map(AnnotationSpec::get)
238+
.collect(toList());
239+
ParameterSpec parameterSpec =
240+
ParameterSpec.builder(type, parameter.name())
241+
.addAnnotations(annotations)
242+
.build();
243+
builder.add(parameterSpec);
229244
}
230245
return builder.build();
231246
}
232247

248+
private static boolean isTypeUseAnnotation(AnnotationMirror mirror) {
249+
Element annotationElement = mirror.getAnnotationType().asElement();
250+
// This is basically equivalent to:
251+
// Target target = annotationElement.getAnnotation(Target.class);
252+
// return target != null
253+
// && Arrays.asList(annotationElement.getAnnotation(Target.class)).contains(TYPE_USE);
254+
// but that might blow up if the annotation is being compiled at the same time and has an
255+
// undefined identifier in its @Target values. The rigmarole below avoids that problem.
256+
Optional<AnnotationMirror> maybeTargetMirror =
257+
Mirrors.getAnnotationMirror(annotationElement, Target.class);
258+
return maybeTargetMirror
259+
.map(
260+
targetMirror ->
261+
AnnotationValues.getEnums(
262+
AnnotationMirrors.getAnnotationValue(targetMirror, "value"))
263+
.stream()
264+
.map(VariableElement::getSimpleName)
265+
.anyMatch(name -> name.contentEquals("TYPE_USE")))
266+
.orElse(false);
267+
}
268+
233269
private static void addCheckNotNullMethod(
234270
TypeSpec.Builder factory, FactoryDescriptor descriptor) {
235271
if (shouldGenerateCheckNotNull(descriptor)) {
@@ -284,17 +320,20 @@ private static boolean shouldGenerateCheckNotNull(FactoryDescriptor descriptor)
284320
* {@code @AutoFactory class Foo} has a constructor parameter of type {@code BarFactory} and
285321
* {@code @AutoFactory class Bar} has a constructor parameter of type {@code FooFactory}. We did
286322
* in fact find instances of this in Google's source base.
323+
*
324+
* <p>If the type has type annotations then include those in the returned {@link TypeName}.
287325
*/
288326
private TypeName resolveTypeName(TypeMirror type) {
289-
if (type.getKind() != TypeKind.ERROR) {
290-
return TypeName.get(type);
291-
}
292-
ImmutableSet<PackageAndClass> factoryNames = factoriesBeingCreated.get(type.toString());
293-
if (factoryNames.size() == 1) {
294-
PackageAndClass packageAndClass = Iterables.getOnlyElement(factoryNames);
295-
return ClassName.get(packageAndClass.packageName(), packageAndClass.className());
327+
TypeName typeName = TypeName.get(type);
328+
if (type.getKind() == TypeKind.ERROR) {
329+
ImmutableSet<PackageAndClass> factoryNames = factoriesBeingCreated.get(type.toString());
330+
if (factoryNames.size() == 1) {
331+
PackageAndClass packageAndClass = Iterables.getOnlyElement(factoryNames);
332+
typeName = ClassName.get(packageAndClass.packageName(), packageAndClass.className());
333+
}
296334
}
297-
return TypeName.get(type);
335+
return typeName.annotated(
336+
type.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(toList()));
298337
}
299338

300339
private static ImmutableSet<TypeVariableName> getFactoryTypeVariables(

factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package com.google.auto.factory.processor;
1717

18+
import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
19+
import static com.google.common.truth.TruthJUnit.assume;
1820
import static com.google.testing.compile.CompilationSubject.assertThat;
1921
import static java.lang.Math.max;
2022
import static java.lang.Math.min;
@@ -427,6 +429,11 @@ public void customNullableType() {
427429

428430
@Test
429431
public void checkerFrameworkNullableType() {
432+
// TYPE_USE annotations are pretty much unusable with annotation processors on Java 8 because
433+
// of bugs that mean they only appear in the javax.lang.model API when the compiler feels like
434+
// it. Checking for a java.specification.version that does not start with "1." eliminates 8 and
435+
// any earlier version.
436+
assume().that(JAVA_SPECIFICATION_VERSION.value()).doesNotMatch("1\\..*");
430437
Compilation compilation =
431438
javac.compile(JavaFileObjects.forResource("good/CheckerFrameworkNullable.java"));
432439
assertThat(compilation).succeededWithoutWarnings();

factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package tests;
1717

18+
import java.util.Map;
1819
import javax.annotation.processing.Generated;
1920
import javax.inject.Inject;
2021
import javax.inject.Provider;
@@ -29,19 +30,27 @@ final class CheckerFrameworkNullableFactory {
2930

3031
private final Provider<String> java_lang_StringProvider;
3132

33+
private final Provider<Map.@NullableType Entry<?, ?>> providedNestedNullableTypeProvider;
34+
3235
@Inject
3336
CheckerFrameworkNullableFactory(
34-
Provider<String> java_lang_StringProvider) {
37+
Provider<String> java_lang_StringProvider,
38+
Provider<Map.@NullableType Entry<?, ?>> providedNestedNullableTypeProvider) {
3539
this.java_lang_StringProvider = checkNotNull(java_lang_StringProvider, 1);
40+
this.providedNestedNullableTypeProvider = checkNotNull(providedNestedNullableTypeProvider, 2);
3641
}
3742

3843
CheckerFrameworkNullable create(
39-
@NullableDecl String nullableDecl, @NullableType String nullableType) {
44+
@NullableDecl String nullableDecl,
45+
@NullableType String nullableType,
46+
Map.@NullableType Entry<?, ?> nestedNullableType) {
4047
return new CheckerFrameworkNullable(
4148
nullableDecl,
4249
java_lang_StringProvider.get(),
4350
nullableType,
44-
java_lang_StringProvider.get());
51+
java_lang_StringProvider.get(),
52+
nestedNullableType,
53+
providedNestedNullableTypeProvider.get());
4554
}
4655

4756
private static <T> T checkNotNull(T reference, int argumentIndex) {

factory/src/test/resources/good/CheckerFrameworkNullable.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import com.google.auto.factory.AutoFactory;
1919
import com.google.auto.factory.Provided;
20+
import java.util.Map;
2021
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
2122
import org.checkerframework.checker.nullness.compatqual.NullableType;
2223

@@ -27,5 +28,7 @@ final class CheckerFrameworkNullable {
2728
@NullableDecl String nullableDecl,
2829
@Provided @NullableDecl String providedNullableDecl,
2930
@NullableType String nullableType,
30-
@Provided @NullableType String providedNullableType) {}
31+
@Provided @NullableType String providedNullableType,
32+
Map.@NullableType Entry<?, ?> nestedNullableType,
33+
@Provided Map.@NullableType Entry<?, ?> providedNestedNullableType) {}
3134
}

0 commit comments

Comments
 (0)