Skip to content

Commit 4d01ce6

Browse files
eamonnmcmanusGoogle Java Core Libraries
authored andcommitted
If any method signature in an @AutoValue class mentions @Nullable, copy that @Nullable to the parameter of equals(Object).
This only works for `TYPE_USE` annotations, which is what everybody should be using these days. RELNOTES=The parameter of `equals(Object)` is annotated with `@Nullable` if any method signature mentions `@Nullable`. PiperOrigin-RevId: 364642711
1 parent 078bd36 commit 4d01ce6

File tree

5 files changed

+387
-18
lines changed

5 files changed

+387
-18
lines changed

value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
*/
1616
package com.google.auto.value;
1717

18+
import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
1819
import static com.google.common.truth.Truth.assertThat;
1920
import static com.google.common.truth.Truth.assertWithMessage;
2021
import static com.google.common.truth.Truth8.assertThat;
22+
import static com.google.common.truth.TruthJUnit.assume;
2123
import static com.google.testing.compile.CompilationSubject.assertThat;
2224
import static org.junit.Assert.assertThrows;
2325
import static org.junit.Assume.assumeTrue;
@@ -153,6 +155,19 @@ public void testNullablePropertiesCanBeNull() {
153155
.isEqualTo("NullableProperties{nullableString=null, randomInt=23}");
154156
}
155157

158+
@Test
159+
public void testEqualsParameterIsAnnotated() throws NoSuchMethodException {
160+
// Sadly we can't rely on JDK 8 to handle type annotations correctly.
161+
// Some versions do, some don't. So skip the test unless we are on at least JDK 9.
162+
double javaVersion = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value());
163+
assume().that(javaVersion).isAtLeast(9.0);
164+
Method equals =
165+
NullableProperties.create(null, 23).getClass().getMethod("equals", Object.class);
166+
AnnotatedType[] parameterTypes = equals.getAnnotatedParameterTypes();
167+
assertThat(parameterTypes).hasLength(1);
168+
assertThat(parameterTypes[0].getAnnotation(Nullable.class)).isNotNull();
169+
}
170+
156171
@AutoAnnotation
157172
static Nullable nullable() {
158173
return new AutoAnnotation_AutoValueJava8Test_nullable();

value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import java.util.OptionalInt;
5757
import java.util.Set;
5858
import java.util.function.Predicate;
59+
import java.util.stream.IntStream;
5960
import javax.annotation.processing.AbstractProcessor;
6061
import javax.annotation.processing.ProcessingEnvironment;
6162
import javax.annotation.processing.RoundEnvironment;
@@ -383,7 +384,7 @@ final ImmutableSet<Property> propertySet(
383384
(propertyMethod, returnType) -> {
384385
String propertyType =
385386
TypeEncoder.encodeWithAnnotations(
386-
returnType, getExcludedAnnotationTypes(propertyMethod));
387+
returnType, ImmutableList.of(), getExcludedAnnotationTypes(propertyMethod));
387388
String propertyName = methodToPropertyName.get(propertyMethod);
388389
String identifier = methodToIdentifier.get(propertyMethod);
389390
ImmutableList<String> fieldAnnotations =
@@ -432,7 +433,8 @@ final void defineSharedVarsForType(
432433
vars.toString = methodsToGenerate.containsKey(ObjectMethod.TO_STRING);
433434
vars.equals = methodsToGenerate.containsKey(ObjectMethod.EQUALS);
434435
vars.hashCode = methodsToGenerate.containsKey(ObjectMethod.HASH_CODE);
435-
vars.equalsParameterType = equalsParameterType(methodsToGenerate);
436+
Optional<DeclaredType> nullable = Nullables.nullableMentionedInMethods(methods);
437+
vars.equalsParameterType = equalsParameterType(methodsToGenerate, nullable);
436438
vars.serialVersionUID = getSerialVersionUID(type);
437439
}
438440

@@ -567,12 +569,9 @@ static Optional<String> nullableAnnotationFor(Element element, TypeMirror elemen
567569
}
568570

569571
private static OptionalInt nullableAnnotationIndex(List<? extends AnnotationMirror> annotations) {
570-
for (int i = 0; i < annotations.size(); i++) {
571-
if (isNullable(annotations.get(i))) {
572-
return OptionalInt.of(i);
573-
}
574-
}
575-
return OptionalInt.empty();
572+
return IntStream.range(0, annotations.size())
573+
.filter(i -> isNullable(annotations.get(i)))
574+
.findFirst();
576575
}
577576

578577
private static boolean isNullable(AnnotationMirror annotation) {
@@ -706,14 +705,40 @@ private static Map<ObjectMethod, ExecutableElement> determineObjectMethodsToGene
706705
* Returns the encoded parameter type of the {@code equals(Object)} method that is to be
707706
* generated, or an empty string if the method is not being generated. The parameter type includes
708707
* any type annotations, for example {@code @Nullable}.
708+
*
709+
* @param methodsToGenerate the Object methods that are being generated
710+
* @param nullable the type of a {@code @Nullable} type annotation that we have found, if any
709711
*/
710-
static String equalsParameterType(Map<ObjectMethod, ExecutableElement> methodsToGenerate) {
712+
static String equalsParameterType(
713+
Map<ObjectMethod, ExecutableElement> methodsToGenerate, Optional<DeclaredType> nullable) {
711714
ExecutableElement equals = methodsToGenerate.get(ObjectMethod.EQUALS);
712715
if (equals == null) {
713716
return ""; // this will not be referenced because no equals method will be generated
714717
}
715718
TypeMirror parameterType = equals.getParameters().get(0).asType();
716-
return TypeEncoder.encodeWithAnnotations(parameterType);
719+
// Add @Nullable if we know one and the parameter doesn't already have one.
720+
// The @Nullable we add will be a type annotation, but if the parameter already has @Nullable
721+
// then that might be a type annotation or an annotation on the parameter.
722+
ImmutableList<AnnotationMirror> extraAnnotations =
723+
nullable.isPresent() && !nullableAnnotationFor(equals, parameterType).isPresent()
724+
? ImmutableList.of(annotationMirror(nullable.get()))
725+
: ImmutableList.of();
726+
return TypeEncoder.encodeWithAnnotations(parameterType, extraAnnotations, ImmutableSet.of());
727+
}
728+
729+
private static AnnotationMirror annotationMirror(DeclaredType annotationType) {
730+
return new AnnotationMirror() {
731+
@Override
732+
public DeclaredType getAnnotationType() {
733+
return annotationType;
734+
}
735+
736+
@Override
737+
public ImmutableMap<? extends ExecutableElement, ? extends AnnotationValue>
738+
getElementValues() {
739+
return ImmutableMap.of();
740+
}
741+
};
717742
}
718743

719744
/**
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2021 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 com.google.auto.value.processor;
17+
18+
import static java.util.stream.Collectors.toList;
19+
20+
import com.google.common.collect.ImmutableList;
21+
import java.util.Collection;
22+
import java.util.List;
23+
import java.util.Objects;
24+
import java.util.Optional;
25+
import java.util.stream.Stream;
26+
import javax.lang.model.element.AnnotationMirror;
27+
import javax.lang.model.element.Element;
28+
import javax.lang.model.element.ExecutableElement;
29+
import javax.lang.model.type.ArrayType;
30+
import javax.lang.model.type.DeclaredType;
31+
import javax.lang.model.type.IntersectionType;
32+
import javax.lang.model.type.TypeMirror;
33+
import javax.lang.model.type.TypeVariable;
34+
import javax.lang.model.type.WildcardType;
35+
import javax.lang.model.util.SimpleTypeVisitor8;
36+
37+
class Nullables {
38+
/**
39+
* Returns the type of a {@code @Nullable} type-annotation, if one is found anywhere in the
40+
* signatures of the given methods.
41+
*/
42+
static Optional<DeclaredType> nullableMentionedInMethods(Collection<ExecutableElement> methods) {
43+
return methods.stream()
44+
.flatMap(
45+
method ->
46+
Stream.concat(
47+
Stream.of(method.getReturnType()),
48+
method.getParameters().stream().map(Element::asType)))
49+
.map(Nullables::nullableIn)
50+
.filter(Optional::isPresent)
51+
.findFirst()
52+
.orElse(Optional.empty());
53+
}
54+
55+
private static Optional<DeclaredType> nullableIn(TypeMirror type) {
56+
return new NullableFinder().visit(type);
57+
}
58+
59+
private static Optional<DeclaredType> nullableIn(List<? extends AnnotationMirror> annotations) {
60+
return annotations.stream()
61+
.map(AnnotationMirror::getAnnotationType)
62+
.filter(t -> t.asElement().getSimpleName().contentEquals("Nullable"))
63+
.findFirst();
64+
}
65+
66+
private static class NullableFinder extends SimpleTypeVisitor8<Optional<DeclaredType>, Void> {
67+
private final TypeMirrorSet visiting = new TypeMirrorSet();
68+
69+
NullableFinder() {
70+
super(Optional.empty());
71+
}
72+
73+
// Primitives can't be @Nullable so we don't check that.
74+
75+
@Override
76+
public Optional<DeclaredType> visitDeclared(DeclaredType t, Void unused) {
77+
if (!visiting.add(t)) {
78+
return Optional.empty();
79+
}
80+
return nullableIn(t.getAnnotationMirrors())
81+
.map(Optional::of)
82+
.orElseGet(() -> visitAll(t.getTypeArguments()));
83+
}
84+
85+
@Override
86+
public Optional<DeclaredType> visitTypeVariable(TypeVariable t, Void unused) {
87+
if (!visiting.add(t)) {
88+
return Optional.empty();
89+
}
90+
return nullableIn(t.getAnnotationMirrors())
91+
.map(Optional::of)
92+
.orElseGet(() -> visitAll(ImmutableList.of(t.getUpperBound(), t.getLowerBound())));
93+
}
94+
95+
@Override
96+
public Optional<DeclaredType> visitArray(ArrayType t, Void unused) {
97+
return nullableIn(t.getAnnotationMirrors())
98+
.map(Optional::of)
99+
.orElseGet(() -> visit(t.getComponentType()));
100+
}
101+
102+
@Override
103+
public Optional<DeclaredType> visitWildcard(WildcardType t, Void unused) {
104+
return nullableIn(t.getAnnotationMirrors())
105+
.map(Optional::of)
106+
.orElseGet(
107+
() ->
108+
visitAll(
109+
Stream.of(t.getExtendsBound(), t.getSuperBound())
110+
.filter(Objects::nonNull)
111+
.collect(toList())));
112+
}
113+
114+
@Override
115+
public Optional<DeclaredType> visitIntersection(IntersectionType t, Void unused) {
116+
return nullableIn(t.getAnnotationMirrors())
117+
.map(Optional::of)
118+
.orElseGet(() -> visitAll(t.getBounds()));
119+
}
120+
121+
private Optional<DeclaredType> visitAll(List<? extends TypeMirror> types) {
122+
return types.stream()
123+
.map(this::visit)
124+
.filter(Optional::isPresent)
125+
.findFirst()
126+
.orElse(Optional.empty());
127+
}
128+
}
129+
130+
private Nullables() {}
131+
}

value/src/main/java/com/google/auto/value/processor/TypeEncoder.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
import com.google.auto.common.MoreElements;
2323
import com.google.auto.common.MoreTypes;
2424
import com.google.auto.value.processor.MissingTypes.MissingTypeException;
25+
import com.google.common.collect.ImmutableList;
2526
import com.google.common.collect.ImmutableSet;
2627
import java.util.List;
2728
import java.util.OptionalInt;
2829
import java.util.Set;
30+
import java.util.function.Function;
2931
import javax.annotation.processing.ProcessingEnvironment;
3032
import javax.lang.model.element.AnnotationMirror;
3133
import javax.lang.model.element.TypeElement;
@@ -97,20 +99,36 @@ static String encodeRaw(TypeMirror type) {
9799
* covers the details of annotation encoding.
98100
*/
99101
static String encodeWithAnnotations(TypeMirror type) {
100-
return encodeWithAnnotations(type, ImmutableSet.of());
102+
return encodeWithAnnotations(type, ImmutableList.of(), ImmutableSet.of());
101103
}
102104

103105
/**
104106
* Encodes the given type and its type annotations. The class comment for {@link TypeEncoder}
105107
* covers the details of annotation encoding.
106108
*
109+
* @param extraAnnotations additional type annotations to include with the type
107110
* @param excludedAnnotationTypes annotations not to include in the encoding. For example, if
108111
* {@code com.example.Nullable} is in this set then the encoding will not include this
109112
* {@code @Nullable} annotation.
110113
*/
111-
static String encodeWithAnnotations(TypeMirror type, Set<TypeMirror> excludedAnnotationTypes) {
114+
static String encodeWithAnnotations(
115+
TypeMirror type,
116+
ImmutableList<AnnotationMirror> extraAnnotations,
117+
Set<TypeMirror> excludedAnnotationTypes) {
112118
StringBuilder sb = new StringBuilder();
113-
return new AnnotatedEncodingTypeVisitor(excludedAnnotationTypes).visit2(type, sb).toString();
119+
// A function that is equivalent to t.getAnnotationMirrors() except when the t in question is
120+
// our starting type. In that case we also add extraAnnotations to the result.
121+
Function<TypeMirror, List<? extends AnnotationMirror>> getTypeAnnotations =
122+
t ->
123+
(t == type)
124+
? ImmutableList.<AnnotationMirror>builder()
125+
.addAll(t.getAnnotationMirrors())
126+
.addAll(extraAnnotations)
127+
.build()
128+
: t.getAnnotationMirrors();
129+
return new AnnotatedEncodingTypeVisitor(excludedAnnotationTypes, getTypeAnnotations)
130+
.visit2(type, sb)
131+
.toString();
114132
}
115133

116134
/**
@@ -308,9 +326,13 @@ void appendTypeArguments(DeclaredType type, StringBuilder sb) {}
308326
*/
309327
private static class AnnotatedEncodingTypeVisitor extends EncodingTypeVisitor {
310328
private final Set<TypeMirror> excludedAnnotationTypes;
329+
private final Function<TypeMirror, List<? extends AnnotationMirror>> getTypeAnnotations;
311330

312-
AnnotatedEncodingTypeVisitor(Set<TypeMirror> excludedAnnotationTypes) {
331+
AnnotatedEncodingTypeVisitor(
332+
Set<TypeMirror> excludedAnnotationTypes,
333+
Function<TypeMirror, List<? extends AnnotationMirror>> getTypeAnnotations) {
313334
this.excludedAnnotationTypes = excludedAnnotationTypes;
335+
this.getTypeAnnotations = getTypeAnnotations;
314336
}
315337

316338
private void appendAnnotationsWithExclusions(
@@ -330,15 +352,15 @@ private void appendAnnotationsWithExclusions(
330352

331353
@Override
332354
public StringBuilder visitPrimitive(PrimitiveType type, StringBuilder sb) {
333-
appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb);
355+
appendAnnotationsWithExclusions(getTypeAnnotations.apply(type), sb);
334356
// We can't just append type.toString(), because that will also have the annotation, but
335357
// without encoding.
336358
return sb.append(type.getKind().toString().toLowerCase());
337359
}
338360

339361
@Override
340362
public StringBuilder visitTypeVariable(TypeVariable type, StringBuilder sb) {
341-
appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb);
363+
appendAnnotationsWithExclusions(getTypeAnnotations.apply(type), sb);
342364
return sb.append(type.asElement().getSimpleName());
343365
}
344366

@@ -350,7 +372,7 @@ public StringBuilder visitTypeVariable(TypeVariable type, StringBuilder sb) {
350372
@Override
351373
public StringBuilder visitArray(ArrayType type, StringBuilder sb) {
352374
visit2(type.getComponentType(), sb);
353-
List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors();
375+
List<? extends AnnotationMirror> annotationMirrors = getTypeAnnotations.apply(type);
354376
if (!annotationMirrors.isEmpty()) {
355377
sb.append(" ");
356378
appendAnnotationsWithExclusions(annotationMirrors, sb);
@@ -360,7 +382,7 @@ public StringBuilder visitArray(ArrayType type, StringBuilder sb) {
360382

361383
@Override
362384
public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) {
363-
List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors();
385+
List<? extends AnnotationMirror> annotationMirrors = getTypeAnnotations.apply(type);
364386
if (annotationMirrors.isEmpty()) {
365387
super.visitDeclared(type, sb);
366388
} else {

0 commit comments

Comments
 (0)