polynull - jspecify/jspecify GitHub Wiki

A polynull method is typically one that can return null only if a particular argument expression is null.

This page will explain the need and then break the news that JSpecify doesn't support it... yet.

The problem (example)

Canonical examples are Class.cast and Optional.orElse. The argument might be null, and the return value might be null, so the standard way to annotate it is like this (simplified depiction):

@NullMarked
class Class</*non-null*/ T> {
  @Nullable T cast(@Nullable Object obj) { ... }
}

Note first that this is not wrong. The problem is that it "loses information": whenever obj is a non-null reference, we know it will return a non-null reference too (it's the same reference!). Since we haven't represented this fact, the caller has to deal with spurious warnings or with unnecessary requireNonNull tests.

(This example happens to be an "if and only if" case, but it's enough to know that non-nullness in guarantees non-nullness out; the other direction is irrelevant.)

A bad and partial workaround

In this particular example we could band-aid it by declaring class Class<T extends @Nullable Object>, but:

  • It's unnatural to force a client to use different Class<String> and Class<@Nullable String> references to affect how this method behaves.
  • And this "band-aid" isn't always an option anyway.

Wishful thinking

What we really wish we could do is this:

@NullMarked
class Class</*non-null*/ T> {
  // this method as before
  @Nullable T cast(@Nullable Object obj) { ... }

  // but also this "overload"!
  T cast(Object obj) { ... }
}

If only nullness information could be used during overload resolution, this would say exactly what we mean!

@PolyNull

A @PolyNull annotation could address this case:

@NullMarked
class Class</*non-null*/ T> {
  @PolyNull T cast(@PolyNull Object obj) { ... }
}

It declares that nullness is conserved between inputs and outputs, or in layperson's terms, "null thing goes in, null thing comes out".

(Note that @PolyNull could appear in three or more places. If used, it must appear on at least one in-type and at least one out-type. Regardless of the number, it simulates the method having two overloads: one where all appearances of @PolyNull are treated as @Nullable, and one where all are ignored.)

However...

JSpecify does not currently include @PolyNull. It might in the future; possibly even before 1.0. In the meantime you have two options:

  • Just mark the types nullable, and give this promise in your documentation instead. This tells callers they are safe to assign the result to a non-nullable type and suppress the consequent nullness finding.
  • Use a tool-specific @PolyNull annotation... and @Nullable. Yes, this is sad. It could be resolved by our supporting @Implies(Nullable.class) in the future.
⚠️ **GitHub.com Fallback** ⚠️