-
Notifications
You must be signed in to change notification settings - Fork 26
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
Questions about cast expressions [working decision: only type components are nullness-applicable] #252
Comments
Looking for feedback from tool owners @msridhar @lazaroclapp @wmdietl @artempyanykh @akozlova. I expect library owners to not care strongly about this one. |
I personally don't have a strong opinion here right now. We don't currently support such casts in NullAway and I don't immediately see why we would need to. Something might arise in the context of generics support; other tool owners can comment on that. If at the spec level we are going to prohibit |
@msridhar I can see not supporting 1/2/4, but the way 3 is worded, one of the approaches listed must apply to you, I think. Which is it, I'd be curious to know? Same for other tools. |
Sorry, I'm having a hard time following 3, could you give a code example? |
Surely.
We don't worry about the nullness of the list itself; an analyzer knows it to be non-null inside the It seems we want the result of the cast to have the type And if that doesn't suitably match But the question is:
Pro-mandatory: we're training users to see Pro-optional: this is a case where nullness information does not need to be declarative, it's fully inferrable. I could be misunderstanding things, of course. |
Perhaps I'm failing to see some angle specific to casts here, but the question about casts in my head is closely related to variable declarations and method calls where we also need to decide 'when we infer and when we don't'. What we settled on is the following:
For instance, List<String> ls = List.of("Hello", null);
We treat casts the same way. On the subject of possibility of inference: in our experience Java's "target typing" makes inference really hard in certain situations; at least it doesn't play nicely with the kind of forward analysis of CFG we use in Nullsafe. This is another reason why we treat explicit type declarations as the source of truth. |
We haven't dug into this in as much detail as @artempyanykh but I am 100% in favor of decisions that do not make the difficult inference problem even more so. So I'm on board with requiring repetition of nullness annotations in casts. |
I think I have some new clarity on what I'm really driving at here. The hope: to keep things simple for users, ideally, all type usages will be neatly classifiable (by simple rules) into just one of these two buckets:
I'm aware of these inapplicable cases only:
The "hope" (above) is that we can complete that list and then make the applicable cases list be:
This issue and #261 are runs at this goal; I want to nail down which bucket they go in, or whether a new third bucket is forced. I think these original questions are both answerable "no":
I will claim that this "no" is in fact already explained by the list above, because the type in a cast expression qualifies as a "categorically non-null type usage". (Sure, the expression will cast null to null, but that's just its standard behavior; the type "operand" in the expression has no bearing on it.) So it's down to the non-root type usages in the cast expression. I'm not sure there's any useful or meaningful way that you can "alter" these nullness bits between the from and to types. If you tried to alter any annotation between the from and to types, I suspect this is either safely "widening", so you shouldn't need to cast; or not widening, so casting only trades one warning for another anyway. (widening example: casting from So our choice is maybe between:
Aside: every time I come back to this and #261 and get myself confused again, I try again to persuade myself that we can just throw up our hands and say "it's implementation code, we don't have to care". But, it would feel a lot better if we were leaving things undefined to accommodate genuine disagreements between the priorities of different tools, and not just because someone got too confused when trying to discuss the issue at all. :-) So, I am trying to hold fast to what I said here:
|
So who would support this option?
Most casts would provide exactly the expected annotations, but by making them be specified explicitly, the code won't be misleading. Some casts might alter them in safe ways that don't need a warning -- this sees like a real edge case, but maybe it could help with type inference in cases? Lastly, most variations of the annotations should produce a warning; as far as I know, it could still be that the cast helps with type inference somehow. |
(Sounds good!)
Ah, I thought I had done a search for this before, but it looks like I hadn't. Here's some code in Guava: return ((Comparator<@Nullable Object>) comparator).compare(o1, o2); I want to say that That said... the way we usually do that is indeed the "trading one warning for another" scenario: SortedSet<@Nullable Object> self = (SortedSet<@Nullable Object>) this; That is, we extract a local variable so that we can localize the |
While I'd still be happy to hear from @wmdietl @artempyanykh @msridhar and others, I think we can consider this working-decided for now. In a cast expression, the root type is "nullness-inapplicable" and other component types are "nullness-applicable", as explained 3 comments up. |
Somewhat belated, but I'm in favour of the current working decision 👍 |
Let me propose a slight restatement: On non-root type components, which as decided here are nullness-applicable, their nullness (?, !, %, *) can differ from what would otherwise have been inferred, and an analyzer can use that new information. It's only that the analyzer should also produce a finding in that location [edit: at least, if the type was made more specific]. (The user might suppress that finding, and that seems like a valid enough way for them to alter the nullness.) Of course, I still maintain that it's not sensible to permit this on the root type at all, as there is a reference-or-lack-of-reference sitting right there that can simply be checked. I am still happy about cleanly defining such locations to be nullness-inapplicable. |
Current decision: Argument for changing: No strong case. Timing: This must be decided before version 1.0 of the spec. Proposal for 1.0: Finalize the current decision. If you agree, please add a thumbs-up emoji (👍) to this comment. If you disagree, please add a thumbs-down emoji (👎) to this comment and briefly explain your disagreement. Please only add a thumbs-down if you feel you can make a strong case why this decision will be materially worse for users or tool providers than an alternative. |
FWIW, my last restatement had two parts and we only voted on the second part. |
For what kinds of usages within cast expressions would JSpecify annotations (
@Nullable/@NonNull
) be required/prohibited/optional?JSpecify has a much higher priority to standardize how APIs are annotated than to do the same for implementation code. That seems to argue for leaving this tool-dependent. But, pre-1.0, it would seem smarter to at least attempt agreement first, and then back off if/when tool owners sufficiently protest.
So:
Can the root type of the cast type be annotated
@NonNull
? The user's goal must be to signal to an analyzer that an expression seen as nullable can't actually have a null value, so that it can be safely assigned to a non-null type (or, I suppose, dereferenced). But there seem to be plenty of good ways to do that already: a conditional, an assertion, passing it throughcheckNotNull
, or suppression. (Plus a less-good-but-okay option too: passing it through a method likeuncheckedCastToNonNull
which just returns its argument.) Plus, even if it was legal, it should generate a warning anyway, by analogy with(T) o
-- both violate the expectation that (non-widening) casts are fully validated at runtime. So little is gained by allowing it.Can the root type in the cast expression be annotated
@Nullable
? As a widening cast... where would this ever be needed? Perhaps for some type inference scenario?When the user is casting "anyway" (i.e. from one base type to another), should the appropriate non-root usages of
@Nullable
(i.e., that you expect in the resulting type) be mandatory/explicit, or optional but inferrable, or prohibited and only inferred? (Or heaven forbid, ignored and only inferred?) My first question is whether they always can be inferred. Mandatory/explicit also helps preserve the general notion that "string means string".3b. If 3 is answered mandatory or optional, how are these to be validated?
The text was updated successfully, but these errors were encountered: