Skip to content

@NonNull method type param for a @Nullable class type param confusion #1094

@ben-manes

Description

@ben-manes

I think that I am getting contradictory warnings for this complex scenario. I am trying to annotate a test adapter where a Guava cache implements Caffeine's interfaces so that parameterized tests can assert against both implementations. This allows Guava's to be a golden reference for regression testing, compatibility, and intentional deviations.

The Caffeine cache was annotated where a nullable value is allowed for Map.compute style behaviors, even though null cannot be stored in the cache.

@NullMarked
public interface Cache<K, V extends @Nullable Object> {

  @Nullable
  V getIfPresent(K key);

  V get(K key, Function<? super K, ? extends V> mappingFunction);

  Map<K, @NonNull V> getAll(
      Iterable<? extends K> keys,
      Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends @NonNull V>>
          mappingFunction);
}

As the adapter may return null, I tried adding the annotations

static class GuavaCache<K, V> implements Cache<K, @Nullable V>, Serializable {

  @Override
  public Map<K, @NonNull V> getAll(Iterable<? extends K> keys, Function<? super Set<? extends K>,
      ? extends Map<? extends K, ? extends V>> mappingFunction) { ... }

but this emits the warning

/Users/ben/projects/caffeine/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/GuavaCacheFromContext.java:203: warning: [NullAway] Method returns Map<K, @org.jspecify.annotations.NonNull V>, but overridden method returns Map<K, @org.jspecify.annotations.Nullable V>, which has mismatched type parameter nullability
    public Map<K, @NonNull V> getAll(Iterable<? extends K> keys, Function<? super Set<? extends K>,
                              ^
    (see http://t.uber.com/nullaway )
  Did you mean '@SuppressWarnings("NullAway") @Override'

If I specify that as @Nullable then it is happy and warns at the implementation's return logic. However, I don't believe this is correct given the interface makes it explicitly non-null. I can suppress these or use @NullUnmarked on the implementation. I'm unsure if this is a declaration or analysis mistake (cc @cpovirk).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions