Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In JSpecify mode, Generic<Void> is not accepted as a subtype of Generic<@Nullable Void> #1172

Closed
Stephan202 opened this issue Mar 22, 2025 · 5 comments

Comments

@Stephan202
Copy link

See this discussion for context.

This reproduction case shows that the issue is specific to generic type usages:

import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;

final class Test {
  // No warning here: correct.
  @Nullable Void pass() {
    return (Void) null;
  }

  // But there is a warning here: incorrect.
  List<@Nullable Void> fail() {
    return new ArrayList<Void>();
  }
}

This produces the following warning:

[WARNING] /path/toTest.java:[13,12] [NullAway] Cannot return expression of type ArrayList<Void> from method with return type List<@Nullable Void> due to mismatched nullability of type parameters
    (see http://t.uber.com/nullaway )
@msridhar
Copy link
Collaborator

@Stephan202 haven't looked in detail, but in general, Foo<@Nullable Bar> is not a subtype of Foo<Bar> under the JSpecify spec; they are incompatible types. I don't think there is any special case for Foo<@Nullable Void> and Foo<Void>. So maybe you just need to write new ArrayList<@Nullable Void>()? Also I'm pretty sure NullAway won't report an error right now in JSpecify mode if you write new ArrayList<>().

@Stephan202
Copy link
Author

Foo<@Nullable Bar> is not a subtype of Foo<Bar>

Ah, this is about to opposite case. I must admit that I didn't read a recent version of the JSpecify spec, but I would expect Foo<Bar> to be a subtype of Foo<@Nullable Bar>, as the former is "more specific" (as in, "same domain minus null"). Or is that too naive a way to think about it?

So maybe you just need to write new ArrayList<@Nullable Void>()?

Yep, that would work in the given example. Unfortunately the real-world cases involve Mono#then() and other methods that declare a return type of Mono<Void> rather than Mono<@Nullable Void>. (I can work around this by changing the return types in my own code to also omit @Nullable, but that would require disabling VoidMissingNullable, while that rule does make sense.)

@msridhar
Copy link
Collaborator

msridhar commented Mar 23, 2025

Ah, this is about to opposite case. I must admit that I didn't read a recent version of the JSpecify spec, but I would expect Foo<Bar> to be a subtype of Foo<@Nullable Bar>, as the former is "more specific" (as in, "same domain minus null"). Or is that too naive a way to think about it?

Yeah, generics are always confusing. Foo<Bar> and Foo<@Nullable Bar> are actually unrelated by subtyping; neither one is a subtype of the other. The same is true of Java generic types ignoring nullability, as discussed, e.g., here.

Unfortunately the real-world cases involve Mono#then() and other methods that declare a return type of Mono<Void> rather than Mono<@Nullable Void>. (I can work around this by changing the return types in my own code to also omit @Nullable, but that would require disabling VoidMissingNullable, while that rule does make sense.)

Yeah, I think that ideally, that library would be updated to use @Nullable Void everywhere. I think for now, in JSpecify mode you are stuck suppressing warnings for these cases, until the library is updated. Due to NullAway hacks you won't get warnings outside of JSpecify mode. Depending on how this plays out on more codebases we may add a flag to treat Void as @Nullable Void even in JSpecify mode but I'd like to avoid that if at all possible to avoid deviating from the spec.

See jspecify/jspecify#51 for some discussion from the JSpecify side of things.

@Stephan202
Copy link
Author

Foo<Bar> and Foo<@Nullable Bar> are actually unrelated by subtyping

I just realized that subconsciously I've been interpreting the latter as Foo<? extends @Nullable Bar>. 🤦 And indeed, in the original code, instead of SuppressWarnings("NullAway"), I can also avoid the warnings by declaring the return types as Mono<? extends @Nullable Void>. I'll ponder which way to go, for now.

I might also file an issue with Project Reactor to see whether they're open to using @Nullable Void; if so, I'll link it back here. Thanks for the support (and great tool)!

@msridhar
Copy link
Collaborator

Regarding wildcards, NullAway support for that is basically non-existent even in JSpecify mode. So you might get errors in the future 🙂 if we can get the library updated to use JSpecify that's probably the best way to go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants