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

Naming the third nullness annotation (the one that matches the behavior of unannotated code) #32

Closed
kevinb9n opened this issue Jun 19, 2019 · 48 comments
Labels
design An issue that is resolved by making a decision, about whether and how something should work. nullness For issues specific to nullness analysis.
Milestone

Comments

@kevinb9n
Copy link
Collaborator

kevinb9n commented Jun 19, 2019

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

@donnerpeter
Copy link
Collaborator

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"?

@kevinb9n
Copy link
Collaborator Author

You are right. After saying "it should involve the word legacy", I made the list of names and ended up thinking of @NullUnchecked, and now I'm growing to like it a lot. As an adjective in prose, "null-unchecked" or (when context is clear) "unchecked".

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?

@cushon
Copy link
Collaborator

cushon commented Jun 20, 2019

The analogy to unchecked is interesting. One potential disadvantage is that it already has a specific meaning / baggage. I also see parallels with rawtypes: aren't legacy nullable types raw with respect to nullness information?

@eaftan
Copy link
Contributor

eaftan commented Jun 20, 2019

I like something like @UnknownNullness. It is descriptive and expresses program semantics.

@kevinb9n
Copy link
Collaborator Author

kevinb9n commented Jun 20, 2019

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 Map.get.

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".)

@donnerpeter
Copy link
Collaborator

"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 @PolyNull), "unchecked" could be wrong, because such declarations would still be checked, just in a more sophisticated way.

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.

@kevinb9n
Copy link
Collaborator Author

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 @Unchecked.

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?)

@kevinb9n kevinb9n changed the title Naming the legacy nullness annotation Naming the third nullness annotation (the one that matches the behavior of unannotated code) Jun 21, 2019
@kevinb9n
Copy link
Collaborator Author

kevinb9n commented Jun 21, 2019

Resolving this probably depends on resolving #32. (EDIT: ha, I meant #33)

@wmdietlGC
Copy link
Collaborator

I don't like @Unchecked for the reasons @donnerpeter gives.
@UnknownNullness seems better.
Some other options: @JavaNullness, @ClassicNullness, @StandardNullness, or @MachineNullness. But they all have their downsides.

@cushon
Copy link
Collaborator

cushon commented Jun 21, 2019

@UnknownNullness is looking OK to me too.

@kevinb9n
Copy link
Collaborator Author

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.

@kevinb9n
Copy link
Collaborator Author

We seem to be converging on @NullnessUnknown.

(Should I still make a case for @NullUnknown?)

@stephan-herrmann
Copy link

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 List<#RAW>, where actually List says the same already.

@wmdietlGC
Copy link
Collaborator

The discussion of why we make the third state explicitly expressible can be found here: #2

@cpovirk
Copy link
Collaborator

cpovirk commented Aug 2, 2019

I like the sound of "unspecified." And it's easy to see how, when you start annotating the class Foo, you might start with...

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 Map.get. It's not wrong to call it "unspecified": If it's explicitly annotated @UnspecifiedNullness in the stubs, then it's by definition unspecified :) But it doesn't get at the question of why. Not that I expect for our annotations to be about the "why" in general. Maybe what's really getting me is that the "why" seems so different here from the usual reason that nullness is unspecified, the usual reason being that no one has gotten around to it yet? That's probably fine: Map.get is going to require some explanation in a FAQ no matter what. I guess I just wish that we had some magical other name that was just as precise as "unspecified" for the case of unannotated code but also general enough to cover the Map.get case, "We are deliberately specifying that this is unspecified" -- which could also be the thing that seems weird to me?

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 @UseTheDefaultNullnessThatIsSetForTheCompilerRunConsumingThisCode or @AssumptionsAboutNullnessMightBeUnsound or whatever.

@cpovirk
Copy link
Collaborator

cpovirk commented Aug 17, 2019

The discussion on #45 is making me wonder if a term like @FuzzyNullness would do a better job of distinguishing this case from parametric nullness. But maybe even "fuzzy" sounds like a possible name for parametric? I do kind of like its general, non-technical feel, since that fits well with its use for Map.get and other judgment calls we make based on human factors. But maybe the name is too generic for its own good.

@cpovirk
Copy link
Collaborator

cpovirk commented Aug 17, 2019

(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 @Nullable as a type argument, the type usages' nullness values remain unclear. Tools might try to look at them and draw conclusions, but they can't get a clear picture or whether an operation is safe or not.)

@cpovirk
Copy link
Collaborator

cpovirk commented Aug 19, 2019

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.

@cpovirk
Copy link
Collaborator

cpovirk commented Aug 19, 2019

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 @LenientNullness? Or there are other similar words we could use:

  • lax
  • relaxed
  • loose
  • tolerant
  • liberal

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 Map.get and unannotated code with this feature. Or we might prefer something neutral (which I've tried to achieve with "fuzzy").

(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.)

@kevinb9n
Copy link
Collaborator Author

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. :-)

@kevin1e100
Copy link
Collaborator

"gullable" (or maybe the more-dictionary-approved "gullible"), as in @Gullable, not @GullableNullness, was suggested to us at JVM Language Summit (credit to Neal Gafter).

"unclear", "undefined", "undecided" are other "negative" possibilities.
"legacy", "runtime", "JVM", "platform" or something like that are maybe also an option that tries to describe it more based on where it comes from.

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 @NullnessUnknown is not "specifier for unknown nullness" ;).

FWIW I like "unspecified", I just worry that it'll make for very long annotation names, but maybe we could use "unspeced" there

@cpovirk
Copy link
Collaborator

cpovirk commented Aug 19, 2019

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 List<T extends @Nullable Object> has unspecified nullness, and the element type in List<@NotNull String> is specified to be 'not null.'"

(In our meeting today, someone also raised the strangeness of annotating an element @UnspecifiedNullness to "specify that it's unspecified." I could live with that weirdness if we otherwise like the term.)

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.

@cpovirk
Copy link
Collaborator

cpovirk commented Aug 20, 2019

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.

@stephan-herrmann
Copy link

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?

@stephan-herrmann
Copy link

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.
By comparison, if I look at how much raw types are still used in today's code (judging from incoming compiler bug reports) I wonder if "raw" as a word is still too nice, not sufficiently clear in signaling that this should go away few years after the introduction of generics ...

@cpovirk
Copy link
Collaborator

cpovirk commented Aug 20, 2019

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?

I think that someone could still reasonably believe any of the following:

  • "A type with parametric nullness doesn't have a specified nullness: The nullness is specified by the instantiation." [wrong]
  • "A type with unconstrained nullness doesn't have a specified nullness: The nullness is specified by the instantiation." [wrong]
  • "A type with unbounded nullness doesn't have a specified nullness: The nullness is specified by the instantiation." [wrong]

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:

  • "A type with unbounded nullness has fuzzy nullness: The nullness is specified by the instantiation." [wrong]

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.

We still have some an unresolved question around whether "lenient" mode is solely a legacy/migration mode or not. The main case is Map.get (which I know you've advocated for @Nullable on in #10 and elsewhere, and which has also been discussed somewhat in #2 and #27, but maybe it deserves its own issue?). If we were to throw out the Map.get case, then that's definite motivation for picking a "bad"-sounding name.

@cpovirk
Copy link
Collaborator

cpovirk commented Sep 6, 2019

Another interesting/annoying case: Guava's Joiner. Many of its methods, like join(Iterable), either accept or reject nulls, depending on whether some other method was called during setup:

Joiner.on(",").join(valuesIncludingNull) // throws NullPointerException
Joiner.on(",").skipNulls().join(valuesIncludingNull) // succeeds
Joiner.on(",").useForNull("null").join(valuesIncludingNull) // succeeds

Fortunately, Joiner is at least somewhat waning in popularity now that the JDK supports joining directly. We'll have to annotate the API with something, though:

  • We could pretend that it requires non-null values, even though that will leave a small (but significant in aggregate) number of users out in the cold. (I might try to estimate the number, but doing so is a little trickier than it should be because some of our Joiner users were automatically migrated from an existing, null-accepting API to the behavior-preserving Joiner equivalent.)
  • We could pretend that it accepts null values, but we've seen bugs filed because of the Joiner NullPointerException. (Some were just cases in which the code needed to be changed to call skipNulls() or useForNull(...). Others were cases in which null should never have gotten into the system in the first place. The latter should become less common as nullness annotations become more common.) This is what the Checker Framework's annotated Guava does, but I'm not sure how complete that project is at this point, so they may have just inherited that from Guava's own annotations, which use @Nullable differently (even though it is in fact the Checker Framework's @Nullable...).
  • We could give it unspecified nullness. Probably this will let users pass a list of non-null values without a warning (since Iterable<@NotNull Foo> is likely to be a subtype of Iterable<? extends @UnknownNullness Object>), and it will give a "strict-mode" warning to other callers.

(When I look at annotating Guava next quarter, maybe I'll have more examples.)

@donnerpeter
Copy link
Collaborator

Not strongly, but I'm mildly opposed to abbreviations in API like @NullUnspec. @NullUnspecified for me means that some null is unspecified, not nullness, and that also feels confusing.

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.

@cpovirk
Copy link
Collaborator

cpovirk commented Dec 3, 2019

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 @NullnessUnspecified, but I grant that I didn't find evidence that Clang's shorter name is confusing. (Maybe someone knows a Clang person to ask about their experiences? @kevinb9n ?)

(And of course we can also see this as yet another vote in favor of the term "unspecified.")

@kevin1e100
Copy link
Collaborator

kevin1e100 commented Dec 3, 2019 via email

@cpovirk
Copy link
Collaborator

cpovirk commented Apr 3, 2020

Elsewhere, @mernst says:

The name @NullnessUnspecified confuses me because it suggests that no one has yet considered how to annotate the code -- somewhat like "legacy code".

Can we think of a better name? Here are some suggestions.
@NullableUnspecifiableByJspecify
@NullnessUnspecifiable
@NullableSometimes
I think they are better than the current name, but I would welcome suggestions of even better names.

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 Activity.findViewById. So we're considering using the same annotation for both purposes -- or at least we're anticipating that users (like Android) may do that in their own APIs. (Or at least we're anticipating that if we decide to provide such an annotation at all. I think we're more likely to than not, but it's up in the air.)

It would be great if we could find a name that covered both purposes. Unfortunately, most more generic names (like @NullableSometimes -- and, to be fair, arguably even @NullnessUnspecified itself) are so generic that they could describe cases other than legacy code. In particular, they might describe what we're calling parametric nullness.

@mernst
Copy link
Contributor

mernst commented Apr 3, 2020

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.

  • A specification should first and foremost be clear and precise. Smooshing together two cases makes specifications less clear, or requires a comment at each use to indicate which of the meanings is intended.
  • We should not assume that every tool will want to handle the cases in the same way. Some tools may already have more precise handling of some cases, such as the Checker Framework does for Map.get. But I would argue this even if I didn't have a concrete example to hand, just based on getting burned by similar assumptions in the past.

@wmdietlGC
Copy link
Collaborator

#27 discussed @LessEnforced as a separate concept from @NullnessUnspecified.

@cpovirk
Copy link
Collaborator

cpovirk commented Apr 6, 2020

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:

  • The case of "I don't want to annotate this yet" might come up a lot when users don't want to annotate whole methods or classes (for which users would use declaration annotations), but...
  • I don't think users will commonly need to annotate only some of the types in a single method signature (for which they would use type annotations). (And if they do, we might supply them with ways to do that besides by using a @NullnessUnspecified type annotation.)

So maybe we end up with a declaration annotation like @AnnotatedForNullness(false) but a type-use annotation like @NullnessUnspecifiable.

@mernst
Copy link
Contributor

mernst commented Apr 6, 2020

It sounds like you are in agreement that we need to distinguish the cases of:

  • I don't want to annotate this yet.
  • I have considered this method and its semantics cannot be precisely captured by the currnt version of Jspecify.

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.

@cpovirk
Copy link
Collaborator

cpovirk commented Apr 13, 2020

Switching gears:

I forget whether we have previously discussed using @Nullable for this but with an element/method/value, as in @Nullable(weWouldLikeToDoBetterThanOutrightNullableButJspecifyIsLettingUsDown = true).

The reason that I thought of this is that it could help with #100 (by making @Nullable @NullnessUnspecified impossible). But I don't think that should be a significant motivation.

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 @Nullable, so they might understand this new annotation as "nullable, but ...." We can still hold out hope that all we really need to provide is @DefaultNonNull and @Nullable: Such a simple API makes it easy for users to get started, and it gives us some breathing room to add other nullness annotations down the line (@PolyNull? @EnsuresNonNull?).

On the other hand, putting the "unspecified" case into @Nullable is in some ways the opposite of hiding it: I would have to look into how IDEs handle autocompletion for annotations, but we might be inadvertently thrusting the "unspecified" case in front of users who are just trying to type @Nullable.

(Another way to attempt the "hiding" goal (but not to help with #100) is to make this third annotation a nested type enclosed by @Nullable: @Nullable.WeWouldLikeToDoBetterThanOutrightNullableButJspecifyIsLettingUsDown. This might be better or worse than the approach of using an annotation element, depending on IDE autocompletion, Javadoc rendering, and probably other factors.)

@cpovirk cpovirk mentioned this issue Apr 13, 2020
@cpovirk
Copy link
Collaborator

cpovirk commented Apr 13, 2020

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 @PolyNull.

Would we want it to be possible to express onlyJointly and weWouldLikeToDoBetterThanOutrightNullableButJspecifyIsLettingUsDown together? Probably not. That's an argument for not doing both as annotation elements. Not that anyone was proposing doing even one of them that way until just now :)

@kevin1e100
Copy link
Collaborator

kevin1e100 commented Apr 13, 2020 via email

@cpovirk
Copy link
Collaborator

cpovirk commented Sep 23, 2020

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:

error: incompatible types: @Nullable Map<@NullnessUnspecified Foo, @NullnessUnspecified Bar> cannot be converted to Map<Foo, Bar>

In Kotlin-like terms, that's saying:

Type mismatch: inferred type is Map<Foo!, Bar!>? but Map<Foo, Bar> was expected

The appearance of @NullnessUnspecified in error messages changes how I feel about the long name:

  • Before: If people should rarely write @NullnessUnspecified in source code, we probably don't mind making them write a long name.

  • After: If people may see @NullnessUnspecified frequently in error messages, then we may worry more about the length. In particular, I'm sad that @NullnessUnspecified is so noisy when it is never the cause of the failure -- at least in "lenient mode." Look back at the error message above: @NullnessUnspecified is the most prominent but least important part! (That's arguably made even sadder by the fact that the token "@NullnessUnspecified" never appears in the source file I'm imagining.)

Possible solutions (some of which require action only by tool authors, not spec authors):

  1. Lenient-mode tools could hide @NullnessUnspecified from their output altogether. The downside: This will eventually confuse someone: "Why can I return a @Nullable T from this method that returns T [which secretly means @NullnessUnspecified T] but not from this other method that returns T [which means 'just T']?" This confusion would not exist if we were honest and said "@NullnessUnspecified T" in the first place. Similarly, users may be confused when they get a NullPointerException from dereferencing an object that their tools say is of type "String" (when the tools mean "@NullnessUnspecified String"). @NullnessUnspecified is already complicated, given that it's the default for unannotated code, so I worry about making it more complicated.

  2. Tools could print error messages in "Kotlin style" -- that is, by printing ! and ? instead of annotations. The downside: This style is less familiar to some users (though more familiar to others). And somehow, users need to understand the mappings @Nullable <-> ? and @NullnessUnspecified <-> ! (unlike in pure Kotlin, which uses ? for both source code and error messages). (Additionally, I don't love the use of ! for "unspecified": To me, ! suggests "not null" (for which Kotlin uses !!). Maybe tools would pick a different symbol (~, ¿, ...) but at the cost of familiarity to Kotlin users.)

  3. We could pick a shorter name for the annotation. The downside: Not only is the name less clear in source, it is less clear in error messages -- which, again, is when many users will encounter it for the first time.

Another thing to recall is that we haven't fully settled whether a @NullnessUnspecified type annotation will even exist [edit: #137]. Suppose for a moment that it does not:

  • In one way, that simplifies things: We can say whatever we want in error messages, since there will be no equivalent source code that we would want them to correspond to.

  • In another way, it complicates things: No matter what we pick for our error messages (@NullnessUnspecified, unspecified, [@Nullable?], @???, !, ...), users who want to understand it can no longer just look up the class with that name. (And note that symbols like ? are particularly hard to query for in your favorite search engine. Tools can at least try to help by linking to useful documentation.)

@kevin1e100
Copy link
Collaborator

kevin1e100 commented Sep 28, 2020

@NullnessUnspecified is so noisy when it is never the cause of the failure

Thanks for observing this.

To me, ! suggests "not null"

That is in fact what it meant in Spec# I believe. I've at times used !?, FWIW, but am not claiming it's a great solution.

It may not be popular, but besides String!? there does seem to be another reasonable solution: just String could be used to mean @NullnessUnspecified String / String!? in error messages as long as both other options, @NonNull String / String!! and @Nullable String / String?, are always made explicit. This would remove "unspecified" clutter as desired and is consistent with the observation that

the token "@NullnessUnspecified" never appears in the source file I'm imagining.

I understand that this would take us away from Kotlin's error messages and is incompatible with the desire to generally write String!! as String. Instead, this solution would leverage "unspecified" as the default-default (so should still be reasonably intuitive to Java users).

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.

@cpovirk
Copy link
Collaborator

cpovirk commented Sep 28, 2020

On the topic of using symbols for types, I opened #129 and left a reply there.

@kevinb9n
Copy link
Collaborator Author

Reopen if we reverse course on #137.

@kevinb9n kevinb9n closed this as not planned Won't fix, can't repro, duplicate, stale Sep 20, 2022
@kevinb9n kevinb9n modified the milestones: Post-1.0, 0.3 Dec 2, 2022
@kevinb9n kevinb9n added the design An issue that is resolved by making a decision, about whether and how something should work. label Mar 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design An issue that is resolved by making a decision, about whether and how something should work. nullness For issues specific to nullness analysis.
Projects
None yet
Development

No branches or pull requests

10 participants