Conceptually, the defaultValue parameters to the stringValue and stringSetValue methods can be @Nullable, but IFoo#put can not take null. So I'm looking at making approximately this change:
static IFoo<@PolyNull String> stringValue(@PolyNull String defaultValue) { ... }
// Here's where things start to get weird - where do we specify @PolyNull in the return type?
static IStringSetFoo stringSetValue(@PolyNull ImmutableSet<String> defaultValue) { ... }
interface IFoo<@PolyNull T> {
@PolyNull T get(); // If defaultValue was null, this might return null; otherwise, @NonNull
void put(@NonNull T t); // This can never take null
}
interface IStringSetFoo extends IFoo<@PolyNull Set<String>> {
@PolyNull ImmutableSet<String> get();
}
This slightly tweaked code compiles fine and the nullness checker on the library itself passes, but when I try to use this from a client and run the checker:
IStringSetFoo subclassNonNull = stringSetValue(ImmutableSet.of());
IStringSetFoo subclassNullable = stringSetValue(null);
static void doThingWithSubclass(IStringSetFoo foo) { foo.get(); }
static void doThingWithSuperclass(IFoo<Set<String>> foo) { foo.get(); }
static void doThingWithSuperclassNullable(IFoo<@Nullable Set<String>> foo) { foo.get(); }
static void doThingWithSuperclassNonNull(IFoo<@NonNull Set<String>> foo) { foo.get(); }
static void doThingWithSuperclassPolyNull(IFoo<@PolyNull Set<String>> foo) { foo.get(); }
// What happens when we use the implicit @NonNull variant?
doThingWithSubclass(subclassNonNull); // OK
// found : @Initialized @NonNull IStringSetFoo
// required: @Initialized @NonNull IFoo<@Initialized @NonNull Set<@Initialized @NonNull String>>
doThingWithSuperclass(subclassNonNull); // Should conceptually be fine
// found : @Initialized @NonNull IStringSetFoo
// required: @Initialized @NonNull IFoo<@Initialized @Nullable Set<@Initialized @NonNull String>>
doThingWithSuperclassNullable(subclassNonNull); // Should conceptually be fine
// found : @Initialized @NonNull IStringSetFoo
// required: @Initialized @NonNull IFoo<@Initialized @NonNull Set<@Initialized @NonNull String>>
doThingWithSuperclassNonNull(subclassNonNull); // Should conceptually be fine
doThingWithSuperclassPolyNull(subclassNonNull); // OK
// What about the @Nullable variant?
doThingWithSubclass(subclassNullable); // OK
// found : @Initialized @NonNull IStringSetFoo
// required: @Initialized @NonNull IFoo<@Initialized @NonNull Set<@Initialized @NonNull String>>
doThingWithSuperclass(nullable); // Should probably conceptually be an error, because there's an implicit @NonNull on the generic type in the method signature
// found : @Initialized @NonNull IStringSetFoo
// required: @Initialized @NonNull IFoo<@Initialized @Nullable Set<@Initialized @NonNull String>>
doThingWithSuperclassNullable(subclassNullable); // Should conceptually be fine
// found : @Initialized @NonNull IStringSetFoo
// required: @Initialized @NonNull IFoo<@Initialized @NonNull Set<@Initialized @NonNull String>>
doThingWithSuperclassNonNull(subclassNullable); // Certainly a real error
doThingWithSuperclassPolyNull(subclassNullable); // OK