Feature Request: Allow Supplier<@NonNull Foo> -> Supplier<@Nullable Foo>
It's not safe in general to convert nullness of generic types, but I believe it is safe in one specific (and common) scenario: When the generic type is used only for return values.
E.g.:
String s1;
@Nullable String s2;
s2 = s1; // This is safe
Supplier<String> sup1;
Supplier<@Nullable String> sup2;
sup2 = sup1; // This is safe also.
Currently, sup2 = sup1 triggers a warning, but I think if NullAway could check that generic types are used only in method return types, then it need not warn in this case. Perhaps this would be expensive to compute, and if so, maybe a new annotation?
interface MyInterface<T extends @Nullable @ReturnTypesOnly Object> {...}
(with library model to set this for Supplier)J
This is outside of JSpecify, but looks like Checker Framework supports this with an (unchecked) Covariant annotation (use in Supplier). We could consider supporting something similar in the future.
(related JSpecify issues: https://github.com/jspecify/jspecify/issues/40, https://github.com/jspecify/jspecify/issues/72, and maybe more)
In principle, to do this fully correctly, it's necessary to check not only the implementation of Supplier itself but also any subclasses of Supplier. (Or, alternatively, it's necessary to issue warnings/errors during a downcast from Supplier<Foo> to SubclassOfSupplier<Foo>.) Otherwise, users can declare a subtype that is not actually covariant, as in this example:
$ cat NonCovariant.java
import java.util.function.Supplier;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class NonCovariant {
static class SettableSupplier<T extends @Nullable Object> implements Supplier<T> {
T value;
SettableSupplier(T value) {
this.value = value;
}
@Override
public T get() {
return value;
}
}
public static void main(String[] args) {
Supplier<String> supplier = new SettableSupplier<>("");
Supplier<@Nullable String> nullableSupplier = supplier;
SettableSupplier<@Nullable String> cast = (SettableSupplier<@Nullable String>) nullableSupplier;
cast.value = null;
supplier.get().length();
}
}
$ java -cp $HOME/.m2/repository/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar NonCovariant.java
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because the return value of "java.util.function.Supplier.get()" is null
at NonCovariant.main(NonCovariant.java:26)
But that may not be a big issue in practice: Kotlin's implementation of variance has a long-standing soundness bug, which allows us to write the Kotlin equivalent of the Java code above without any warning, leading to NPE.