-
Notifications
You must be signed in to change notification settings - Fork 32
type usages
This page describes type usages in the manner adopted by the JSpecify project. It's largely based on Java Language Specification 4.11, but there are a few intentional discrepancies.
A type usage happens wherever some type is being indicated: in a variable declaration, return type, cast expression, etc.
In var s = "";
and in class Foo {}
we have implicit type usages of String
and Object
. Whether we care about including implicit type usages as "real" type usages depends on the context.
The compiler computes a deterministic static type for each expression and expression context. Although these also "use" the type in some sense, they aren't considered type usages. (Another example: in class StringList implements List<String> { @Override public NotAString get(int i) { ... } }
, the NotAString
is checked against such a computed type.)
A type usage always appears in a type context, either directly or as a type component of some other type expression.
Just as an "expression context" is a "slot" that an expression fills in, a type context is a place where a type usage can appear. Generally, anything that sits in a type context is a type usage. We'll now list all the kinds of type contexts (as of Java 22).
The type of a...
- field declaration (for an enum constant, this is implicit)
- method return type (if it has one)
-
parameter of a method or constructor (treating
...
the same as[]
) - record component (since it serves as all three of the above at once)
-
supertype in a class or type parameter declaration (by default,
Object
) -
thrown exception (after
throws
in a signature)
The type...
- of a variable declaration (local variable, exception parameter, or lambda expression parameter; if a type is given)
- in a cast, or
instanceof
used as the type comparison operator - after
new
or before::new
(class or array type to instantiate, or supertype for an anonymous class) - of a type argument supplied to a generic method invocation or class instance creation expression (e.g., in
Lists.<String>of()
), including via member reference - in an unbound method reference to an instance method (e.g. the
String
inString::toUpperCase
) - in a type pattern that declares a pattern variable
Footnote: Resource variables and pattern variables are considered local variables.
The other kind of type usage is as a type component within some broader compound type (which in turn sits, directly or indirectly, in a type context).
Kind of compound type | Example | Has these direct type components | Example |
---|---|---|---|
array type | String[][] |
its component type | String[] |
parameterized type | Map<K, ? extends Number> |
each non-wildcard type argument or wildcard bound |
K , Number
|
inner type | Foo<Bar>.Qux |
its outer type | Foo<Bar> |
A type contains another type if it has that type as one of its type components.
Every type contains itself. So, technically every type usage is of a type component.
A type component is always a complete type: the components of String[]
are not String
and []
; they are String
and String[]
. Likewise, the inner type Foo<Bar>.Qux
contains Bar
, Foo<Bar>
, and Foo<Bar>.Qux
; Qux
by itself is not a valid type and so is not among them.
Footnote: The kinds of compound types listed are not disjoint: a type can have type arguments and be an inner type at the same time.
Every type, thereby, has a built-in tree structure.
We use the term root type to refer to the "entire" type that sits directly in a type context, not contained by another type. For example, in public List<String[]> foo();
, type usages of String
and String[]
are present, but only List<String[]>
itself is the root type.
A type that is not compound is a simple type. It's usually represented as a single token (though possibly name-qualified, and Foo<?>
is an apparent exception). But there's no meaningful difference between a simple type and a compound type; just whether its tree depth is one, or greater than one.
Footnote: Because Map.Entry
is a static nested class, not an "inner class", the token Map
in Map.Entry<Foo, Bar>
is not a type component; it's covered in the next section on class usages.
Declaring a class automatically defines a type. That type can then have type usages of the above kinds (even if, like System
, it would rather not!). But the class can also have class usages, where it is used as just a class.
- in an import declaration (either as the class to import, or qualifying it)
- as name-qualification of a static member (including a static nested class)
- in a class-typed class literal, before
.class
- in a constructor declaration, before
(
- for disambiguation before
.this
or.super
- in an annotation, after
@
- as the record class name in a record pattern, before
(
- in a sealed class header, after
permits
- in a module declaration, after
uses
/provides
/with
Class usages are not type usages.
You are probably looking at a type usage, if:
- a compound or primitive type could make logical sense here
- a non-instantiable class like
System
would make no logical sense here -
List
used here would feel like a "raw type" - what's being expressed implicitly applies to subclasses/subtypes too
In the language grammar we write a "type expression" to indicate a type; the JLS calls this an UnannType
. When talking about a type usage, we also take into account some surrounding context: any type-use annotations which may have preceded that (including those to the left of modifiers and declaration annotations!), as well as any C-style array brackets which might appear later.
An annotation marked with @Target(TYPE_USE)
can be attached to any kind of explicit type usage listed above. But it also automatically lets it be used in a few other places:
- On a class or type parameter declaration. This declaration doesn't use a type; it defines one.
- On a wildcard. Not a type usage, however, the bounds of the wildcard (if any) are.
- On a constructor declaration. This one is, at least, a class usage. But a constructor doesn't have a "return type"; that type gets assembled using type arguments determined at the caller's side (which, accordingly, does constitute a type usage).
While Java permits type-use annotations in these locations, JSpecify defines its annotation types to have no meaning when used in those locations.
Footnote: JLS describes certain constructs as "denoting the use of a type", but neither explains what the phrase means nor ever uses it again. These are: unbounded wildcards, constructor declarations, and the ...
in a varargs parameter declaration. This is irrelevant for our purposes: the first two are already excluded above, and we address the third case differently, by considering <type>
...
to be an implicit type usage of <type>
[]
.
Note: The contents of this wiki are not final, and represent current thinking and discussions.