aboutsummaryrefslogtreecommitdiff
path: root/value/src
diff options
context:
space:
mode:
authorÉamonn McManus <emcmanus@google.com>2020-11-16 10:46:55 -0800
committerGoogle Java Core Libraries <java-core-libraries-team+copybara@google.com>2020-11-16 10:47:30 -0800
commitf2cb2247898dcdcb05d52f1faf9689ecef4c0c41 (patch)
treea614486b8af8a1bb88281887c2f84b98fa6e2f08 /value/src
parent97863af8fda6e87250dae9e477f98de62d1ff28b (diff)
downloadauto-f2cb2247898dcdcb05d52f1faf9689ecef4c0c41.tar.gz
The methods returned by `BuilderContext.buildMethod()` and `.toBuilderMethods()` can be inherited.
This change addresses cases like this: ```java interface Parent<BuilderT> { BuilderT toBuilder(); interface Builder<BuilderT, BuiltT> {", BuilderT setThing(String x);", BuiltT build();", } } @AutoValue abstract class Foo implements Parent<Foo.Builder> { @AutoValue.Builder abstract static class Builder implements Parent.Builder<Builder, Foo> {} } ``` Previously, there would be two problems with this. First, inheriting `toBuilder()` like this didn't work at all, because its return type is `BuilderT` and not `Foo.Builder`. Now we correctly interpret the inherited return type in the context of `Foo`. Second, in `BuilderContext.buildMethod()` we were looking for methods returning `Foo`, and again this failed because the inherited `build()` method returns `BuiltT`. The fix in both cases is to use `Types.asMemberOf` to determine the correct return type in context. Fixes https://github.com/google/auto/issues/933. RELNOTES=The methods returned by `BuilderContext.buildMethod()` and `.toBuilderMethods()` can be inherited. PiperOrigin-RevId: 342670816
Diffstat (limited to 'value/src')
-rw-r--r--value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java31
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java6
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderSpec.java34
-rw-r--r--value/src/test/java/com/google/auto/value/processor/ExtensionTest.java72
4 files changed, 133 insertions, 10 deletions
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
index 346bc53a..88e2233c 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
@@ -1874,6 +1874,37 @@ public class AutoValueTest {
assertEquals((Integer) 17, instance3.u());
}
+ public interface ToBuilder<BuilderT> {
+ BuilderT toBuilder();
+ }
+
+ @AutoValue
+ public abstract static class InheritedToBuilder<T, U>
+ implements ToBuilder<InheritedToBuilder.Builder<T, U>> {
+
+ public abstract T t();
+ public abstract U u();
+
+ public static <T, U> Builder<T, U> builder() {
+ return new AutoValue_AutoValueTest_InheritedToBuilder.Builder<T, U>();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder<T, U> {
+ public abstract Builder<T, U> setT(T t);
+ public abstract Builder<T, U> setU(U u);
+ public abstract InheritedToBuilder<T, U> build();
+ }
+ }
+
+ @Test
+ public void testInheritedToBuilder() {
+ InheritedToBuilder<Integer, String> x =
+ InheritedToBuilder.<Integer, String>builder().setT(17).setU("wibble").build();
+ InheritedToBuilder<Integer, String> y = x.toBuilder().setT(23).build();
+ assertThat(y.u()).isEqualTo("wibble");
+ }
+
@AutoValue
public abstract static class BuilderWithSet<T extends Comparable<T>> {
public abstract List<T> list();
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
index 51387b2c..326b22a7 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
@@ -19,6 +19,7 @@ import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.intersection;
+import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
@@ -36,7 +37,6 @@ import com.google.common.collect.Iterables;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
@@ -136,7 +136,7 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor {
AutoValueExtension.IncrementalExtensionType incrementalType =
extensions.stream()
.map(e -> e.incrementalType(processingEnv))
- .min(Comparator.naturalOrder())
+ .min(naturalOrder())
.orElse(AutoValueExtension.IncrementalExtensionType.ISOLATING);
builder.add(OMIT_IDENTIFIERS_OPTION).addAll(optionsFor(incrementalType));
for (AutoValueExtension extension : extensions) {
@@ -207,7 +207,7 @@ public class AutoValueProcessor extends AutoValueOrOneOfProcessor {
Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder();
ImmutableSet<ExecutableElement> toBuilderMethods;
if (builder.isPresent()) {
- toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), abstractMethods);
+ toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), type, abstractMethods);
} else {
toBuilderMethods = ImmutableSet.of();
}
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
index 7e5b17c9..34095202 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
@@ -25,6 +25,7 @@ import static java.util.stream.Collectors.toSet;
import static javax.lang.model.util.ElementFilter.methodsIn;
import static javax.lang.model.util.ElementFilter.typesIn;
+import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.value.extension.AutoValueExtension;
import com.google.auto.value.processor.AutoValueOrOneOfProcessor.Property;
@@ -49,6 +50,7 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
@@ -142,14 +144,23 @@ class BuilderSpec {
@Override
public Optional<ExecutableElement> buildMethod() {
- return methodsIn(builderTypeElement.getEnclosedElements()).stream()
+ Types typeUtils = processingEnv.getTypeUtils();
+ DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderTypeElement.asType());
+ return MoreElements.getLocalAndInheritedMethods(
+ builderTypeElement, typeUtils, processingEnv.getElementUtils())
+ .stream()
.filter(
m ->
m.getSimpleName().contentEquals("build")
&& !m.getModifiers().contains(Modifier.PRIVATE)
&& !m.getModifiers().contains(Modifier.STATIC)
- && m.getParameters().isEmpty()
- && erasedTypeIs(m.getReturnType(), autoValueClass))
+ && m.getParameters().isEmpty())
+ .filter(
+ m -> {
+ ExecutableType methodMirror =
+ MoreTypes.asExecutable(typeUtils.asMemberOf(builderTypeMirror, m));
+ return erasedTypeIs(methodMirror.getReturnType(), autoValueClass);
+ })
.findFirst();
}
@@ -201,18 +212,27 @@ class BuilderSpec {
* <p>We currently impose that there cannot be more than one such method.
*/
ImmutableSet<ExecutableElement> toBuilderMethods(
- Types typeUtils, Set<ExecutableElement> abstractMethods) {
+ Types typeUtils,
+ TypeElement autoValueType,
+ Set<ExecutableElement> abstractMethods) {
List<String> builderTypeParamNames =
builderTypeElement.getTypeParameters().stream()
.map(e -> e.getSimpleName().toString())
.collect(toList());
+ DeclaredType autoValueTypeMirror = MoreTypes.asDeclared(autoValueType.asType());
ImmutableSet.Builder<ExecutableElement> methods = ImmutableSet.builder();
for (ExecutableElement method : abstractMethods) {
- if (builderTypeElement.equals(typeUtils.asElement(method.getReturnType()))) {
+ if (!method.getParameters().isEmpty()) {
+ continue;
+ }
+ ExecutableType methodMirror =
+ MoreTypes.asExecutable(typeUtils.asMemberOf(autoValueTypeMirror, method));
+ TypeMirror returnTypeMirror = methodMirror.getReturnType();
+ if (builderTypeElement.equals(typeUtils.asElement(returnTypeMirror))) {
methods.add(method);
- DeclaredType returnType = MoreTypes.asDeclared(method.getReturnType());
+ DeclaredType returnType = MoreTypes.asDeclared(returnTypeMirror);
List<String> typeArguments =
returnType.getTypeArguments().stream()
.filter(t -> t.getKind().equals(TypeKind.TYPEVAR))
@@ -452,7 +472,7 @@ class BuilderSpec {
return nullableAnnotation;
}
- public String copy(AutoValueProcessor.Property property) {
+ public String copy(Property property) {
String copy = copier.copy.apply(property.toString());
if (property.isNullable() && !copier.acceptsNull) {
copy = String.format("(%s == null ? null : %s)", property, copy);
diff --git a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
index ce9eeed0..8f74d5df 100644
--- a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
@@ -1075,6 +1075,78 @@ public class ExtensionTest {
}
@Test
+ public void builderContextWithInheritance() {
+ JavaFileObject parent = JavaFileObjects.forSourceLines(
+ "foo.bar.Parent",
+ "package foo.bar;",
+ "",
+ "interface Parent<BuilderT> {",
+ " BuilderT toBuilder();",
+ " interface Builder<T, BuilderT, BuiltT> {",
+ " BuilderT setThing(T x);",
+ " BuiltT build();",
+ " }",
+ "}");
+ JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
+ "foo.bar.Baz",
+ "package foo.bar;",
+ "",
+ "import com.google.auto.value.AutoValue;",
+ "",
+ "@AutoValue",
+ "abstract class Baz<T> implements Parent<Baz.Builder<T>> {",
+ " abstract T thing();",
+ " static <T> Builder<T> builder() {",
+ " return new AutoValue_Baz.Builder<>();",
+ " }",
+ "",
+ " @AutoValue.Builder",
+ " abstract static class Builder<T> implements Parent.Builder<T, Builder<T>, Baz<T>> {",
+ " }",
+ "}");
+ ContextChecker checker =
+ context -> {
+ assertThat(context.builder()).isPresent();
+ BuilderContext builderContext = context.builder().get();
+
+ assertThat(builderContext.builderType().getQualifiedName().toString())
+ .isEqualTo("foo.bar.Baz.Builder");
+
+ Set<ExecutableElement> builderMethods = builderContext.builderMethods();
+ assertThat(builderMethods).hasSize(1);
+ ExecutableElement builderMethod = Iterables.getOnlyElement(builderMethods);
+ assertThat(builderMethod.getSimpleName().toString()).isEqualTo("builder");
+
+ Set<ExecutableElement> toBuilderMethods = builderContext.toBuilderMethods();
+ assertThat(toBuilderMethods).hasSize(1);
+ ExecutableElement toBuilderMethod = Iterables.getOnlyElement(toBuilderMethods);
+ assertThat(toBuilderMethod.getSimpleName().toString()).isEqualTo("toBuilder");
+
+ Optional<ExecutableElement> buildMethod = builderContext.buildMethod();
+ assertThat(buildMethod).isPresent();
+ assertThat(buildMethod.get().getSimpleName().toString()).isEqualTo("build");
+ assertThat(buildMethod.get().getParameters()).isEmpty();
+ assertThat(buildMethod.get().getReturnType().toString()).isEqualTo("BuiltT");
+
+ ExecutableElement autoBuildMethod = builderContext.autoBuildMethod();
+ assertThat(autoBuildMethod).isEqualTo(buildMethod.get());
+
+ Map<String, Set<ExecutableElement>> setters = builderContext.setters();
+ assertThat(setters.keySet()).containsExactly("thing");
+ Set<ExecutableElement> thingSetters = setters.get("thing");
+ assertThat(thingSetters).hasSize(1);
+ ExecutableElement thingSetter = Iterables.getOnlyElement(thingSetters);
+ assertThat(thingSetter.getSimpleName().toString()).isEqualTo("setThing");
+ };
+ ContextCheckingExtension extension = new ContextCheckingExtension(checker);
+ Compilation compilation =
+ javac()
+ .withProcessors(new AutoValueProcessor(ImmutableList.of(extension)))
+ .compile(autoValueClass, parent);
+ assertThat(compilation).succeededWithoutWarnings();
+ }
+
+ @Test
public void oddBuilderContext() {
JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
"foo.bar.Baz",