diff options
author | cpovirk <cpovirk@google.com> | 2021-09-24 08:19:21 -0700 |
---|---|---|
committer | Google Java Core Libraries <java-core-libraries-team+copybara@google.com> | 2021-09-24 08:22:07 -0700 |
commit | fe3b76240b05c5421e7fe84862705db582f51f55 (patch) | |
tree | daf4dd7f6cbafae35634c820b0b0284050c15c38 | |
parent | f9b25b423ac6c7201d77387efe079ff540954de2 (diff) | |
download | guava-fe3b76240b05c5421e7fe84862705db582f51f55.tar.gz |
Annotate `ClassToInstanceMap` and `MutableClassToInstanceMap` for nullness.
The docs in `ClassToInstanceMap` describe some compromises we have to make to do this as best we can with our existing nullness annotations.
RELNOTES=n/a
PiperOrigin-RevId: 398734814
6 files changed, 88 insertions, 16 deletions
diff --git a/android/guava/src/com/google/common/collect/ClassToInstanceMap.java b/android/guava/src/com/google/common/collect/ClassToInstanceMap.java index 8d454c0c6..9bd826fd9 100644 --- a/android/guava/src/com/google/common/collect/ClassToInstanceMap.java +++ b/android/guava/src/com/google/common/collect/ClassToInstanceMap.java @@ -20,7 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotMock; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import javax.annotation.CheckForNull; /** * A map, each entry of which maps a Java <a href="http://tinyurl.com/2cmwkz">raw type</a> to an @@ -30,8 +30,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableDecl; * <p>Like any other {@code Map<Class, Object>}, this map may contain entries for primitive types, * and a primitive type and its corresponding wrapper type may map to different values. * + * <p>This class's support for {@code null} requires some explanation: From release 31.0 onward, + * Guava specifies the nullness of its types through annotations. In the case of {@code + * ClassToInstanceMap}, it specifies that both the key and value types are restricted to + * non-nullable types. This specification is reasonable for <i>keys</i>, which must be non-null + * classes. This is in contrast to the specification for <i>values</i>: Null values <i>are</i> + * supported by the implementation {@link MutableClassToInstanceMap}, even though that + * implementation and this interface specify otherwise. Thus, if you use a nullness checker, you can + * safely suppress any warnings it produces when you write null values into a {@code + * MutableClassToInstanceMap}. Just be sure to be prepared for null values when reading from it, + * since nullness checkers will assume that vaules are non-null then, too. + * * <p>See the Guava User Guide article on <a href= - * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap"> {@code + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap">{@code * ClassToInstanceMap}</a>. * * <p>To map a generic type to an instance of that type, use {@link @@ -43,12 +54,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableDecl; */ @DoNotMock("Use ImmutableClassToInstanceMap or MutableClassToInstanceMap") @GwtCompatible +@ElementTypesAreNonnullByDefault +// If we ever support non-null projections (https://github.com/jspecify/jspecify/issues/86), we +// we might annotate this as... +// ClassToInstanceMap<B extends @Nullable Object> extends Map<Class<? extends @Nonnull B>, B> +// ...and change its methods similarly (<T extends @Nonnull B> or Class<@Nonnull T>). public interface ClassToInstanceMap<B> extends Map<Class<? extends B>, B> { /** * Returns the value the specified class is mapped to, or {@code null} if no entry for this class * is present. This will only return a value that was bound to this specific class, not a value * that may have been bound to a subtype. */ + @CheckForNull <T extends B> T getInstance(Class<T> type); /** @@ -59,5 +76,6 @@ public interface ClassToInstanceMap<B> extends Map<Class<? extends B>, B> { * null} if there was no previous entry. */ @CanIgnoreReturnValue - <T extends B> T putInstance(Class<T> type, @NullableDecl T value); + @CheckForNull + <T extends B> T putInstance(Class<T> type, T value); } diff --git a/android/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java b/android/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java index 337b4008e..aeabe5d3a 100644 --- a/android/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java @@ -190,7 +190,6 @@ public final class ImmutableClassToInstanceMap<B> extends ForwardingMap<Class<? @Override @DoNotCall("Always throws UnsupportedOperationException") @CheckForNull - @SuppressWarnings("nullness") // TODO(cpovirk): Remove after annotating supertype. public <T extends B> T putInstance(Class<T> type, T value) { throw new UnsupportedOperationException(); } diff --git a/android/guava/src/com/google/common/collect/MutableClassToInstanceMap.java b/android/guava/src/com/google/common/collect/MutableClassToInstanceMap.java index 1f228a8f6..2fcf0d664 100644 --- a/android/guava/src/com/google/common/collect/MutableClassToInstanceMap.java +++ b/android/guava/src/com/google/common/collect/MutableClassToInstanceMap.java @@ -27,20 +27,26 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A mutable class-to-instance map backed by an arbitrary user-provided map. See also {@link * ImmutableClassToInstanceMap}. * * <p>See the Guava User Guide article on <a href= - * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap"> {@code + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap">{@code * ClassToInstanceMap}</a>. * + * <p>This implementation <i>does</i> support null values, despite how it is annotated; see + * discussion at {@link ClassToInstanceMap}. + * * @author Kevin Bourrillion * @since 2.0 */ @GwtIncompatible @SuppressWarnings("serial") // using writeReplace instead of standard serialization +@ElementTypesAreNonnullByDefault public final class MutableClassToInstanceMap<B> extends ForwardingMap<Class<? extends B>, B> implements ClassToInstanceMap<B>, Serializable { @@ -108,11 +114,20 @@ public final class MutableClassToInstanceMap<B> extends ForwardingMap<Class<? ex @Override public Object[] toArray() { - return standardToArray(); + /* + * standardToArray returns `@Nullable Object[]` rather than `Object[]` but only because it + * can be used with collections that may contain null. This collection is a collection of + * non-null Entry objects (Entry objects that might contain null values but are not + * themselves null), so we can treat it as a plain `Object[]`. + */ + @SuppressWarnings("nullness") + Object[] result = standardToArray(); + return result; } @Override - public <T> T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public <T extends @Nullable Object> T[] toArray(T[] array) { return standardToArray(array); } }; @@ -120,6 +135,7 @@ public final class MutableClassToInstanceMap<B> extends ForwardingMap<Class<? ex @Override @CanIgnoreReturnValue + @CheckForNull public B put(Class<? extends B> key, B value) { return super.put(key, cast(key, value)); } @@ -135,17 +151,20 @@ public final class MutableClassToInstanceMap<B> extends ForwardingMap<Class<? ex @CanIgnoreReturnValue @Override + @CheckForNull public <T extends B> T putInstance(Class<T> type, T value) { return cast(type, put(type, value)); } @Override + @CheckForNull public <T extends B> T getInstance(Class<T> type) { return cast(type, get(type)); } @CanIgnoreReturnValue - private static <B, T extends B> T cast(Class<T> type, B value) { + @CheckForNull + private static <B, T extends B> T cast(Class<T> type, @CheckForNull B value) { return Primitives.wrap(type).cast(value); } diff --git a/guava/src/com/google/common/collect/ClassToInstanceMap.java b/guava/src/com/google/common/collect/ClassToInstanceMap.java index a173556b1..9bd826fd9 100644 --- a/guava/src/com/google/common/collect/ClassToInstanceMap.java +++ b/guava/src/com/google/common/collect/ClassToInstanceMap.java @@ -20,7 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotMock; import java.util.Map; -import org.checkerframework.checker.nullness.qual.Nullable; +import javax.annotation.CheckForNull; /** * A map, each entry of which maps a Java <a href="http://tinyurl.com/2cmwkz">raw type</a> to an @@ -30,8 +30,19 @@ import org.checkerframework.checker.nullness.qual.Nullable; * <p>Like any other {@code Map<Class, Object>}, this map may contain entries for primitive types, * and a primitive type and its corresponding wrapper type may map to different values. * + * <p>This class's support for {@code null} requires some explanation: From release 31.0 onward, + * Guava specifies the nullness of its types through annotations. In the case of {@code + * ClassToInstanceMap}, it specifies that both the key and value types are restricted to + * non-nullable types. This specification is reasonable for <i>keys</i>, which must be non-null + * classes. This is in contrast to the specification for <i>values</i>: Null values <i>are</i> + * supported by the implementation {@link MutableClassToInstanceMap}, even though that + * implementation and this interface specify otherwise. Thus, if you use a nullness checker, you can + * safely suppress any warnings it produces when you write null values into a {@code + * MutableClassToInstanceMap}. Just be sure to be prepared for null values when reading from it, + * since nullness checkers will assume that vaules are non-null then, too. + * * <p>See the Guava User Guide article on <a href= - * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap"> {@code + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap">{@code * ClassToInstanceMap}</a>. * * <p>To map a generic type to an instance of that type, use {@link @@ -43,12 +54,18 @@ import org.checkerframework.checker.nullness.qual.Nullable; */ @DoNotMock("Use ImmutableClassToInstanceMap or MutableClassToInstanceMap") @GwtCompatible +@ElementTypesAreNonnullByDefault +// If we ever support non-null projections (https://github.com/jspecify/jspecify/issues/86), we +// we might annotate this as... +// ClassToInstanceMap<B extends @Nullable Object> extends Map<Class<? extends @Nonnull B>, B> +// ...and change its methods similarly (<T extends @Nonnull B> or Class<@Nonnull T>). public interface ClassToInstanceMap<B> extends Map<Class<? extends B>, B> { /** * Returns the value the specified class is mapped to, or {@code null} if no entry for this class * is present. This will only return a value that was bound to this specific class, not a value * that may have been bound to a subtype. */ + @CheckForNull <T extends B> T getInstance(Class<T> type); /** @@ -59,5 +76,6 @@ public interface ClassToInstanceMap<B> extends Map<Class<? extends B>, B> { * null} if there was no previous entry. */ @CanIgnoreReturnValue - <T extends B> T putInstance(Class<T> type, @Nullable T value); + @CheckForNull + <T extends B> T putInstance(Class<T> type, T value); } diff --git a/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java b/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java index 337b4008e..aeabe5d3a 100644 --- a/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java +++ b/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java @@ -190,7 +190,6 @@ public final class ImmutableClassToInstanceMap<B> extends ForwardingMap<Class<? @Override @DoNotCall("Always throws UnsupportedOperationException") @CheckForNull - @SuppressWarnings("nullness") // TODO(cpovirk): Remove after annotating supertype. public <T extends B> T putInstance(Class<T> type, T value) { throw new UnsupportedOperationException(); } diff --git a/guava/src/com/google/common/collect/MutableClassToInstanceMap.java b/guava/src/com/google/common/collect/MutableClassToInstanceMap.java index c3ad9080d..83fbe9447 100644 --- a/guava/src/com/google/common/collect/MutableClassToInstanceMap.java +++ b/guava/src/com/google/common/collect/MutableClassToInstanceMap.java @@ -28,20 +28,26 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.Spliterator; +import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A mutable class-to-instance map backed by an arbitrary user-provided map. See also {@link * ImmutableClassToInstanceMap}. * * <p>See the Guava User Guide article on <a href= - * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap"> {@code + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap">{@code * ClassToInstanceMap}</a>. * + * <p>This implementation <i>does</i> support null values, despite how it is annotated; see + * discussion at {@link ClassToInstanceMap}. + * * @author Kevin Bourrillion * @since 2.0 */ @GwtIncompatible @SuppressWarnings("serial") // using writeReplace instead of standard serialization +@ElementTypesAreNonnullByDefault public final class MutableClassToInstanceMap<B> extends ForwardingMap<Class<? extends B>, B> implements ClassToInstanceMap<B>, Serializable { @@ -119,11 +125,20 @@ public final class MutableClassToInstanceMap<B> extends ForwardingMap<Class<? ex @Override public Object[] toArray() { - return standardToArray(); + /* + * standardToArray returns `@Nullable Object[]` rather than `Object[]` but only because it + * can be used with collections that may contain null. This collection is a collection of + * non-null Entry objects (Entry objects that might contain null values but are not + * themselves null), so we can treat it as a plain `Object[]`. + */ + @SuppressWarnings("nullness") + Object[] result = standardToArray(); + return result; } @Override - public <T> T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public <T extends @Nullable Object> T[] toArray(T[] array) { return standardToArray(array); } }; @@ -131,6 +146,7 @@ public final class MutableClassToInstanceMap<B> extends ForwardingMap<Class<? ex @Override @CanIgnoreReturnValue + @CheckForNull public B put(Class<? extends B> key, B value) { return super.put(key, cast(key, value)); } @@ -146,17 +162,20 @@ public final class MutableClassToInstanceMap<B> extends ForwardingMap<Class<? ex @CanIgnoreReturnValue @Override + @CheckForNull public <T extends B> T putInstance(Class<T> type, T value) { return cast(type, put(type, value)); } @Override + @CheckForNull public <T extends B> T getInstance(Class<T> type) { return cast(type, get(type)); } @CanIgnoreReturnValue - private static <B, T extends B> T cast(Class<T> type, B value) { + @CheckForNull + private static <B, T extends B> T cast(Class<T> type, @CheckForNull B value) { return Primitives.wrap(type).cast(value); } |