diff options
author | Éamonn McManus <emcmanus@google.com> | 2020-11-16 10:46:55 -0800 |
---|---|---|
committer | Google Java Core Libraries <java-core-libraries-team+copybara@google.com> | 2020-11-16 10:47:30 -0800 |
commit | f2cb2247898dcdcb05d52f1faf9689ecef4c0c41 (patch) | |
tree | a614486b8af8a1bb88281887c2f84b98fa6e2f08 /value/src | |
parent | 97863af8fda6e87250dae9e477f98de62d1ff28b (diff) | |
download | auto-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')
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", |