-
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
Naming the third nullness annotation (the one that matches the behavior of unannotated code) #32
Comments
Cases like Map.get might be rare among declarations, but are quite prominent usage-wise, and the word "legacy" in such core an API might scare or confuse people. How about something more neutral, e.g. "Unknown" or "Unspecified" or "Any"? |
You are right. After saying "it should involve the word legacy", I made the list of names and ended up thinking of I believe the connection to the "unchecked warnings" developers are familiar with actually might be a good one: it's another case of a type conversion where the types are not truly assignable but we let you make the type conversion anyway. Does anyone dislike this name? |
The analogy to |
I like something like |
The more rare the word, the stronger the connection should have to be for us to want to use it. The generics concept of "raw" is more parallel, but it's a more unusual word so we should be convinced it's "a lot" more parallel. We think there's virtually no reason to ever use a raw type (in the generics sense) aside from compatibility and ~3 very narrow edge cases. Would we say the same for this? I thought we would, and that was behind my preference for "legacy", but now I'm moving away from that thanks to things like The word "unchecked", root "check", feels pretty basic/common. We describe the systems that use our annotation as "checkers". In some sense, this annotation is akin to telling those checkers to back off a bit on what they do. The more accurate that description is then the better this name is. That the nullness is "unknown" is something this shares in common with parametric nullness. The difference, I think, is that we're fine with saying that "parametric is not assignable to not-null", full stop, just like nullable is not assignable to not-null, whereas with legacy/unknown/unchecked/whatever, we're willing to make accommodations. (And similarly with "nullable is not assignable to parametric".) |
"Unchecked" for me implies some specific behavior expectations from checkers, and I thought we didn't intend to prescribe how they should work. If combined with more specific annotations (in spirit of This name also seems to imply that the annotations are there mostly for checkers, and not e.g. for documentation or other purposes. This might be fine, I'm just not totally sure it is. |
Thanks for bringing that up. You're right, avoiding seeming to dictate what a checker should do is important, and that should even have an effect on how things are named. So this is a negative for The following may seem weird, but I think it is possible that there is much less of a problem with implying what checkers should not do than there is with implying what they should. The latter part I think we might agree solidly that we need to avoid, but the first part might not be harmful and even be virtuous in making sure library owners are able to annotate properly. I don't know. (In these discussions I think we will lapse into "and the checkers we imagine will do this" over and over. Perhaps it is inevitable that we have to have some imagined checker to shape our thoughts around, and maybe it will be enough if we remember to repeatedly ask ourselves "okay, but how much of that actually needs to be required of checkers?) |
I don't like |
|
Hmm, "unknown" used to always be on the list, and then there was some reason on Monday that seemed clear to me it could be ruled out. Now I don't know why that was. Back on the table. |
We seem to be converging on (Should I still make a case for |
In my experience words like "unknown" will easily entail questions like: "why doesn't the tool know? Can't it figure it out?" etc. At Eclipse we had similar fights with the word "potential", where users are confused whether the danger depends on (a) control flow, (b) instantiation of a generic type, (c) some nullable value (which could still be different from null 😄) etc. pp. From that experience I tend to be explicit about what is the source of uncertainty. In the case here: it's unspecified. OTOH, could s.o. please point me to the discussion, why this concept (which surely is necessary for the semantics) must have an explicit expression in the syntax? To me it feels a bit like requesting a syntax like |
The discussion of why we make the third state explicitly expressible can be found here: #2 |
I like the sound of "unspecified." And it's easy to see how, when you start annotating the class interface Foo {
String a();
String b();
String c();
} ...and move on to... @DefaultNotNull
interface Foo {
String a();
@UnspecifiedNullness String b();
@UnspecifiedNullness String c();
} There, you're telling readers to behave as if the new default specification isn't in effect for some methods' return types (since you, the code owner, haven't evaluated them yet). The main downside I can think of is As @kevinb9n said above, it might help to decide on #33 first. Then maybe we'll have a settled model for what the annotations really mean, like |
The discussion on #45 is making me wonder if a term like |
(I also like "fuzzy" as a description of how the spec proposes for it to work on type parameters: Even if you try to specify |
Our meeting today touched on the idea that we're also naming the "modes" that specify the different behaviors for the third nullness annotation. We've called them "strict" and "lenient," but it could be nice for the names to tie more closely to whatever name we choose for this annotation. |
Or we can approach that in the other direction and name the annotation based on the modes: For example, how is
One concern is whether some of these words sound negative (like "muddy"/"foggy") or positive (like "forgiving"). We might like negative or positive names, depending on whether we choose to handle both (In the spirit of brainstorming: Another idea similar to "raw" (mentioned earlier) is "erased." However, it doesn't really make sense to talk about "erased" nullness on types that were never annotated for nullness.) |
What has appealed to me is that the annotation captures the idea that there is a lack of knowledge/determinacy, and the "modes" are about "how should we behave when we lack knowledge/determinacy?" The latter question leads me directly to the notions of strict/lenient or pessimistic/optimistic. Better leave conservative/liberal out of it these days. :-) |
"gullable" (or maybe the more-dictionary-approved "gullible"), as in "unclear", "undefined", "undecided" are other "negative" possibilities. BTW, it may make sense to separate how we name the concept from how we name the annotation. That has the added advantage that the javadoc for FWIW I like "unspecified", I just worry that it'll make for very long annotation names, but maybe we could use "unspeced" there |
Keeping the names of modes and annotations separate makes sense to me. With 2 terms already in play, I'm hoping that the name for the annotation can match the name of the concept, and the Javadoc can elaborate on that. But if 3 terms works best, then it works best. I still like "unspecified" reasonably well. My newfound fear is that it, like "unknown," could be interpreted as "parametric," as in "The element type in (In our meeting today, someone also raised the strangeness of annotating an element It might help to get a large number of people's opinions on the names (once we have a set of proposals that we're happy with). But I'm not sure how to do that in a realistic way, since real users aren't going to sit down and read the whole manual -- possibly not even any part of the manual. |
The glossary defines (currently) "unknown nullness." It also gives an antomym (which is "known nullness," which leads to the ambiguity over whether parametric nullness is "known," discussed above). It might be nice if the term we picked had a natural antonym (preferably better than "non-fuzzy"...). Or it might be nice for the term not to have a natural antonym, since that means we won't refer to "known" or "specified" nullness in a context in which it's unclear whether that includes parametric nullness or not :) It's not clear to me that we'll need to refer to "known" nullness often, and when we do, it might be just as clear to say "Usually x, but in case of unknown nullness, y" or something similar, without needing a term for the normal case. |
As mentioned in #62 the ambiguity whether "unspecified" includes the parametric case, can be reduced by consistently naming that latter case, either as "parametric", or - as I prefer - to speak of a "type parameter with unconstrained nullness". Would something like "unbounded nullness" in this position be more convincing / closer to Java terminology? |
Thinking of naming the checking modes: does the "lenient" mode exactly mean to be tolerant towards legacy code? Does this make it a "migration" mode? I'd like to signal that this is a mode that should eventually loose significance. |
I think that someone could still reasonably believe any of the following:
I do still think that "unspecified" is better than "unknown" in this regard. And I still worry that "fuzzy" is a little vulnerable to it, too:
We still have some an unresolved question around whether "lenient" mode is solely a legacy/migration mode or not. The main case is |
Another interesting/annoying case: Guava's Joiner.on(",").join(valuesIncludingNull) // throws NullPointerException
Joiner.on(",").skipNulls().join(valuesIncludingNull) // succeeds
Joiner.on(",").useForNull("null").join(valuesIncludingNull) // succeeds Fortunately,
(When I look at annotating Guava next quarter, maybe I'll have more examples.) |
Not strongly, but I'm mildly opposed to abbreviations in API like Having a long name for an annotation that's not supposed to be widely used doesn't seem an issue to me, it might even be an advantage for that purpose. |
I just learned that Clang calls this "null unspecified" (including, IIUC, letting users explicitly specify a type as unspecified). I still like the more explicit (And of course we can also see this as yet another vote in favor of the term "unspecified.") |
FWIW I updated the proposal doc to use NullnessUnspecified. Maybe we can
even dare close this for now?
|
To recap some of the naming discussion earlier in this thread: I agree that "specified to be unspecified" is unfortunate. The reason that we're using that name is that we expect the annotation sometimes will be used as a way to say "no one has considered how to annotate this yet" (i.e., "this is legacy code"). This could come up as someone incrementally annotates a class: Before: class Foo {
Bar bar();
Baz baz();
} Intermediate step: @AnnotatedForNullness
class Foo {
@Nullable Bar bar();
@NullnessUnspecified Baz baz();
} Later: @AnnotatedForNullness
class Foo {
@Nullable Bar bar();
@Nullable Baz baz();
} But it just so happens that the way checkers are likely to handle "legacy code" is exactly the way that some people would like to treat methods like It would be great if we could find a name that covered both purposes. Unfortunately, most more generic names (like |
Thanks for this background and rationale. That is helpful. I think we should use different names for the different cases, rather than seeking a single name to cover both cases.
|
#27 discussed |
If we can't find a name that serves both use cases, we should also consider shifting the focus to what you call the "unspecifiable" case. My thinking:
So maybe we end up with a declaration annotation like |
It sounds like you are in agreement that we need to distinguish the cases of:
For the former, a declaration rather than type annotation for this is a nice solution. For the latter, just "unspecifiable" is a misleading name. The semantics is not unspecifiable, it's just unspecifiable by this version of Jspecify. |
Switching gears: I forget whether we have previously discussed using The reason that I thought of this is that it could help with #100 (by making What we might like more is that it "hides" the "unspecified" case better than a top-level annotation does. After all, we expect that case to be rare. And users who do need that case have surely already seen On the other hand, putting the "unspecified" case into (Another way to attempt the "hiding" goal (but not to help with #100) is to make this third annotation a nested type enclosed by |
If we want to think about an annotation element or nested type, we should also think more about whether we would use those features for possible future additions -- like using it for Would we want it to be possible to express |
While I understand the desire to allow documenting "why" something is
annotated @NullnessUnspecified, I have to say I don't relish the idea of
"hardcoding" various reasons into the annotation language. There's the
obvious problem that we may not cover all reasonable reasons, and then
people will still need to use something. An additional concern of mine is
that having an annotation like this at all is somewhat surprising/confusing
already, and I personally basically hope most people don't have to know
about it; having multiple annotations for the same thing seemingly just
calls more (undue) attention to it. It seems that comments can be used to
express intent, or there was at some point the idea of giving type-use
annotations an explanation string "value" attribute (i.e.,
@NullnessUnspecified("TODO: look at this more closely").
Regarding making "unspecified" a flavor of @nullable, I think that was the
idea behind @LessEnforced. It seemingly has appeal b/c "strict" tools may
be able to "just ignore" the less enforced part and
treat @NullnessUnspecified as just @nullable. I think there were other
reasons against this, but one issue with that is that we may get into hot
water with our "unchecked conversions" that we want tools to apply. For
instance, if a base class method has a parameter
annotated @nullable(butDontTellMe=true), and I override it with @nonnull,
then we want that to be legal (with optional warning), but a tool that
ignores "butDontTellMe" would flag it as a violation of behavioral
subtyping. That is to say, it appears we need *all* tools to do special
things for "unspecified" sometimes (though those cases are admittedly rare,
as most of it falls under checker-specific discretion), and to me it seems
the best way to get that is by having a separate annotation for it.
|
Switching gears again: When will users first be forced to understand unspecified nullness? Often, it will happen when they encounter an error message that mentions it. For example:
In Kotlin-like terms, that's saying:
The appearance of
Possible solutions (some of which require action only by tool authors, not spec authors):
Another thing to recall is that we haven't fully settled whether a
|
Thanks for observing this.
That is in fact what it meant in Spec# I believe. I've at times used It may not be popular, but besides
I understand that this would take us away from Kotlin's error messages and is incompatible with the desire to generally write The other thing I'll say is that while good error messages will be important, I suspect it's probably out of scope for us to specify error messages exactly. So while I support the effort to make sure tools can produce reasonable messages, I also think we want to be careful not to wade too far into the area: seems likely to me that tools will want some room for experimentation. |
On the topic of using symbols for types, I opened #129 and left a reply there. |
Reopen if we reverse course on #137. |
We have seemed fairly comfortable with
@Nullable
and@NotNull
(the latter being a shorthand for@NotNullable
), but oy, the third one.The word "legacy" should be involved; we have never come up with something more appropriate. It is the nullness that all Java types have by default before someone goes through and annotates. Admittedly, people will probably use it (rarely) for special cases with non-legacy code, but we think we want to at least discourage that.
I think we also don't want to talk about it as being more closely related to
@Nullable
than to@NotNull
; its relationship to these two should be symmetric. It represents indecision between the two. Conservative analysis treats it more like@Nullable
when assigning from, but more like@NotNull
when assigning to; lenient analysis would have it the other way around.Names
@LegacyNull
/@DefaultLegacyNull
@LegacyNullness
/@DefaultLegacyNullness
@NullableLegacy
/@DefaultNullableLegacy
(sorts/groups better)@NullUnchecked
/@DefauiltNullUnchecked
(same)@SchroedingersNull
The text was updated successfully, but these errors were encountered: