-
Notifications
You must be signed in to change notification settings - Fork 10.9k
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
Nullness javadocs are unclear and break certain static analyzers #6126
Comments
I appreciate this report and am sorry for the trouble. I think we have the following situation:
So I think the main question here is whether our change was premature; i.e. whether there's a reasonable way for us to execute this transition more smoothly for you. Note that the old signature did have a bug: it would not accept an It is true that in the new world, type parameters are declared as either nullable-bounded ( There is a lot more information about the new semantics to be published very soon. I'm working on it right now. I'm happy to continue this conversation, too. |
NullAway maintainer here. This definitely is already in our radar (cross-ref: uber/NullAway#628, cc: @msridhar) It appears that in all cases where
Are we getting this right? Also, if we are, is this something we can rely on for Guava 31+ (at least for the foreseeable future)? I think the best path forward for NullAway is to, in the short term, force In the long term, this will be moot once we support full JSpecify semantics. In fact, we will then have to remove the alias to avoid the lost of precision involved in always considering |
Thanks, Richard, and sorry for the trouble. One question to help me understand the scope of the problem: Are you setting the |
Thanks all for the quick responses. Greatly appreciated. A few things to call out.
I actually avoided mentioning this because AFAIK this was intended to be a package-private implementation detail of gauva. I actually think at least some of the documentation problems could be avoided by making it public (and having a single consistent annotation with more documentation) and thus also making it clear on the parameter/method that the nullness properties are indeed defined by the generic type. I think this also may have some nice fail-safe behaviour whereas people can decide what this means until we support it more fully. One thing to call out here is that the
I can confirm that we are indeed doing this in common build logic. Agree that this does make the problem less widespread, but still sad. Guava is also big enough that I think we'd be sad about turning this off for the entirety of the 31 version, but curious to hear your thoughts.
Super excited to hear about this. I think this item that you called out above is most critical: "I think the main question here is whether our change was premature; i.e. whether there's a reasonable way for us to execute this transition more smoothly for you." I'm 100% excited about having better annotations here, I'm just hoping that we can make sure the transition to that (hopefully near) future is smooth. |
For what it's worth, this is not an uncommon configuration. I think that, if not currently, at various points we have also relied internally on the fact that Guava is annotated for nullness (not that it isn't anymore, but the change in how it is annotated will definitely confuse various tools, including NullAway).
Ah, I actually didn't realize the annotation was package private! I think NullAway should be able to see it either way, but it certainly makes me even more concerned to depend heavily on it. Is there any way we could get this upgraded to at least a transitional annotation that tools that don't currently support nullable annotations on type parameters can rely upon? |
RE: As for the meaning, you've got it, including that the main consumer is Kotlin. (See the only marginally improved docs from the latest release.) It's defined to undo the effect of The package-private I was going to say that there's agreement between us and NullAway that that makes If NullAway does someday recognize |
) Guava versions from 31.0 onwards do away with annotating methods as returning or taking `@Nullable` if nullability depends on the type parameter. Instead adopting JSpecify semantics for precise handling of type parameters. For compatibility (with Kotlin, among other tools), Guava still marks such returns and arguments specially as `@ParametricNullness`. While imprecise, we can handle Guava as annotated code to at least the same level as we did previously, by treating `@ParametricNullness` as an alias for `@Nullable`, until we have full type parameter / generics support within NullAway. To test this change without bumping our own Guava dependency, we introduce a new test target: `:guava-recent-unit-tests`. Additionally, since there are actually multiple instances of `@ParametricNullness` in Guava (one per subpackage), we hard-code a check for `com.google.common.*.ParametricNullness` in our core `isNullableAnnotation(...)` check. An alternative would be to extend `-XepOpt:NullAway:CustomNullableAnnotations` to allow simple names or regular expressions, but that would make the mechanism more costly in common case. See also: #628, google/guava#6126
😉 In all fairness, that's on master branch right now. We are aiming to cut a release before the end of the week if we can, though. |
Appreciate all the help and support on this issue everyone. Looking forward to see what happens with the jspecify.dev stuff |
Sorry, I have to take that back—though I do think I have a workaround. The problem is that we didn't just replace parameters' String go(AbstractFuture<Object> future) throws Exception {
return future.get().toString();
}
My tentative plan for a workaround is to change our return types to use a different annotation, one that will still be recognized by Kotlin but that will be ignored by NullAway. That should mean that uber/NullAway@6ab3dcd will do the right thing after we cut a new Guava release. I'm hoping to get to that early next week. |
A few questions about this. First, AbstractFuture looks to have a Second, my understanding is that there are going to be a few different cases here:
Is the new annotation going to only be used for the first case? What does ParametricNullness mean for the purpose of the two return types? Third, what does this mean for version compatibility and does this matter? I believe that it would mean that if Nullaway increments from version 0.9.8 to 0.9.9, and guava goes from 31.1 to 31.2 that:
|
Nowadays, Previously, the parameter type was indeed The goal at this point is to annotate things completely. It's true that, among the many places where we could have used I want to think more about your questions about version compatibility. It might be that we're better off picking two new names for our annotations in the next Guava release. I fear that there are tradeoffs either way: The best option for NullAway users who've included us in (I'm leaving hanging your questions about the meaning of |
So, reading this discussion, it sounds to me like:
Not sure to what degree we expect (2) to be a big departure in terms of what has been previously annotated Either way, it looks like releasing NullAway 0.9.9 with p.s. Not sure how realistic "skip this Guava version" is as general advice, Guava tends to be a dependency that gets forced to one version or another by virtue of what the rest of your dependencies are built against. |
Based on @lazaroclapp 's comment, including enhancements to arrive in NullAway 0.9.9, we'll try leaving Guava as it is for now: Putting Guava in The good news is that we didn't add We'd like to hear how this goes for users once they're able to adopt the NullAway change. (I'm also trying to experiment with this a little myself.) The best way for us to minimize user pain will be for Guava and tools to adopt an annotation model that is more suited to generics, but in the meantime, we can monitor the effects of this intermediate step and consider alternatives. I'm also merging some expanded docs for |
I'm looking at the changes in 31.0 for enhanced null-checking, and I'm trying to validate that my understanding of the change is correct and bring attention to some issues I am having with the approach.
As an example, let's look at one of the methods that changed from 30 to 31:
Iterables.findFirst()
. Comparing the signatures in javadoc we have:public static <T extends @Nullable Object> T getFirst(Iterable<? extends T> iterable, T defaultValue)
public static <T> @Nullable T getFirst(Iterable<? extends T> iterable, @Nullable T defaultValue)
If I understand correctly, nothing has changed in the contract, and in both versions the returned object can be null, the
defaultValue
can be null and theiterable
cannot be null. The issues we are having are as follows:@Nullable String value = Iterables.getFirst(ImmutableList.<String>of(), null)
because it appears to the static analyzer that second parameter ofIterable#getFirst
is null hostile even though this is correct code.V
is actually nullable. I have to look at the class signature itself. Compare this to the old javadoc. Even for static methods, it's unclear that the Nullable actually applies to instances of typeT
in the signature as the annotation is on the base typeObject
. Is this a common java convention that I just haven't seen before?There are a couple other questions I have (how does this convention apply when there is a non-null param and a nullable return, or vice versa) but the ones above are the most pressing.
The text was updated successfully, but these errors were encountered: