Skip to content

Commit 9057ae8

Browse files
octylFractalronshapiro
authored andcommitted
Allow an Optional property to be set in a builder through a method with a @nullable parameter.
This is based on #353 by Kenzie Togami. Closes #353. RELNOTES: Allow an Optional property to be set in a builder through a method with a @nullable parameter. NOTE: As a side-effect of this change, a setter for a @nullable property must have its parameter also marked @nullable. Previously, if the @nullable was not a TYPE_USE @nullable, it was copied from the getter to the parameter of the generated setter implementation. For TYPE_USE @nullable it was already necessary to mark the setter parameter @nullable. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=195471227
1 parent 55407e1 commit 9057ae8

File tree

9 files changed

+186
-50
lines changed

9 files changed

+186
-50
lines changed

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

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.google.auto.value;
1717

1818
import static com.google.common.truth.Truth.assertThat;
19+
import static com.google.common.truth.Truth8.assertThat;
1920
import static com.google.testing.compile.CompilationSubject.assertThat;
2021
import static org.junit.Assert.fail;
2122
import static org.junit.Assume.assumeTrue;
@@ -37,6 +38,7 @@
3738
import java.lang.reflect.TypeVariable;
3839
import java.util.Arrays;
3940
import java.util.List;
41+
import java.util.Optional;
4042
import java.util.Set;
4143
import javax.annotation.processing.AbstractProcessor;
4244
import javax.annotation.processing.RoundEnvironment;
@@ -368,6 +370,54 @@ public void testOmitNullableWithBuilder() {
368370
}
369371
}
370372

373+
@AutoValue
374+
public abstract static class OptionalPropertyWithNullableBuilder {
375+
public abstract String notOptional();
376+
377+
public abstract Optional<String> optional();
378+
379+
public static Builder builder() {
380+
return new AutoValue_AutoValueJava8Test_OptionalPropertyWithNullableBuilder.Builder();
381+
}
382+
383+
@AutoValue.Builder
384+
public interface Builder {
385+
Builder notOptional(String s);
386+
387+
Builder optional(@Nullable String s);
388+
389+
OptionalPropertyWithNullableBuilder build();
390+
}
391+
}
392+
393+
@Test
394+
public void testOmitOptionalWithNullableBuilder() {
395+
OptionalPropertyWithNullableBuilder instance1 =
396+
OptionalPropertyWithNullableBuilder.builder().notOptional("hello").build();
397+
assertThat(instance1.notOptional()).isEqualTo("hello");
398+
assertThat(instance1.optional()).isEmpty();
399+
400+
OptionalPropertyWithNullableBuilder instance2 =
401+
OptionalPropertyWithNullableBuilder.builder().notOptional("hello").optional(null).build();
402+
assertThat(instance2.notOptional()).isEqualTo("hello");
403+
assertThat(instance2.optional()).isEmpty();
404+
assertThat(instance1).isEqualTo(instance2);
405+
406+
OptionalPropertyWithNullableBuilder instance3 =
407+
OptionalPropertyWithNullableBuilder.builder()
408+
.notOptional("hello")
409+
.optional("world")
410+
.build();
411+
assertThat(instance3.notOptional()).isEqualTo("hello");
412+
assertThat(instance3.optional()).hasValue("world");
413+
414+
try {
415+
OptionalPropertyWithNullableBuilder.builder().build();
416+
fail("Expected IllegalStateException for unset non-Optional property");
417+
} catch (IllegalStateException expected) {
418+
}
419+
}
420+
371421
@AutoValue
372422
public abstract static class BuilderWithUnprefixedGetters<T extends Comparable<T>> {
373423
public abstract ImmutableList<T> list();
@@ -497,20 +547,23 @@ abstract static class FunkyNullable {
497547

498548
abstract @Nullable String foo();
499549

550+
abstract Optional<String> bar();
551+
500552
static Builder builder() {
501553
return new AutoValue_AutoValueJava8Test_FunkyNullable.Builder();
502554
}
503555

504556
@AutoValue.Builder
505557
interface Builder {
506558
Builder setFoo(@Nullable String foo);
559+
Builder setBar(@Nullable String bar);
507560
FunkyNullable build();
508561
}
509562
}
510563

511564
@Test
512565
public void testFunkyNullable() {
513-
FunkyNullable explicitNull = FunkyNullable.builder().setFoo(null).build();
566+
FunkyNullable explicitNull = FunkyNullable.builder().setFoo(null).setBar(null).build();
514567
FunkyNullable implicitNull = FunkyNullable.builder().build();
515568
assertThat(explicitNull).isEqualTo(implicitNull);
516569
}

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,6 +1511,54 @@ public void testOmitOptionalWithBuilder() {
15111511
assertThat(suppliedDirectly.optionalInteger()).hasValue(23);
15121512
}
15131513

1514+
@AutoValue
1515+
public abstract static class OptionalPropertyWithNullableBuilder {
1516+
public abstract String notOptional();
1517+
1518+
public abstract com.google.common.base.Optional<String> optional();
1519+
1520+
public static Builder builder() {
1521+
return new AutoValue_AutoValueTest_OptionalPropertyWithNullableBuilder.Builder();
1522+
}
1523+
1524+
@AutoValue.Builder
1525+
public interface Builder {
1526+
Builder notOptional(String s);
1527+
1528+
Builder optional(@Nullable String s);
1529+
1530+
OptionalPropertyWithNullableBuilder build();
1531+
}
1532+
}
1533+
1534+
@Test
1535+
public void testOmitOptionalWithNullableBuilder() {
1536+
OptionalPropertyWithNullableBuilder instance1 =
1537+
OptionalPropertyWithNullableBuilder.builder().notOptional("hello").build();
1538+
assertThat(instance1.notOptional()).isEqualTo("hello");
1539+
assertThat(instance1.optional()).isAbsent();
1540+
1541+
OptionalPropertyWithNullableBuilder instance2 =
1542+
OptionalPropertyWithNullableBuilder.builder().notOptional("hello").optional(null).build();
1543+
assertThat(instance2.notOptional()).isEqualTo("hello");
1544+
assertThat(instance2.optional()).isAbsent();
1545+
assertThat(instance1).isEqualTo(instance2);
1546+
1547+
OptionalPropertyWithNullableBuilder instance3 =
1548+
OptionalPropertyWithNullableBuilder.builder()
1549+
.notOptional("hello")
1550+
.optional("world")
1551+
.build();
1552+
assertThat(instance3.notOptional()).isEqualTo("hello");
1553+
assertThat(instance3.optional()).hasValue("world");
1554+
1555+
try {
1556+
OptionalPropertyWithNullableBuilder.builder().build();
1557+
fail("Expected IllegalStateException for unset non-Optional property");
1558+
} catch (IllegalStateException expected) {
1559+
}
1560+
}
1561+
15141562
@AutoValue
15151563
public abstract static class NullableOptionalPropertiesWithBuilder {
15161564
@Nullable

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import com.google.auto.common.MoreTypes;
2626
import com.google.auto.service.AutoService;
2727
import com.google.common.collect.ImmutableBiMap;
28-
import com.google.common.collect.ImmutableList;
2928
import com.google.common.collect.ImmutableListMultimap;
3029
import com.google.common.collect.ImmutableMap;
3130
import com.google.common.collect.ImmutableSet;
@@ -246,8 +245,7 @@ private void defineVarsForType(
246245
}
247246

248247
@Override
249-
Optional<String> nullableAnnotationForMethod(
250-
ExecutableElement propertyMethod, ImmutableList<AnnotationMirror> methodAnnotations) {
248+
Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) {
251249
return Optional.empty();
252250
}
253251

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

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ public Optionalish getOptional() {
222222
* but empty.
223223
*/
224224
public final String getNullableAnnotation() {
225-
return nullableAnnotation.map(s -> s + " ").orElse("");
225+
return nullableAnnotation.orElse("");
226226
}
227227

228228
public boolean isNullable() {
@@ -318,12 +318,8 @@ public final boolean process(Set<? extends TypeElement> annotations, RoundEnviro
318318
* {@code @Nullable}, this method returns {@code Optional.of("")}, to indicate that the property
319319
* is nullable but the <i>method</i> isn't. The {@code @Nullable} annotation will instead appear
320320
* when the return type of the method is spelled out in the implementation.
321-
*
322-
* @param methodAnnotations the annotations to include on {@code propertyMethod}. This is
323-
* governed by the {@code AutoValue.CopyAnnotations} logic.
324321
*/
325-
abstract Optional<String> nullableAnnotationForMethod(
326-
ExecutableElement propertyMethod, ImmutableList<AnnotationMirror> methodAnnotations);
322+
abstract Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod);
327323

328324
/**
329325
* Returns the ordered set of {@link Property} definitions for the given {@code @AutoValue} or
@@ -357,8 +353,7 @@ final ImmutableSet<Property> propertySet(
357353
ImmutableList<AnnotationMirror> annotationMirrors =
358354
annotatedPropertyMethods.get(propertyMethod);
359355
ImmutableList<String> annotations = annotationStrings(annotationMirrors);
360-
Optional<String> nullableAnnotation =
361-
nullableAnnotationForMethod(propertyMethod, annotationMirrors);
356+
Optional<String> nullableAnnotation = nullableAnnotationForMethod(propertyMethod);
362357
Property p = new Property(
363358
propertyName, identifier, propertyMethod, propertyType, annotations, nullableAnnotation);
364359
props.add(p);
@@ -394,8 +389,7 @@ final void defineSharedVarsForType(
394389
}
395390

396391
/** Returns the spelling to be used in the generated code for the given list of annotations. */
397-
static ImmutableList<String> annotationStrings(
398-
ImmutableList<AnnotationMirror> annotations) {
392+
static ImmutableList<String> annotationStrings(List<? extends AnnotationMirror> annotations) {
399393
// TODO(b/68008628): use ImmutableList.toImmutableList() when that works.
400394
return ImmutableList.copyOf(annotations
401395
.stream()

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

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import javax.annotation.processing.SupportedAnnotationTypes;
4646
import javax.annotation.processing.SupportedOptions;
4747
import javax.lang.model.element.AnnotationMirror;
48+
import javax.lang.model.element.Element;
4849
import javax.lang.model.element.ElementKind;
4950
import javax.lang.model.element.ExecutableElement;
5051
import javax.lang.model.element.TypeElement;
@@ -369,19 +370,35 @@ private void defineVarsForType(
369370
}
370371

371372
@Override
372-
Optional<String> nullableAnnotationForMethod(
373-
ExecutableElement propertyMethod, ImmutableList<AnnotationMirror> methodAnnotations) {
374-
OptionalInt nullableAnnotationIndex = nullableAnnotationIndex(methodAnnotations);
373+
Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) {
374+
return nullableAnnotationFor(propertyMethod, propertyMethod.getReturnType());
375+
}
376+
377+
/**
378+
* Returns an appropriate annotation spelling to indicate the nullability of an element. If the
379+
* return value is a non-empty Optional, that indicates that the element is nullable, and the
380+
* string should be used to annotate it. If the return value is an empty Optional, the element
381+
* is not nullable. The return value can be {@code Optional.of("")}, which indicates that the
382+
* element is nullable but that the nullability comes from a type annotation. In this case, the
383+
* annotation will appear when the type is written, and must not be specified again. If the
384+
* Optional contains a present non-empty string then that string will end with a space.
385+
*
386+
* @param element the element that might be {@code @Nullable}, either a method or a parameter.
387+
* @param elementType the relevant type of the element: the return type for a method, or the
388+
* parameter type for a parameter.
389+
*/
390+
static Optional<String> nullableAnnotationFor(Element element, TypeMirror elementType) {
391+
List<? extends AnnotationMirror> typeAnnotations = elementType.getAnnotationMirrors();
392+
if (nullableAnnotationIndex(typeAnnotations).isPresent()) {
393+
return Optional.of("");
394+
}
395+
List<? extends AnnotationMirror> elementAnnotations = element.getAnnotationMirrors();
396+
OptionalInt nullableAnnotationIndex = nullableAnnotationIndex(elementAnnotations);
375397
if (nullableAnnotationIndex.isPresent()) {
376-
ImmutableList<String> annotations = annotationStrings(methodAnnotations);
377-
return Optional.of(annotations.get(nullableAnnotationIndex.getAsInt()));
398+
ImmutableList<String> annotations = annotationStrings(elementAnnotations);
399+
return Optional.of(annotations.get(nullableAnnotationIndex.getAsInt()) + " ");
378400
} else {
379-
List<? extends AnnotationMirror> typeAnnotations =
380-
propertyMethod.getReturnType().getAnnotationMirrors();
381-
return
382-
nullableAnnotationIndex(typeAnnotations).isPresent()
383-
? Optional.of("")
384-
: Optional.empty();
401+
return Optional.empty();
385402
}
386403
}
387404

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

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import javax.lang.model.element.Modifier;
4141
import javax.lang.model.element.TypeElement;
4242
import javax.lang.model.element.TypeParameterElement;
43+
import javax.lang.model.element.VariableElement;
4344
import javax.lang.model.type.DeclaredType;
4445
import javax.lang.model.type.TypeKind;
4546
import javax.lang.model.type.TypeMirror;
@@ -207,7 +208,8 @@ void defineVars(
207208
String property = entry.getKey();
208209
ExecutableElement setter = entry.getValue();
209210
TypeMirror propertyType = getterToPropertyName.inverse().get(property).getReturnType();
210-
setterBuilder.put(property, new PropertySetter(setter, propertyType));
211+
setterBuilder.put(
212+
property, new PropertySetter(setter, propertyType, processingEnv.getTypeUtils()));
211213
}
212214
vars.builderSetters = setterBuilder.build();
213215

@@ -282,32 +284,29 @@ public Optionalish getOptional() {
282284
* method {@code setFoo(Collection<String> foos)}. And, if {@code T} is {@code Optional},
283285
* it can have a setter with a type that can be copied to {@code T} through {@code Optional.of}.
284286
*/
285-
public class PropertySetter {
287+
public static class PropertySetter {
286288
private final String access;
287289
private final String name;
288290
private final String parameterTypeString;
289291
private final boolean primitiveParameter;
292+
private final String nullableAnnotation;
290293
private final String copyOf;
291294

292-
public PropertySetter(ExecutableElement setter, TypeMirror propertyType) {
295+
PropertySetter(ExecutableElement setter, TypeMirror propertyType, Types typeUtils) {
293296
this.access = SimpleMethod.access(setter);
294297
this.name = setter.getSimpleName().toString();
295-
TypeMirror parameterType = Iterables.getOnlyElement(setter.getParameters()).asType();
298+
VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters());
299+
TypeMirror parameterType = parameterElement.asType();
296300
primitiveParameter = parameterType.getKind().isPrimitive();
297301
this.parameterTypeString = parameterTypeString(setter, parameterType);
298-
Types typeUtils = processingEnv.getTypeUtils();
299-
TypeMirror erasedPropertyType = typeUtils.erasure(propertyType);
300-
boolean sameType = typeUtils.isSameType(typeUtils.erasure(parameterType), erasedPropertyType);
301-
if (sameType) {
302-
this.copyOf = null;
303-
} else {
304-
String rawTarget = TypeEncoder.encodeRaw(erasedPropertyType);
305-
String of = Optionalish.isOptional(propertyType) ? "of" : "copyOf";
306-
this.copyOf = rawTarget + "." + of + "(%s)";
307-
}
302+
Optional<String> maybeNullable =
303+
AutoValueProcessor.nullableAnnotationFor(parameterElement, parameterType);
304+
this.nullableAnnotation = maybeNullable.orElse("");
305+
boolean nullable = maybeNullable.isPresent();
306+
this.copyOf = copyOfString(propertyType, parameterType, typeUtils, nullable);
308307
}
309308

310-
private String parameterTypeString(ExecutableElement setter, TypeMirror parameterType) {
309+
private static String parameterTypeString(ExecutableElement setter, TypeMirror parameterType) {
311310
if (setter.isVarArgs()) {
312311
TypeMirror componentType = MoreTypes.asArray(parameterType).getComponentType();
313312
// This is a bit ugly. It's OK to annotate just the component type, because if it is
@@ -321,6 +320,26 @@ private String parameterTypeString(ExecutableElement setter, TypeMirror paramete
321320
}
322321
}
323322

323+
private static String copyOfString(
324+
TypeMirror propertyType, TypeMirror parameterType, Types typeUtils, boolean nullable) {
325+
TypeMirror erasedPropertyType = typeUtils.erasure(propertyType);
326+
boolean sameType = typeUtils.isSameType(typeUtils.erasure(parameterType), erasedPropertyType);
327+
if (sameType) {
328+
return null;
329+
}
330+
String rawTarget = TypeEncoder.encodeRaw(erasedPropertyType);
331+
String of;
332+
Optionalish optionalish = Optionalish.createIfOptional(propertyType);
333+
if (optionalish == null) {
334+
of = "copyOf";
335+
} else if (nullable) {
336+
of = optionalish.ofNullable();
337+
} else {
338+
of = "of";
339+
}
340+
return rawTarget + "." + of + "(%s)";
341+
}
342+
324343
public String getAccess() {
325344
return access;
326345
}
@@ -337,6 +356,10 @@ public boolean getPrimitiveParameter() {
337356
return primitiveParameter;
338357
}
339358

359+
public String getNullableAnnotation() {
360+
return nullableAnnotation;
361+
}
362+
340363
public String copy(AutoValueProcessor.Property property) {
341364
if (copyOf == null) {
342365
return property.toString();

0 commit comments

Comments
 (0)