-
Notifications
You must be signed in to change notification settings - Fork 26
notation
This page discusses notations to use for null-augmented types (not in code).
(You might want to skip straight to the description of the shorthand notation.)
In prose and in other non-code contexts, how can we identify a specific augmented type? We wish this scheme to be:
- Unambiguous: it is always known precisely which augmented type is meant.
- Unique: we'd rather there weren't two valid ways to refer to the same type.
- Informative: we'd like the nullness of each type component to be readily apparent from looking at it.
- Structured: each type component should be visible as a contiguous substring of the whole type's representation.
- We'll assume that names are fully-qualified whenever necessary; that takes care of name ambiguity.
- We'll assume that unqualified type variable names like
T
are used only when the intended type parameter declaration is clear from context. - We'll set aside questions of how to identify non-denotable types.
Why not just write the code?
With Java base types, this works: we can just write the same string of characters we would write in Java code, like Map<? extends Number, List<T>>
.
But trying to do the same for our augmented types hits a few problems:
- Which augmented type is meant depends on whether we are within null-marked code.
- In null-marked code (or for an "intrinsically non-null type usage"),
Foo
and@NonNull Foo
appear to be distinct types, but are not. - The nullness of an unprojected type variable is not shown (to see whether it is non-null or parametric, we have to go look up its bounds).
- For $reasons, array types have to be written in code in a surprising arrangement:
@Inner String @Outer [] @Middle [] field;
Each of these flaws fails one of our four goals, respectively.
A simple fix to continue using literal Java code is to follow two rules:
- Assume null-UNmarked context.
- Always include a literal
@NonNull
on every non-null type usage, even when redundant. - Just forget about arrays making any sense.
This will serve, except for type variables with parametric nullness, and you might see it in some contexts.
Since non-null types are common, it can be highly unpleasant: @NonNull Map<@NonNull Key, @NonNull int @NonNull []>
. Yuck!
Augmented types can instead be indicated in prose using a shorthand, combining a base type with a symbol representing a nullness indicator.
-
String!
represents the non-null form of the base typeString
: a reference to an actual string object. This might be expressed in Java code as@NonNull String
, or, within null-marked code, as justString
. -
String?
represents the nullable form of the base typeString
: a "string-or-null". This is usually expressed in Java code as@Nullable String
. -
String*
represents the unspecified form, as we would find in fully unannotated code. This is included here to round out the set; a proper explanation of unspecified nullness is in the User Guide. -
T%
(used only for type variables) has parametric nullness.
For a parameterized type, the suffix appears immediately after the class name (Foo!<Bar?>
) rather than after the entire type (Foo<Bar?>!
). (This improves readability, and makes more sense the more you think about it.)
Array notation follows the same principles, but can be a little surprising since we write array types "little-endian". Compare: the type of a nullable list containing non-null lists, in turn containing strings of unspecified nullness, would be written List?<List!<String*>>
. But to do the same thing using arrays instead of lists gives us String*[]![]?
. These look quite different, but in both cases, the component types appear as substrings of the full type (goal #4).
Note: The contents of this wiki are not final, and represent current thinking and discussions.