Skip to content

Commit 4bb91ca

Browse files
eamonnmcmanusGoogle Java Core Libraries
authored andcommitted
Propagate parameter annotations in generated factory code.
Example: ```java @autofactory final class SomeClass { ... SomeClass(@provided @AQualifier @foo String provideDepA, @bar String depB) {...} } ``` The generated `SomeClassFactory` will have a `create` method like this: ```java SomeClass create(@bar String depB) {...} ``` This was already true if `@Bar` was a `TYPE_USE` annotation, but now it is also true for other annotations. This may affect compilation. For example, if `@Bar` is Kotlin's `@NotNull`, this change plugs a hole in Kotlin's nullness checking and will break callers that were previously passing a `@Nullable` value. The generated factory already null-checked such parameters, so in Kotlin you can add `!!` with no change in effect. If the `!!` causes a `NullPointerException`, there would already have been a `NullPointerException` from the generated factory. We do not currently copy annotations, other than `@Qualifier` annotations, from `@Provided` parameters to the generated factory constructor. In the example, `@Foo` would not be copied. PiperOrigin-RevId: 481174930
1 parent dffe1f8 commit 4bb91ca

File tree

5 files changed

+121
-41
lines changed

5 files changed

+121
-41
lines changed

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

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.google.auto.factory.processor;
1717

1818
import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec;
19+
import static com.google.auto.common.MoreStreams.toImmutableList;
1920
import static com.squareup.javapoet.MethodSpec.constructorBuilder;
2021
import static com.squareup.javapoet.MethodSpec.methodBuilder;
2122
import static com.squareup.javapoet.TypeSpec.classBuilder;
@@ -27,15 +28,12 @@
2728
import static javax.lang.model.element.Modifier.PUBLIC;
2829
import static javax.lang.model.element.Modifier.STATIC;
2930

30-
import com.google.auto.common.AnnotationMirrors;
31-
import com.google.auto.common.AnnotationValues;
3231
import com.google.auto.common.MoreTypes;
3332
import com.google.common.collect.ImmutableList;
3433
import com.google.common.collect.ImmutableSet;
3534
import com.google.common.collect.ImmutableSetMultimap;
3635
import com.google.common.collect.Iterables;
3736
import com.google.common.collect.Sets;
38-
import com.google.common.collect.Streams;
3937
import com.squareup.javapoet.AnnotationSpec;
4038
import com.squareup.javapoet.ClassName;
4139
import com.squareup.javapoet.CodeBlock;
@@ -47,19 +45,13 @@
4745
import com.squareup.javapoet.TypeSpec;
4846
import com.squareup.javapoet.TypeVariableName;
4947
import java.io.IOException;
50-
import java.lang.annotation.Target;
5148
import java.util.Iterator;
5249
import java.util.List;
53-
import java.util.Optional;
54-
import java.util.stream.Stream;
5550
import javax.annotation.processing.Filer;
5651
import javax.annotation.processing.ProcessingEnvironment;
5752
import javax.inject.Inject;
5853
import javax.inject.Provider;
5954
import javax.lang.model.SourceVersion;
60-
import javax.lang.model.element.AnnotationMirror;
61-
import javax.lang.model.element.Element;
62-
import javax.lang.model.element.VariableElement;
6355
import javax.lang.model.type.DeclaredType;
6456
import javax.lang.model.type.TypeKind;
6557
import javax.lang.model.type.TypeMirror;
@@ -228,42 +220,17 @@ private ImmutableList<ParameterSpec> parameters(Iterable<Parameter> parameters)
228220
ImmutableList.Builder<ParameterSpec> builder = ImmutableList.builder();
229221
for (Parameter parameter : parameters) {
230222
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))
223+
ImmutableList<AnnotationSpec> annotations =
224+
parameter.annotations().stream()
237225
.map(AnnotationSpec::get)
238-
.collect(toList());
226+
.collect(toImmutableList());
239227
ParameterSpec parameterSpec =
240228
ParameterSpec.builder(type, parameter.name()).addAnnotations(annotations).build();
241229
builder.add(parameterSpec);
242230
}
243231
return builder.build();
244232
}
245233

246-
private static boolean isTypeUseAnnotation(AnnotationMirror mirror) {
247-
Element annotationElement = mirror.getAnnotationType().asElement();
248-
// This is basically equivalent to:
249-
// Target target = annotationElement.getAnnotation(Target.class);
250-
// return target != null
251-
// && Arrays.asList(annotationElement.getAnnotation(Target.class)).contains(TYPE_USE);
252-
// but that might blow up if the annotation is being compiled at the same time and has an
253-
// undefined identifier in its @Target values. The rigmarole below avoids that problem.
254-
Optional<AnnotationMirror> maybeTargetMirror =
255-
Mirrors.getAnnotationMirror(annotationElement, Target.class);
256-
return maybeTargetMirror
257-
.map(
258-
targetMirror ->
259-
AnnotationValues.getEnums(
260-
AnnotationMirrors.getAnnotationValue(targetMirror, "value"))
261-
.stream()
262-
.map(VariableElement::getSimpleName)
263-
.anyMatch(name -> name.contentEquals("TYPE_USE")))
264-
.orElse(false);
265-
}
266-
267234
private static void addCheckNotNullMethod(
268235
TypeSpec.Builder factory, FactoryDescriptor descriptor) {
269236
if (shouldGenerateCheckNotNull(descriptor)) {

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

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

18+
import static com.google.auto.common.MoreStreams.toImmutableList;
1819
import static com.google.auto.factory.processor.Mirrors.unwrapOptionalEquivalence;
1920
import static com.google.auto.factory.processor.Mirrors.wrapOptionalInEquivalence;
2021
import static com.google.common.base.Preconditions.checkArgument;
21-
import static java.util.stream.Collectors.toList;
2222

2323
import com.google.auto.common.AnnotationMirrors;
2424
import com.google.auto.common.MoreElements;
2525
import com.google.auto.common.MoreTypes;
2626
import com.google.auto.value.AutoValue;
2727
import com.google.common.base.Equivalence;
28+
import com.google.common.collect.ImmutableList;
2829
import com.google.common.collect.ImmutableSet;
2930
import com.google.common.collect.Lists;
3031
import com.google.common.collect.Sets;
@@ -65,26 +66,37 @@ boolean isPrimitive() {
6566

6667
abstract Key key();
6768

69+
/** Annotations on the parameter (not its type). */
70+
abstract ImmutableList<Equivalence.Wrapper<AnnotationMirror>> annotationWrappers();
71+
72+
ImmutableList<AnnotationMirror> annotations() {
73+
return annotationWrappers().stream().map(Equivalence.Wrapper::get).collect(toImmutableList());
74+
}
75+
6876
abstract Optional<Equivalence.Wrapper<AnnotationMirror>> nullableWrapper();
6977

7078
Optional<AnnotationMirror> nullable() {
7179
return unwrapOptionalEquivalence(nullableWrapper());
7280
}
73-
7481
private static Parameter forVariableElement(
7582
VariableElement variable, TypeMirror type, Types types) {
76-
List<AnnotationMirror> annotations =
83+
ImmutableList<AnnotationMirror> annotations =
7784
Stream.of(variable.getAnnotationMirrors(), type.getAnnotationMirrors())
7885
.flatMap(List::stream)
79-
.collect(toList());
86+
.collect(toImmutableList());
8087
Optional<AnnotationMirror> nullable =
8188
annotations.stream().filter(Parameter::isNullable).findFirst();
89+
ImmutableList<Equivalence.Wrapper<AnnotationMirror>> annotationWrappers =
90+
variable.getAnnotationMirrors().stream()
91+
.map(AnnotationMirrors.equivalence()::wrap)
92+
.collect(toImmutableList());
8293

8394
Key key = Key.create(type, annotations, types);
8495
return new AutoValue_Parameter(
8596
MoreTypes.equivalence().wrap(type),
8697
variable.getSimpleName().toString(),
8798
key,
99+
annotationWrappers,
88100
wrapOptionalInEquivalence(AnnotationMirrors.equivalence(), nullable));
89101
}
90102

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,16 @@ public void generics() {
524524
.hasSourceEquivalentTo(loadExpectedFile("expected/Generics_FooImplWithClassFactory.java"));
525525
}
526526

527+
@Test
528+
public void parameterAnnotations() {
529+
JavaFileObject file = JavaFileObjects.forResource("good/ParameterAnnotations.java");
530+
Compilation compilation = javac.compile(file);
531+
assertThat(compilation).succeededWithoutWarnings();
532+
assertThat(compilation)
533+
.generatedSourceFile("tests.ParameterAnnotationsFactory")
534+
.hasSourceEquivalentTo(loadExpectedFile("expected/ParameterAnnotationsFactory.java"));
535+
}
536+
527537
private JavaFileObject loadExpectedFile(String resourceName) {
528538
if (isJavaxAnnotationProcessingGeneratedAvailable()) {
529539
return JavaFileObjects.forResource(resourceName);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package tests;
17+
18+
import javax.annotation.processing.Generated;
19+
import javax.inject.Inject;
20+
import javax.inject.Provider;
21+
22+
@Generated(
23+
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
24+
comments = "https://github.com/google/auto/tree/master/factory"
25+
)
26+
final class ParameterAnnotationsFactory {
27+
private final Provider<@ParameterAnnotations.NullableType String> fooProvider;
28+
29+
@Inject
30+
ParameterAnnotationsFactory(Provider<@ParameterAnnotations.NullableType String> fooProvider) {
31+
this.fooProvider = checkNotNull(fooProvider, 1);
32+
}
33+
34+
ParameterAnnotations create(@ParameterAnnotations.NullableParameter Integer bar, @ParameterAnnotations.Nullable Long baz, @ParameterAnnotations.NullableType Thread buh) {
35+
return new ParameterAnnotations(checkNotNull(fooProvider.get(), 1), checkNotNull(bar, 2), baz, checkNotNull(buh, 4));
36+
}
37+
38+
private static <T> T checkNotNull(T reference, int argumentIndex) {
39+
if (reference == null) {
40+
throw new NullPointerException("@AutoFactory method argument is null but is not marked @Nullable. Argument index: " + argumentIndex);
41+
}
42+
return reference;
43+
}
44+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package tests;
17+
18+
import static java.lang.annotation.ElementType.PARAMETER;
19+
import static java.lang.annotation.ElementType.TYPE_USE;
20+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
21+
22+
import com.google.auto.factory.AutoFactory;
23+
import com.google.auto.factory.Provided;
24+
import java.lang.annotation.Retention;
25+
import java.lang.annotation.Target;
26+
27+
@AutoFactory
28+
final class ParameterAnnotations {
29+
@Retention(RUNTIME)
30+
@Target(PARAMETER)
31+
@interface NullableParameter {}
32+
33+
// We have special treatment of @Nullable; make sure it doesn't get copied twice.
34+
@Retention(RUNTIME)
35+
@Target(PARAMETER)
36+
@interface Nullable {}
37+
38+
@Retention(RUNTIME)
39+
@Target(TYPE_USE)
40+
@interface NullableType {}
41+
42+
ParameterAnnotations(
43+
@Provided @NullableParameter @NullableType String foo,
44+
@NullableParameter Integer bar,
45+
@Nullable Long baz,
46+
@NullableType Thread buh) {}
47+
}

0 commit comments

Comments
 (0)