diff options
author | Éamonn McManus <emcmanus@google.com> | 2021-05-25 15:41:13 -0700 |
---|---|---|
committer | Google Java Core Libraries <java-libraries-firehose+copybara@google.com> | 2021-05-25 15:41:42 -0700 |
commit | c84e6affef24a0d390b5fb7415c3b3ebd246fac3 (patch) | |
tree | 7a6508aa2daff7ef44f98f0a9571cd68aa768ddb /value/src/main/java/com/google/auto | |
parent | 123cc71251e1b1ed5a4e4f95c7e7224c8c7fb1b5 (diff) | |
download | auto-c84e6affef24a0d390b5fb7415c3b3ebd246fac3.tar.gz |
Add the [JSpecify](http://jspecify.org) `@Nullable` to `equals(Object)` if it is available.
More exactly, we will generate `equals(@Nullable Object)` if we know a `@Nullable` annotation. We know a `@Nullable` annotation if one is mentioned in a method parameter or return type of the `@AutoValue` [etc] class; this was already true before this CL. What's new is that we also know a `@Nullable` annotation if one is available in the compilation environment and matches the one we expect. That in turn is `@org.jspecify.nullness.Nullable` by default, but can be overridden or suppressed with a new `-A` option.
We also insert `@Nullable` for `@AutoAnnotation` implementations, though currently we don't have logic to find one that might be referenced from methods.
RELNOTES=n/a
PiperOrigin-RevId: 375811563
Diffstat (limited to 'value/src/main/java/com/google/auto')
7 files changed, 115 insertions, 12 deletions
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java index eb87287b..edfa41ce 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java @@ -27,6 +27,7 @@ import com.google.auto.common.SuperficialValidation; import com.google.auto.service.AutoService; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.hash.Hashing; @@ -46,6 +47,7 @@ import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -79,11 +81,30 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; public class AutoAnnotationProcessor extends AbstractProcessor { public AutoAnnotationProcessor() {} + private Elements elementUtils; + private Types typeUtils; + private Nullables nullables; + private TypeMirror javaLangObject; + @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } + @Override + public ImmutableSet<String> getSupportedOptions() { + return ImmutableSet.of(Nullables.NULLABLE_OPTION); + } + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + this.elementUtils = processingEnv.getElementUtils(); + this.typeUtils = processingEnv.getTypeUtils(); + this.nullables = new Nullables(processingEnv); + this.javaLangObject = elementUtils.getTypeElement("java.lang.Object").asType(); + } + /** * Issue a compilation error. This method does not throw an exception, since we want to continue * processing and perhaps report other errors. @@ -104,13 +125,8 @@ public class AutoAnnotationProcessor extends AbstractProcessor { return new AbortProcessingException(); } - private Elements elementUtils; - private Types typeUtils; - @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { - elementUtils = processingEnv.getElementUtils(); - typeUtils = processingEnv.getTypeUtils(); process(roundEnv); return false; } @@ -163,6 +179,7 @@ public class AutoAnnotationProcessor extends AbstractProcessor { vars.generated = getGeneratedTypeName(); vars.members = members; vars.params = parameters; + vars.equalsParameterType = equalsParameterType(); vars.pkg = pkg; vars.wrapperTypesUsedInCollections = wrapperTypesUsedInCollections; vars.gwtCompatible = isGwtCompatible(annotationElement); @@ -186,6 +203,18 @@ public class AutoAnnotationProcessor extends AbstractProcessor { .orElse(""); } + private String equalsParameterType() { + // Unlike AutoValue, we don't currently try to guess a @Nullable based on the methods in your + // class. It's the default one or nothing. + ImmutableList<AnnotationMirror> equalsParameterAnnotations = + nullables + .appropriateNullableGivenMethods(ImmutableSet.of()) + .map(ImmutableList::of) + .orElse(ImmutableList.of()); + return TypeEncoder.encodeWithAnnotations( + javaLangObject, equalsParameterAnnotations, ImmutableSet.of()); + } + /** * Returns the hashCode of the given AnnotationValue, if that hashCode is guaranteed to be always * the same. The hashCode of a String or primitive type never changes. The hashCode of a Class or diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java index dc39e06b..11bc896c 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java @@ -35,6 +35,12 @@ class AutoAnnotationTemplateVars extends TemplateVars { */ Map<String, AutoAnnotationProcessor.Parameter> params; + /** + * A string representing the parameter type declaration of the equals(Object) method, including + * any annotations. + */ + String equalsParameterType; + /** The encoded form of the {@code Generated} class, or empty if it is not available. */ String generated; diff --git a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java index b48d2ea4..a1daebba 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java @@ -69,6 +69,11 @@ public class AutoOneOfProcessor extends AutoValueishProcessor { } @Override + public ImmutableSet<String> getSupportedOptions() { + return ImmutableSet.of(Nullables.NULLABLE_OPTION); + } + + @Override void processType(TypeElement autoOneOfType) { if (autoOneOfType.getKind() != ElementKind.CLASS) { errorReporter() 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 ef1f3098..acc01981 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 @@ -131,14 +131,17 @@ public class AutoValueProcessor extends AutoValueishProcessor { } @Override - public Set<String> getSupportedOptions() { + public ImmutableSet<String> getSupportedOptions() { ImmutableSet.Builder<String> builder = ImmutableSet.builder(); AutoValueExtension.IncrementalExtensionType incrementalType = extensions.stream() .map(e -> e.incrementalType(processingEnv)) .min(naturalOrder()) .orElse(AutoValueExtension.IncrementalExtensionType.ISOLATING); - builder.add(OMIT_IDENTIFIERS_OPTION).addAll(optionsFor(incrementalType)); + builder + .add(OMIT_IDENTIFIERS_OPTION) + .add(Nullables.NULLABLE_OPTION) + .addAll(optionsFor(incrementalType)); for (AutoValueExtension extension : extensions) { builder.addAll(extension.getSupportedOptions()); } 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 a494b9a6..203a5ea5 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 @@ -109,11 +109,13 @@ abstract class AutoValueishProcessor extends AbstractProcessor { private String simpleAnnotationName; private ErrorReporter errorReporter; + private Nullables nullables; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); errorReporter = new ErrorReporter(processingEnv); + nullables = new Nullables(processingEnv); } final ErrorReporter errorReporter() { @@ -461,7 +463,7 @@ abstract class AutoValueishProcessor extends AbstractProcessor { vars.toString = methodsToGenerate.containsKey(ObjectMethod.TO_STRING); vars.equals = methodsToGenerate.containsKey(ObjectMethod.EQUALS); vars.hashCode = methodsToGenerate.containsKey(ObjectMethod.HASH_CODE); - Optional<AnnotationMirror> nullable = Nullables.nullableMentionedInMethods(methods); + Optional<AnnotationMirror> nullable = nullables.appropriateNullableGivenMethods(methods); vars.equalsParameterType = equalsParameterType(methodsToGenerate, nullable); vars.serialVersionUID = getSerialVersionUID(type); } diff --git a/value/src/main/java/com/google/auto/value/processor/Nullables.java b/value/src/main/java/com/google/auto/value/processor/Nullables.java index 3df23222..bcb3ca0a 100644 --- a/value/src/main/java/com/google/auto/value/processor/Nullables.java +++ b/value/src/main/java/com/google/auto/value/processor/Nullables.java @@ -17,13 +17,20 @@ package com.google.auto.value.processor; import static java.util.stream.Collectors.toList; +import com.google.auto.common.MoreTypes; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.ArrayType; @@ -36,9 +43,49 @@ import javax.lang.model.util.SimpleTypeVisitor8; class Nullables { /** + * If set to a non-empty string, defines which {@code @Nullable} type annotation should be used by + * default. If set to an empty string, does not insert {@code @Nullable} unless it is referenced + * in the {@code @AutoValue} methods. If unset, defaults to {@value #DEFAULT_NULLABLE}. + */ + static final String NULLABLE_OPTION = "com.google.auto.value.NullableTypeAnnotation"; + + private static final String DEFAULT_NULLABLE = "org.jspecify.nullness.Nullable"; + + private final Optional<AnnotationMirror> defaultNullable; + + Nullables(ProcessingEnvironment processingEnv) { + // -Afoo without `=` sets "foo" to null in the getOptions() map. + String nullableOption = + Strings.nullToEmpty( + processingEnv.getOptions().getOrDefault(NULLABLE_OPTION, DEFAULT_NULLABLE)); + this.defaultNullable = + (!nullableOption.isEmpty() + && processingEnv.getSourceVersion().ordinal() >= SourceVersion.RELEASE_8.ordinal()) + ? Optional.ofNullable(processingEnv.getElementUtils().getTypeElement(nullableOption)) + .map(t -> annotationMirrorOf(MoreTypes.asDeclared(t.asType()))) + : Optional.empty(); + } + + private static AnnotationMirror annotationMirrorOf(DeclaredType annotationType) { + return new AnnotationMirror() { + @Override + public DeclaredType getAnnotationType() { + return annotationType; + } + + @Override + public ImmutableMap<? extends ExecutableElement, ? extends AnnotationValue> + getElementValues() { + return ImmutableMap.of(); + } + }; + } + + /** * Returns the type of a {@code @Nullable} type-annotation, if one is found anywhere in the * signatures of the given methods. */ + @VisibleForTesting static Optional<AnnotationMirror> nullableMentionedInMethods( Collection<ExecutableElement> methods) { return methods.stream() @@ -53,6 +100,19 @@ class Nullables { .orElse(Optional.empty()); } + /** + * Returns the type of an appropriate {@code @Nullable} type-annotation, given a set of methods + * that are known to be in the same compilation as the code being generated. If one of those + * methods contains an appropriate {@code @Nullable} annotation on a parameter or return type, + * this method will return that. Otherwise, if the <a href="http://jspecify.org">JSpecify</a> + * {@code @Nullable} is available, this method will return it. Otherwise, this method will return + * an empty {@code Optional}. + */ + Optional<AnnotationMirror> appropriateNullableGivenMethods( + Collection<ExecutableElement> methods) { + return nullableMentionedInMethods(methods).map(Optional::of).orElse(defaultNullable); + } + private static Optional<AnnotationMirror> nullableIn(TypeMirror type) { return new NullableFinder().visit(type); } @@ -61,7 +121,7 @@ class Nullables { List<? extends AnnotationMirror> annotations) { return annotations.stream() .filter(a -> a.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) - .map(a -> (AnnotationMirror) a) // get rid of the pesky wildcard + .map(a -> (AnnotationMirror) a) // get rid of the pesky wildcard .findFirst(); } @@ -128,6 +188,4 @@ class Nullables { .orElse(Optional.empty()); } } - - private Nullables() {} } diff --git a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm index 590992ee..5cdad629 100644 --- a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm +++ b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm @@ -213,7 +213,7 @@ final class $className implements $annotationName, `java.io.Serializable` { #end @`java.lang.Override` - public boolean equals(Object o) { + public boolean equals($equalsParameterType o) { if (o == this) { return true; } |