notation - jspecify/jspecify GitHub Wiki

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

The problem

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.

Non-goals

  • 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.

The simple way doesn't work

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.

Notation

Longhand notation

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 type String: a reference to an actual string object. This might be expressed in Java code as @NonNull String, or, within null-marked code, as just String.
  • String? represents the nullable form of the base type String: 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).

⚠️ **GitHub.com Fallback** ⚠️