Subclasses put their processing logic in {@link Step} implementations. The steps are passed to * the processor by returning them in the {@link #steps()} method, and can access the {@link @@ -69,11 +73,12 @@ *
Any logic that needs to happen once per round can be specified by overriding {@link * #postRound(RoundEnvironment)}. * - *
A non-package element is considered well-formed if its type, type parameters, parameters, * default values, supertypes, annotations, and enclosed elements are. Package elements are treated @@ -85,7 +90,7 @@ * because the element will never be fully complete. All such compilations will fail with an error * message on the offending type that describes the issue. * - *
Each {@code Step} can defer elements by including them in the set returned by {@link
* Step#process(ImmutableSetMultimap)}; elements deferred by a step will be passed back to that step
@@ -106,18 +111,32 @@
*/
public abstract class BasicAnnotationProcessor extends AbstractProcessor {
- private final Set Note that the elements deferred by processing steps are guaranteed to be well-formed;
+ * therefore, they are ignored (not returned) here, and they will be considered directly in the
+ * {@link #process(ImmutableSetMultimap)} method.
*/
- private ImmutableSetMultimap It's unfortunate that we have to track types and packages separately, but since there are
- * two different methods to look them up in {@link Elements}, we end up with a lot of parallel
- * logic. :(
+ * A factory for an annotated element.
*
- * Packages declared (and annotated) in {@code package-info.java} are tracked as deferred
- * packages, type elements are tracked directly, and all other elements are tracked via their
- * nearest enclosing type.
+ * Instead of saving elements, an {@code ElementFactory} is saved since there is no guarantee
+ * that any particular element will always be represented by the same object. (Reference: {@link
+ * Element}) For example, Eclipse compiler uses different {@code Element} instances per round. The
+ * factory allows us to reconstruct an equivalent element in a later round.
*/
- private static final class ElementName {
- private enum Kind {
- PACKAGE_NAME,
- TYPE_NAME,
+ private abstract static class ElementFactory {
+ final String toString;
+
+ private ElementFactory(Element element) {
+ this.toString = element.toString();
+ }
+
+ /** An {@link ElementFactory} for an annotated element. */
+ static ElementFactory forAnnotatedElement(Element element, Messager messager) {
+ /* The name of the ElementKind constants is used instead to accommodate for RECORD
+ * and RECORD_COMPONENT kinds, which are introduced in Java 16.
+ */
+ switch (element.getKind().name()) {
+ case "PACKAGE":
+ return new PackageElementFactory(element);
+ case "CLASS":
+ case "ENUM":
+ case "INTERFACE":
+ case "ANNOTATION_TYPE":
+ case "RECORD":
+ return new TypeElementFactory(element);
+ case "TYPE_PARAMETER":
+ return new TypeParameterElementFactory(element, messager);
+ case "FIELD":
+ case "ENUM_CONSTANT":
+ case "RECORD_COMPONENT":
+ return new FieldOrRecordComponentElementFactory(element);
+ case "CONSTRUCTOR":
+ case "METHOD":
+ return new ExecutableElementFactory(element);
+ case "PARAMETER":
+ return new ParameterElementFactory(element);
+ default:
+ messager.printMessage(
+ WARNING,
+ String.format(
+ "%s does not support element type %s.",
+ ElementFactory.class.getCanonicalName(), element.getKind()));
+ return new UnsupportedElementFactory(element);
+ }
}
- private final Kind kind;
- private final String name;
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (this == object) {
+ return true;
+ } else if (!(object instanceof ElementFactory)) {
+ return false;
+ }
- private ElementName(Kind kind, Name name) {
- this.kind = checkNotNull(kind);
- this.name = name.toString();
+ ElementFactory that = (ElementFactory) object;
+ return this.toString.equals(that.toString);
+ }
+
+ @Override
+ public int hashCode() {
+ return toString.hashCode();
}
/**
- * An {@link ElementName} for an annotated element. If {@code element} is a package, uses the
- * fully qualified name of the package. If it's a type, uses its fully qualified name.
- * Otherwise, uses the fully-qualified name of the nearest enclosing type.
- *
- * A package can be annotated if it has a {@code package-info.java} with annotations on the
- * package declaration.
+ * Returns the {@link Element} corresponding to the name information saved in this factory, or
+ * null if none exists.
*/
- static ElementName forAnnotatedElement(Element element) {
- return element.getKind() == PACKAGE
- ? new ElementName(Kind.PACKAGE_NAME, asPackage(element).getQualifiedName())
- : new ElementName(Kind.TYPE_NAME, getEnclosingType(element).getQualifiedName());
+ abstract @Nullable Element getElement(Elements elementUtils);
+ }
+
+ /**
+ * Saves the Element reference and returns it when inquired, with the hope that the same object
+ * still represents that element, or the required information is present.
+ */
+ private static final class UnsupportedElementFactory extends ElementFactory {
+ private final Element element;
+
+ private UnsupportedElementFactory(Element element) {
+ super(element);
+ this.element = element;
+ }
+
+ @Override
+ Element getElement(Elements elementUtils) {
+ return element;
+ }
+ }
+
+ /* It's unfortunate that we have to track types and packages separately, but since there are
+ * two different methods to look them up in {@link Elements}, we end up with a lot of parallel
+ * logic. :(
+ */
+ private static final class PackageElementFactory extends ElementFactory {
+ private PackageElementFactory(Element element) {
+ super(element);
+ }
+
+ @Override
+ @Nullable PackageElement getElement(Elements elementUtils) {
+ return elementUtils.getPackageElement(toString);
+ }
+ }
+
+ private static final class TypeElementFactory extends ElementFactory {
+ private TypeElementFactory(Element element) {
+ super(element);
+ }
+
+ @Override
+ @Nullable TypeElement getElement(Elements elementUtils) {
+ return elementUtils.getTypeElement(toString);
+ }
+ }
+
+ private static final class TypeParameterElementFactory extends ElementFactory {
+ private final ElementFactory enclosingElementFactory;
+
+ private TypeParameterElementFactory(Element element, Messager messager) {
+ super(element);
+ this.enclosingElementFactory =
+ ElementFactory.forAnnotatedElement(element.getEnclosingElement(), messager);
+ }
+
+ @Override
+ @Nullable TypeParameterElement getElement(Elements elementUtils) {
+ Parameterizable enclosingElement =
+ (Parameterizable) enclosingElementFactory.getElement(elementUtils);
+ if (enclosingElement == null) {
+ return null;
+ }
+ return enclosingElement.getTypeParameters().stream()
+ .filter(typeParamElement -> toString.equals(typeParamElement.toString()))
+ .collect(onlyElement());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (this == object) {
+ return true;
+ } else if (!(object instanceof TypeParameterElementFactory)) {
+ return false;
+ }
+
+ TypeParameterElementFactory that = (TypeParameterElementFactory) object;
+ return this.toString.equals(that.toString)
+ && this.enclosingElementFactory.equals(that.enclosingElementFactory);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(toString, enclosingElementFactory);
}
+ }
+
+ /** Represents FIELD, ENUM_CONSTANT, and RECORD_COMPONENT */
+ private static class FieldOrRecordComponentElementFactory extends ElementFactory {
+ private final TypeElementFactory enclosingTypeElementFactory;
+ private final ElementKind elementKind;
- /** The fully-qualified name of the element. */
- String name() {
- return name;
+ private FieldOrRecordComponentElementFactory(Element element) {
+ super(element); // toString is its simple name.
+ this.enclosingTypeElementFactory = new TypeElementFactory(getEnclosingType(element));
+ this.elementKind = element.getKind();
}
+ @Override
+ @Nullable Element getElement(Elements elementUtils) {
+ TypeElement enclosingTypeElement = enclosingTypeElementFactory.getElement(elementUtils);
+ if (enclosingTypeElement == null) {
+ return null;
+ }
+ return enclosingTypeElement.getEnclosedElements().stream()
+ .filter(
+ element ->
+ elementKind.equals(element.getKind()) && toString.equals(element.toString()))
+ .collect(onlyElement());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (!super.equals(object) || !(object instanceof FieldOrRecordComponentElementFactory)) {
+ return false;
+ }
+ // To distinguish between a field and record_component
+ FieldOrRecordComponentElementFactory that = (FieldOrRecordComponentElementFactory) object;
+ return this.elementKind == that.elementKind;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(toString, elementKind);
+ }
+ }
+
+ /**
+ * Represents METHOD and CONSTRUCTOR.
+ *
+ * The {@code equals()} and {@code hashCode()} have been overridden since the {@code toString}
+ * alone is not sufficient to make a distinction in all overloaded cases. For example, {@code The executable element is retrieved by saving its enclosing type element, simple name, and
+ * ordinal position in the source code relative to other related overloaded methods, meaning those
+ * with the same simple name. This is possible because according to Java language specification
+ * for {@link TypeElement#getEnclosedElements()}: "As a particular instance of the general
+ * accuracy requirements and the ordering behavior required of this interface, the list of
+ * enclosed elements will be returned to the natural order for the originating source of
+ * information about the type. For example, if the information about the type is originating from
+ * a source file, the elements will be returned in source code order. (However, in that case the
+ * ordering of implicitly declared elements, such as default constructors, is not specified.)"
+ *
+ * Simple name is saved since comparing the toString is not reliable when at least one
+ * parameter references ERROR, possibly because it is not generated yet. For example, method
+ * {@code void m(SomeGeneratedClass sgc)}, before the generation of {@code SomeGeneratedClass} has
+ * the toString {@code m(SomeGeneratedClass)}; however, after the generation it will have toString
+ * equal to {@code m(test.SomeGeneratedClass)} assuming that the package name is "test".
+ */
+ private static final class ExecutableElementFactory extends ElementFactory {
+ private final TypeElementFactory enclosingTypeElementFactory;
+ private final Name simpleName;
+
/**
- * The {@link Element} whose fully-qualified name is {@link #name()}. Empty if the relevant
- * method on {@link Elements} returns {@code null}.
+ * The index of the element among all elements of the same kind within the enclosing type. If
+ * this is method {@code foo(...)} and the index is 0, that means that the method is the first
+ * method called {@code foo} in the enclosing type.
*/
- Optional extends Element> getElement(Elements elements) {
- return Optional.ofNullable(
- kind == Kind.PACKAGE_NAME
- ? elements.getPackageElement(name)
- : elements.getTypeElement(name));
+ private final int sameNameIndex;
+
+ private ExecutableElementFactory(Element element) {
+ super(element);
+ TypeElement enclosingTypeElement = getEnclosingType(element);
+ this.enclosingTypeElementFactory = new TypeElementFactory(enclosingTypeElement);
+ this.simpleName = element.getSimpleName();
+
+ ImmutableList Note that for a method to get "hidden" like this, it should reside after the ERROR
+ * referencing method, and it should not have any distinguishing characteristic like different
+ * name, different number of parameter, or a clear parameter type mismatch.
+ */
+ @Test
+ public void properlyDefersProcessing_errorTypeReferencingOverloadedMethods() {
+ JavaFileObject testFileObject =
+ JavaFileObjects.forSourceLines(
+ "test.ClassA",
+ "package test;",
+ "",
+ "public class ClassA {",
+ " @" + OneMethodAtATime.class.getCanonicalName(),
+ " void overloadedMethod(SomeGeneratedClass c) {}",
+ " @" + OneMethodAtATime.class.getCanonicalName(),
+ " void overloadedMethod(int c) {}",
+ "}");
+
+ JavaFileObject generatesCodeFileObject =
+ JavaFileObjects.forSourceLines(
+ "test.ClassB",
+ "package test;",
+ "",
+ "@" + GeneratesCode.class.getCanonicalName(),
+ "public class ClassB {}");
+
+ OneOverloadedMethodAtATimeProcessor oneOverloadedMethodAtATimeProcessor =
+ new OneOverloadedMethodAtATimeProcessor();
+ Compilation compilation =
+ javac()
+ .withProcessors(oneOverloadedMethodAtATimeProcessor, new GeneratesCodeProcessor())
+ .compile(testFileObject, generatesCodeFileObject);
+
+ assertThat(compilation).succeeded();
+ assertThat(oneOverloadedMethodAtATimeProcessor.rejectedRounds).isEqualTo(1);
+
+ assertThat(oneOverloadedMethodAtATimeProcessor.processArguments())
+ .comparingElementsUsing(setMultimapValuesByString())
+ .containsExactly(
+ ImmutableSetMultimap.of(
+ OneMethodAtATime.class.getCanonicalName(),
+ "overloadedMethod(test.SomeGeneratedClass)",
+ OneMethodAtATime.class.getCanonicalName(),
+ "overloadedMethod(int)"),
+ ImmutableSetMultimap.of(
+ OneMethodAtATime.class.getCanonicalName(), "overloadedMethod(int)"))
+ .inOrder();
+ }
+
@Test
public void properlySkipsMissingAnnotations_generatesClass() {
JavaFileObject source =
@@ -464,13 +881,12 @@ public void reportsMissingType() {
"public class ClassA {",
" SomeGeneratedClass bar;",
"}");
- assertAbout(javaSources())
- .that(ImmutableList.of(classAFileObject))
- .processedWith(new RequiresGeneratedCodeProcessor())
- .failsToCompile()
- .withErrorContaining(RequiresGeneratedCodeProcessor.class.getCanonicalName())
- .in(classAFileObject)
- .onLine(4);
+ Compilation compilation =
+ javac().withProcessors(new RequiresGeneratedCodeProcessor()).compile(classAFileObject);
+ assertThat(compilation)
+ .hadErrorContaining(RequiresGeneratedCodeProcessor.class.getCanonicalName())
+ .inFile(classAFileObject)
+ .onLineContaining("class ClassA");
}
@Test
@@ -482,12 +898,9 @@ public void reportsMissingTypeSuppressedWhenOtherErrors() {
"",
"@" + CauseError.class.getCanonicalName(),
"public class ClassA {}");
- assertAbout(javaSources())
- .that(ImmutableList.of(classAFileObject))
- .processedWith(new CauseErrorProcessor())
- .failsToCompile()
- .withErrorCount(1)
- .withErrorContaining("purposeful");
+ Compilation compilation =
+ javac().withProcessors(new CauseErrorProcessor()).compile(classAFileObject);
+ assertThat(compilation).hadErrorContaining("purposeful");
}
@Test
diff --git a/factory/pom.xml b/factory/pom.xml
index ee9144896a..d55eba510f 100644
--- a/factory/pom.xml
+++ b/factory/pom.xml
@@ -31,10 +31,10 @@
The {@code @Inject} and {@code Provider} classes come from either the legacy package {@code
+ * javax.inject} or the updated package {@code jakarta.inject}. {@code jakarta.inject} is used if it
+ * is on the classpath. Compile with {@code -Acom.google.auto.factory.InjectApi=javax} if you want
+ * to use {@code javax.inject} even when {@code jakarta.inject} is available.
*
* @author Gregory Kick
*/
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
index 0654eed10e..05f631898c 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
@@ -41,11 +41,13 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
@@ -60,6 +62,7 @@
import javax.tools.Diagnostic.Kind;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
+import org.checkerframework.checker.nullness.qual.Nullable;
/**
* The annotation processor that generates factories for {@link AutoFactory} annotations.
@@ -68,13 +71,25 @@
*/
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
@AutoService(Processor.class)
+@SupportedOptions(AutoFactoryProcessor.INJECT_API_OPTION)
public final class AutoFactoryProcessor extends AbstractProcessor {
+ static final String INJECT_API_OPTION = "com.google.auto.factory.InjectApi";
+
+ private static final ImmutableSet If {@code type} is a {@code Provider For example:
+ *
*
*
*/
- static Key create(TypeMirror type, CollectionInput type {@code Key.type()}
* {@code String} {@code String}
@@ -64,18 +62,19 @@ Optional {@code int} {@code Integer}
*
When the AutoValue processor runs for a class {@code Foo}, it will ask each Extension whether - * it is {@linkplain #applicable applicable}. Suppose two Extensions reply that they are. Then - * the processor will generate the AutoValue logic in a direct subclass of {@code Foo}, and it - * will ask the first Extension to generate a subclass of that, and the second Extension to generate - * a subclass of the subclass. So we might have this hierarchy: + * it is {@linkplain #applicable applicable}. Suppose two Extensions reply that they are. Then the + * processor will generate the AutoValue logic in a direct subclass of {@code Foo}, and it will ask + * the first Extension to generate a subclass of that, and the second Extension to generate a + * subclass of the subclass. So we might have this hierarchy: * *
* @AutoValue abstract class Foo {...} // the hand-written class @@ -63,8 +63,8 @@ *+ * } + * } * *The first generated class in the hierarchy will always be the one generated by the AutoValue * processor and the last one will always be the one generated by the Extension that {@code * mustBeFinal}, if any. Other than that, the order of the classes in the hierarchy is unspecified. - * The last class in the hierarchy is {@code AutoValue_Foo} and that is the one that the - * {@code Foo} class will reference, for example with {@code new AutoValue_Foo(...)}. + * The last class in the hierarchy is {@code AutoValue_Foo} and that is the one that the {@code Foo} + * class will reference, for example with {@code new AutoValue_Foo(...)}. * *
Each Extension must also be sure to generate a constructor with arguments corresponding to all * properties in {@link com.google.auto.value.extension.AutoValueExtension.Context#propertyTypes()}, @@ -72,8 +72,8 @@ * have at least package visibility. * *
Because the class generated by the AutoValue processor is at the top of the generated - * hierarchy, Extensions can override its methods, for example {@code hashCode()}, - * {@code toString()}, or the implementations of the various {@code bar()} property methods. + * hierarchy, Extensions can override its methods, for example {@code hashCode()}, {@code + * toString()}, or the implementations of the various {@code bar()} property methods. */ public abstract class AutoValueExtension { @@ -99,10 +99,10 @@ public interface Context { /** * The fully-qualified name of the last class in the {@code AutoValue} hierarchy. For an - * {@code @AutoValue} class {@code foo.bar.Baz}, this will be {@code foo.bar.AutoValue_Baz}. - * The class may be generated by an extension, which will be the current extension if the - * {@code isFinal} parameter to {@link AutoValueExtension#generateClass} is true and the - * returned string is not {@code null}. + * {@code @AutoValue} class {@code foo.bar.Baz}, this will be {@code foo.bar.AutoValue_Baz}. The + * class may be generated by an extension, which will be the current extension if the {@code + * isFinal} parameter to {@link AutoValueExtension#generateClass} is true and the returned + * string is not {@code null}. * *
For compatibility reasons, this method has a default implementation that throws an * exception. The AutoValue processor supplies an implementation that behaves as documented. @@ -159,6 +159,14 @@ default Map
propertyTypes() { */ Set abstractMethods(); + /** + * Returns the complete set of abstract methods defined in or inherited by the {@code @Builder} + * class. This includes methods that have been consumed by this or another Extension. + * + * If there is no builder class, then this set is empty. + */ + Set
builderAbstractMethods(); + /** * Returns the complete list of annotations defined on the {@code classToCopyFrom} that should * be added to any generated subclass. Only annotations visible to the {@code @AutoValue} will @@ -203,9 +211,7 @@ default Optional builder() { } } - /** - * Represents a {@code Builder} associated with an {@code @AutoValue} class. - */ + /** Represents a {@code Builder} associated with an {@code @AutoValue} class. */ public interface BuilderContext { /** * Returns the {@code @AutoValue.Builder} interface or abstract class that this object @@ -218,6 +224,7 @@ public interface BuilderContext { * type. * * Consider a class like this: + * *
* {@code @AutoValue} abstract class Foo { * abstract String bar(); @@ -230,8 +237,8 @@ public interface BuilderContext { * } ** - *Here {@code toBuilderMethods()} will return a set containing the method - * {@code Foo.toBuilder()}. + *
Here {@code toBuilderMethods()} will return a set containing the method {@code + * Foo.toBuilder()}. */ Set
toBuilderMethods(); @@ -240,6 +247,7 @@ public interface BuilderContext { * type. * * Consider a class like this: + * *
* {@code @AutoValue} abstract class Foo { * abstract String bar(); @@ -257,19 +265,19 @@ public interface BuilderContext { * } ** - *Here {@code builderMethods()} will return a set containing the method - * {@code Foo.builder()}. Generated code should usually call this method in preference to - * constructing {@code AutoValue_Foo.Builder()} directly, because this method can establish - * default values for properties, as it does here. + *
Here {@code builderMethods()} will return a set containing the method {@code + * Foo.builder()}. Generated code should usually call this method in preference to constructing + * {@code AutoValue_Foo.Builder()} directly, because this method can establish default values + * for properties, as it does here. */ Set
builderMethods(); /** * Returns the method {@code build()} in the builder class, if it exists and returns the - * {@code @AutoValue} type. This is the method that generated code for - * {@code @AutoValue class Foo} should call in order to get an instance of {@code Foo} from its - * builder. The returned method is called {@code build()}; if the builder uses some other name - * then extensions have no good way to guess how they should build. + * {@code @AutoValue} type. This is the method that generated code for {@code @AutoValue class + * Foo} should call in order to get an instance of {@code Foo} from its builder. The returned + * method is called {@code build()}; if the builder uses some other name then extensions have no + * good way to guess how they should build. * * A common convention is for {@code build()} to be a concrete method in the * {@code @AutoValue.Builder} class, which calls an abstract method {@code autoBuild()} that is @@ -281,9 +289,9 @@ public interface BuilderContext { /** * Returns the abstract build method. If the {@code @AutoValue} class is {@code Foo}, this is an * abstract no-argument method in the builder class that returns {@code Foo}. This might be - * called {@code build()}, or, following a common convention, it might be called - * {@code autoBuild()} and used in the implementation of a {@code build()} method that is - * defined in the builder class. + * called {@code build()}, or, following a common convention, it might be called {@code + * autoBuild()} and used in the implementation of a {@code build()} method that is defined in + * the builder class. * *
Extensions should call the {@code build()} method in preference to this one. But they * should override this one if they want to customize build-time behaviour. @@ -292,19 +300,18 @@ public interface BuilderContext { /** * Returns a map from property names to the corresponding setters. A property may have more than - * one setter. For example, an {@code ImmutableList
} might be set by - * {@code setFoo(ImmutableList )} and {@code setFoo(String[])}. + * one setter. For example, an {@code ImmutableList } might be set by {@code + * setFoo(ImmutableList )} and {@code setFoo(String[])}. */ Map > setters(); /** * Returns a map from property names to property builders. For example, if there is a property - * {@code foo} defined by {@code abstract ImmutableList foo();} or - * {@code abstract ImmutableList getFoo();} in the {@code @AutoValue} class, - * then there can potentially be a builder defined by - * {@code abstract ImmutableList.Builder fooBuilder();} in the - * {@code @AutoValue.Builder} class. This map would then map {@code "foo"} to the - * {@link ExecutableElement} representing {@code fooBuilder()}. + * {@code foo} defined by {@code abstract ImmutableList foo();} or {@code abstract + * ImmutableList getFoo();} in the {@code @AutoValue} class, then there can potentially + * be a builder defined by {@code abstract ImmutableList.Builder fooBuilder();} in the + * {@code @AutoValue.Builder} class. This map would then map {@code "foo"} to the {@link + * ExecutableElement} representing {@code fooBuilder()}. */ Map propertyBuilders(); } @@ -328,8 +335,8 @@ public enum IncrementalExtensionType { /** * This extension is aggregating, meaning that it may generate outputs based on several - * annotated input classes and it respects the constraints imposed on aggregating processors. - * It is unusual for AutoValue extensions to be aggregating. + * annotated input classes and it respects the constraints imposed on aggregating processors. It + * is unusual for AutoValue extensions to be aggregating. * * @see Gradle @@ -436,10 +443,10 @@ public Set consumeProperties(Context context) { } /** - * Returns a possible empty set of abstract methods that this Extension intends to implement. This + * Returns a possibly empty set of abstract methods that this Extension intends to implement. This * will prevent AutoValue from generating an implementation, in cases where it would have, and it - * will also avoid warnings about abstract methods that AutoValue doesn't expect. The default set - * returned by this method is empty. + * will also avoid complaints about abstract methods that AutoValue doesn't expect. The default + * set returned by this method is empty. * * Each returned method must be one of the abstract methods in {@link * Context#abstractMethods()}. @@ -457,6 +464,21 @@ public Set
consumeMethods(Context context) { return ImmutableSet.of(); } + /** + * Returns a possibly empty set of abstract methods that this Extension intends to implement. This + * will prevent AutoValue from generating an implementation, in cases where it would have, and it + * will also avoid complaints about abstract methods that AutoValue doesn't expect. The default + * set returned by this method is empty. + * + * Each returned method must be one of the abstract methods in {@link + * Context#builderAbstractMethods()}. + * + * @param context the Context of the code generation for this class. + */ + public Set
consumeBuilderMethods(Context context) { + return ImmutableSet.of(); + } + /** * Returns the generated source code of the class named {@code className} to extend {@code * classToExtend}, or {@code null} if this extension does not generate a class in the hierarchy. @@ -476,7 +498,8 @@ public Set consumeMethods(Context context) { * ... * } * ... - * }}
Here, {@code An extension can also generate a subclass of the nested {@code Builder} class if there is
+ * one. In that case, it should check if {@link BuilderContext#toBuilderMethods()} is empty. If
+ * not, the {@code Builder} subclass should include a "copy constructor", like this:
+ *
+ * Here, {@code {@code @ToPrettyString} is valid on overridden {@code toString()} and other methods alike.
*
- * The reason this class is needed is that certain methods in the {@code javax.lang.model} API
+ * return modified versions of types, for example {@link javax.lang.model.util.Types#asMemberOf}.
+ * Historically, Java compilers were a bit inconsistent about whether those modified types preserve
+ * annotations that were in the original type, but the recent consensus is that they should not.
+ * Suppose for example if we have this:
+ *
+ * If we use {@code asMemberOf} to determine what the return type of {@code Child.thing()} is, we
+ * will discover it is {@code String}. But we really wanted {@code @Nullable String}. To fix that,
+ * we combine the annotations from {@code Parent.thing()} with the resolved type from {@code
+ * Child.thing()}.
+ *
+ * This is only a partial workaround. We aren't able to splice the {@code @Nullable} from {@code
+ * List<@Nullable T>} into a type like {@code List https://bugs.openjdk.org/browse/JDK-8174126 would potentially provide the basis for a cleaner
+ * solution, via a new {@code Types.withAnnotations(type, annotations)} method.
+ *
+ * This class deliberately does not implement {@link TypeMirror}. Making "mutant" {@code
+ * TypeMirror} instances is a bit dangerous, because if you give such a thing to one of the {@code
+ * javax.lang.model} APIs then you will almost certainly get a {@code ClassCastException}. Those
+ * APIs only expect objects that they themselves produced.
+ */
+final class AnnotatedTypeMirror {
+ private final TypeMirror originalType;
+ private final TypeMirror rewrittenType;
+
+ AnnotatedTypeMirror(TypeMirror originalType, TypeMirror rewrittenType) {
+ this.originalType = originalType;
+ this.rewrittenType = rewrittenType;
+ }
+
+ AnnotatedTypeMirror(TypeMirror type) {
+ this(type, type);
+ }
+
+ ImmutableList Importantly, this rewrite loses type annotations, so when those are important we must
- * be careful to look at the original type as reported by the {@link #originalPropertyType}
- * method.
*/
- private final ImmutableMap If the caller is in a version of Eclipse with this bug then the {@code
- * asMemberOf} call will fail if the method is inherited from an interface. We work around that
- * for methods in the {@code @AutoValue} class using {@link EclipseHack#methodReturnTypes} but we
- * don't try to do so here because it should be much less likely. You might need to change {@code
- * ParentBuilder} from an interface to an abstract class to make it work, but you'll often need to
- * do that anyway.
*/
- TypeMirror builderMethodReturnType(ExecutableElement builderMethod) {
- DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
- TypeMirror methodMirror;
- try {
- methodMirror = typeUtils.asMemberOf(builderTypeMirror, builderMethod);
- } catch (IllegalArgumentException e) {
- // Presumably we've hit the Eclipse bug cited.
- return builderMethod.getReturnType();
- }
- return MoreTypes.asExecutable(methodMirror).getReturnType();
+ AnnotatedTypeMirror builderMethodReturnType(ExecutableElement builderMethod) {
+ return MethodSignature.asMemberOf(typeUtils, builderType, builderMethod).returnType();
}
private static String prefixWithSet(String propertyName) {
@@ -696,9 +672,7 @@ private boolean propertyIsNullable(String property) {
/**
* Returns the property type as it appears on the original source program element. This can be
* different from the type stored in {@link #rewrittenPropertyTypes} since that one will refer to
- * type variables in the builder rather than in the original class. Also, {@link
- * #rewrittenPropertyTypes} will not have type annotations even if they were present on the
- * original element, so {@code originalPropertyType} is the right thing to use for those.
+ * type variables in the builder rather than in the original class.
*/
abstract TypeMirror originalPropertyType(E propertyElement);
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java
index d9900b57df..a83f8e1c12 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java
@@ -30,7 +30,6 @@
import java.util.Optional;
import java.util.function.Function;
import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
@@ -50,7 +49,7 @@ private BuilderMethodClassifierForAutoBuilder(
TypeMirror builtType,
TypeElement builderType,
ImmutableBiMap{@code
+ * ...
+ *
+ *
+ * Example
+ * Example
*
*
* {@code @AutoValue}
diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java
index cd2762a25b..9532fc0e46 100644
--- a/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java
+++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java
@@ -41,7 +41,7 @@
*
*
Example
+ * Example
*
*
* {@code @AutoValue}
diff --git a/value/src/main/java/com/google/auto/value/processor/AnnotatedTypeMirror.java b/value/src/main/java/com/google/auto/value/processor/AnnotatedTypeMirror.java
new file mode 100644
index 0000000000..1b0e83b78d
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AnnotatedTypeMirror.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.auto.value.processor;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import java.util.Objects;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * A {@link TypeMirror} and associated annotations.
+ *
+ *
{@code
+ * interface Parent
+ *
+ *
*
*/
public String getBuilderFieldType() {
- if (typeMirror.getKind().isPrimitive()
+ if (annotatedType.getType().getKind().isPrimitive()
|| nullableAnnotation.isPresent()
|| !builderInitializer.isEmpty()
|| availableNullableTypeAnnotations.isEmpty()) {
return type;
}
- return TypeEncoder.encodeWithAnnotations(typeMirror, availableNullableTypeAnnotations);
+ return TypeEncoder.encodeWithAnnotations(annotatedType, availableNullableTypeAnnotations);
}
/**
@@ -263,7 +262,7 @@ public String getName() {
}
TypeMirror getTypeMirror() {
- return typeMirror;
+ return annotatedType.getType();
}
public String getType() {
@@ -271,7 +270,7 @@ public String getType() {
}
public TypeKind getKind() {
- return typeMirror.getKind();
+ return annotatedType.getType().getKind();
}
/**
@@ -332,7 +331,7 @@ public static class GetterProperty extends Property {
String name,
String identifier,
ExecutableElement method,
- TypeMirror typeMirror,
+ AnnotatedTypeMirror annotatedType,
String typeString,
ImmutableList