From 196c8100d43b5212c31a83912d66791c5a64525b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Fri, 25 Feb 2022 13:25:56 -0800 Subject: [PATCH] Allow AutoBuilder to build annotation implementations directly. RELNOTES=AutoBuilder can now be used to build annotation implementations directly. PiperOrigin-RevId: 431017277 --- .../google/auto/value/AutoBuilderTest.java | 57 +++++++ .../com/google/auto/value/AutoAnnotation.java | 13 +- .../AutoBuilderAnnotationTemplateVars.java | 57 +++++++ .../value/processor/AutoBuilderProcessor.java | 151 ++++++++++++++++-- .../value/processor/AutoValueProcessor.java | 2 +- .../processor/AutoValueishProcessor.java | 13 +- .../processor/BuilderMethodClassifier.java | 7 +- ...BuilderMethodClassifierForAutoBuilder.java | 20 ++- .../BuilderMethodClassifierForAutoValue.java | 8 +- .../auto/value/processor/autobuilder.vm | 2 +- .../value/processor/autobuilderannotation.vm | 54 +++++++ .../processor/AutoBuilderCompilationTest.java | 31 ++++ value/userguide/autobuilder.md | 7 + value/userguide/howto.md | 30 ++++ 14 files changed, 417 insertions(+), 35 deletions(-) create mode 100644 value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java create mode 100644 value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java index dba8199207..2407bd3f2a 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java @@ -207,6 +207,63 @@ public void simpleAutoAnnotation() { assertThat(annotation3).isEqualTo(annotation4); } + @AutoBuilder(ofClass = MyAnnotation.class) + public interface MyAnnotationSimpleBuilder { + MyAnnotationSimpleBuilder value(String x); + MyAnnotationSimpleBuilder id(int x); + MyAnnotationSimpleBuilder truthiness(Truthiness x); + MyAnnotation build(); + } + + public static MyAnnotationSimpleBuilder myAnnotationSimpleBuilder() { + return new AutoBuilder_AutoBuilderTest_MyAnnotationSimpleBuilder(); + } + + @Test + public void buildWithoutAutoAnnotation() { + // We don't set a value for `id` or `truthiness`, so AutoBuilder should use the default ones in + // the annotation. + MyAnnotation annotation1 = myAnnotationSimpleBuilder().value("foo").build(); + assertThat(annotation1.value()).isEqualTo("foo"); + assertThat(annotation1.id()).isEqualTo(MyAnnotation.DEFAULT_ID); + assertThat(annotation1.truthiness()).isEqualTo(MyAnnotation.DEFAULT_TRUTHINESS); + + // Now we set `truthiness` but still not `id`. + MyAnnotation annotation2 = + myAnnotationSimpleBuilder().value("bar").truthiness(Truthiness.TRUTHY).build(); + assertThat(annotation2.value()).isEqualTo("bar"); + assertThat(annotation2.id()).isEqualTo(MyAnnotation.DEFAULT_ID); + assertThat(annotation2.truthiness()).isEqualTo(Truthiness.TRUTHY); + + // All three elements set explicitly. + MyAnnotation annotation3 = + myAnnotationSimpleBuilder().value("foo").id(23).truthiness(Truthiness.TRUTHY).build(); + assertThat(annotation3.value()).isEqualTo("foo"); + assertThat(annotation3.id()).isEqualTo(23); + assertThat(annotation3.truthiness()).isEqualTo(Truthiness.TRUTHY); + } + + // This builder doesn't have a setter for the `truthiness` element, so the annotations it builds + // should always get the default value. + @AutoBuilder(ofClass = MyAnnotation.class) + public interface MyAnnotationSimplerBuilder { + MyAnnotationSimplerBuilder value(String x); + MyAnnotationSimplerBuilder id(int x); + MyAnnotation build(); + } + + public static MyAnnotationSimplerBuilder myAnnotationSimplerBuilder() { + return new AutoBuilder_AutoBuilderTest_MyAnnotationSimplerBuilder(); + } + + @Test + public void buildWithoutAutoAnnotation_noSetterForElement() { + MyAnnotation annotation = myAnnotationSimplerBuilder().value("foo").id(23).build(); + assertThat(annotation.value()).isEqualTo("foo"); + assertThat(annotation.id()).isEqualTo(23); + assertThat(annotation.truthiness()).isEqualTo(MyAnnotation.DEFAULT_TRUTHINESS); + } + static class Overload { final int anInt; final String aString; diff --git a/value/src/main/java/com/google/auto/value/AutoAnnotation.java b/value/src/main/java/com/google/auto/value/AutoAnnotation.java index 788e54b4fb..3360d80e74 100644 --- a/value/src/main/java/com/google/auto/value/AutoAnnotation.java +++ b/value/src/main/java/com/google/auto/value/AutoAnnotation.java @@ -71,9 +71,9 @@ * parameter corresponding to an array-valued annotation member, and the implementation of each such * member will also return a clone of the array. * - *

If your annotation has many elements, you may consider using {@code @AutoBuilder} to make it - * easier to construct instances. In that case, {@code default} values from the annotation will - * become default values for the parameters of the {@code @AutoAnnotation} method. For example: + *

If your annotation has many elements, you may consider using {@code @AutoBuilder} instead of + * {@code @AutoAnnotation} to make it easier to construct instances. In that case, {@code default} + * values from the annotation will become default values for the values in the builder. For example: * *

  * class Example {
@@ -82,12 +82,7 @@
  *     int number() default 23;
  *   }
  *
- *   {@code @AutoAnnotation}
- *   static MyAnnotation myAnnotation(String value) {
- *     return new AutoAnnotation_Example_myAnnotation(value);
- *   }
- *
- *   {@code @AutoBuilder(callMethod = "myAnnotation")}
+ *   {@code @AutoBuilder(ofClass = MyAnnotation.class)}
  *   interface MyAnnotationBuilder {
  *     MyAnnotationBuilder name(String name);
  *     MyAnnotationBuilder number(int number);
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java
new file mode 100644
index 0000000000..68df14edb1
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderAnnotationTemplateVars.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2022 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.auto.value.processor.AutoValueishProcessor.Property;
+import com.google.common.collect.ImmutableSet;
+import com.google.escapevelocity.Template;
+
+/** The variables to substitute into the autobuilderannotation.vm template. */
+class AutoBuilderAnnotationTemplateVars extends TemplateVars {
+  private static final Template TEMPLATE = parsedTemplateForResource("autobuilderannotation.vm");
+
+  /** Package of generated class. */
+  String pkg;
+
+  /** The encoding of the {@code Generated} class. Empty if the class is not available. */
+  String generated;
+
+  /** The name of the class to generate. */
+  String className;
+
+  /**
+   * The {@linkplain TypeEncoder#encode encoded} name of the annotation type that the generated code
+   * will build.
+   */
+  String annotationType;
+
+  /**
+   * The {@linkplain TypeEncoder#encode encoded} name of the {@code @AutoBuilder} type that users
+   * will call to build this annotation.
+   */
+  String autoBuilderType;
+
+  /**
+   * The "properties" that the builder will build. These are really just names and types, being the
+   * names and types of the annotation elements.
+   */
+  ImmutableSet props;
+
+  @Override
+  Template parsedTemplate() {
+    return TEMPLATE;
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
index 546da4caf8..a187f1723d 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
@@ -15,6 +15,7 @@
  */
 package com.google.auto.value.processor;
 
+import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation;
 import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
 import static com.google.auto.common.MoreElements.getPackage;
 import static com.google.auto.common.MoreStreams.toImmutableList;
@@ -78,6 +79,7 @@
 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
 public class AutoBuilderProcessor extends AutoValueishProcessor {
   private static final String ALLOW_OPTION = "com.google.auto.value.AutoBuilderIsUnstable";
+  private static final String AUTO_ANNOTATION_CLASS_PREFIX = "AutoBuilderAnnotation_";
 
   public AutoBuilderProcessor() {
     super(AUTO_BUILDER_NAME, /* appliesToInterfaces= */ true);
@@ -96,6 +98,55 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
     javaLangVoid = elementUtils().getTypeElement("java.lang.Void").asType();
   }
 
+  // The handling of @AutoBuilder to generate annotation implementations needs some explanation.
+  // Suppose we have this:
+  //
+  //   public class Annotations {
+  //     @interface MyAnnot {...}
+  //
+  //     @AutoBuilder(ofClass = MyAnnot.class)
+  //     public interface MyAnnotBuilder {
+  //       ...
+  //       MyAnnot build();
+  //     }
+  //
+  //     public static MyAnnotBuilder myAnnotBuilder() {
+  //       return new AutoBuilder_Annotations_MyAnnotBuilder();
+  //     }
+  //   }
+  //
+  // Then we will detect that the ofClass type is an annotation. Since annotations can have neither
+  // constructors nor static methods, we know this isn't a regular @AutoBuilder. We want to
+  // generate an implementation of the MyAnnot annotation, and we know we can do that if we have a
+  // suitable @AutoAnnotation method. So we generate:
+  //
+  //   class AutoBuilderAnnotation_Annotations_MyAnnotBuilder {
+  //     @AutoAnnotation
+  //     static MyAnnot newAnnotation(...) {
+  //       return new AutoAnnotation_AutoBuilderAnnotation_Annotations_MyAnnotBuilder_newAnnotation(
+  //           ...);
+  //     }
+  //   }
+  //
+  // We also "defer" MyAnnotBuilder so that it will be considered again on the next round. At that
+  // point the method AutoBuilderAnnotation_Annotations_MyAnnotBuilder.newAnnotation will exist, and
+  // we just need to tweak the handling of MyAnnotBuilder so that it behaves as if it were:
+  //
+  //   @AutoBuilder(
+  //       callMethod = newAnnotation,
+  //       ofClass = AutoBuilderAnnotation_Annotations_MyAnnotBuilder.class)
+  //   interface MyAnnotBuilder {...}
+  //
+  // Using AutoAnnotation and AutoBuilder together you'd write
+  //
+  // @AutoAnnotation static MyAnnot newAnnotation(...) { ... }
+  // 
+  // @AutoBuilder(callMethod = "newAnnotation", ofClass = Some.class)
+  // interface MyAnnotBuilder { ... }
+  //
+  // If you set ofClass to an annotation class, AutoBuilder generates the @AutoAnnotation method for
+  // you and then acts as if your @AutoBuilder annotation pointed to it.
+
   @Override
   void processType(TypeElement autoBuilderType) {
     if (processingEnv.getOptions().containsKey(ALLOW_OPTION)) {
@@ -107,6 +158,14 @@ void processType(TypeElement autoBuilderType) {
     TypeElement ofClass = getOfClass(autoBuilderType, autoBuilderAnnotation);
     checkModifiersIfNested(ofClass, autoBuilderType, "AutoBuilder ofClass");
     String callMethod = findCallMethodValue(autoBuilderAnnotation);
+    if (ofClass.getKind() == ElementKind.ANNOTATION_TYPE) {
+      buildAnnotation(autoBuilderType, ofClass, callMethod);
+    } else {
+      processType(autoBuilderType, ofClass, callMethod);
+    }
+  }
+
+  private void processType(TypeElement autoBuilderType, TypeElement ofClass, String callMethod) {
     ImmutableSet methods =
         abstractMethodsIn(
             getLocalAndInheritedMethods(autoBuilderType, typeUtils(), elementUtils()));
@@ -114,9 +173,17 @@ void processType(TypeElement autoBuilderType) {
     BuilderSpec builderSpec = new BuilderSpec(ofClass, processingEnv, errorReporter());
     BuilderSpec.Builder builder = builderSpec.new Builder(autoBuilderType);
     TypeMirror builtType = builtType(executable);
+    ImmutableMap propertyInitializers =
+        propertyInitializers(autoBuilderType, executable);
     Optional> maybeClassifier =
         BuilderMethodClassifierForAutoBuilder.classify(
-            methods, errorReporter(), processingEnv, executable, builtType, autoBuilderType);
+            methods,
+            errorReporter(),
+            processingEnv,
+            executable,
+            builtType,
+            autoBuilderType,
+            propertyInitializers.keySet());
     if (!maybeClassifier.isPresent() || errorReporter().errorCount() > 0) {
       // We've already output one or more error messages.
       return;
@@ -125,7 +192,7 @@ void processType(TypeElement autoBuilderType) {
     Map propertyToGetterName =
         Maps.transformValues(classifier.builderGetters(), PropertyGetter::getName);
     AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars();
-    vars.props = propertySet(autoBuilderType, executable, propertyToGetterName);
+    vars.props = propertySet(executable, propertyToGetterName, propertyInitializers);
     builder.defineVars(vars, classifier);
     vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
     String generatedClassName = generatedClassName(autoBuilderType, "AutoBuilder_");
@@ -142,15 +209,9 @@ void processType(TypeElement autoBuilderType) {
   }
 
   private ImmutableSet propertySet(
-      TypeElement autoBuilderType,
       ExecutableElement executable,
-      Map propertyToGetterName) {
-    boolean autoAnnotation =
-        MoreElements.getAnnotationMirror(executable, AUTO_ANNOTATION_NAME).isPresent();
-    ImmutableMap builderInitializers =
-        autoAnnotation
-            ? autoAnnotationInitializers(autoBuilderType, executable)
-            : ImmutableMap.of();
+      Map propertyToGetterName,
+      ImmutableMap builderInitializers) {
     // Fix any parameter names that are reserved words in Java. Java source code can't have
     // such parameter names, but Kotlin code might, for example.
     Map identifiers =
@@ -188,11 +249,16 @@ private Property newProperty(
         builderInitializer);
   }
 
-  private ImmutableMap autoAnnotationInitializers(
-      TypeElement autoBuilderType, ExecutableElement autoAnnotationMethod) {
+  private ImmutableMap propertyInitializers(
+      TypeElement autoBuilderType, ExecutableElement executable) {
+    boolean autoAnnotation =
+        MoreElements.getAnnotationMirror(executable, AUTO_ANNOTATION_NAME).isPresent();
+    if (!autoAnnotation) {
+      return ImmutableMap.of();
+    }
     // We expect the return type of an @AutoAnnotation method to be an annotation type. If it isn't,
     // AutoAnnotation will presumably complain, so we don't need to complain further.
-    TypeMirror returnType = autoAnnotationMethod.getReturnType();
+    TypeMirror returnType = executable.getReturnType();
     if (!returnType.getKind().equals(TypeKind.DECLARED)) {
       return ImmutableMap.of();
     }
@@ -452,4 +518,63 @@ Optional nullableAnnotationForMethod(ExecutableElement propertyMethod) {
     // TODO(b/183005059): implement
     return Optional.empty();
   }
+
+  private void buildAnnotation(
+      TypeElement autoBuilderType, TypeElement annotationType, String callMethod) {
+    if (!callMethod.isEmpty()) {
+      errorReporter()
+          .abortWithError(
+              autoBuilderType,
+              "[AutoBuilderAnnotationMethod] @AutoBuilder for an annotation must have an empty"
+                  + " callMethod, not \"%s\"",
+              callMethod);
+    }
+    String autoAnnotationClassName =
+        generatedClassName(autoBuilderType, AUTO_ANNOTATION_CLASS_PREFIX);
+    TypeElement autoAnnotationClass = elementUtils().getTypeElement(autoAnnotationClassName);
+    if (autoAnnotationClass != null) {
+      processType(autoBuilderType, autoAnnotationClass, "newAnnotation");
+      return;
+    }
+    AutoBuilderAnnotationTemplateVars vars = new AutoBuilderAnnotationTemplateVars();
+    vars.autoBuilderType = TypeEncoder.encode(autoBuilderType.asType());
+    vars.props = annotationBuilderPropertySet(annotationType);
+    vars.pkg = TypeSimplifier.packageNameOf(autoBuilderType);
+    vars.generated =
+        generatedAnnotation(elementUtils(), processingEnv.getSourceVersion())
+            .map(annotation -> TypeEncoder.encode(annotation.asType()))
+            .orElse("");
+    vars.className = TypeSimplifier.simpleNameOf(autoAnnotationClassName);
+    vars.annotationType = TypeEncoder.encode(annotationType.asType());
+    String text = vars.toText();
+    text = TypeEncoder.decode(text, processingEnv, vars.pkg, /* baseType= */ javaLangVoid);
+    text = Reformatter.fixup(text);
+    writeSourceFile(autoAnnotationClassName, text, autoBuilderType);
+    addDeferredType(autoBuilderType);
+  }
+
+  private ImmutableSet annotationBuilderPropertySet(TypeElement annotationType) {
+    // Translate the annotation elements into fake Property instances. We're really only interested
+    // in the name and type, so we can use them to declare a parameter of the generated
+    // @AutoAnnotation method. We'll generate a parameter for every element, even elements that
+    // don't have setters in the builder. The generated builder implementation will pass the default
+    // value from the annotation to those parameters.
+    return methodsIn(annotationType.getEnclosedElements()).stream()
+        .filter(m -> m.getParameters().isEmpty() && !m.getModifiers().contains(Modifier.STATIC))
+        .map(AutoBuilderProcessor::annotationBuilderProperty)
+        .collect(toImmutableSet());
+  }
+
+  private static Property annotationBuilderProperty(ExecutableElement annotationMethod) {
+    String name = annotationMethod.getSimpleName().toString();
+    TypeMirror type = annotationMethod.getReturnType();
+    return new Property(
+        name,
+        name,
+        TypeEncoder.encode(type),
+        type,
+        /* nullableAnnotation= */ Optional.empty(),
+        /* getter= */ "",
+        /* maybeBuilderInitializer= */ Optional.empty());
+  }
 }
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 7619db325c..78cb899377 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
@@ -178,7 +178,7 @@ void processType(TypeElement type) {
           .abortWithError(
               type,
               "[AutoValueImplAnnotation] @AutoValue may not be used to implement an annotation"
-                  + " interface; try using @AutoAnnotation instead");
+                  + " interface; try using @AutoAnnotation or @AutoBuilder instead");
     }
 
     // We are going to classify the methods of the @AutoValue class into several categories.
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
index 35f0fe5c0b..e17e4e7155 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
@@ -345,6 +345,13 @@ public int hashCode() {
     }
   }
 
+  void addDeferredType(TypeElement type) {
+    // We save the name of the type rather
+    // than its TypeElement because it is not guaranteed that it will be represented by
+    // the same TypeElement on the next round.
+    deferredTypeNames.add(type.getQualifiedName().toString());
+  }
+
   @Override
   public final boolean process(Set annotations, RoundEnvironment roundEnv) {
     if (annotationType == null) {
@@ -396,10 +403,8 @@ public final boolean process(Set annotations, RoundEnviro
         // We abandoned this type, but only because we needed another type that it references and
         // that other type was missing. It is possible that the missing type will be generated by
         // further annotation processing, so we will try again on the next round (perhaps failing
-        // again and adding it back to the list). We save the name of the @AutoValue type rather
-        // than its TypeElement because it is not guaranteed that it will be represented by
-        // the same TypeElement on the next round.
-        deferredTypeNames.add(type.getQualifiedName().toString());
+        // again and adding it back to the list).
+        addDeferredType(type);
       } catch (RuntimeException e) {
         String trace = Throwables.getStackTraceAsString(e);
         errorReporter.reportError(
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
index 1bd68c847f..e6ec640c7f 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
@@ -69,6 +69,7 @@ abstract class BuilderMethodClassifier {
   private final Elements elementUtils;
   private final TypeMirror builtType;
   private final TypeElement builderType;
+  private final ImmutableSet propertiesWithDefaults;
 
   /**
    * Property types, rewritten to refer to type variables in the builder. For example, suppose you
@@ -101,13 +102,15 @@ abstract class BuilderMethodClassifier {
       ProcessingEnvironment processingEnv,
       TypeMirror builtType,
       TypeElement builderType,
-      ImmutableMap rewrittenPropertyTypes) {
+      ImmutableMap rewrittenPropertyTypes,
+      ImmutableSet propertiesWithDefaults) {
     this.errorReporter = errorReporter;
     this.typeUtils = processingEnv.getTypeUtils();
     this.elementUtils = processingEnv.getElementUtils();
     this.builtType = builtType;
     this.builderType = builderType;
     this.rewrittenPropertyTypes = rewrittenPropertyTypes;
+    this.propertiesWithDefaults = propertiesWithDefaults;
     this.eclipseHack = new EclipseHack(processingEnv);
   }
 
@@ -193,7 +196,7 @@ boolean classifyMethods(Iterable methods, boolean autoValueHa
               propertyBuilder.getBuilderTypeMirror(),
               propertyType);
         }
-      } else if (!hasSetter) {
+      } else if (!hasSetter && !propertiesWithDefaults.contains(property)) {
         // We have neither barBuilder() nor setBar(Bar), so we should complain.
         String setterName = settersPrefixed ? prefixWithSet(property) : property;
         errorReporter.reportError(
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 55a319833a..5dc44740dc 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
@@ -25,6 +25,7 @@
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -51,8 +52,15 @@ private BuilderMethodClassifierForAutoBuilder(
       TypeMirror builtType,
       TypeElement builderType,
       ImmutableBiMap paramToPropertyName,
-      ImmutableMap rewrittenPropertyTypes) {
-    super(errorReporter, processingEnv, builtType, builderType, rewrittenPropertyTypes);
+      ImmutableMap rewrittenPropertyTypes,
+      ImmutableSet propertiesWithDefaults) {
+    super(
+        errorReporter,
+        processingEnv,
+        builtType,
+        builderType,
+        rewrittenPropertyTypes,
+        propertiesWithDefaults);
     this.executable = executable;
     this.paramToPropertyName = paramToPropertyName;
   }
@@ -66,6 +74,8 @@ private BuilderMethodClassifierForAutoBuilder(
    * @param executable the constructor or static method that AutoBuilder will call.
    * @param builtType the type to be built.
    * @param builderType the builder class or interface within {@code ofClass}.
+   * @param propertiesWithDefaults properties that have a default value, so it is not an error for
+   *     them not to have a setter.
    * @return an {@code Optional} that contains the results of the classification if it was
    *     successful or nothing if it was not.
    */
@@ -75,7 +85,8 @@ static Optional> classify(
       ProcessingEnvironment processingEnv,
       ExecutableElement executable,
       TypeMirror builtType,
-      TypeElement builderType) {
+      TypeElement builderType,
+      ImmutableSet propertiesWithDefaults) {
     ImmutableBiMap paramToPropertyName =
         executable.getParameters().stream()
             .collect(toImmutableBiMap(v -> v, v -> v.getSimpleName().toString()));
@@ -89,7 +100,8 @@ static Optional> classify(
             builtType,
             builderType,
             paramToPropertyName,
-            rewrittenPropertyTypes);
+            rewrittenPropertyTypes,
+            propertiesWithDefaults);
     if (classifier.classifyMethods(methods, false)) {
       return Optional.of(classifier);
     } else {
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java
index dde449bb5f..0c2a8c5b3b 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java
@@ -41,7 +41,13 @@ private BuilderMethodClassifierForAutoValue(
       TypeElement builderType,
       ImmutableBiMap getterToPropertyName,
       ImmutableMap rewrittenPropertyTypes) {
-    super(errorReporter, processingEnv, builtType, builderType, rewrittenPropertyTypes);
+    super(
+        errorReporter,
+        processingEnv,
+        builtType,
+        builderType,
+        rewrittenPropertyTypes,
+        ImmutableSet.of());
     this.errorReporter = errorReporter;
     this.getterToPropertyName = getterToPropertyName;
     this.getterNameToGetter =
diff --git a/value/src/main/java/com/google/auto/value/processor/autobuilder.vm b/value/src/main/java/com/google/auto/value/processor/autobuilder.vm
index 01f61b9c32..0aa0c19e87 100644
--- a/value/src/main/java/com/google/auto/value/processor/autobuilder.vm
+++ b/value/src/main/java/com/google/auto/value/processor/autobuilder.vm
@@ -12,7 +12,7 @@
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 
-## Template for each generated AutoValue_Foo class.
+## Template for each generated AutoBuilder_Foo class.
 ## This template uses the Apache Velocity Template Language (VTL).
 ## The variables ($pkg, $props, and so on) are defined by the fields of AutoBuilderTemplateVars.
 ##
diff --git a/value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm b/value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm
new file mode 100644
index 0000000000..427957cc32
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/autobuilderannotation.vm
@@ -0,0 +1,54 @@
+## Copyright 2022 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.
+
+## Template for each generated AutoBuilderAnnotation_Foo_Bar class.
+## This template uses the Apache Velocity Template Language (VTL).
+## The variables ($pkg, $props, and so on) are defined by the fields of
+## AutoBuilderAnnotationTemplateVars.
+##
+## Comments, like this one, begin with ##. The comment text extends up to and including the newline
+## character at the end of the line. So comments also serve to join a line to the next one.
+## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there.
+## That does mean that we sometimes need an extra blank line after such a directive.
+##
+## Post-processing will remove unwanted spaces and blank lines, but will not join two lines.
+## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to
+## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not.
+
+#if (!$pkg.empty)
+package $pkg;
+#end
+
+## The following line will be replaced by the required imports during post-processing.
+`import`
+
+#if (!$generated.empty)
+@${generated}("com.google.auto.value.processor.AutoBuilderProcessor")
+#else
+// Generated by com.google.auto.value.processor.AutoBuilderProcessor
+#end
+class $className {
+  @`com.google.auto.value.AutoAnnotation`
+  static ${annotationType} newAnnotation(
+#foreach ($p in $props)
+      $p.type $p #if ($foreach.hasNext) , #end
+#end
+      ) {
+    return new AutoAnnotation_${className}_newAnnotation(
+#foreach ($p in $props)
+        $p #if ($foreach.hasNext) , #end
+#end
+        );
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java
index 3e935d43c2..06ebc14bbf 100644
--- a/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java
@@ -910,4 +910,35 @@ public void typeParamMismatch() {
         .inFile(javaFileObject)
         .onLineContaining("interface Builder");
   }
+
+  @Test
+  public void annotationWithCallMethod() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "class Baz {",
+            "  @interface MyAnnot {",
+            "    boolean broken();",
+            "  }",
+            "",
+            "  @AutoBuilder(callMethod = \"annotationType\", ofClass = MyAnnot.class)",
+            "  interface Builder {",
+            "    abstract Builder broken(boolean x);",
+            "    abstract MyAnnot build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac().withProcessors(new AutoBuilderProcessor()).compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderAnnotationMethod] @AutoBuilder for an annotation must have an empty"
+                + " callMethod, not \"annotationType\"")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
 }
diff --git a/value/userguide/autobuilder.md b/value/userguide/autobuilder.md
index af9058bda4..6e8a22e30d 100644
--- a/value/userguide/autobuilder.md
+++ b/value/userguide/autobuilder.md
@@ -342,6 +342,13 @@ The builder in the example is an abstract class rather than an interface. An
 abstract class allows us to distinguish between public methods for users of the
 builder to call, and package-private methods that the builder's own logic uses.
 
+## Building annotation instances
+
+AutoBuilder can build instances of annotation interfaces. When the annotation
+has no elements (methods in the annotation), or only one, then AutoAnnotation is
+simpler to use. But when there are several elements, a builder is helpful. See
+[here](howto.md#annotation) for examples of both.
+
 ## Naming conventions
 
 A setter method for the parameter `foo` can be called either `setFoo` or `foo`.
diff --git a/value/userguide/howto.md b/value/userguide/howto.md
index e64109670a..6067181759 100644
--- a/value/userguide/howto.md
+++ b/value/userguide/howto.md
@@ -337,6 +337,36 @@ public class Names {
 }
 ```
 
+If your annotation has several elements, you may prefer to use `@AutoBuilder`:
+
+```java
+public @interface Named {
+  String value();
+  int priority() default 0;
+  int size() default 0;
+}
+
+public class Names {
+  @AutoBuilder(ofClass = Named.class)
+  public interface NamedBuilder {
+    NamedBuilder value(String x);
+    NamedBuilder priority(int x);
+    NamedBuilder size(int x);
+    Named build();
+  }
+
+  public static NamedBuilder namedBuilder() {
+    return new AutoBuilder_Names_namedBuilder();
+  }
+
+  ...
+    Named named1 = namedBuilder().value("O'Cruiskeen").priority(17).size(23).build();
+    Named named2 = namedBuilder().value("O'Cruiskeen").build();
+    // priority and size get their default values
+  ...
+}
+```
+
 For more details, see the [`AutoAnnotation`
 javadoc](http://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/AutoAnnotation.java#L24).