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.

Static types

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 ... as []
  • Method return type
  • Record component (serving as all of the above at once)
  • Thrown exception (i.e., after throws)
  • Supertype in a class declaration (implicitly Object)
  • Upper bound in a type parameter declaration (implicitly Object)
  • Receiver parameter (rare)

In implementation code (expressions and statements)

The type...

  • 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 instanceof expression
  • After new or before ::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>.Qux has 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 int or the null type
  • User-defined types
    • By class declaration, like String or an anonymous class type
    • By type parameter declaration, called a "type variable", like E within java.util.List
  • Composed types, assembled ad-hoc from other types, like List<String[]>

Even non-denotable types can have type usages (implicit ones, of course).

Not type usages

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

Other allowed TYPE_USE targets

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.
  • The ... in a varargs parameter declaration. It does make sense for this to be annotatable, but we address it differently, by saying above that <type> ... constitutes a type usage of <type> [].

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 new or in similar member reference
  • A thrown or caught exception type (you simply can't throw null;)
  • The outer type of an inner type expression in any context
  • A supertype in a class declaration
  • The type in instanceof (will never match null)
  • 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)

Links

JLS 4.11.

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