Skip to content

Commit efd48fd

Browse files
eamonnmcmanusronshapiro
authored andcommitted
Improve the logic for checking setter parameter types against getter return types.
We translate the getter return types into the type context of the builder. For example, if the @autovalue class is Foo<T> then its builder must be Foo.Builder<T>. A getter like `abstract List<T> getList()` is compatible with a setter like `Builder setList(List<T> list)`, even though the <T> parameter in each case is different. So we convert `List<T-{Foo}> getList()` into `List<T{Foo.Builder}> getList()` before comparing types. RELNOTES=Better checking of types in setters against types in getters. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=238100041
1 parent 5beb73a commit efd48fd

File tree

9 files changed

+455
-90
lines changed

9 files changed

+455
-90
lines changed

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,4 +735,39 @@ public void testTypeAnnotationOnBuilderCopiedToImplementation() {
735735
.asList()
736736
.contains(nullable());
737737
}
738+
739+
// b/127701294
740+
@AutoValue
741+
abstract static class OptionalOptional {
742+
abstract Optional<Optional<String>> maybeJustMaybe();
743+
744+
static Builder builder() {
745+
return new AutoValue_AutoValueJava8Test_OptionalOptional.Builder();
746+
}
747+
748+
@AutoValue.Builder
749+
abstract static class Builder {
750+
abstract Builder maybeJustMaybe(Optional<String> maybe);
751+
abstract OptionalOptional build();
752+
}
753+
}
754+
755+
@Test
756+
public void testOptionalOptional_empty() {
757+
OptionalOptional empty = OptionalOptional.builder().build();
758+
assertThat(empty.maybeJustMaybe()).isEmpty();
759+
}
760+
761+
@Test
762+
public void testOptionalOptional_ofEmpty() {
763+
OptionalOptional ofEmpty = OptionalOptional.builder().maybeJustMaybe(Optional.empty()).build();
764+
assertThat(ofEmpty.maybeJustMaybe()).hasValue(Optional.empty());
765+
}
766+
767+
@Test
768+
public void testOptionalOptional_ofSomething() {
769+
OptionalOptional ofSomething =
770+
OptionalOptional.builder().maybeJustMaybe(Optional.of("foo")).build();
771+
assertThat(ofSomething.maybeJustMaybe()).hasValue(Optional.of("foo"));
772+
}
738773
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3048,4 +3048,27 @@ public void testRedeclareJavaLangClasses() {
30483048
.build();
30493049
assertThat(x).isNotNull();
30503050
}
3051+
3052+
// b/28382293
3053+
@AutoValue
3054+
abstract static class GenericExtends {
3055+
abstract ImmutableSet<Number> metrics();
3056+
3057+
static Builder builder() {
3058+
return new AutoValue_AutoValueTest_GenericExtends.Builder();
3059+
}
3060+
3061+
@AutoValue.Builder
3062+
abstract static class Builder {
3063+
abstract Builder setMetrics(ImmutableSet<? extends Number> metrics);
3064+
abstract GenericExtends build();
3065+
}
3066+
}
3067+
3068+
@Test
3069+
public void testGenericExtends() {
3070+
ImmutableSet<Integer> ints = ImmutableSet.of(1, 2, 3);
3071+
GenericExtends g = GenericExtends.builder().setMetrics(ints).build();
3072+
assertThat(g.metrics()).isEqualTo(ints);
3073+
}
30513074
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,6 @@ private void defineVarsForType(
415415
// We can't use ImmutableList.toImmutableList() for obscure Google-internal reasons.
416416
vars.toBuilderMethods =
417417
ImmutableList.copyOf(toBuilderMethods.stream().map(SimpleMethod::new).collect(toList()));
418-
ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
419-
propertyNameToMethodMap(propertyMethods).inverse();
420418
ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields =
421419
propertyFieldAnnotationMap(type, propertyMethods);
422420
ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods =
@@ -426,6 +424,8 @@ private void defineVarsForType(
426424
vars.serialVersionUID = getSerialVersionUID(type);
427425
// Check for @AutoValue.Builder and add appropriate variables if it is present.
428426
if (builder.isPresent()) {
427+
ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
428+
propertyNameToMethodMap(propertyMethods).inverse();
429429
builder.get().defineVars(vars, methodToPropertyName);
430430
}
431431
}

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

Lines changed: 96 additions & 63 deletions
Large diffs are not rendered by default.

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

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import com.google.common.collect.Iterables;
3131
import java.util.LinkedHashSet;
3232
import java.util.List;
33-
import java.util.Map;
3433
import java.util.Optional;
3534
import java.util.Set;
3635
import javax.annotation.processing.ProcessingEnvironment;
@@ -42,6 +41,7 @@
4241
import javax.lang.model.element.TypeParameterElement;
4342
import javax.lang.model.element.VariableElement;
4443
import javax.lang.model.type.DeclaredType;
44+
import javax.lang.model.type.ExecutableType;
4545
import javax.lang.model.type.TypeKind;
4646
import javax.lang.model.type.TypeMirror;
4747
import javax.lang.model.util.ElementFilter;
@@ -173,6 +173,13 @@ void defineVars(
173173
ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
174174
Iterable<ExecutableElement> builderMethods = abstractMethods(builderTypeElement);
175175
boolean autoValueHasToBuilder = !toBuilderMethods.isEmpty();
176+
ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType =
177+
TypeVariables.rewriteReturnTypes(
178+
processingEnv.getElementUtils(),
179+
processingEnv.getTypeUtils(),
180+
getterToPropertyName.keySet(),
181+
autoValueClass,
182+
builderTypeElement);
176183
Optional<BuilderMethodClassifier> optionalClassifier =
177184
BuilderMethodClassifier.classify(
178185
builderMethods,
@@ -181,6 +188,7 @@ void defineVars(
181188
autoValueClass,
182189
builderTypeElement,
183190
getterToPropertyName,
191+
getterToPropertyType,
184192
autoValueHasToBuilder);
185193
if (!optionalClassifier.isPresent()) {
186194
return;
@@ -207,15 +215,21 @@ void defineVars(
207215
vars.buildMethod = Optional.of(new SimpleMethod(buildMethod));
208216
vars.builderGetters = classifier.builderGetters();
209217

218+
DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderTypeElement.asType());
210219
ImmutableMultimap.Builder<String, PropertySetter> setterBuilder = ImmutableMultimap.builder();
211-
for (Map.Entry<String, ExecutableElement> entry :
212-
classifier.propertyNameToSetters().entries()) {
213-
String property = entry.getKey();
214-
ExecutableElement setter = entry.getValue();
215-
TypeMirror propertyType = getterToPropertyName.inverse().get(property).getReturnType();
216-
setterBuilder.put(
217-
property, new PropertySetter(setter, propertyType, processingEnv.getTypeUtils()));
218-
}
220+
classifier.propertyNameToSetters().forEach(
221+
(property, setter) -> {
222+
ExecutableElement getter = getterToPropertyName.inverse().get(property);
223+
// Get the effective parameter type, which might need asMemberOf in cases where the method
224+
// is inherited from a generic ancestor type and references type parameters.
225+
ExecutableType setterMirror = MoreTypes.asExecutable(
226+
processingEnv.getTypeUtils().asMemberOf(builderTypeMirror, setter));
227+
TypeMirror parameterType = Iterables.getOnlyElement(setterMirror.getParameterTypes());
228+
TypeMirror propertyType = getterToPropertyType.get(getter);
229+
PropertySetter propertySetter = new PropertySetter(
230+
setter, parameterType, propertyType, processingEnv.getTypeUtils());
231+
setterBuilder.put(property, propertySetter);
232+
});
219233
vars.builderSetters = setterBuilder.build();
220234

221235
vars.builderPropertyBuilders =
@@ -297,13 +311,16 @@ public static class PropertySetter {
297311
private final String nullableAnnotation;
298312
private final String copyOf;
299313

300-
PropertySetter(ExecutableElement setter, TypeMirror propertyType, Types typeUtils) {
314+
PropertySetter(
315+
ExecutableElement setter,
316+
TypeMirror parameterType,
317+
TypeMirror propertyType,
318+
Types typeUtils) {
301319
this.access = SimpleMethod.access(setter);
302320
this.name = setter.getSimpleName().toString();
303-
VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters());
304-
TypeMirror parameterType = parameterElement.asType();
305321
primitiveParameter = parameterType.getKind().isPrimitive();
306322
this.parameterTypeString = parameterTypeString(setter, parameterType);
323+
VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters());
307324
Optional<String> maybeNullable =
308325
AutoValueOrOneOfProcessor.nullableAnnotationFor(parameterElement, parameterType);
309326
this.nullableAnnotation = maybeNullable.orElse("");
@@ -325,13 +342,13 @@ private static String parameterTypeString(ExecutableElement setter, TypeMirror p
325342
}
326343
}
327344

328-
private static String copyOfString(
345+
private String copyOfString(
329346
TypeMirror propertyType, TypeMirror parameterType, Types typeUtils, boolean nullable) {
330-
TypeMirror erasedPropertyType = typeUtils.erasure(propertyType);
331-
boolean sameType = typeUtils.isSameType(typeUtils.erasure(parameterType), erasedPropertyType);
347+
boolean sameType = typeUtils.isAssignable(parameterType, propertyType);
332348
if (sameType) {
333349
return null;
334350
}
351+
TypeMirror erasedPropertyType = typeUtils.erasure(propertyType);
335352
String rawTarget = TypeEncoder.encodeRaw(erasedPropertyType);
336353
String of;
337354
Optionalish optionalish = Optionalish.createIfOptional(propertyType);

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,19 @@
4040
* @author Éamonn McManus
4141
*/
4242
class EclipseHack {
43-
private final ProcessingEnvironment processingEnv;
43+
private final Elements elementUtils;
44+
private final Types typeUtils;
4445

4546
EclipseHack(ProcessingEnvironment processingEnv) {
46-
this.processingEnv = processingEnv;
47+
this(processingEnv.getElementUtils(), processingEnv.getTypeUtils());
48+
}
49+
50+
EclipseHack(Elements elementUtils, Types typeUtils) {
51+
this.elementUtils = elementUtils;
52+
this.typeUtils = typeUtils;
4753
}
4854

4955
TypeMirror methodReturnType(ExecutableElement method, DeclaredType in) {
50-
Types typeUtils = processingEnv.getTypeUtils();
5156
try {
5257
TypeMirror methodMirror = typeUtils.asMemberOf(in, method);
5358
return MoreTypes.asExecutable(methodMirror).getReturnType();
@@ -71,7 +76,6 @@ TypeMirror methodReturnType(ExecutableElement method, DeclaredType in) {
7176
*/
7277
ImmutableMap<ExecutableElement, TypeMirror> methodReturnTypes(
7378
Set<ExecutableElement> methods, DeclaredType in) {
74-
Types typeUtils = processingEnv.getTypeUtils();
7579
ImmutableMap.Builder<ExecutableElement, TypeMirror> map = ImmutableMap.builder();
7680
Map<Name, ExecutableElement> noArgMethods = null;
7781
for (ExecutableElement method : methods) {
@@ -102,8 +106,6 @@ ImmutableMap<ExecutableElement, TypeMirror> methodReturnTypes(
102106
* does in Eclipse.
103107
*/
104108
private Map<Name, ExecutableElement> noArgMethodsIn(DeclaredType in) {
105-
Types typeUtils = processingEnv.getTypeUtils();
106-
Elements elementUtils = processingEnv.getElementUtils();
107109
TypeElement autoValueType = MoreElements.asType(typeUtils.asElement(in));
108110
List<ExecutableElement> allMethods =
109111
ElementFilter.methodsIn(elementUtils.getAllMembers(autoValueType));

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@
2323
/**
2424
* Handle error reporting for an annotation processor.
2525
*
26-
* @see AutoValue
2726
* @author Éamonn McManus
2827
*/
2928
class ErrorReporter {
3029
private final Messager messager;
31-
private boolean anyErrors;
30+
private int errorCount;
3231

3332
ErrorReporter(ProcessingEnvironment processingEnv) {
3433
this.messager = processingEnv.getMessager();
@@ -65,7 +64,7 @@ void reportWarning(String msg, Element e) {
6564
*/
6665
void reportError(String msg, Element e) {
6766
messager.printMessage(Diagnostic.Kind.ERROR, msg, e);
68-
anyErrors = true;
67+
errorCount++;
6968
}
7069

7170
/**
@@ -80,9 +79,14 @@ void abortWithError(String msg, Element e) {
8079
throw new AbortProcessingException();
8180
}
8281

82+
/** The number of errors that have been output by calls to {@link #reportError}. */
83+
int errorCount() {
84+
return errorCount;
85+
}
86+
8387
/** Abandon the processing of this class if any errors have been output. */
8488
void abortIfAnyError() {
85-
if (anyErrors) {
89+
if (errorCount > 0) {
8690
throw new AbortProcessingException();
8791
}
8892
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (C) 2019 Google, Inc.
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 com.google.common.base.Preconditions;
19+
import com.google.common.collect.ImmutableMap;
20+
import java.util.Collection;
21+
import java.util.List;
22+
import javax.lang.model.element.ExecutableElement;
23+
import javax.lang.model.element.TypeElement;
24+
import javax.lang.model.element.TypeParameterElement;
25+
import javax.lang.model.type.DeclaredType;
26+
import javax.lang.model.type.TypeMirror;
27+
import javax.lang.model.util.Elements;
28+
import javax.lang.model.util.Types;
29+
30+
/**
31+
* Methods for handling type variables.
32+
*/
33+
final class TypeVariables {
34+
private TypeVariables() {}
35+
36+
/**
37+
* Returns a map from methods to return types, where the return types are not necessarily the
38+
* original return types of the methods. Consider this example:
39+
*
40+
* <pre>
41+
* &#64;AutoValue class {@code Foo<T>} {
42+
* abstract T getFoo();
43+
*
44+
* &#64;AutoValue.Builder
45+
* abstract class {@code Builder<T>} {
46+
* abstract Builder setFoo(T t);
47+
* abstract {@code Foo<T>} build();
48+
* }
49+
* }
50+
* </pre>
51+
*
52+
* We want to be able to check that the parameter type of {@code setFoo} is the same as the
53+
* return type of {@code getFoo}. But in fact it isn't, because the {@code T} of {@code Foo<T>}
54+
* is not the same as the {@code T} of {@code Foo.Builder<T>}. So we create a parallel
55+
* {@code Foo<T>} where the {@code T} <i>is</i> the one from {@code Foo.Builder<T>}. That way the
56+
* types do correspond. This method then returns the return types of the given methods as they
57+
* appear in that parallel class, meaning the type given for {@code getFoo()} is the {@code T} of
58+
* {@code Foo.Builder<T>}.
59+
*
60+
* <p>We do the rewrite this way around (applying the type parameter from {@code Foo.Builder} to
61+
* {@code Foo}) because if we hit one of the historical Eclipse bugs with {@link Types#asMemberOf}
62+
* then {@link EclipseHack#methodReturnType} can use fallback logic, which only works for methods
63+
* with no arguments.
64+
*
65+
* @param methods the methods whose return types are to be rewritten.
66+
* @param sourceType the class containing those methods ({@code Foo} in the example).
67+
* @param targetType the class to translate the methods into ({@code Foo.Builder<T>}) in the
68+
* example.
69+
*/
70+
static ImmutableMap<ExecutableElement, TypeMirror> rewriteReturnTypes(
71+
Elements elementUtils,
72+
Types typeUtils,
73+
Collection<ExecutableElement> methods,
74+
TypeElement sourceType,
75+
TypeElement targetType) {
76+
List<? extends TypeParameterElement> sourceTypeParameters = sourceType.getTypeParameters();
77+
List<? extends TypeParameterElement> targetTypeParameters = targetType.getTypeParameters();
78+
Preconditions.checkArgument(
79+
sourceTypeParameters.toString().equals(targetTypeParameters.toString()),
80+
"%s != %s",
81+
sourceTypeParameters,
82+
targetTypeParameters);
83+
// What we're doing is only valid if the type parameters are "the same". The check here even
84+
// requires the names to be the same. The logic would still work without that, but we impose
85+
// that requirement elsewhere and it means we can check in this simple way.
86+
87+
if (sourceTypeParameters.isEmpty()) {
88+
return methods.stream()
89+
.collect(ImmutableMap.toImmutableMap(m -> m, ExecutableElement::getReturnType));
90+
}
91+
EclipseHack eclipseHack = new EclipseHack(elementUtils, typeUtils);
92+
TypeMirror[] targetTypeParameterMirrors = new TypeMirror[targetTypeParameters.size()];
93+
for (int i = 0; i < targetTypeParameters.size(); i++) {
94+
targetTypeParameterMirrors[i] = targetTypeParameters.get(i).asType();
95+
}
96+
DeclaredType parallelSource = typeUtils.getDeclaredType(sourceType, targetTypeParameterMirrors);
97+
return methods.stream()
98+
.collect(
99+
ImmutableMap.toImmutableMap(
100+
m -> m, m -> eclipseHack.methodReturnType(m, parallelSource)));
101+
}
102+
}

0 commit comments

Comments
 (0)