type usages - jspecify/jspecify Wiki
The length of this page might make type usages seem complicated! And it's true that there is a lot to say -- but it is at heart a simple concept.
Wherever a program uses a value in some way, there is a static type, which controls which values are acceptable. No value that doesn't conform to that static type will get through. Static types also crop up in a few other places, like a class's supertype, where they'll serve that same purpose but indirectly.
This static type might be explicit in the source code, or it might be implicit (for example, wherever
var is used), but it is always deterministic at compile-time.
Static types include:
- A type for every non-void expression (the type it will produce), which is calculated by the type system rather than explicit in the code
- An expected type for every expression context (anything that contains an expression, consuming its value), also calculated
- And the topic of this page: type usages; usually (but not always) explicit
In essence we've just defined "type usages" as "all the appearances of a static type except expression types and expression-context types", which is admittedly a weaksauce definition. But the easiest way to understand what it is is to browse all the different kinds; it should become clear what unites them.
Kinds of type usages
In type contexts (directly)
Most type usages sit directly in a type context. A type context has one of the following kinds.
In signatures / APIs
The type of a:
- Field (for an enum constant, this is implicit)
Parameter, in a method or constructor, treating
- Method return type
- Record component (serving as all of the above at once)
Thrown exception (i.e., after
Supertype in a class declaration (implicitly
Upper bound in a type parameter declaration (implicitly
- Receiver parameter (rare)
In implementation code (expressions and statements)
- Of a variable
- A local variable (in a statement or pattern)
- A caught exception (exception parameter)
- A lambda parameter (often implicit)
- In a cast or
::new(class to instantiate, supertype for an anonymous class, or array type)
- Of a type argument supplied to a generic method or constructor (often implicit)
- In an unbound method reference (a reference to a static method has only a class usage)
As type components
A type usage may also be a type component within some other type (which itself sits, at some depth, in one of the type contexts above).
- In a parameterized type: a type argument, or a bound of a wildcard type argument
- In an array type: its component type
- In an inner type expression: its outer type; for example,
Foo<Bar>.Quxhas a type component
Foo<Bar>(name-qualifying a static nested type, like
Map.Entry<Foo, Bar>, is not an inner type expression)
- In an intersection type: its constituent types (TODO: does this make sense?)
What can these types be?
The types that can serve as these static types include:
- Predefined types, like
intor the null type
- User-defined types
- By class declaration, like
Stringor an anonymous class type
- By type parameter declaration, called a "type variable", like
- By class declaration, like
- Composed types, assembled ad-hoc from other types, like
Even non-denotable types can have type usages (implicit ones, of course).
Not type usages
An import statement is an example of a class usage, not a type usage. Many kinds are listed here.
Don't try to memorize the list, though. An easy way to tell the difference is if the usage would still make logical sense even for a non-instantiable class like
java.lang.System. For all the kinds of type usages above it would be quite surprising to see
System there. But, as the link illustrates, class usages of
System do still make sense.
To let an annotation type apply to type usages we use the target type
ElementType.TYPE_USE. Unfortunately, it automatically implies
ElementType.TYPE (which means "class") and
ElementType.TYPE_PARAMETER as well, and there's no way to tell it not to. But class declarations and type parameter declarations are not type usages.
"Denote the use of a type"?
JLS claims these cases "denote the use of a type", and allows type-use annotations to be used on them. But they are not type usages, and it is nonobvious what type-use annotations in these positions should mean.
- An unbounded wildcard. Actually, type-use annotations end up being allowed on any wildcard. But a wildcard is not a type usage (its bounds, if it has any, are).
- A constructor declaration. There is a class usage here. But a constructor doesn't have a "return type" or its equivalent; that type is assembled using type arguments determined at the caller's side.
...in a varargs parameter declaration. It does make sense for this to be annotatable, but we address it differently, by saying above that
...constitutes a type usage of
Categorically non-null type usages
These kinds of type usages naturally exclude
null, so nullness annotations are not relevant there:
- Any usage of a value type (primitive type)
- The type after
newor in similar member reference
- A thrown or caught exception type (you simply can't
- The outer type of an inner type expression in any context
- A supertype in a class declaration
- The type in
instanceof(will never match
- The type in a cast expression (yes, it casts null-to-null, but that's just standard behavior regardless of what type is given)
- A receiver parameter type (rare)