-
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
A single shorthand scheme for augmented types, for prose and other non-source-code contexts #129
Comments
Thanks for the reference to Spec#. I could probably go along with As for making (Maybe this is solvable by replacing I also feel a little sad that annotating more code for nullness may make error messages slightly longer (by adding And I do acknowledge the upside that using |
I'm definitely in favor of this project developing a body of recommendations for tool authors in addition to our specifications. And I think consensus guidelines about error/warning messages are a great fit for that. And I heartily approve of concise notations for this ( |
I've realized that I want a good way to talk about nullness-augmented types in prose, and I think that need and this may be the same need. We often do this in prose by writing out source code, but it's unwieldy. Plus technically we aren't bound by this convention, especially since what source code shows isn't really types anyway; it only shows declarations of types and usages of types, never types "themselves". Optimistically, we can teach users that the source code So then what notation for the other two kinds? I would suggest:
I think it at least makes sense to decorate all three kinds for these reasons
|
I think I'd be good with that in prose. In error messages, I'd feel a little sad adding even one character for the non-null case (i.e., saying Also, the difference from Kotlin's meaning is unfortunate. Neither of those is a deal-breaker on its own. But put them together, and I think we'd be better served by I think it would be OK for prose and error messages to differ in this respect: Prose needs to express "When you write |
I would very very much like us to be able to settle on ONE scheme for how to notate these types. So I think we have some tension over whether non-null should be explicitly marked with One complication is that we're supporting non-null projection of type variables (for the moment), meaning that |
I do think this is important to nail down and rely on in communication/documentation/etc. The suffix that goes naturally with each of the 4 nullness descriptors in Chris's current spec are:
For type variables these are all distinct and all make sense. (Well, maybe the last doesn't, but let's move on) For non-variable reference types in null-marked code, I think the only case I left uncovered is value types (i.e. the 8 primitives) and suffixes should just not apply in that case. So the proposal:
Also, as I think maybe David was the first to suggest, I think I think our reference checker should follow this scheme in its error messages. But if Tool X doesn't want to, okay. We could try out letting these actually show to our internal users and see how it goes. |
I like this proposal generally. I'm a little worried about leaving out the What's wrong with always using |
In error messages in particular, do you think that the lack of For example, if a user saw this...
...I think that it would be pretty clear that the method was returning a nullable value when a non-nullable value was expected. Maybe there are cases in which |
I think we should consider specifications separately from tool outputs. I think specifications, including these design discussions and conformance tests, should always prefer the most unambiguous terms, including On the other hand, tool outputs such as error messages can be freer to prefer shorter forms when they expect users to take context into account. In any case, we don't intend to constrain tool output very harshly. I'd like not to care very much about tool outputs. I don't expect Kotlin to change how they refer to platform types (with |
News: I've been pushing hard that the value type always be spelled
My feeling is that people would find it strange, distracting, and/or overzealous. |
Among tools, my proposal is addressing only the reference checker's output; we won't tell tool owners how to write their error messages. As the issue title says, tools "may take after" these decisions, though.
Note that I'm adding user-facing documentation to that list. We have to call the types we're creating something, and calling them e.g. |
What I feel strongly about is that a lack of a single consistent scheme that we follow everywhere (user guide, javadoc, conformance tests, ref checker messages, glossary, user communications, etc.) will lead to confusion -- again, tool owners will do what they please, but we do have our sphere of control and of influence, and I think we should try to influence them in this direction. It is already an issue of some size that our scheme must diverge from Kotlin's. It can at least be consistent with itself.
I acknowledge those arguments, but I'm proposing that the need for a single consistent scheme outweighs them. |
Hmm, I am confused:
|
I don't think the reference checker has to be concise in its error messages. I do think it would be good if the reference checker's output matched what we do in specifications and conformance tests. I think we should be aggressively unambiguous in specifications and conformance tests. |
If I were drawing a line, I'd probably have:
vs.
In particular, I think users would be best served by agreement between user-facing docs and other checkers -- not that we're trying to force checkers to do anything or vice versa, but we can each nudge one another in either direction. It's awkward because, for the second bucket, both of the following are true:
|
Hey tool owners @artempyanykh @wmdietl @lazaroclapp @msridhar @akozlova @elizarov and anyone else. JSpecify wants to have a consistent shorthand for augmented types, and overall I feel good about the proposal in #129 (comment) What we don't know is whether, assuming JSpecify tried to advance that as the official shorthand, your tools would feel motivated to match it in your error messages and documentation and whatnot. It would help to have some idea about that. We do realize that it is slightly different from what Kotlin does, and Kotlin would not change. |
One possible nice thing about using
vs.
(That still doesn't help once type variables enter the picture, since I imagine that different people could have different opinions on different error message as to whether the advantages of alignment outweigh the disadvantages of the extra
(A fully correct process would have to be more complex than either of those, but they seem like reasonable starting points / heuristics.) (RE: Kotlin: If only we had Unicode everywhere and could output |
I really like this proposal. I've been pondering the idea of using a special notation for a while now; we already do a bit of customization in how we render types, so switching from annotations to symbolic operators would be pretty easy. When it comes to the notation itself:
Regarding p.3 – it makes sense to use Also, we have quite a bit of Kotlin and I can predict that I guess, what I want to understand is why would we prefer explicit Assuming, we always denote types from null-unmarked code as
|
Thanks Artem. I do find this absolutely debatable, a main reason the issue remains open. META-QUESTION I see a lot of value in "one consistent recommended scheme (for everyone who's able to adopt it)". Could we agree that there's enough value that we'd each be willing to follow it even if the CLARIFYING THE TWO OPTIONS First I don't think the options are fully specified yet:
ARGUMENTS Some arguments for no-suffix-means-non-null:
Some arguments for always-suffix-for-non-null:
|
So, I definitely don't mind having a compact, semi-formal, non-context-dependent (e.g. That said, as for making it part of NullAway's error messages, I see two issues with it at a glance:
p.s. Note that, after years of NullAway deployment and successful usage at Uber, we still get plenty of questions that imply that some developers don't have a mental model of NullAway as a type system / type extension. This is not a judgement on them; my understanding of, say, Hadoop, whenever I have to deal with it, is equally shallow. Ideal tooling error messages are: a) meaningful to those who wish to understand them, even without a lot of area knowledge, b) helpful even to those who just wish to 'cargo cult' their way around the tool 😉 cc: @msridhar |
I should have said that in any scenario where a tool is able and willing to explain in readable, pastable code form, it is always welcome to do that. The "consistency" I keep referring to was meant to only cover "when a shorthand is desired, can we use a consistent one". I think, anyway, that it will be desired in enough circumstances including just in prose. |
Agreed here. Not arguing against the main point, just noting that I think we would be unlikely to use such notation in user-facing messages. It might be useful within comments on NullAway's codebase itself or issues in our tracker, and definitely for communicating divergences/similarities with other tools :) |
Would it be okay for our documentation to describe the two notation schemes and to say that some tools, in some contexts, may use one or the other scheme, but that JSpecify specs and documentation will consistently use one (I assume require-suffix)? |
So, separate from whether we will use it in error messages: I wouldn't hate either This decreases ambiguity and makes the meaning explicit without people having to refer to our definition of the notation (i.e. if people see |
From Werner
|
It seems like @wmdietl's concern is based on the fact that our proposed notation occurs after (to the right of) the base type, while annotations occur before (left of). I'm not so concerned about that. The alternative might be |
I'd also be concerned about the verbosity of spelling out annotations, as well as the fact that most code (eventually) would omit |
Rewinding a bunch:
I think I could go for that. It's a little weird not to have the I'm curious if the Kotlin folks (@erokhins) gave thought to this when they were choosing their syntax. I note that they didn't have the array issue, since they use |
The rule is consistent: the mark comes directly after the type component. A nullable array of non-null |
Well... We had the nullable types forever (way before flexible types) and used (Sorry, I'm not in the context, but I hope I got the question +- right) |
This comment assumes #275 is closed with no change (as I've proposed there). Rereading this comment above ... I still find myself more strongly persuaded by the pro- But, if we decide to always use a one-character suffix in all cases, then if anyone really is determined enough to drop the We'd need a character to represent "parametric nullness", like Perhaps depending on #248 then, we might also need a character for "assumed parametric nullness". :-/ |
I still don't love
¹ As outlined in #275 (comment), knowing that a type "has parametric nullness" doesn't exhaustively answer the nullness-subtyping questions you could ask about it. And as for needing another glyph for #248: I just replied to a non-GitHub discussion about it with the claim that both the options for #248 produce another "kind of nullness." So that may be more motivation not to try to represent each "kind" with a distinct glyph. |
That's a fair concern. I've noticed that any time I've used the shorthand I've felt compelled to give a link to the "key", over and over, and something feels a bit dodgy about that. Let's flesh out a "scheme" that achieves the same goals as this one except for not being into the whole, you know, brevity thing. What are those goals?
Without a brevity goal, it makes all the sense in the world for that to look as much like code as possible. (Note that this is really separate from the case of a tool that wishes to output literally pasteable code; I take it as given that when that's what it wants, it should just do that. It doesn't address all the goals here though.)
Overall, it doesn't feel like this would make anyone happy. I think that when writing a type usage in code vs. when talking about the type in a non-code context we may just have different requirements. |
How should we refer to wildcards?
How do we refer to the types returned by
|
Great question. I'm going to capture my slow thought process here even though you're already ahead of most of it. Expression types (and expression context or 'sink' types) are post-capture, so they're sometimes anonymous. Javac error messages get away with calling them Here's
I guess we are stuck defining a syntax, to mean "a 'fresh type-variable' having the following 1 or more upper bounds and the following 0 or more lower bounds". I suppose(?) that might as well be: The upper bounds can come from two places: 0 or more from the wildcard, 1 or more from the corresponding type parameter declaration, and the order of all these bounds should not matter. I don't know whether we should make any attempt to simplify the bound list (but really, if any upper bound is The lower bound can only come from the wildcard (so I don't think there can be multiple). Say Then And for But now we have Since we write " |
It is unfortunate that we end up still using Of course, when inquiring whether something matches a specified type then which capture you labeled as |
I think it should work to separate the topic of defining what the shorthand and longhand forms are from the topic of when we should use the shorthand vs the longhand. And this issue is just trying to define the shorthand.
Yes, let's agree that we can't express everything. I think this gets close enough to "useful", in the spirit of comment. We can go with
I am happy to not need to represent "assumed parametric nullness" as its own thing. |
(It occurs to me that we can get visual alignment by using a trailing space, too:
I can see that, but I'm wondering how often users need that: Typically, they're going to see types because a checker is telling them that those types are incorrect. So they might see, under a javac-style message:
Or:
When you see that, I suspect that either you already understand (in which case an added
Maybe people with more Kotlin experience have a sense of how often (I want to relate this to my favorite conversation, about static imports: It's straightforward to argue for making any given piece of information prominent for the user—in that case, more information about where a method is declared; this this case, more information about the nullness of a type variable. But in the common case, it's information that you don't need, so it ever so slightly makes the information you do need harder to find.) I think that part of the argument for |
Perspectives from people who actually use Kotlin regularly would be welcome, but I dug up a couple threads: These don't seem wildly common, though it's hard to know for sure from the searches I did. (The other bit of good news, of course, is that users will typically need to explicitly write |
Wiki page for this notation exists now. Incomplete, but spells out the goals I think we're shooting for.
Reminiscent of putting slashes around regular expressions when you mention them. (But, most people don't do that.) My guess is that people would bother to do that rarely enough that no one would understand what it means when they do anyway. It also feels a wee bit like an "extra" detail to have to teach people besides just the idea of a suffix. IssuesIssue 1: non-null characterIF a non-null character is needed, what should it be?
Issue 2: parametric characterIF a parametric character is needed, what should it be?
Issue 3: Scheme
Comparison of optionsFor the table below I'll just arbitrarily assume Example signature:
|
I still find myself really reluctant to give this up in the current docs, so I'm going to quasi-optimisticallly retarget for 0.3, knowing we could still bump it back out again. |
To get a "working decision" for now, I claim the simplest thing we can do is:
I can see a likely enough change to For now we are marking issues closed when they have a working decision. |
There's a detailed reasoned argument against putting the nullness signifier before the type arguments. Enough work was put into it that I'm reopening this for now, but the doc I'm referring to should be published. @eamonnmcmanus |
kevin1e100 and I touched on this a bit in #32, and I think it's come up elsewhere. It's not a question that it's JSpecify job to answer, but we should keep it in mind as we discuss naming of annotations -- and maybe features that could add more information to augmented types (like having both lower and upper bounds?). Plus, we have many tool authors following these issues, and the GitHub issue UI is nice (and permits cross-linking with our other discussions). So I'm creating an issue here about the question. If nothing else, the issue may help me resist the urge to talk about this further on other issues :)
One way for tools to print augmented types (in their error messages, in tooltips in IDEs, etc.) is to make them look much like the source --
@Nullable Foo
,@NullnessUnspecified T
, etc. [edit: This is complicated by the existence of both declaration and type-use annotations, either or both of which may be in use in a given file.]Another way is to use special symbols, similar to Kotlin --
Foo?
,T!
, etc.We already gave some thoughts about symbols (especially
!
) in the linked discussion on issue 32. (And I owe kevin1e100 a reply, which I'll put below.)Another thing to think about is that Java already uses
?
for wildcards. This isn't ideal, but it doesn't have to be a fatal flaw: I don't think there's true ambiguity. (I do seem to recall that, the Checker Framework sometimes says things like@Nullable ?
. Under Kotlin rules, that would become??
, which is no good. Perhaps we'd say(?)?
or something.... But I expect most tools to refer to captured type variables instead thereby sidestepping the problem (by producingT1?
, etc.).)We can also consider whether we'll someday have non-nullness-related kinds of type annotations. I don't know that we will, but you can at least imagine having a type-use
@CompileTimeConstant
,@FormatString
,@TypeCompatibleWith
, something related to immutability and thread-safety?? Anyway, my point is that we might end up ouputting types using special symbols for nullness but using annotations for the other cases. That's probably tolerable, too.The text was updated successfully, but these errors were encountered: