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 934cae6a6e..4581f6e1e2 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 @@ -18,7 +18,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; +import static java.lang.annotation.ElementType.TYPE_USE; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; @@ -27,6 +30,7 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.math.BigInteger; import java.time.LocalTime; import java.util.AbstractSet; @@ -37,6 +41,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import javax.lang.model.SourceVersion; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -751,4 +756,35 @@ public void builderAnnotationsCopiedIfRequested() { .asList() .contains(myAnnotationBuilder().value("thing").build()); } + + @Target(TYPE_USE) + public @interface Nullable {} + + public static T frob(T arg, U notNull) { + return arg; + } + + @AutoBuilder(callMethod = "frob") + interface FrobCaller { + FrobCaller arg(T arg); + + FrobCaller notNull(U notNull); + + T call(); + + static FrobCaller caller() { + return new AutoBuilder_AutoBuilderTest_FrobCaller<>(); + } + } + + @Test + public void builderTypeVariableWithNullableBound() { + // The Annotation Processing API doesn't see the @Nullable Object bound on Java 8. + assumeTrue(SourceVersion.latest().ordinal() > SourceVersion.RELEASE_8.ordinal()); + assertThat(FrobCaller.<@Nullable String, String>caller().arg(null).notNull("foo").call()) + .isNull(); + assertThrows( + NullPointerException.class, + () -> FrobCaller.<@Nullable String, String>caller().arg(null).notNull(null).call()); + } } diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java index b925487433..279727a166 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java @@ -30,6 +30,7 @@ import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; +import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -39,8 +40,8 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.TypeVariable; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.OptionalDouble; @@ -934,7 +935,7 @@ public void nestedOptionalGetter() { public abstract static class PropertyBuilderWildcard { public abstract List list(); - public static PropertyBuilderWildcard.Builder builder() { + public static PropertyBuilderWildcard.Builder builder() { return new AutoValue_AutoValueJava8Test_PropertyBuilderWildcard.Builder<>(); } @@ -964,4 +965,82 @@ public void propertyBuilderWildcard() { builder.listBuilder().add("foo"); assertThat(builder.build().list()).containsExactly("foo"); } + + @AutoValue + public abstract static class NullableBound { + public abstract T maybeNullable(); + + public static NullableBound create(T maybeNullable) { + return new AutoValue_AutoValueJava8Test_NullableBound<>(maybeNullable); + } + } + + @Test + public void propertyCanBeNullIfNullableBound() { + assumeTrue(javacHandlesTypeAnnotationsCorrectly); + // The generated class doesn't know what the actual type argument is, so it can't know whether + // it is @Nullable. Because of the @Nullable bound, it omits an explicit null check, under the + // assumption that some static-checking framework is validating type uses. + NullableBound<@Nullable String> x = NullableBound.create(null); + assertThat(x.maybeNullable()).isNull(); + } + + @AutoValue + public abstract static class NullableIntersectionBound< + T extends @Nullable Object & @Nullable Serializable> { + public abstract T maybeNullable(); + + public static + NullableIntersectionBound create(T maybeNullable) { + return new AutoValue_AutoValueJava8Test_NullableIntersectionBound<>(maybeNullable); + } + } + + @Test + public void propertyCanBeNullIfNullableIntersectionBound() { + assumeTrue(javacHandlesTypeAnnotationsCorrectly); + // The generated class doesn't know what the actual type argument is, so it can't know whether + // it is @Nullable. Because of the @Nullable bound, it omits an explicit null check, under the + // assumption that some static-checking framework is validating type uses. + NullableIntersectionBound<@Nullable String> x = NullableIntersectionBound.create(null); + assertThat(x.maybeNullable()).isNull(); + } + + @AutoValue + public abstract static class PartlyNullableIntersectionBound< + T extends @Nullable Object & Serializable> { + public abstract T notNullable(); + + public static + PartlyNullableIntersectionBound create(T notNullable) { + return new AutoValue_AutoValueJava8Test_PartlyNullableIntersectionBound<>(notNullable); + } + } + + @Test + public void propertyCannotBeNullWithPartlyNullableIntersectionBound() { + assumeTrue(javacHandlesTypeAnnotationsCorrectly); + assertThrows(NullPointerException.class, () -> PartlyNullableIntersectionBound.create(null)); + } + + @AutoValue + public abstract static class NullableVariableBound { + public abstract T nullOne(); + + public abstract U nullTwo(); + + public static NullableVariableBound create( + T nullOne, U nullTwo) { + return new AutoValue_AutoValueJava8Test_NullableVariableBound<>(nullOne, nullTwo); + } + } + + @Test + public void nullableVariableBound() { + assumeTrue(javacHandlesTypeAnnotationsCorrectly); + NullableVariableBound<@Nullable CharSequence, @Nullable String> x = + NullableVariableBound.create(null, null); + assertThat(x.nullOne()).isNull(); + assertThat(x.nullTwo()).isNull(); + } } diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java index bd92efa627..a42929076e 100644 --- a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java +++ b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java @@ -80,11 +80,13 @@ public static void setSourceRoot() { private static final Predicate JAVA_FILE = f -> f.getName().endsWith(".java") && !IGNORED_TEST_FILES.contains(f.getName()); - private static final Predicate JAVA8_TEST = - f -> - f.getName().equals("AutoValueJava8Test.java") - || f.getName().equals("AutoOneOfJava8Test.java") - || f.getName().equals("EmptyExtension.java"); + private static final ImmutableSet JAVA8_TEST_FILES = + ImmutableSet.of( + "AutoBuilderTest.java", + "AutoOneOfJava8Test.java", + "AutoValueJava8Test.java", + "EmptyExtension.java"); + private static final Predicate JAVA8_TEST = f -> JAVA8_TEST_FILES.contains(f.getName()); @Test public void compileWithEclipseJava7() throws Exception { 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 a7293f26d1..b7bee4297c 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 @@ -78,6 +78,7 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleAnnotationValueVisitor8; @@ -715,8 +716,7 @@ private static boolean gettersAllPrefixed(Set methods) { * parameter type for a parameter. */ static Optional nullableAnnotationFor(Element element, TypeMirror elementType) { - List typeAnnotations = elementType.getAnnotationMirrors(); - if (nullableAnnotationIndex(typeAnnotations).isPresent()) { + if (isNullable(elementType)) { return Optional.of(""); } List elementAnnotations = element.getAnnotationMirrors(); @@ -736,6 +736,34 @@ private static OptionalInt nullableAnnotationIndex(List 10) { + return false; + } + List typeAnnotations = type.getAnnotationMirrors(); + // TODO(emcmanus): also check if there is a @NonNull bound and return false if so. + if (nullableAnnotationIndex(typeAnnotations).isPresent()) { + return true; + } + if (type.getKind().equals(TypeKind.TYPEVAR)) { + TypeVariable typeVariable = MoreTypes.asTypeVariable(type); + TypeMirror bound = typeVariable.getUpperBound(); + if (bound.getKind().equals(TypeKind.INTERSECTION)) { + return MoreTypes.asIntersection(bound).getBounds().stream() + .allMatch(t -> isNullable(t, depth + 1)); + } + return isNullable(bound, depth + 1); + } + return false; + } + private static boolean isNullable(AnnotationMirror annotation) { return annotation.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable"); }